1
0
mirror of https://github.com/libssh2/libssh2.git synced 2026-10-24 03:48:58 +03:00
Files
libssh2/src/misc.c
Viktor Szakats 9c421761bc src: use abstract internal time type, bump resolution to milliseconds
To honor the actual millisecond timeout set via
`libssh2_session_set_timeout()`, to open the possibility for the same in
other APIs, and to use monotonic time if available.

- add internal abstract time types and macros for handling point in time
  and time intervals, and use them throughout the codebase.
- bump internal abstract time resolution to milliseconds (from seconds).
  Use signed 64-bit integers.
- replace existing millisecond-resolution code in `libssh2_poll()` with
  the internal abstract time interface.
- bump 'now' function to retrieve millisecond-resolution monotonic time
  from the system, if available.

Also:
- append `_s` or `_ms` to public API arguments to tell the time unit.
- keepalive: minor tidy-ups.
- make `ssh2_now()` return at least 1 in the fallback,
  `time(NULL)`-based codepath to not confuse a zero value with unset
  time.
  Credits: Stefan Eissing
  Ref: 5649b21297
  Ref: https://github.com/curl/curl/pull/21034
- this leaves existing `ssh2_gettimeofday()` (together with its
  Windows-specific local implementation) with a single caller,
  `ssh2_deb_low()`.

Refs:
https://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_gettime.html
https://www.man7.org/linux/man-pages/man3/clock_gettime.3.html
https://pubs.opengroup.org/onlinepubs/009604599/functions/gettimeofday.html
https://www.man7.org/linux/man-pages/man2/gettimeofday.2.html
https://learn.microsoft.com/windows/win32/api/profileapi/nf-profileapi-queryperformancefrequency
https://learn.microsoft.com/windows/win32/api/profileapi/nf-profileapi-queryperformancecounter
https://stackoverflow.com/questions/5404277/porting-clock-gettime-to-windows

Reported-and-patch-proposal-by: Trzik on github
Based-on-patch-proposals-by: Ryan Kelley
Fixes #1497
Ref: #1818
Closes #1893
Follow-up to 772abc7a85 #1919

Closes #2341
2026-07-19 02:56:04 +02:00

1267 lines
35 KiB
C

/* Copyright (C) Sara Golemon <sarag@libssh2.org>
* Copyright (C) Daniel Stenberg
* Copyright (C) Simon Josefsson
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "libssh2_priv.h"
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <assert.h>
#ifdef _WIN32
/* Force parameter type. */
#define SSH2_RECV_LOW(s, b, l, f) recv(s, b, (int)(l), f)
#define SSH2_SEND_LOW(s, b, l, f) send(s, b, (int)(l), f)
#else
#define SSH2_RECV_LOW recv
#define SSH2_SEND_LOW send
#endif
#if defined(_MSC_VER) && _MSC_VER < 1900
/* snprintf is not in pre-VS2015 CRTs and _snprintf dangerously incompatible.
Replicate standard snprintf using _vsnprintf_s and _vscprintf. */
#if _MSC_VER < 1800 /* for VS2010, VS2012 */
#define va_copy(dest, src) ((dest) = (src))
#endif
int ssh2_vsnprintf(char *buf, size_t buf_len, const char *fmt, va_list args)
{
if(buf && buf_len) {
int ret;
va_list args_dupe;
va_copy(args_dupe, args);
ret = _vsnprintf_s(buf, buf_len, _TRUNCATE, fmt, args_dupe);
va_end(args_dupe);
if(ret >= 0)
return ret;
}
return _vscprintf(fmt, args);
}
int ssh2_snprintf(char *buf, size_t buf_len, const char *fmt, ...)
{
int ret;
va_list args;
va_start(args, fmt);
ret = ssh2_vsnprintf(buf, buf_len, fmt, args);
va_end(args);
return ret;
}
#endif
int ssh2_err_flags(LIBSSH2_SESSION *session, int errcode,
const char *errmsg, int errflags)
{
if(!session) {
ssh2_deb((session, LIBSSH2_TRACE_ERROR,
"ssh2_err_flags: session is NULL, error: %s",
errmsg ? errmsg : "(null)"));
return errcode;
}
if(session->err_flags & SSH2_ERR_FLAG_DUP)
SSH2_FREE(session, SSH2_UNCONST(session->err_msg));
session->err_code = errcode;
session->err_flags = 0;
if(errmsg && (errflags & SSH2_ERR_FLAG_DUP) != 0) {
size_t len = strlen(errmsg);
char *copy = SSH2_ALLOC(session, len + 1);
if(copy) {
memcpy(copy, errmsg, len + 1);
session->err_flags = SSH2_ERR_FLAG_DUP;
session->err_msg = copy;
}
else
/* Out of memory: this code path is unlikely */
session->err_msg = "former error forgotten (OOM)";
}
else
session->err_msg = errmsg;
#ifdef LIBSSH2DEBUG
if(errcode == LIBSSH2_ERROR_EAGAIN && !session->api_block_mode)
/* if this is EAGAIN and we are in non-blocking mode, do not generate
a debug output for this */
return errcode;
ssh2_deb((session, LIBSSH2_TRACE_ERROR, "%d - %s", session->err_code,
session->err_msg));
#endif
return errcode;
}
int ssh2_err(LIBSSH2_SESSION *session, int errcode, const char *errmsg)
{
return ssh2_err_flags(session, errcode, errmsg, 0);
}
#ifdef _WIN32
int ssh2_wsa2errno(void)
{
switch(WSAGetLastError()) {
case WSAEWOULDBLOCK:
return EAGAIN;
case WSAENOTSOCK:
return EBADF;
case WSAEINTR:
return EINTR;
default:
/* It is most important to ensure errno does not stay at EAGAIN
* when a different error occurs so set errno to a generic error */
return EIO;
}
}
#endif
/*
* Replacement for the standard recv, return -errno on failure.
*/
ssize_t ssh2_recv(libssh2_socket_t socket, void *buffer, size_t length,
int flags, void **abstract)
{
ssize_t rc;
(void)abstract;
rc = SSH2_RECV_LOW(socket, buffer, length, flags);
if(rc < 0) {
int sockerr = SSH2_ERRNO();
/* Profiling tools that use SIGPROF can cause EINTR responses.
recv() does not modify its arguments when it returns EINTR,
but there may be data waiting, so the caller should try again */
if(sockerr == EINTR)
return -EAGAIN;
/* Sometimes the first recv() function call sets errno to ENOENT on
Solaris and HP-UX */
if(sockerr == ENOENT)
return -EAGAIN;
if(sockerr == EWOULDBLOCK)
return -EAGAIN;
return -sockerr;
}
return rc;
}
/*
* Replacement for the standard send, return -errno on failure.
*/
ssize_t ssh2_send(libssh2_socket_t socket,
const void *buffer, size_t length,
int flags, void **abstract)
{
ssize_t rc;
(void)abstract;
rc = SSH2_SEND_LOW(socket, buffer, length, flags);
if(rc < 0) {
int sockerr = SSH2_ERRNO();
/* Profiling tools that use SIGPROF can cause EINTR responses.
send() is defined as not yet sending any data when it returns EINTR,
so the caller should try again */
if(sockerr == EINTR)
return -EAGAIN;
if(sockerr == EWOULDBLOCK)
return -EAGAIN;
return -sockerr;
}
return rc;
}
uint32_t ssh2_ntohu32(const unsigned char *buf)
{
return
((uint32_t)buf[0] << 24) |
((uint32_t)buf[1] << 16) |
((uint32_t)buf[2] << 8) |
((uint32_t)buf[3]);
}
libssh2_uint64_t ssh2_ntohu64(const unsigned char *buf)
{
return
((libssh2_uint64_t)buf[0] << 56) |
((libssh2_uint64_t)buf[1] << 48) |
((libssh2_uint64_t)buf[2] << 40) |
((libssh2_uint64_t)buf[3] << 32) |
((libssh2_uint64_t)buf[4] << 24) |
((libssh2_uint64_t)buf[5] << 16) |
((libssh2_uint64_t)buf[6] << 8) |
((libssh2_uint64_t)buf[7]);
}
void ssh2_htonu32(unsigned char *buf, uint32_t value)
{
buf[0] = (unsigned char)((value >> 24) & 0xFF);
buf[1] = (unsigned char)((value >> 16) & 0xFF);
buf[2] = (unsigned char)((value >> 8) & 0xFF);
buf[3] = (unsigned char)(value & 0xFF);
}
void ssh2_store_u32(unsigned char **buf, uint32_t value)
{
ssh2_htonu32(*buf, value);
*buf += sizeof(uint32_t);
}
void ssh2_store_u64(unsigned char **buf, libssh2_uint64_t value)
{
unsigned char *ptr = *buf;
ptr[0] = (unsigned char)((value >> 56) & 0xFF);
ptr[1] = (unsigned char)((value >> 48) & 0xFF);
ptr[2] = (unsigned char)((value >> 40) & 0xFF);
ptr[3] = (unsigned char)((value >> 32) & 0xFF);
ptr[4] = (unsigned char)((value >> 24) & 0xFF);
ptr[5] = (unsigned char)((value >> 16) & 0xFF);
ptr[6] = (unsigned char)((value >> 8) & 0xFF);
ptr[7] = (unsigned char)(value & 0xFF);
*buf += sizeof(libssh2_uint64_t);
}
int ssh2_store_str(unsigned char **buf, const char *str, size_t len)
{
uint32_t len_stored = (uint32_t)len;
ssh2_store_u32(buf, len_stored);
if(len_stored) {
memcpy(*buf, str, len_stored);
*buf += len_stored;
}
assert(len_stored == len);
return len_stored == len;
}
int ssh2_store_hybrid_str(unsigned char **buf, const char *str_1,
size_t len_1, const char *str_2, size_t len_2)
{
uint32_t len_stored;
if(len_1 > UINT32_MAX - len_2)
return 0;
len_stored = (uint32_t)len_1 + (uint32_t)len_2;
ssh2_store_u32(buf, len_stored);
if(len_1) {
memcpy(*buf, str_1, len_1);
*buf += len_1;
}
if(len_2) {
memcpy(*buf, str_2, len_2);
*buf += len_2;
}
assert(len_stored == len_1 + len_2);
return len_stored == len_1 + len_2;
}
int ssh2_store_bignum_bytes(unsigned char **buf,
const unsigned char *bytes, size_t len)
{
uint32_t len_stored;
uint32_t extraByte;
const unsigned char *p;
for(p = bytes; len > 0 && *p == 0; --len, ++p)
;
extraByte = (len > 0 && (p[0] & 0x80) != 0);
len_stored = (uint32_t)len;
if(extraByte && len_stored == UINT32_MAX)
len_stored--;
ssh2_store_u32(buf, len_stored + extraByte);
if(extraByte) {
(*buf)[0] = 0;
*buf += 1;
}
if(len_stored) {
memcpy(*buf, p, len_stored);
*buf += len_stored;
}
assert(len_stored == len);
return len_stored == len;
}
int ssh2_hash(ssh2_hash_alg alg, const void *input, size_t input_len,
void *digest, size_t digest_len)
{
ssh2_hash_ctx ctx;
int success = ssh2_hash_init(&ctx, alg);
if(success) {
success &= ssh2_hash_update(&ctx, input, input_len);
success &= ssh2_hash_final(&ctx, digest, digest_len);
}
return success;
}
/* Base64 Conversion */
static const short ssh2_base64_reverse_table[256] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
};
#ifndef LIBSSH2_NO_DEPRECATED
/*
* Legacy public function. (DEPRECATED, DO NOT USE!)
*/
int libssh2_base64_decode(LIBSSH2_SESSION *session,
char **dest, unsigned int *dest_len,
const char *src, unsigned int src_len)
{
int rc;
size_t dlen;
rc = ssh2_base64_decode(session, dest, &dlen, src, src_len);
if(dest_len)
*dest_len = (unsigned int)dlen;
return rc;
}
#endif
/*
* Decode a base64 chunk and store it into a newly alloc'd buffer
*/
int ssh2_base64_decode(LIBSSH2_SESSION *session,
char **data, size_t *datalen,
const char *src, size_t src_len)
{
unsigned char *d;
const char *s;
short v;
size_t i = 0, len = 0;
*datalen = 0;
*data = SSH2_ALLOC(session, src_len);
d = (unsigned char *)*data;
if(!d)
return ssh2_err(session, LIBSSH2_ERROR_ALLOC,
"Unable to allocate memory for base64 decoding");
for(s = src; s < (src + src_len); s++) {
v = ssh2_base64_reverse_table[(unsigned char)*s];
if(v < 0)
continue;
switch(i % 4) {
case 0:
d[len] = (unsigned char)(v << 2);
break;
case 1:
d[len++] |= (unsigned char)(v >> 4);
d[len] = (unsigned char)(v << 4);
break;
case 2:
d[len++] |= (unsigned char)(v >> 2);
d[len] = (unsigned char)(v << 6);
break;
case 3:
d[len++] |= (unsigned char)v;
break;
}
i++;
}
if((i % 4) == 1) {
/* Invalid -- We have a byte which belongs exclusively to a partial
octet */
SSH2_SAFEFREE(session, *data);
return ssh2_err(session, LIBSSH2_ERROR_INVAL, "Invalid base64");
}
*datalen = len;
return 0;
}
/* ---- Base64 Encoding/Decoding Table --- */
static const char table64[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/*
* Returns the length of the newly created base64 string. The third argument
* is a pointer to an allocated area holding the base64 data. If something
* went wrong, 0 is returned.
*/
size_t ssh2_base64_encode(LIBSSH2_SESSION *session,
const char *inp, size_t insize, char **outptr)
{
unsigned char ibuf[3];
unsigned char obuf[4];
int i;
int inputparts;
char *output;
char *base64data;
const char *indata = inp;
*outptr = NULL; /* set to NULL in case of failure before we reach the
end */
if(insize == 0)
return 0; /* nothing to encode */
base64data = output = SSH2_ALLOC(session, insize * 4 / 3 + 4);
if(!output)
return 0;
while(insize > 0) {
for(i = inputparts = 0; i < 3; i++) {
if(insize > 0) {
inputparts++;
ibuf[i] = *indata;
indata++;
insize--;
}
else
ibuf[i] = 0;
}
obuf[0] = (unsigned char) ((ibuf[0] & 0xFC) >> 2);
obuf[1] = (unsigned char)(((ibuf[0] & 0x03) << 4) | \
((ibuf[1] & 0xF0) >> 4));
obuf[2] = (unsigned char)(((ibuf[1] & 0x0F) << 2) | \
((ibuf[2] & 0xC0) >> 6));
obuf[3] = (unsigned char) (ibuf[2] & 0x3F);
switch(inputparts) {
case 1: /* only one byte read */
output[0] = table64[obuf[0]];
output[1] = table64[obuf[1]];
output[2] = '=';
output[3] = '=';
break;
case 2: /* two bytes read */
output[0] = table64[obuf[0]];
output[1] = table64[obuf[1]];
output[2] = table64[obuf[2]];
output[3] = '=';
break;
default:
output[0] = table64[obuf[0]];
output[1] = table64[obuf[1]];
output[2] = table64[obuf[2]];
output[3] = table64[obuf[3]];
break;
}
output += 4;
}
*output = 0;
*outptr = base64data; /* make it return the actual data memory */
return strlen(base64data); /* return the length of the new data */
}
/* ---- End of Base64 Encoding ---- */
void libssh2_free(LIBSSH2_SESSION *session, void *ptr)
{
SSH2_FREE(session, ptr);
}
#ifdef LIBSSH2DEBUG
int libssh2_trace(LIBSSH2_SESSION *session, int bitmask)
{
if(!session)
return LIBSSH2_ERROR_BAD_USE;
session->showmask = bitmask;
return LIBSSH2_ERROR_NONE;
}
int libssh2_trace_sethandler(LIBSSH2_SESSION *session, void *context,
libssh2_trace_handler_func callback)
{
if(!session)
return LIBSSH2_ERROR_BAD_USE;
session->tracehandler = callback;
session->tracehandler_context = context;
return LIBSSH2_ERROR_NONE;
}
void ssh2_deb_low(LIBSSH2_SESSION *session, int context,
const char *format, ...)
{
static const char * const contexts[] = {
"Unknown",
"Transport",
"Key Ex",
"Userauth",
"Conn",
"SCP",
"SFTP",
"Failure Event",
"Publickey",
"Socket",
};
static long firstsec;
char buffer[1536];
int len, msglen, buflen = sizeof(buffer);
va_list vargs;
struct timeval now;
const char *contexttext = contexts[0];
unsigned int contextindex;
if(session && !(session->showmask & context))
return; /* no such output asked for */
/* Find the first matching context string for this message */
for(contextindex = 0; contextindex < SSH2_ARRAYSIZE(contexts);
contextindex++) {
if((context & (1 << contextindex)) != 0) {
contexttext = contexts[contextindex];
break;
}
}
ssh2_gettimeofday(&now, NULL);
if(!firstsec)
firstsec = now.tv_sec;
now.tv_sec -= firstsec;
/* '[libssh2] 9999999999.9999999999 Failure Event: ' */
len = ssh2_snprintf(buffer, buflen, "[libssh2] %d.%06d %s: ",
(int)now.tv_sec, (int)now.tv_usec, contexttext);
if(len < 0 || len >= buflen) {
msglen = len < 0 ? 0 : (buflen - 1);
buffer[msglen] = 0;
}
else {
buflen -= len;
msglen = len;
va_start(vargs, format);
#if defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#endif
len = ssh2_vsnprintf(buffer + msglen, buflen, format, vargs);
#if defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic pop
#endif
va_end(vargs);
if(len < 0 || len >= buflen) {
msglen += len < 0 ? 0 : (buflen - 1);
buffer[msglen] = 0;
}
else
msglen += len;
}
if(session && session->tracehandler)
session->tracehandler(session, session->tracehandler_context, buffer,
msglen);
else
/* !checksrc! disable BANNEDFUNC 1 */
fprintf(stderr, "%s\n", buffer);
}
#else /* !LIBSSH2DEBUG */
int libssh2_trace(LIBSSH2_SESSION *session, int bitmask)
{
(void)session;
(void)bitmask;
return LIBSSH2_ERROR_NONE;
}
int libssh2_trace_sethandler(LIBSSH2_SESSION *session, void *context,
libssh2_trace_handler_func callback)
{
(void)session;
(void)context;
(void)callback;
return LIBSSH2_ERROR_NONE;
}
#endif
/* init the list head */
void ssh2_list_init(struct list_head *head)
{
head->first = head->last = NULL;
}
/* add a node to the list */
void ssh2_list_add(struct list_head *head, struct list_node *entry)
{
/* store a pointer to the head */
entry->head = head;
/* we add this entry at the "top" so it has no next */
entry->next = NULL;
/* make our prev point to what the head thinks is last */
entry->prev = head->last;
/* and make head's last be us now */
head->last = entry;
/* make sure our 'prev' node points to us next */
if(entry->prev)
entry->prev->next = entry;
else
head->first = entry;
}
/* return the "first" node in the list this head points to */
void *ssh2_list_first(struct list_head *head)
{
return head->first;
}
/* return the next node in the list */
void *ssh2_list_next(struct list_node *node)
{
return node->next;
}
/* return the prev node in the list */
void *ssh2_list_prev(struct list_node *node)
{
return node->prev;
}
/* remove this node from the list */
void ssh2_list_remove(struct list_node *entry)
{
if(entry->prev)
entry->prev->next = entry->next;
else
entry->head->first = entry->next;
if(entry->next)
entry->next->prev = entry->prev;
else
entry->head->last = entry->prev;
}
#if 0
/* insert a node before the given 'after' entry */
void ssh2_list_insert(struct list_node *after, /* insert before this */
struct list_node *entry)
{
/* 'after' is next to 'entry' */
entry->next = after;
/* entry's prev is then made to be the prev after current has */
entry->prev = after->prev;
/* the node that is now before 'entry' was previously before 'after'
and must be made to point to 'entry' correctly */
if(entry->prev)
entry->prev->next = entry;
else
/* there was no node before this, so we make sure we point the head
pointer to this node */
after->head->first = entry;
/* after's prev entry points back to entry */
after->prev = entry;
/* after's next entry is still the same as before */
/* entry's head is the same as after's */
entry->head = after->head;
}
#endif
ssh2_time_t ssh2_now(void) /* ms */
{
#ifdef _WIN32
ssh2_time_t sec, ns;
LARGE_INTEGER freq, count;
/* These never fail on supported Windows versions */
QueryPerformanceFrequency(&freq);
QueryPerformanceCounter(&count);
sec = (ssh2_time_t)(count.QuadPart / freq.QuadPart);
ns = (ssh2_time_t)(((count.QuadPart % freq.QuadPart) *
1000000000) / freq.QuadPart);
return sec * 1000 + ns / 1000000;
#else /* !_WIN32 */
#if defined(CLOCK_MONOTONIC_RAW) /* Apple/Linux */
struct timespec ts;
if(!clock_gettime(CLOCK_MONOTONIC_RAW, &ts))
return (ssh2_time_t)ts.tv_sec * 1000 +
(ssh2_time_t)ts.tv_nsec / 1000000;
#elif defined(CLOCK_MONOTONIC) /* POSIX */
struct timespec ts;
if(!clock_gettime(CLOCK_MONOTONIC, &ts))
return (ssh2_time_t)ts.tv_sec * 1000 +
(ssh2_time_t)ts.tv_nsec / 1000000;
#elif defined(HAVE_GETTIMEOFDAY)
struct timeval tv;
if(!gettimeofday(&tv, NULL))
return (ssh2_time_t)tv.tv_sec * 1000 + (ssh2_time_t)tv.tv_usec / 1000;
#endif
{
ssh2_time_t ms = (ssh2_time_t)time(NULL) * 1000;
return ms ? ms : 1;
}
#endif /* _WIN32 */
}
#ifndef HAVE_GETTIMEOFDAY
/*
* Implementation according to:
* The Open Group Base Specifications Issue 6
* IEEE Std 1003.1, 2004 Edition
*
* THIS SOFTWARE IS NOT COPYRIGHTED
*
* This source code is offered for use in the public domain. You may
* use, modify or distribute it freely.
*
* This code is distributed in the hope that it is useful but
* WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESS OR IMPLIED ARE HEREBY
* DISCLAIMED. This includes but is not limited to warranties of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
int ssh2_gettimeofday(struct timeval *tp, void *tzp)
{
(void)tzp;
if(tp) {
#ifdef _WIN32
/* Offset between 1601-01-01 and 1970-01-01 in 100 nanosec units */
#define SSH2_WIN32_FT_OFFSET 116444736000000000
union {
libssh2_uint64_t ns100; /* time since 1 Jan 1601 in 100ns units */
FILETIME ft;
} now;
GetSystemTimeAsFileTime(&now.ft);
tp->tv_usec = (long)((now.ns100 / 10) % 1000000);
tp->tv_sec = (long)((now.ns100 - SSH2_WIN32_FT_OFFSET) / 10000000);
#else
/* Platforms without a native implementation or local replacement */
tp->tv_usec = 0;
tp->tv_sec = 0;
#endif
}
/* Always return 0 as per Open Group Base Specifications Issue 6.
Do not set errno on error. */
return 0;
}
#endif /* !HAVE_GETTIMEOFDAY */
void *ssh2_calloc(LIBSSH2_SESSION *session, size_t size)
{
void *p = SSH2_ALLOC(session, size);
if(p)
memset(p, 0, size);
return p;
}
/* XOR operation on buffers input1 and input2, result in output.
It is safe to use an input buffer as the output buffer. */
void ssh2_xor_data(unsigned char *output,
const unsigned char *input1,
const unsigned char *input2,
size_t length)
{
size_t i;
for(i = 0; i < length; i++)
*output++ = *input1++ ^ *input2++;
}
#ifdef LIBSSH2_MEMZERO
static void *(* const volatile memset_libssh)(void *, int, size_t) = memset;
void ssh2_memzero(void *buf, size_t size)
{
memset_libssh(buf, 0, size);
}
#endif
/* String buffer */
struct string_buf *ssh2_string_buf_new(LIBSSH2_SESSION *session)
{
struct string_buf *ret;
ret = ssh2_calloc(session, sizeof(*ret));
if(!ret)
return NULL;
return ret;
}
void ssh2_string_buf_free(LIBSSH2_SESSION *session, struct string_buf *buf)
{
if(!buf)
return;
if(buf->data)
SSH2_FREE(session, buf->data);
SSH2_FREE(session, buf);
}
int ssh2_get_byte(struct string_buf *buf, unsigned char *out)
{
if(!ssh2_check_length(buf, 1))
return -1;
*out = buf->dataptr[0];
buf->dataptr += 1;
return 0;
}
int ssh2_get_boolean(struct string_buf *buf, unsigned char *out)
{
if(!ssh2_check_length(buf, 1))
return -1;
*out = buf->dataptr[0] == 0 ? 0 : 1;
buf->dataptr += 1;
return 0;
}
int ssh2_get_u32(struct string_buf *buf, uint32_t *out)
{
if(!ssh2_check_length(buf, 4))
return -1;
*out = ssh2_ntohu32(buf->dataptr);
buf->dataptr += 4;
return 0;
}
int ssh2_get_u64(struct string_buf *buf, libssh2_uint64_t *out)
{
if(!ssh2_check_length(buf, 8))
return -1;
*out = ssh2_ntohu64(buf->dataptr);
buf->dataptr += 8;
return 0;
}
int ssh2_match_string(struct string_buf *buf, const char *match)
{
unsigned char *out;
size_t len = 0;
if(ssh2_get_string(buf, &out, &len) || len != strlen(match) ||
strncmp((const char *)out, match, strlen(match)))
return -1;
return 0;
}
int ssh2_get_string(struct string_buf *buf, unsigned char **outbuf,
size_t *outlen)
{
uint32_t data_len;
if(!buf || ssh2_get_u32(buf, &data_len) != 0)
return -1;
if(!ssh2_check_length(buf, data_len))
return -1;
*outbuf = buf->dataptr;
buf->dataptr += data_len;
if(outlen)
*outlen = (size_t)data_len;
return 0;
}
int ssh2_copy_string(LIBSSH2_SESSION *session, struct string_buf *buf,
unsigned char **outbuf, size_t *outlen)
{
size_t str_len;
unsigned char *str;
if(ssh2_get_string(buf, &str, &str_len))
return -1;
if(str_len) {
*outbuf = SSH2_ALLOC(session, str_len);
if(*outbuf)
memcpy(*outbuf, str, str_len);
else
return -1;
}
else
*outbuf = NULL;
if(outlen)
*outlen = str_len;
return 0;
}
int ssh2_get_bignum_bytes(struct string_buf *buf, unsigned char **outbuf,
size_t *outlen)
{
uint32_t data_len;
uint32_t bn_len;
unsigned char *bnptr;
if(ssh2_get_u32(buf, &data_len))
return -1;
if(!ssh2_check_length(buf, data_len))
return -1;
bn_len = data_len;
bnptr = buf->dataptr;
/* trim leading zeros */
while(bn_len > 0 && *bnptr == 0x00) {
bn_len--;
bnptr++;
}
*outbuf = bnptr;
buf->dataptr += data_len;
if(outlen)
*outlen = (size_t)bn_len;
return 0;
}
/* Given the current location in buf, ssh2_check_length() ensures
callers can read the next len number of bytes out of the buffer
before reading the buffer content */
int ssh2_check_length(struct string_buf *buf, size_t requested_len)
{
unsigned char *endp = &buf->data[buf->len];
size_t left = endp - buf->dataptr;
return requested_len <= left && left <= buf->len;
}
int ssh2_eob(struct string_buf *buf)
{
unsigned char *endp = &buf->data[buf->len];
return buf->dataptr >= endp;
}
int ssh2_timingsafe_bcmp(const void *b1, const void *b2, size_t n)
{
const unsigned char *p1 = (const unsigned char *)b1;
const unsigned char *p2 = (const unsigned char *)b2;
int ret = 0;
for(; n > 0; n--)
ret |= *p1++ ^ *p2++;
return ret != 0;
}
#ifndef LIBSSH2_KEY_SK
int ssh2_sk_pub_keyfilememory(LIBSSH2_SESSION *session,
unsigned char **method,
size_t *method_len,
unsigned char **pubkeydata,
size_t *pubkeydata_len,
int *algorithm,
unsigned char *flags,
const char **application,
const unsigned char **key_handle,
size_t *handle_len,
const char *privatekeydata,
size_t privatekeydata_len,
const unsigned char *passphrase)
{
(void)method;
(void)method_len;
(void)pubkeydata;
(void)pubkeydata_len;
(void)algorithm;
(void)flags;
(void)application;
(void)key_handle;
(void)handle_len;
(void)privatekeydata;
(void)privatekeydata_len;
(void)passphrase;
return ssh2_err(session, LIBSSH2_ERROR_FILE,
"Unable to extract public SK key from private key file: "
"Method unimplemented in "
SSH2_CRYPTO_ENGINE_NAME " backend");
}
#endif
#ifdef _WIN32
#include <share.h> /* for _SH_DENYNO */
#include <stdlib.h> /* for malloc(), free() */
#include <tchar.h> /* for _tcsncmp() */
#ifdef _UNICODE
static wchar_t *ssh2_win32_fn_convert_UTF8_to_wchar(const char *str_utf8)
{
wchar_t *str_w = NULL;
if(str_utf8) {
int str_w_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
str_utf8, -1, NULL, 0);
if(str_w_len > 0) {
str_w = malloc(str_w_len * sizeof(wchar_t));
if(str_w) {
if(MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
str_utf8, -1, str_w, str_w_len) == 0) {
free(str_w);
return NULL;
}
}
}
}
return str_w;
}
#endif
/* declare GetFullPathNameW for mingw-w64 UWP builds targeting old Windows */
#if defined(LIBSSH2_WINDOWS_UWP) && defined(__MINGW32__) && \
(_WIN32_WINNT < _WIN32_WINNT_WIN10)
WINBASEAPI DWORD WINAPI GetFullPathNameW(LPCWSTR, DWORD, LPWSTR, LPWSTR *);
#endif
/* Fix excessive paths (paths that exceed MAX_PATH length of 260).
*
* This is a helper function to fix paths that would exceed the MAX_PATH
* limitation check done by Windows APIs. It does so by normalizing the passed
* in filename or path 'in' to its full canonical path, and if that path is
* longer than MAX_PATH then setting 'out' to "\\?\" prefix + that full path.
*
* For example 'in' filename255chars in current directory C:\foo\bar is
* fixed as \\?\C:\foo\bar\filename255chars for 'out' which tells Windows
* it is ok to access that filename even though the actual full path is longer
* than 260 chars.
*
* For non-Unicode builds this function may fail sometimes because only the
* Unicode versions of some Windows API functions can access paths longer than
* MAX_PATH, for example GetFullPathNameW which is used in this function. When
* the full path is then converted from Unicode to multibyte that fails if any
* directories in the path contain characters not in the current codepage.
*/
static int ssh2_win32_fix_excessive_path(const TCHAR *in, TCHAR **out)
{
size_t needed, count;
const wchar_t *in_w;
wchar_t *fbuf = NULL;
/* MS-documented "approximate" limit for the maximum path length */
const size_t max_path_len = 32767;
#ifndef _UNICODE
wchar_t *ibuf = NULL;
char *obuf = NULL;
#endif
*out = NULL;
/* skip paths already normalized */
if(!_tcsncmp(in, _TEXT("\\\\?\\"), 4))
goto cleanup;
#ifndef _UNICODE
/* convert multibyte input to unicode */
if(mbstowcs_s(&needed, NULL, 0, in, 0))
goto cleanup;
if(!needed || needed >= max_path_len)
goto cleanup;
ibuf = malloc(needed * sizeof(wchar_t));
if(!ibuf)
goto cleanup;
if(mbstowcs_s(&count, ibuf, needed, in, needed - 1))
goto cleanup;
if(count != needed)
goto cleanup;
in_w = ibuf;
#else
in_w = in;
#endif
/* GetFullPathNameW returns the normalized full path in unicode. It
converts forward slashes to backslashes, processes .. to remove
directory segments, etc. Unlike GetFullPathNameA it can process
paths that exceed MAX_PATH. */
needed = (size_t)GetFullPathNameW(in_w, 0, NULL, NULL);
if(!needed || needed > max_path_len)
goto cleanup;
/* skip paths that are not excessive and do not need modification */
if(needed <= MAX_PATH)
goto cleanup;
fbuf = malloc(needed * sizeof(wchar_t));
if(!fbuf)
goto cleanup;
count = (size_t)GetFullPathNameW(in_w, (DWORD)needed, fbuf, NULL);
if(!count || count >= needed)
goto cleanup;
/* prepend \\?\ or \\?\UNC\ to the excessively long path.
*
* c:\longpath ---> \\?\c:\longpath
* \\.\c:\longpath ---> \\?\c:\longpath
* \\?\c:\longpath ---> \\?\c:\longpath (unchanged)
* \\server\c$\longpath ---> \\?\UNC\server\c$\longpath
*
* https://learn.microsoft.com/dotnet/standard/io/file-path-formats
*/
if(!wcsncmp(fbuf, L"\\\\?\\", 4))
; /* do nothing */
else if(!wcsncmp(fbuf, L"\\\\.\\", 4))
fbuf[2] = '?';
else if(!wcsncmp(fbuf, L"\\\\.", 3) || !wcsncmp(fbuf, L"\\\\?", 3))
/* Unexpected, not UNC. The formatting doc does not allow this
AFAICT. */
goto cleanup;
else {
wchar_t *temp;
if(!wcsncmp(fbuf, L"\\\\", 2)) {
/* "\\?\UNC\" + full path without "\\" + null */
needed = 8 + (count - 2) + 1;
if(needed > max_path_len)
goto cleanup;
temp = malloc(needed * sizeof(wchar_t));
if(!temp)
goto cleanup;
if(wcsncpy_s(temp, needed, L"\\\\?\\UNC\\", 8)) {
free(temp);
goto cleanup;
}
if(wcscpy_s(temp + 8, needed, fbuf + 2)) {
free(temp);
goto cleanup;
}
}
else {
/* "\\?\" + full path + null */
needed = 4 + count + 1;
if(needed > max_path_len)
goto cleanup;
temp = malloc(needed * sizeof(wchar_t));
if(!temp)
goto cleanup;
if(wcsncpy_s(temp, needed, L"\\\\?\\", 4)) {
free(temp);
goto cleanup;
}
if(wcscpy_s(temp + 4, needed, fbuf)) {
free(temp);
goto cleanup;
}
}
free(fbuf);
fbuf = temp;
}
#ifndef _UNICODE
/* convert unicode full path to multibyte output */
if(wcstombs_s(&needed, NULL, 0, fbuf, 0))
goto cleanup;
if(!needed || needed >= max_path_len)
goto cleanup;
obuf = malloc(needed);
if(!obuf)
goto cleanup;
if(wcstombs_s(&count, obuf, needed, fbuf, needed - 1))
goto cleanup;
if(count != needed)
goto cleanup;
*out = obuf;
obuf = NULL;
#else
*out = fbuf;
fbuf = NULL;
#endif
cleanup:
free(fbuf);
#ifndef _UNICODE
free(ibuf);
free(obuf);
#endif
return !!*out;
}
FILE *ssh2_fopen(const char *filename, const char *mode)
{
FILE *fp = NULL;
TCHAR *fixed = NULL;
const TCHAR *target = NULL;
#ifdef _UNICODE
wchar_t *filename_w = ssh2_win32_fn_convert_UTF8_to_wchar(filename);
wchar_t *mode_w = ssh2_win32_fn_convert_UTF8_to_wchar(mode);
if(filename_w && mode_w) {
if(ssh2_win32_fix_excessive_path(filename_w, &fixed))
target = fixed;
else
target = filename_w;
fp = _wfsopen(target, mode_w, _SH_DENYNO);
}
else
errno = EINVAL;
free(filename_w);
free(mode_w);
#else
if(ssh2_win32_fix_excessive_path(filename, &fixed))
target = fixed;
else
target = filename;
fp = _fsopen(target, mode, _SH_DENYNO);
#endif
free(fixed);
return fp;
}
#endif /* _WIN32 */