diff --git a/src/assuan-defs.h b/src/assuan-defs.h index e56d7b1..e7ca918 100644 --- a/src/assuan-defs.h +++ b/src/assuan-defs.h @@ -1,435 +1,436 @@ /* assuan-defs.h - Internal definitions to Assuan * Copyright (C) 2001, 2002, 2004, 2005, 2007, 2008, * 2009, 2010 Free Software Foundation, Inc. * * This file is part of Assuan. * * Assuan is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * Assuan is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1+ */ #ifndef ASSUAN_DEFS_H #define ASSUAN_DEFS_H #ifdef HAVE_SYS_TYPES_H # include #endif #ifndef HAVE_W32_SYSTEM # include # include #else # ifdef HAVE_WINSOCK2_H # /* Avoid inclusion of winsock.h via windows.h. */ # include # endif # include #endif #ifdef HAVE_UNISTD_H # include #endif #include "assuan.h" #if __GNUC__ > 2 # define ASSUAN_GCC_A_PURE __attribute__ ((__pure__)) #else # define ASSUAN_GCC_A_PURE #endif #ifndef HAVE_W32_SYSTEM #define DIRSEP_C '/' #else #define DIRSEP_C '\\' #endif #define LINELENGTH ASSUAN_LINELENGTH struct cmdtbl_s { const char *name; assuan_handler_t handler; const char *helpstr; }; /* The context we use with most functions. */ struct assuan_context_s { /* Members managed by the generic routines in assuan.c. */ /* The error source for errors generated from this context. */ gpg_err_source_t err_source; #ifdef HAVE_W32_SYSTEM /* The per-context w32 error string. */ char w32_strerror[256]; #endif /* The allocation hooks. */ struct assuan_malloc_hooks malloc_hooks; /* Logging callback handler. */ assuan_log_cb_t log_cb; void *log_cb_data; void *user_pointer; /* Context specific flags (cf. assuan_flag_t). */ struct { unsigned int no_waitpid : 1; unsigned int confidential : 1; unsigned int no_fixsignals : 1; unsigned int convey_comments : 1; unsigned int no_logging : 1; unsigned int force_close : 1; } flags; /* If set, this is called right before logging an I/O line. */ assuan_io_monitor_t io_monitor; void *io_monitor_data; /* Callback handlers replacing system I/O functions. */ struct assuan_system_hooks system; int peercred_valid; /* Whether this structure has valid information. */ struct _assuan_peercred peercred; /* Now come the members specific to subsystems or engines. FIXME: This is not developed yet. See below for the legacy members. */ struct { void (*release) (assuan_context_t ctx); /* Routine to read from input_fd. Sets errno on failure. */ ssize_t (*readfnc) (assuan_context_t, void *, size_t); /* Routine to write to output_fd. Sets errno on failure. */ ssize_t (*writefnc) (assuan_context_t, const void *, size_t); /* Send a file descriptor. */ gpg_error_t (*sendfd) (assuan_context_t, assuan_fd_t); /* Receive a file descriptor. */ gpg_error_t (*receivefd) (assuan_context_t, assuan_fd_t *); } engine; /* Engine specific or other subsystem members. */ /* assuan-logging.c. Does not require deallocation from us. */ FILE *log_fp; /* assuan-util.c */ gpg_error_t err_no; const char *err_str; int is_server; /* Set if this is context belongs to a server */ int in_inquire; int in_process_next; int process_complete; int in_command; /* The following members are used by assuan_inquire_ext. */ gpg_error_t (*inquire_cb) (void *cb_data, gpg_error_t rc, unsigned char *buf, size_t len); void *inquire_cb_data; void *inquire_membuf; char *hello_line; char *okay_line; /* See assuan_set_okay_line() */ struct { assuan_fd_t fd; int eof; char line[LINELENGTH]; int linelen; /* w/o CR, LF - might not be the same as strlen(line) due to embedded nuls. However a nul is always written at this pos. */ struct { char line[LINELENGTH]; int linelen ; int pending; /* i.e. at least one line is available in the attic */ } attic; } inbound; struct { assuan_fd_t fd; struct { FILE *fp; char line[LINELENGTH]; int linelen; int error; } data; } outbound; int max_accepts; /* If we can not handle more than one connection, set this to 1, otherwise to -1. */ pid_t pid; /* The pid of the peer. */ assuan_fd_t listen_fd; /* The fd we are listening on (used by socket servers) */ assuan_sock_nonce_t listen_nonce; /* Used with LISTEN_FD. */ assuan_fd_t connected_fd; /* helper */ /* Used for Unix domain sockets. */ struct sockaddr_un myaddr; struct sockaddr_un serveraddr; /* Structure used for unix domain sockets. */ struct { assuan_fd_t pendingfds[5]; /* Array to save received descriptors. */ int pendingfdscount; /* Number of received descriptors. */ } uds; gpg_error_t (*accept_handler)(assuan_context_t); void (*finish_handler)(assuan_context_t); struct cmdtbl_s *cmdtbl; size_t cmdtbl_used; /* used entries */ size_t cmdtbl_size; /* allocated size of table */ /* The name of the command currently processed by a command handler. This is a pointer into CMDTBL. NULL if not in a command handler. */ const char *current_cmd_name; assuan_handler_t bye_notify_fnc; assuan_handler_t reset_notify_fnc; assuan_handler_t cancel_notify_fnc; gpg_error_t (*option_handler_fnc)(assuan_context_t,const char*, const char*); assuan_handler_t input_notify_fnc; assuan_handler_t output_notify_fnc; /* This function is called right before a command handler is called. */ gpg_error_t (*pre_cmd_notify_fnc)(assuan_context_t, const char *cmd); /* This function is called right after a command has been processed. It may be used to command related cleanup. */ void (*post_cmd_notify_fnc)(assuan_context_t, gpg_error_t); assuan_fd_t input_fd; /* Set by the INPUT command. */ assuan_fd_t output_fd; /* Set by the OUTPUT command. */ }; /* Generate an error code specific to a context. */ static GPG_ERR_INLINE gpg_error_t _assuan_error (assuan_context_t ctx, gpg_err_code_t errcode) { return gpg_err_make (ctx?ctx->err_source: GPG_ERR_SOURCE_ASSUAN, errcode); } /* Release all resources associated with an engine operation. */ void _assuan_reset (assuan_context_t ctx); /* Default log handler. */ int _assuan_log_handler (assuan_context_t ctx, void *hook, unsigned int cat, const char *msg); /* Manage memory specific to a context. */ void *_assuan_malloc (assuan_context_t ctx, size_t cnt); void *_assuan_realloc (assuan_context_t ctx, void *ptr, size_t cnt); void *_assuan_calloc (assuan_context_t ctx, size_t cnt, size_t elsize); void _assuan_free (assuan_context_t ctx, void *ptr); /* System hooks. */ void _assuan_usleep (assuan_context_t ctx, unsigned int usec); int _assuan_pipe (assuan_context_t ctx, assuan_fd_t fd[2], int inherit_idx); int _assuan_close (assuan_context_t ctx, assuan_fd_t fd); int _assuan_close_inheritable (assuan_context_t ctx, assuan_fd_t fd); ssize_t _assuan_read (assuan_context_t ctx, assuan_fd_t fd, void *buffer, size_t size); ssize_t _assuan_write (assuan_context_t ctx, assuan_fd_t fd, const void *buffer, size_t size); int _assuan_recvmsg (assuan_context_t ctx, assuan_fd_t fd, assuan_msghdr_t msg, int flags); int _assuan_sendmsg (assuan_context_t ctx, assuan_fd_t fd, assuan_msghdr_t msg, int flags); int _assuan_spawn (assuan_context_t ctx, pid_t *r_pid, const char *name, const char *argv[], assuan_fd_t fd_in, assuan_fd_t fd_out, assuan_fd_t *fd_child_list, void (*atfork) (void *opaque, int reserved), void *atforkvalue, unsigned int flags); pid_t _assuan_waitpid (assuan_context_t ctx, pid_t pid, int nowait, int *status, int options); int _assuan_socketpair (assuan_context_t ctx, int namespace, int style, int protocol, assuan_fd_t filedes[2]); int _assuan_socket (assuan_context_t ctx, int namespace, int style, int protocol); int _assuan_connect (assuan_context_t ctx, int sock, struct sockaddr *addr, socklen_t length); extern struct assuan_system_hooks _assuan_system_hooks; /* Copy the system hooks struct, paying attention to version differences. SRC is usually from the user, DST MUST be from the library. */ void _assuan_system_hooks_copy (assuan_system_hooks_t dst, assuan_system_hooks_t src); /*-- assuan-pipe-server.c --*/ void _assuan_release_context (assuan_context_t ctx); /*-- assuan-uds.c --*/ void _assuan_uds_close_fds (assuan_context_t ctx); void _assuan_uds_deinit (assuan_context_t ctx); void _assuan_init_uds_io (assuan_context_t ctx); /*-- assuan-handler.c --*/ gpg_error_t _assuan_register_std_commands (assuan_context_t ctx); /*-- assuan-buffer.c --*/ gpg_error_t _assuan_read_line (assuan_context_t ctx); int _assuan_cookie_write_data (void *cookie, const char *buffer, size_t size); int _assuan_cookie_write_flush (void *cookie); gpg_error_t _assuan_write_line (assuan_context_t ctx, const char *prefix, const char *line, size_t len); /*-- client.c --*/ gpg_error_t _assuan_read_from_server (assuan_context_t ctx, assuan_response_t *okay, int *off, int convey_comments); /*-- assuan-error.c --*/ /*-- assuan-inquire.c --*/ gpg_error_t _assuan_inquire_ext_cb (assuan_context_t ctx); void _assuan_inquire_release (assuan_context_t ctx); /* Check if ERR means EAGAIN. */ int _assuan_error_is_eagain (assuan_context_t ctx, gpg_error_t err); #define set_error(c,e,t) \ assuan_set_error ((c), _assuan_error (c,e), (t)) #ifdef HAVE_W32_SYSTEM char *_assuan_w32_strerror (assuan_context_t ctx, int ec); #endif /*HAVE_W32_SYSTEM*/ /*-- assuan-logging.c --*/ void _assuan_init_log_envvars (void); void _assuan_log_control_channel (assuan_context_t ctx, int outbound, const char *string, const void *buffer1, size_t length1, const void *buffer2, size_t length2); /*-- assuan-io.c --*/ ssize_t _assuan_simple_read (assuan_context_t ctx, void *buffer, size_t size); ssize_t _assuan_simple_write (assuan_context_t ctx, const void *buffer, size_t size); /*-- assuan-socket.c --*/ assuan_fd_t _assuan_sock_new (assuan_context_t ctx, int domain, int type, int proto); int _assuan_sock_connect (assuan_context_t ctx, assuan_fd_t sockfd, struct sockaddr *addr, int addrlen); int _assuan_sock_bind (assuan_context_t ctx, assuan_fd_t sockfd, struct sockaddr *addr, int addrlen); int _assuan_sock_set_sockaddr_un (const char *fname, struct sockaddr *addr, int *r_redirected); int _assuan_sock_get_nonce (assuan_context_t ctx, struct sockaddr *addr, int addrlen, assuan_sock_nonce_t *nonce); int _assuan_sock_check_nonce (assuan_context_t ctx, assuan_fd_t fd, assuan_sock_nonce_t *nonce); #ifdef HAVE_W32_SYSTEM +wchar_t *_assuan_utf8_to_wchar (const char *string); int _assuan_sock_wsa2errno (int err); #endif #ifdef HAVE_FOPENCOOKIE /* We have to implement funopen in terms of glibc's fopencookie. */ FILE *_assuan_funopen(void *cookie, cookie_read_function_t *readfn, cookie_write_function_t *writefn, cookie_seek_function_t *seekfn, cookie_close_function_t *closefn); #define funopen(a,r,w,s,c) _assuan_funopen ((a), (r), (w), (s), (c)) #endif /*HAVE_FOPENCOOKIE*/ /*-- sysutils.c --*/ const char *_assuan_sysutils_blurb (void); #ifdef HAVE_W32CE_SYSTEM #define getpid() GetCurrentProcessId () char *_assuan_getenv (const char *name); #define getenv(a) _assuan_getenv ((a)) #endif /*HAVE_W32CE_SYSTEM*/ /* Prototypes for replacement functions. */ #ifndef HAVE_MEMRCHR void *memrchr (const void *block, int c, size_t size); #endif #ifndef HAVE_STPCPY char *stpcpy (char *dest, const char *src); #endif #ifndef HAVE_SETENV #define setenv _assuan_setenv #define unsetenv _assuan_unsetenv #define clearenv _assuan_clearenv int setenv (const char *name, const char *value, int replace); #endif #ifndef HAVE_PUTC_UNLOCKED int putc_unlocked (int c, FILE *stream); #endif #define DIM(v) (sizeof(v)/sizeof((v)[0])) /* To avoid that a compiler optimizes memset calls away, these macros can be used. */ #define wipememory2(_ptr,_set,_len) do { \ volatile char *_vptr=(volatile char *)(_ptr); \ size_t _vlen=(_len); \ while(_vlen) { *_vptr=(_set); _vptr++; _vlen--; } \ } while(0) #define wipememory(_ptr,_len) wipememory2(_ptr,0,_len) #if HAVE_W64_SYSTEM # define SOCKET2HANDLE(s) ((void *)(uintptr_t)(s)) # define HANDLE2SOCKET(h) ((uintptr_t)(h)) #elif HAVE_W32_SYSTEM # define SOCKET2HANDLE(s) ((void *)(s)) # define HANDLE2SOCKET(h) ((unsigned int)(h)) #else # define SOCKET2HANDLE(s) (s) # define HANDLE2SOCKET(h) (h) #endif void _assuan_client_finish (assuan_context_t ctx); void _assuan_client_release (assuan_context_t ctx); void _assuan_server_finish (assuan_context_t ctx); void _assuan_server_release (assuan_context_t ctx); /* Encode the C formatted string SRC and return the malloc'ed result. */ char *_assuan_encode_c_string (assuan_context_t ctx, const char *src); #endif /*ASSUAN_DEFS_H*/ diff --git a/src/assuan-socket.c b/src/assuan-socket.c index 9a24f1a..97b2312 100644 --- a/src/assuan-socket.c +++ b/src/assuan-socket.c @@ -1,1532 +1,1532 @@ /* assuan-socket.c - Socket wrapper * Copyright (C) 2004, 2005, 2009 Free Software Foundation, Inc. * Copyright (C) 2001-2015 g10 Code GmbH * * This file is part of Assuan. * * Assuan is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * Assuan is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1+ */ #ifdef HAVE_CONFIG_H #include #endif #include #include #ifdef HAVE_W32_SYSTEM # define WIN32_LEAN_AND_MEAN # include # include #ifndef HAVE_W32CE_SYSTEM # include #endif #else # include # include # include # include #endif #include #ifdef HAVE_SYS_STAT_H # include #endif #ifdef HAVE_FCNTL_H #include #endif #include #include "assuan-defs.h" #include "debug.h" /* Hacks for Slowaris. */ #ifndef PF_LOCAL # ifdef PF_UNIX # define PF_LOCAL PF_UNIX # else # define PF_LOCAL AF_UNIX # endif #endif #ifndef AF_LOCAL # define AF_LOCAL AF_UNIX #endif #ifdef HAVE_W32_SYSTEM #ifndef S_IRUSR # define S_IRUSR 0 # define S_IWUSR 0 #endif #ifndef S_IRGRP # define S_IRGRP 0 # define S_IWGRP 0 #endif #ifndef ENOTSUP #define ENOTSUP 129 #endif #ifndef EPROTO #define EPROTO 134 #endif #ifndef EPROTONOSUPPORT #define EPROTONOSUPPORT 135 #endif #ifndef ENETDOWN #define ENETDOWN 116 #endif #ifndef ENETUNREACH #define ENETUNREACH 118 #endif #ifndef EHOSTUNREACH #define EHOSTUNREACH 110 #endif #ifndef ECONNREFUSED #define ECONNREFUSED 107 #endif #ifndef ETIMEDOUT #define ETIMEDOUT 138 #endif #endif #ifndef ENAMETOOLONG # define ENAMETOOLONG EINVAL #endif #ifndef SUN_LEN # define SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path) \ + strlen ((ptr)->sun_path)) #endif #ifndef INADDR_LOOPBACK # define INADDR_LOOPBACK ((in_addr_t) 0x7f000001) /* 127.0.0.1. */ #endif /* The standard SOCKS and TOR port. */ #define SOCKS_PORT 1080 #define TOR_PORT 9050 #define TOR_PORT2 9150 /* The Tor browser is listening there. */ /* In the future, we can allow access to sock_ctx, if that context's hook functions need to be overridden. There can only be one global assuan_sock_* user (one library or one application) with this convenience interface, if non-standard hook functions are needed. */ static assuan_context_t sock_ctx; /* This global flag can be set using assuan_sock_set_flag to enable TOR or SOCKS mode for all sockets. It may not be reset. The value is the port to be used. */ static unsigned short tor_mode; #ifdef HAVE_W32_SYSTEM /* A table of active Cygwin connections. This is only used for listening socket which should be only a few. We do not enter sockets after a connect into this table. */ static assuan_fd_t cygwin_fdtable[16]; /* A critical section to guard access to the table of Cygwin connections. */ static CRITICAL_SECTION cygwin_fdtable_cs; /* Return true if SOCKFD is listed as Cygwin socket. */ static int is_cygwin_fd (assuan_fd_t sockfd) { int ret = 0; int i; EnterCriticalSection (&cygwin_fdtable_cs); for (i=0; i < DIM(cygwin_fdtable); i++) { if (cygwin_fdtable[i] == sockfd) { ret = 1; break; } } LeaveCriticalSection (&cygwin_fdtable_cs); return ret; } /* Insert SOCKFD into the table of Cygwin sockets. Return 0 on success or -1 on error. */ static int insert_cygwin_fd (assuan_fd_t sockfd) { int ret = 0; int mark = -1; int i; EnterCriticalSection (&cygwin_fdtable_cs); for (i=0; i < DIM(cygwin_fdtable); i++) { if (cygwin_fdtable[i] == sockfd) goto leave; /* Already in table. */ else if (cygwin_fdtable[i] == ASSUAN_INVALID_FD) mark = i; } if (mark == -1) { gpg_err_set_errno (EMFILE); ret = -1; } else cygwin_fdtable[mark] = sockfd; leave: LeaveCriticalSection (&cygwin_fdtable_cs); return ret; } /* Delete SOCKFD from the table of Cygwin sockets. */ static void delete_cygwin_fd (assuan_fd_t sockfd) { int i; EnterCriticalSection (&cygwin_fdtable_cs); for (i=0; i < DIM(cygwin_fdtable); i++) { if (cygwin_fdtable[i] == sockfd) { cygwin_fdtable[i] = ASSUAN_INVALID_FD; break; } } LeaveCriticalSection (&cygwin_fdtable_cs); return; } -static wchar_t * -utf8_to_wchar (const char *string) +wchar_t * +_assuan_utf8_to_wchar (const char *string) { int n; size_t nbytes; wchar_t *result; if (!string) return NULL; n = MultiByteToWideChar (CP_UTF8, 0, string, -1, NULL, 0); if (n < 0) return NULL; nbytes = (size_t)(n+1) * sizeof(*result); if (nbytes / sizeof(*result) != (n+1)) { SetLastError (ERROR_INVALID_PARAMETER); return NULL; } result = malloc (nbytes); if (!result) return NULL; n = MultiByteToWideChar (CP_UTF8, 0, string, -1, result, n); if (n < 0) { n = GetLastError (); free (result); result = NULL; SetLastError (n); } return result; } static HANDLE MyCreateFile (LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwSharedMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) { wchar_t *filename; HANDLE result; int err; - filename = utf8_to_wchar (lpFileName); + filename = _assuan_utf8_to_wchar (lpFileName); if (!filename) return INVALID_HANDLE_VALUE; result = CreateFileW (filename, dwDesiredAccess, dwSharedMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); err = GetLastError (); free (filename); SetLastError (err); return result; } static int MyDeleteFile (LPCSTR lpFileName) { wchar_t *filename; int result, err; - filename = utf8_to_wchar (lpFileName); + filename = _assuan_utf8_to_wchar (lpFileName); if (!filename) return 0; result = DeleteFileW (filename); err = GetLastError (); free (filename); SetLastError (err); return result; } int _assuan_sock_wsa2errno (int err) { switch (err) { case WSAENOTSOCK: return EINVAL; case WSAEWOULDBLOCK: return EAGAIN; case ERROR_BROKEN_PIPE: return EPIPE; case WSANOTINITIALISED: return ENOSYS; case WSAECONNREFUSED: return ECONNREFUSED; default: return EIO; } } /* W32: Fill BUFFER with LENGTH bytes of random. Returns -1 on failure, 0 on success. Sets errno on failure. */ static int get_nonce (char *buffer, size_t nbytes) { HCRYPTPROV prov; int ret = -1; if (!CryptAcquireContext (&prov, NULL, NULL, PROV_RSA_FULL, (CRYPT_VERIFYCONTEXT|CRYPT_SILENT)) ) gpg_err_set_errno (ENODEV); else { if (!CryptGenRandom (prov, nbytes, (unsigned char *) buffer)) gpg_err_set_errno (ENODEV); else ret = 0; CryptReleaseContext (prov, 0); } return ret; } /* W32: The buffer for NONCE needs to be at least 16 bytes. Returns 0 on success and sets errno on failure. If FNAME has a Cygwin socket descriptor True is stored at CYGWIN. */ static int read_port_and_nonce (const char *fname, unsigned short *port, char *nonce, int *cygwin) { estream_t fp; char buffer[50], *p; size_t nread; int aval; *cygwin = 0; fp = gpgrt_fopen (fname, "rb"); if (!fp) return -1; nread = gpgrt_fread (buffer, 1, sizeof buffer - 1, fp); gpgrt_fclose (fp); if (!nread) { gpg_err_set_errno (ENOENT); return -1; } buffer[nread] = 0; if (!strncmp (buffer, "!", 10)) { /* This is the Cygwin compatible socket emulation. The format * of the file is: * * "!%u %c %08x-%08x-%08x-%08x\x00" * * %d for port number, %c for kind of socket (s for STREAM), and * we have 16-byte random bytes for nonce. We only support * stream mode. */ unsigned int u0; int narr[4]; if (sscanf (buffer+10, "%u s %08x-%08x-%08x-%08x", &u0, narr+0, narr+1, narr+2, narr+3) != 5 || u0 < 1 || u0 > 65535) { gpg_err_set_errno (EINVAL); return -1; } *port = u0; memcpy (nonce, narr, 16); *cygwin = 1; } else { /* This is our own socket emulation. */ aval = atoi (buffer); if (aval < 1 || aval > 65535) { gpg_err_set_errno (EINVAL); return -1; } *port = (unsigned int)aval; for (p=buffer; nread && *p != '\n'; p++, nread--) ; if (*p != '\n' || nread != 17) { gpg_err_set_errno (EINVAL); return -1; } p++; nread--; memcpy (nonce, p, 16); } return 0; } #endif /*HAVE_W32_SYSTEM*/ #ifndef HAVE_W32_SYSTEM /* Find a redirected socket name for fname and return a malloced setup filled sockaddr. If this does not work out NULL is returned and ERRNO is set. If the file seems to be a redirect True is stored at R_REDIRECT. Note that this function uses the standard malloc and not the assuan wrapped one. The format of the file is: %Assuan% socket=NAME where NAME is the actual socket to use. No white spaces are allowed, both lines must be terminated by a single LF, extra lines are not allowed. Environment variables are interpreted in NAME if given in "${VAR} notation; no escape characters are defined, if "${" shall be used verbatim, you need to use an environment variable with that content. The use of an absolute NAME is strongly suggested. The length of the file is limited to 511 bytes which is more than sufficient for that common value of 107 for sun_path. */ static struct sockaddr_un * eval_redirection (const char *fname, int *r_redirect) { FILE *fp; char buffer[512], *name; size_t n; struct sockaddr_un *addr; char *p, *pend; const char *s; *r_redirect = 0; fp = fopen (fname, "rb"); if (!fp) return NULL; n = fread (buffer, 1, sizeof buffer - 1, fp); fclose (fp); if (!n) { gpg_err_set_errno (ENOENT); return NULL; } buffer[n] = 0; /* Check that it is a redirection file. We also check that the first byte of the name is not a LF because that would lead to an zero length name. */ if (n < 17 || buffer[n-1] != '\n' || memcmp (buffer, "%Assuan%\nsocket=", 16) || buffer[16] == '\n') { gpg_err_set_errno (EINVAL); return NULL; } buffer[n-1] = 0; name = buffer + 16; *r_redirect = 1; addr = calloc (1, sizeof *addr); if (!addr) return NULL; addr->sun_family = AF_LOCAL; n = 0; for (p=name; *p; p++) { if (*p == '$' && p[1] == '{') { p += 2; pend = strchr (p, '}'); if (!pend) { free (addr); gpg_err_set_errno (EINVAL); return NULL; } *pend = 0; if (*p && (s = getenv (p))) { for (; *s; s++) { if (n < sizeof addr->sun_path - 1) addr->sun_path[n++] = *s; else { free (addr); gpg_err_set_errno (ENAMETOOLONG); return NULL; } } } p = pend; } else if (*p == '\n') break; /* Be nice and stop at the first LF. */ else if (n < sizeof addr->sun_path - 1) addr->sun_path[n++] = *p; else { free (addr); gpg_err_set_errno (ENAMETOOLONG); return NULL; } } return addr; } #endif /*!HAVE_W32_SYSTEM*/ /* Return a new socket. Note that under W32 we consider a socket the same as an System Handle; all functions using such a handle know about this dual use and act accordingly. */ assuan_fd_t _assuan_sock_new (assuan_context_t ctx, int domain, int type, int proto) { #ifdef HAVE_W32_SYSTEM assuan_fd_t res; if (domain == AF_UNIX || domain == AF_LOCAL) domain = AF_INET; res = SOCKET2HANDLE(_assuan_socket (ctx, domain, type, proto)); return res; #else return _assuan_socket (ctx, domain, type, proto); #endif } int _assuan_sock_set_flag (assuan_context_t ctx, assuan_fd_t sockfd, const char *name, int value) { (void)ctx; if (!strcmp (name, "cygwin")) { #ifdef HAVE_W32_SYSTEM if (!value) delete_cygwin_fd (sockfd); else if (insert_cygwin_fd (sockfd)) return -1; #else /* Setting the Cygwin flag on non-Windows is ignored. */ #endif } else if (!strcmp (name, "tor-mode") || !strcmp (name, "socks")) { /* If SOCKFD is ASSUAN_INVALID_FD this controls global flag to switch AF_INET and AF_INET6 into TOR mode by using a SOCKS5 proxy on localhost:9050. It may only be switched on and this needs to be done before any new threads are started. Once TOR mode has been enabled, TOR mode can be disabled for a specific socket by using SOCKFD with a VALUE of 0. */ if (sockfd == ASSUAN_INVALID_FD) { if (tor_mode && !value) { gpg_err_set_errno (EPERM); return -1; /* Clearing the global flag is not allowed. */ } else if (value) { if (*name == 's') tor_mode = SOCKS_PORT; else tor_mode = TOR_PORT; } } else if (tor_mode && sockfd != ASSUAN_INVALID_FD) { /* Fixme: Disable/enable tormode for the given context. */ } else { gpg_err_set_errno (EINVAL); return -1; } } else { gpg_err_set_errno (EINVAL); return -1; } return 0; } int _assuan_sock_get_flag (assuan_context_t ctx, assuan_fd_t sockfd, const char *name, int *r_value) { (void)ctx; if (!strcmp (name, "cygwin")) { #ifdef HAVE_W32_SYSTEM *r_value = is_cygwin_fd (sockfd); #else *r_value = 0; #endif } else if (!strcmp (name, "tor-mode")) { /* FIXME: Find tor-mode for the given socket. */ *r_value = tor_mode == TOR_PORT; } else if (!strcmp (name, "socks")) { *r_value = tor_mode == SOCKS_PORT; } else { gpg_err_set_errno (EINVAL); return -1; } return 0; } /* Read NBYTES from SOCKFD into BUFFER. Return 0 on success. Handle EAGAIN and EINTR. */ static int do_readn (assuan_context_t ctx, assuan_fd_t sockfd, void *buffer, size_t nbytes) { char *p = buffer; ssize_t n; while (nbytes) { n = _assuan_read (ctx, sockfd, p, nbytes); if (n < 0 && errno == EINTR) ; else if (n < 0 && errno == EAGAIN) _assuan_usleep (ctx, 100000); /* 100ms */ else if (n < 0) return -1; else if (!n) { gpg_err_set_errno (EIO); return -1; } else { p += n; nbytes -= n; } } return 0; } /* Write NBYTES from BUFFER to SOCKFD. Return 0 on success; on error return -1 and set ERRNO. */ static int do_writen (assuan_context_t ctx, assuan_fd_t sockfd, const void *buffer, size_t nbytes) { int ret; ret = _assuan_write (ctx, sockfd, buffer, nbytes); if (ret >= 0 && ret != nbytes) { gpg_err_set_errno (EIO); ret = -1; } else if (ret >= 0) ret = 0; return ret; } #define TIMEOUT_NOT_WAITING_SOCKS5_FOREVER 1 /* in second(s) */ /* Connect using the SOCKS5 protocol. */ static int socks5_connect (assuan_context_t ctx, assuan_fd_t sock, unsigned short socksport, const char *credentials, const char *hostname, unsigned short hostport, struct sockaddr *addr, socklen_t length) { int ret; /* struct sockaddr_in6 proxyaddr_in6; */ struct sockaddr_in proxyaddr_in; struct sockaddr *proxyaddr; size_t proxyaddrlen; union { struct sockaddr *addr; struct sockaddr_in *addr_in; struct sockaddr_in6 *addr_in6; } addru; unsigned char buffer[22+512]; /* The extra 512 gives enough space for username/password or the hostname. */ size_t buflen, hostnamelen; int method; fd_set fds; struct timeval tv = { TIMEOUT_NOT_WAITING_SOCKS5_FOREVER, 0 }; addru.addr = addr; FD_ZERO (&fds); FD_SET (HANDLE2SOCKET (sock), &fds); /* memset (&proxyaddr_in6, 0, sizeof proxyaddr_in6); */ memset (&proxyaddr_in, 0, sizeof proxyaddr_in); /* Either HOSTNAME or ADDR may be given. */ if (hostname && addr) { gpg_err_set_errno (EINVAL); return -1; } /* If a hostname is given it must fit into our buffer and it must be less than 256 so that its length can be encoded in one byte. */ hostnamelen = hostname? strlen (hostname) : 0; if (hostnamelen > 255) { gpg_err_set_errno (ENAMETOOLONG); return -1; } /* Connect to local host. */ /* Fixme: First try to use IPv6 but note that _assuan_sock_connect_byname created the socket with AF_INET. */ proxyaddr_in.sin_family = AF_INET; proxyaddr_in.sin_port = htons (socksport); proxyaddr_in.sin_addr.s_addr = htonl (INADDR_LOOPBACK); proxyaddr = (struct sockaddr *)&proxyaddr_in; proxyaddrlen = sizeof proxyaddr_in; ret = _assuan_connect (ctx, HANDLE2SOCKET (sock), proxyaddr, proxyaddrlen); if (ret && socksport == TOR_PORT && errno == ECONNREFUSED) { /* Standard Tor port failed - try the Tor browser port. */ proxyaddr_in.sin_port = htons (TOR_PORT2); ret = _assuan_connect (ctx, HANDLE2SOCKET (sock), proxyaddr, proxyaddrlen); } /* If we get an EINPROGRESS here the caller is trying to do a * non-blocking connect (e.g. for custom time out handling) which * fails here. The easiest fix would be to allow the client to tell * us the timeout value and we do the timeout handling later on in the * Socks protocol. */ if (ret) return ret; buffer[0] = 5; /* RFC-1928 VER field. */ buffer[1] = 1; /* NMETHODS */ if (credentials) method = 2; /* Method: username/password authentication. */ else method = 0; /* Method: No authentication required. */ buffer[2] = method; /* Negotiate method. */ ret = do_writen (ctx, sock, buffer, 3); if (ret) return ret; /* There may be a different service at the port, which doesn't respond. Not to be bothred by such a service. */ /* FIXME: Since the process may block on select, it should be npth_select to release thread scheduling if nPth is enabled. Ideally, select is better to be in the system hooks. However, it is considered OK to use select directly; Normal use case is three steps: detect SOCKS5 service before nPth use, configure nPth system hooks, and then use socks5_connect. For the first call, select indeed blocks, but it's only single thread. For succeeding calls, this select should soon return successfully. */ ret = select (HANDLE2SOCKET (sock)+1, &fds, NULL, NULL, &tv); if (!ret) { gpg_err_set_errno (ETIMEDOUT); return -1; } ret = do_readn (ctx, sock, buffer, 2); if (ret) return ret; if (buffer[0] != 5 || buffer[1] != method ) { /* Socks server returned wrong version or does not support our requested method. */ gpg_err_set_errno (ENOTSUP); /* Fixme: Is there a better errno? */ return -1; } if (credentials) { const char *password; int ulen, plen; password = strchr (credentials, ':'); if (!password) { gpg_err_set_errno (EINVAL); /* No password given. */ return -1; } ulen = password - credentials; password++; plen = strlen (password); if (!ulen || ulen > 255 || !plen || plen > 255) { gpg_err_set_errno (EINVAL); return -1; } buffer[0] = 1; /* VER of the sub-negotiation. */ buffer[1] = ulen; buflen = 2; memcpy (buffer+buflen, credentials, ulen); buflen += ulen; buffer[buflen++] = plen; memcpy (buffer+buflen, password, plen); buflen += plen; ret = do_writen (ctx, sock, buffer, buflen); wipememory (buffer, buflen); if (ret) return ret; ret = do_readn (ctx, sock, buffer, 2); if (ret) return ret; if (buffer[0] != 1) { /* SOCKS server returned wrong version. */ gpg_err_set_errno (EPROTONOSUPPORT); return -1; } if (buffer[1]) { /* SOCKS server denied access. */ gpg_err_set_errno (EACCES); return -1; } } if (hostname && !*hostname && !hostport) { /* Empty hostname given. Stop right here to allow the caller to do the actual proxy request. */ return 0; } /* Send request details (rfc-1928, 4). */ buffer[0] = 5; /* VER */ buffer[1] = 1; /* CMD = CONNECT */ buffer[2] = 0; /* RSV */ if (hostname) { buffer[3] = 3; /* ATYP = DOMAINNAME */ buflen = 4; buffer[buflen++] = hostnamelen; memcpy (buffer+buflen, hostname, hostnamelen); buflen += hostnamelen; buffer[buflen++] = (hostport >> 8); /* DST.PORT */ buffer[buflen++] = hostport; } else if (addr->sa_family == AF_INET6) { buffer[3] = 4; /* ATYP = IPv6 */ memcpy (buffer+ 4, &addru.addr_in6->sin6_addr.s6_addr, 16); /* DST.ADDR */ memcpy (buffer+20, &addru.addr_in6->sin6_port, 2); /* DST.PORT */ buflen = 22; } else { buffer[3] = 1; /* ATYP = IPv4 */ memcpy (buffer+4, &addru.addr_in->sin_addr.s_addr, 4); /* DST.ADDR */ memcpy (buffer+8, &addru.addr_in->sin_port, 2); /* DST.PORT */ buflen = 10; } ret = do_writen (ctx, sock, buffer, buflen); if (ret) return ret; ret = do_readn (ctx, sock, buffer, 10 /* Length for IPv4 */); if (ret) return ret; if (buffer[0] != 5 || buffer[2] != 0 ) { /* Socks server returned wrong version or the reserved field is not zero. */ gpg_err_set_errno (EPROTONOSUPPORT); return -1; } if (buffer[1]) { switch (buffer[1]) { case 0x01: /* General SOCKS server failure. */ gpg_err_set_errno (ENETDOWN); break; case 0x02: /* Connection not allowed by ruleset. */ gpg_err_set_errno (EACCES); break; case 0x03: /* Network unreachable */ gpg_err_set_errno (ENETUNREACH); break; case 0x04: /* Host unreachable */ gpg_err_set_errno (EHOSTUNREACH); break; case 0x05: /* Connection refused */ gpg_err_set_errno (ECONNREFUSED); break; case 0x06: /* TTL expired */ gpg_err_set_errno (ETIMEDOUT); break; case 0x08: /* Address type not supported */ gpg_err_set_errno (EPROTONOSUPPORT); break; case 0x07: /* Command not supported */ default: gpg_err_set_errno (ENOTSUP); /* Fixme: Is there a better errno? */ } return -1; } if (buffer[3] == 4) { /* ATYP indicates a v6 address. We need to read the remaining 12 bytes. */ ret = do_readn (ctx, sock, buffer+10, 12); if (ret) return ret; } /* FIXME: We have not way to store the actual address used by the server. */ return 0; } /* Return true if SOCKS shall be used. This is the case if tor_mode is enabled and the desired address is not the loopback address. */ static int use_socks (struct sockaddr *addr) { union { struct sockaddr *addr; struct sockaddr_in *addr_in; struct sockaddr_in6 *addr_in6; } addru; addru.addr = addr; if (!tor_mode) return 0; else if (addr->sa_family == AF_INET6) { const unsigned char *s; int i; s = (unsigned char *)&addru.addr_in6->sin6_addr.s6_addr; if (s[15] != 1) return 1; /* Last octet is not 1 - not the loopback address. */ for (i=0; i < 15; i++, s++) if (*s) return 1; /* Non-zero octet found - not the loopback address. */ return 0; /* This is the loopback address. */ } else if (addr->sa_family == AF_INET) { if (*(unsigned char*)&addru.addr_in->sin_addr.s_addr == 127) return 0; /* Loopback (127.0.0.0/8) */ return 1; } else return 0; } int _assuan_sock_connect (assuan_context_t ctx, assuan_fd_t sockfd, struct sockaddr *addr, int addrlen) { #ifdef HAVE_W32_SYSTEM if (addr->sa_family == AF_LOCAL || addr->sa_family == AF_UNIX) { struct sockaddr_in myaddr; struct sockaddr_un *unaddr; unsigned short port; char nonce[16]; int cygwin; int ret; unaddr = (struct sockaddr_un *)addr; if (read_port_and_nonce (unaddr->sun_path, &port, nonce, &cygwin)) return -1; myaddr.sin_family = AF_INET; myaddr.sin_port = htons (port); myaddr.sin_addr.s_addr = htonl (INADDR_LOOPBACK); /* Set return values. */ unaddr->sun_family = myaddr.sin_family; unaddr->sun_port = myaddr.sin_port; unaddr->sun_addr.s_addr = myaddr.sin_addr.s_addr; ret = _assuan_connect (ctx, HANDLE2SOCKET(sockfd), (struct sockaddr *)&myaddr, sizeof myaddr); if (!ret) { /* Send the nonce. */ ret = do_writen (ctx, sockfd, nonce, 16); if (!ret && cygwin) { char buffer[16]; /* The client sends the nonce back - not useful. We do a dummy read. */ ret = do_readn (ctx, sockfd, buffer, 16); if (!ret) { /* Send our credentials. */ int n = getpid (); memcpy (buffer, &n, 4); memset (buffer+4, 0, 4); /* uid = gid = 0 */ ret = do_writen (ctx, sockfd, buffer, 8); if (!ret) { /* Receive credentials. We don't need them. */ ret = do_readn (ctx, sockfd, buffer, 8); } } } } return ret; } else if (use_socks (addr)) { return socks5_connect (ctx, sockfd, tor_mode, NULL, NULL, 0, addr, addrlen); } else { return _assuan_connect (ctx, HANDLE2SOCKET (sockfd), addr, addrlen); } #else # if HAVE_STAT if (addr->sa_family == AF_LOCAL || addr->sa_family == AF_UNIX) { struct sockaddr_un *unaddr; struct stat statbuf; int redirect, res; unaddr = (struct sockaddr_un *)addr; if (!stat (unaddr->sun_path, &statbuf) && !S_ISSOCK (statbuf.st_mode) && S_ISREG (statbuf.st_mode)) { /* The given socket file is not a socket but a regular file. We use the content of that file to redirect to another socket file. This can be used to use sockets on file systems which do not support sockets or if for example a home directory is shared by several machines. */ unaddr = eval_redirection (unaddr->sun_path, &redirect); if (unaddr) { res = _assuan_connect (ctx, sockfd, (struct sockaddr *)unaddr, SUN_LEN (unaddr)); free (unaddr); return res; } if (redirect) return -1; /* Continue using the standard connect. */ } } # endif /*HAVE_STAT*/ if (use_socks (addr)) { return socks5_connect (ctx, sockfd, tor_mode, NULL, NULL, 0, addr, addrlen); } else { return _assuan_connect (ctx, sockfd, addr, addrlen); } #endif } /* Connect to HOST specified as host name on PORT. The current implementation requires that either the flags ASSUAN_SOCK_SOCKS or ASSUAN_SOCK_TOR are given in FLAGS. On success a new socket is returned; on error ASSUAN_INVALID_FD is returned and ERRNO set. If CREDENTIALS is not NULL, it is a string used for password based authentication. Username and password are separated by a colon. RESERVED must be 0. By passing HOST and PORT as 0 the function can be used to check for proxy availability: If the proxy is available a socket will be returned which the caller should then close. */ assuan_fd_t _assuan_sock_connect_byname (assuan_context_t ctx, const char *host, unsigned short port, int reserved, const char *credentials, unsigned int flags) { assuan_fd_t fd; unsigned short socksport; if ((flags & ASSUAN_SOCK_TOR)) socksport = TOR_PORT; else if ((flags & ASSUAN_SOCK_SOCKS)) socksport = SOCKS_PORT; else { gpg_err_set_errno (ENOTSUP); return ASSUAN_INVALID_FD; } if (host && !*host) { /* Error out early on an empty host name. See below. */ gpg_err_set_errno (EINVAL); return ASSUAN_INVALID_FD; } fd = _assuan_sock_new (ctx, AF_INET, SOCK_STREAM, 0); if (fd == ASSUAN_INVALID_FD) return fd; /* For HOST being NULL we pass an empty string which indicates to socks5_connect to stop midway during the proxy negotiation. Note that we can't pass NULL directly as this indicates IP address mode to the called function. */ if (socks5_connect (ctx, fd, socksport, credentials, host? host:"", port, NULL, 0)) { int save_errno = errno; assuan_sock_close (fd); gpg_err_set_errno (save_errno); return ASSUAN_INVALID_FD; } return fd; } int _assuan_sock_bind (assuan_context_t ctx, assuan_fd_t sockfd, struct sockaddr *addr, int addrlen) { #ifdef HAVE_W32_SYSTEM if (addr->sa_family == AF_LOCAL || addr->sa_family == AF_UNIX) { struct sockaddr_in myaddr; struct sockaddr_un *unaddr; HANDLE filehd; int len = sizeof myaddr; int rc; union { char data[16]; int aint[4]; } nonce; char tmpbuf[50+16]; DWORD nwritten; if (get_nonce (nonce.data, 16)) return -1; unaddr = (struct sockaddr_un *)addr; myaddr.sin_port = 0; myaddr.sin_family = AF_INET; myaddr.sin_addr.s_addr = htonl (INADDR_LOOPBACK); filehd = MyCreateFile (unaddr->sun_path, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); if (filehd == INVALID_HANDLE_VALUE) { if (GetLastError () == ERROR_FILE_EXISTS) gpg_err_set_errno (EADDRINUSE); return -1; } rc = bind (HANDLE2SOCKET (sockfd), (struct sockaddr *)&myaddr, len); if (!rc) rc = getsockname (HANDLE2SOCKET (sockfd), (struct sockaddr *)&myaddr, &len); if (rc) { int save_e = errno; CloseHandle (filehd); MyDeleteFile (unaddr->sun_path); gpg_err_set_errno (save_e); return rc; } if (is_cygwin_fd (sockfd)) { snprintf (tmpbuf, sizeof tmpbuf, "!%d s %08x-%08x-%08x-%08x", ntohs (myaddr.sin_port), nonce.aint[0], nonce.aint[1], nonce.aint[2], nonce.aint[3]); len = strlen (tmpbuf) + 1; } else { snprintf (tmpbuf, sizeof tmpbuf-16, "%d\n", ntohs (myaddr.sin_port)); len = strlen (tmpbuf); memcpy (tmpbuf+len, nonce.data,16); len += 16; } if (!WriteFile (filehd, tmpbuf, len, &nwritten, NULL)) { CloseHandle (filehd); MyDeleteFile (unaddr->sun_path); gpg_err_set_errno (EIO); return -1; } CloseHandle (filehd); return 0; } else { int res = bind (HANDLE2SOCKET(sockfd), addr, addrlen); if (res < 0) gpg_err_set_errno ( _assuan_sock_wsa2errno (WSAGetLastError ())); return res; } #else return bind (sockfd, addr, addrlen); #endif } /* Setup the ADDR structure for a Unix domain socket with the socket name FNAME. If this is a redirected socket and R_REDIRECTED is not NULL, it will be setup for the real socket. Returns 0 on success and stores 1 at R_REDIRECTED if it is a redirected socket. On error -1 is returned and ERRNO will be set. */ int _assuan_sock_set_sockaddr_un (const char *fname, struct sockaddr *addr, int *r_redirected) { struct sockaddr_un *unaddr = (struct sockaddr_un *)addr; #if !defined(HAVE_W32_SYSTEM) && defined(HAVE_STAT) struct stat statbuf; #endif if (r_redirected) *r_redirected = 0; #if !defined(HAVE_W32_SYSTEM) && defined(HAVE_STAT) if (r_redirected && !stat (fname, &statbuf) && !S_ISSOCK (statbuf.st_mode) && S_ISREG (statbuf.st_mode)) { /* The given socket file is not a socket but a regular file. We use the content of that file to redirect to another socket file. This can be used to use sockets on file systems which do not support sockets or if for example a home directory is shared by several machines. */ struct sockaddr_un *unaddr_new; int redirect; unaddr_new = eval_redirection (fname, &redirect); if (unaddr_new) { memcpy (unaddr, unaddr_new, sizeof *unaddr); free (unaddr_new); *r_redirected = 1; return 0; } if (redirect) { *r_redirected = 1; return -1; /* Error. */ } /* Fallback to standard setup. */ } #endif /*!HAVE_W32_SYSTEM && HAVE_STAT*/ if (strlen (fname)+1 >= sizeof unaddr->sun_path) { gpg_err_set_errno (ENAMETOOLONG); return -1; } memset (unaddr, 0, sizeof *unaddr); unaddr->sun_family = AF_LOCAL; strncpy (unaddr->sun_path, fname, sizeof unaddr->sun_path - 1); unaddr->sun_path[sizeof unaddr->sun_path - 1] = 0; return 0; } int _assuan_sock_get_nonce (assuan_context_t ctx, struct sockaddr *addr, int addrlen, assuan_sock_nonce_t *nonce) { #ifdef HAVE_W32_SYSTEM if (addr->sa_family == AF_LOCAL || addr->sa_family == AF_UNIX) { struct sockaddr_un *unaddr; unsigned short port; int dummy; if (sizeof nonce->nonce != 16) { gpg_err_set_errno (EINVAL); return -1; } nonce->length = 16; unaddr = (struct sockaddr_un *)addr; if (read_port_and_nonce (unaddr->sun_path, &port, nonce->nonce, &dummy)) return -1; } else { nonce->length = 42; /* Arbitrary value to detect unitialized nonce. */ nonce->nonce[0] = 42; } #else (void)addr; (void)addrlen; nonce->length = 0; #endif return 0; } int _assuan_sock_check_nonce (assuan_context_t ctx, assuan_fd_t fd, assuan_sock_nonce_t *nonce) { #ifdef HAVE_W32_SYSTEM char buffer[16]; int n; if (sizeof nonce->nonce != 16) { gpg_err_set_errno (EINVAL); return -1; } if (nonce->length == 42 && nonce->nonce[0] == 42) return 0; /* Not a Unix domain socket. */ if (nonce->length != 16) { gpg_err_set_errno (EINVAL); return -1; } if (do_readn (ctx, fd, buffer, 16)) return -1; if (memcmp (buffer, nonce->nonce, 16)) { gpg_err_set_errno (EACCES); return -1; } if (is_cygwin_fd (fd)) { /* Send the nonce back to the client. */ if (do_writen (ctx, fd, buffer, 16)) return -1; /* Read the credentials. Cygwin uses the struct ucred { pid_t pid; uid_t uid; gid_t gid; }; with pid_t being an int (4 bytes) and uid_t and gid_t being shorts (2 bytes). Thus we need to read 8 bytes. However we we ignore the values because they are not kernel controlled. */ if (do_readn (ctx, fd, buffer, 8)) return -1; /* Send our credentials: We use the uid and gid we received but our own pid. */ n = getpid (); memcpy (buffer, &n, 4); if (do_writen (ctx, fd, buffer, 8)) return -1; } #else (void)fd; (void)nonce; #endif return 0; } /* Public API. */ gpg_error_t assuan_sock_init () { gpg_error_t err; #ifdef HAVE_W32_SYSTEM WSADATA wsadat; #endif if (sock_ctx != NULL) return 0; #ifdef HAVE_W32_SYSTEM InitializeCriticalSection (&cygwin_fdtable_cs); #endif err = assuan_new (&sock_ctx); #ifdef HAVE_W32_SYSTEM if (! err) WSAStartup (0x202, &wsadat); #endif return err; } void assuan_sock_deinit () { if (sock_ctx == NULL) return; #ifdef HAVE_W32_SYSTEM WSACleanup (); #endif assuan_release (sock_ctx); sock_ctx = NULL; #ifdef HAVE_W32_SYSTEM DeleteCriticalSection (&cygwin_fdtable_cs); #endif } int assuan_sock_close (assuan_fd_t fd) { #ifdef HAVE_W32_SYSTEM if (fd != ASSUAN_INVALID_FD) delete_cygwin_fd (fd); #endif return _assuan_close (sock_ctx, fd); } assuan_fd_t assuan_sock_new (int domain, int type, int proto) { return _assuan_sock_new (sock_ctx, domain, type, proto); } int assuan_sock_set_flag (assuan_fd_t sockfd, const char *name, int value) { return _assuan_sock_set_flag (sock_ctx, sockfd, name, value); } int assuan_sock_get_flag (assuan_fd_t sockfd, const char *name, int *r_value) { return _assuan_sock_get_flag (sock_ctx, sockfd, name, r_value); } int assuan_sock_connect (assuan_fd_t sockfd, struct sockaddr *addr, int addrlen) { return _assuan_sock_connect (sock_ctx, sockfd, addr, addrlen); } assuan_fd_t assuan_sock_connect_byname (const char *host, unsigned short port, int reserved, const char *credentials, unsigned int flags) { return _assuan_sock_connect_byname (sock_ctx, host, port, reserved, credentials, flags); } int assuan_sock_bind (assuan_fd_t sockfd, struct sockaddr *addr, int addrlen) { return _assuan_sock_bind (sock_ctx, sockfd, addr, addrlen); } int assuan_sock_set_sockaddr_un (const char *fname, struct sockaddr *addr, int *r_redirected) { return _assuan_sock_set_sockaddr_un (fname, addr, r_redirected); } int assuan_sock_get_nonce (struct sockaddr *addr, int addrlen, assuan_sock_nonce_t *nonce) { return _assuan_sock_get_nonce (sock_ctx, addr, addrlen, nonce); } int assuan_sock_check_nonce (assuan_fd_t fd, assuan_sock_nonce_t *nonce) { return _assuan_sock_check_nonce (sock_ctx, fd, nonce); } void assuan_sock_set_system_hooks (assuan_system_hooks_t system_hooks) { if (sock_ctx) _assuan_system_hooks_copy (&sock_ctx->system, system_hooks); } diff --git a/src/system-w32.c b/src/system-w32.c index 9547ce0..a0e9a98 100644 --- a/src/system-w32.c +++ b/src/system-w32.c @@ -1,596 +1,610 @@ /* system-w32.c - System support functions for Windows. * Copyright (C) 2009, 2010 Free Software Foundation, Inc. * * This file is part of Assuan. * * Assuan is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * Assuan is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1+ */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include "assuan-defs.h" #include "debug.h" assuan_fd_t assuan_fdopen (int fd) { assuan_fd_t ifd = (assuan_fd_t) _get_osfhandle (fd); assuan_fd_t ofd; if (! DuplicateHandle(GetCurrentProcess(), ifd, GetCurrentProcess(), &ofd, 0, TRUE, DUPLICATE_SAME_ACCESS)) { gpg_err_set_errno (EIO); return ASSUAN_INVALID_FD; } return ofd; } /* Sleep for the given number of microseconds. Default implementation. */ void __assuan_usleep (assuan_context_t ctx, unsigned int usec) { if (!usec) return; Sleep (usec / 1000); } /* Three simple wrappers, only used because thes function are named in the def file. */ HANDLE _assuan_w32ce_prepare_pipe (int *r_rvid, int write_end) { (void)r_rvid; (void)write_end; return INVALID_HANDLE_VALUE; } HANDLE _assuan_w32ce_finish_pipe (int rvid, int write_end) { (void)rvid; (void)write_end; return INVALID_HANDLE_VALUE; } DWORD _assuan_w32ce_create_pipe (HANDLE *read_hd, HANDLE *write_hd, LPSECURITY_ATTRIBUTES sec_attr, DWORD size) { return CreatePipe (read_hd, write_hd, sec_attr, size); } /* Create a pipe with one inheritable end. Default implementation. */ int __assuan_pipe (assuan_context_t ctx, assuan_fd_t fd[2], int inherit_idx) { HANDLE rh; HANDLE wh; HANDLE th; SECURITY_ATTRIBUTES sec_attr; memset (&sec_attr, 0, sizeof (sec_attr)); sec_attr.nLength = sizeof (sec_attr); sec_attr.bInheritHandle = FALSE; if (!CreatePipe (&rh, &wh, &sec_attr, 0)) { TRACE1 (ctx, ASSUAN_LOG_SYSIO, "__assuan_pipe", ctx, "CreatePipe failed: %s", _assuan_w32_strerror (ctx, -1)); gpg_err_set_errno (EIO); return -1; } if (! DuplicateHandle (GetCurrentProcess(), (inherit_idx == 0) ? rh : wh, GetCurrentProcess(), &th, 0, TRUE, DUPLICATE_SAME_ACCESS )) { TRACE1 (ctx, ASSUAN_LOG_SYSIO, "__assuan_pipe", ctx, "DuplicateHandle failed: %s", _assuan_w32_strerror (ctx, -1)); CloseHandle (rh); CloseHandle (wh); gpg_err_set_errno (EIO); return -1; } if (inherit_idx == 0) { CloseHandle (rh); rh = th; } else { CloseHandle (wh); wh = th; } fd[0] = rh; fd[1] = wh; return 0; } /* Close the given file descriptor, created with _assuan_pipe or one of the socket functions. Default implementation. */ int __assuan_close (assuan_context_t ctx, assuan_fd_t fd) { int rc = closesocket (HANDLE2SOCKET(fd)); if (rc) gpg_err_set_errno ( _assuan_sock_wsa2errno (WSAGetLastError ()) ); if (rc && WSAGetLastError () == WSAENOTSOCK) { rc = CloseHandle (fd); if (rc) /* FIXME. */ gpg_err_set_errno (EIO); } return rc; } /* Return true if HD refers to a socket. */ static int is_socket (HANDLE hd) { /* We need to figure out whether we are working on a socket or on a handle. A trivial way would be to check for the return code of recv and see if it is WSAENOTSOCK. However the recv may block after the server process died and thus the destroy_reader will hang. Another option is to use getsockopt to test whether it is a socket. The bug here is that once a socket with a certain values has been opened, closed and later a CreatePipe returned the same value (i.e. handle), getsockopt still believes it is a socket. What we do now is to use a combination of GetFileType and GetNamedPipeInfo. The specs say that the latter may be used on anonymous pipes as well. Note that there are claims that since winsocket version 2 ReadFile may be used on a socket but only if it is supported by the service provider. Tests on a stock XP using a local TCP socket show that it does not work. */ DWORD dummyflags, dummyoutsize, dummyinsize, dummyinst; if (GetFileType (hd) == FILE_TYPE_PIPE && !GetNamedPipeInfo (hd, &dummyflags, &dummyoutsize, &dummyinsize, &dummyinst)) return 1; /* Function failed; thus we assume it is a socket. */ else return 0; /* Success; this is not a socket. */ } ssize_t __assuan_read (assuan_context_t ctx, assuan_fd_t fd, void *buffer, size_t size) { int res; int ec = 0; if (is_socket (fd)) { int tries = 3; again: ec = 0; res = recv (HANDLE2SOCKET (fd), buffer, size, 0); if (res == -1) ec = WSAGetLastError (); if (ec == WSAEWOULDBLOCK && tries--) { /* EAGAIN: Use select to wait for resources and try again. We do this 3 times and then give up. The higher level layer then needs to take care of EAGAIN. No need to specify a timeout - the socket is not expected to be in blocking mode. */ fd_set fds; FD_ZERO (&fds); FD_SET (HANDLE2SOCKET (fd), &fds); select (0, &fds, NULL, NULL, NULL); goto again; } } else { DWORD nread = 0; if (!ReadFile (fd, buffer, size, &nread, NULL)) { res = -1; ec = GetLastError (); } else res = nread; } if (res == -1) { switch (ec) { case WSAENOTSOCK: gpg_err_set_errno (EBADF); break; case WSAEWOULDBLOCK: gpg_err_set_errno (EAGAIN); break; case WSAECONNRESET: /* Due to the use of recv. */ case ERROR_BROKEN_PIPE: gpg_err_set_errno (EPIPE); break; default: gpg_err_set_errno (EIO); break; } } return res; } ssize_t __assuan_write (assuan_context_t ctx, assuan_fd_t fd, const void *buffer, size_t size) { int res; int ec = 0; if (is_socket (fd)) { int tries = 3; again: ec = 0; res = send (HANDLE2SOCKET (fd), buffer, size, 0); if (res == -1) ec = WSAGetLastError (); if (ec == WSAEWOULDBLOCK && tries--) { /* EAGAIN: Use select to wait for resources and try again. We do this 3 times and then give up. The higher level layer then needs to take care of EAGAIN. No need to specify a timeout - the socket is not expected to be in blocking mode. */ fd_set fds; FD_ZERO (&fds); FD_SET (HANDLE2SOCKET (fd), &fds); select (0, NULL, &fds, NULL, NULL); goto again; } } else { DWORD nwrite; if (!WriteFile (fd, buffer, size, &nwrite, NULL)) { res = -1; ec = GetLastError (); } else res = (int)nwrite; } if (res == -1) { switch (ec) { case WSAENOTSOCK: gpg_err_set_errno (EBADF); break; case WSAEWOULDBLOCK: gpg_err_set_errno (EAGAIN); break; case ERROR_BROKEN_PIPE: case ERROR_NO_DATA: gpg_err_set_errno (EPIPE); break; default: gpg_err_set_errno (EIO); break; } } return res; } int __assuan_recvmsg (assuan_context_t ctx, assuan_fd_t fd, assuan_msghdr_t msg, int flags) { gpg_err_set_errno (ENOSYS); return -1; } int __assuan_sendmsg (assuan_context_t ctx, assuan_fd_t fd, assuan_msghdr_t msg, int flags) { gpg_err_set_errno (ENOSYS); return -1; } /* Build a command line for use with W32's CreateProcess. On success CMDLINE gets the address of a newly allocated string. */ static int build_w32_commandline (assuan_context_t ctx, const char * const *argv, char **cmdline) { int i, n; const char *s; char *buf, *p; *cmdline = NULL; n = 0; for (i=0; (s = argv[i]); i++) { n += strlen (s) + 1 + 2; /* (1 space, 2 quoting */ for (; *s; s++) if (*s == '\"') n++; /* Need to double inner quotes. */ } n++; buf = p = _assuan_malloc (ctx, n); if (! buf) return -1; for (i = 0; argv[i]; i++) { if (i) p = stpcpy (p, " "); if (! *argv[i]) /* Empty string. */ p = stpcpy (p, "\"\""); else if (strpbrk (argv[i], " \t\n\v\f\"")) { p = stpcpy (p, "\""); for (s = argv[i]; *s; s++) { *p++ = *s; if (*s == '\"') *p++ = *s; } *p++ = '\"'; *p = 0; } else p = stpcpy (p, argv[i]); } *cmdline= buf; return 0; } int __assuan_spawn (assuan_context_t ctx, pid_t *r_pid, const char *name, const char **argv, assuan_fd_t fd_in, assuan_fd_t fd_out, assuan_fd_t *fd_child_list, void (*atfork) (void *opaque, int reserved), void *atforkvalue, unsigned int flags) { SECURITY_ATTRIBUTES sec_attr; PROCESS_INFORMATION pi = { NULL, /* Returns process handle. */ 0, /* Returns primary thread handle. */ 0, /* Returns pid. */ 0 /* Returns tid. */ }; - STARTUPINFO si; + STARTUPINFOW si; assuan_fd_t fd; assuan_fd_t *fdp; char *cmdline; + wchar_t *wcmdline = NULL; + wchar_t *wname = NULL; HANDLE nullfd = INVALID_HANDLE_VALUE; + int rc; /* fixme: Actually we should set the "_assuan_pipe_connect_pid" env variable. However this requires us to write a full environment handler, because the strings are expected in sorted order. The suggestion given in the MS Reference Library, to save the old value, change it, create process and restore it, is not thread safe. */ /* Build the command line. */ if (build_w32_commandline (ctx, argv, &cmdline)) return -1; /* Start the process. */ memset (&sec_attr, 0, sizeof sec_attr); sec_attr.nLength = sizeof sec_attr; sec_attr.bInheritHandle = FALSE; memset (&si, 0, sizeof si); si.cb = sizeof (si); si.dwFlags = STARTF_USESTDHANDLES; /* FIXME: Dup to nul if ASSUAN_INVALID_FD. */ si.hStdInput = fd_in; si.hStdOutput = fd_out; /* Dup stderr to /dev/null unless it is in the list of FDs to be passed to the child. */ fd = assuan_fd_from_posix_fd (fileno (stderr)); fdp = fd_child_list; if (fdp) { for (; *fdp != ASSUAN_INVALID_FD && *fdp != fd; fdp++) ; } if (!fdp || *fdp == ASSUAN_INVALID_FD) { nullfd = CreateFileW (L"nul", GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); if (nullfd == INVALID_HANDLE_VALUE) { TRACE1 (ctx, ASSUAN_LOG_SYSIO, "__assuan_spawn", ctx, "can't open `nul': %s", _assuan_w32_strerror (ctx, -1)); _assuan_free (ctx, cmdline); gpg_err_set_errno (EIO); return -1; } si.hStdError = nullfd; } else si.hStdError = fd; /* Note: We inherit all handles flagged as inheritable. This seems to be a security flaw but there seems to be no way of selecting handles to inherit. A fix for this would be to use a helper - process like we have in gpgme. */ + process like we have in gpgme. + Take care: CreateProcessW may modify wpgmname */ /* _assuan_log_printf ("CreateProcess, path=`%s' cmdline=`%s'\n", */ /* name, cmdline); */ - if (!CreateProcess (name, /* Program to start. */ - cmdline, /* Command line arguments. */ - &sec_attr, /* Process security attributes. */ - &sec_attr, /* Thread security attributes. */ - TRUE, /* Inherit handles. */ - (CREATE_DEFAULT_ERROR_MODE - | ((flags & 128)? DETACHED_PROCESS : 0) - | GetPriorityClass (GetCurrentProcess ()) - | CREATE_SUSPENDED), /* Creation flags. */ - NULL, /* Environment. */ - NULL, /* Use current drive/directory. */ - &si, /* Startup information. */ - &pi /* Returns process information. */ - )) + if (name && !(wname = _assuan_utf8_to_wchar (name))) + rc = 0; + else if (!(wcmdline = _assuan_utf8_to_wchar (cmdline))) + rc = 0; + else + rc = CreateProcessW (wname, /* Program to start. */ + wcmdline, /* Command line arguments. */ + &sec_attr, /* Process security attributes. */ + &sec_attr, /* Thread security attributes. */ + TRUE, /* Inherit handles. */ + (CREATE_DEFAULT_ERROR_MODE + | ((flags & 128)? DETACHED_PROCESS : 0) + | GetPriorityClass (GetCurrentProcess ()) + | CREATE_SUSPENDED), /* Creation flags. */ + NULL, /* Environment. */ + NULL, /* Use current drive/directory. */ + &si, /* Startup information. */ + &pi /* Returns process information. */ + ); + if (!rc) { TRACE1 (ctx, ASSUAN_LOG_SYSIO, "pipe_connect_w32", ctx, - "CreateProcess failed: %s", _assuan_w32_strerror (ctx, -1)); + "CreateProcess failed%s: %s", _assuan_w32_strerror (ctx, -1)); + free (wname); + free (wcmdline); _assuan_free (ctx, cmdline); if (nullfd != INVALID_HANDLE_VALUE) CloseHandle (nullfd); gpg_err_set_errno (EIO); return -1; } + free (wname); + free (wcmdline); _assuan_free (ctx, cmdline); if (nullfd != INVALID_HANDLE_VALUE) CloseHandle (nullfd); ResumeThread (pi.hThread); CloseHandle (pi.hThread); /* _assuan_log_printf ("CreateProcess ready: hProcess=%p hThread=%p" */ /* " dwProcessID=%d dwThreadId=%d\n", */ /* pi.hProcess, pi.hThread, */ /* (int) pi.dwProcessId, (int) pi.dwThreadId); */ *r_pid = (pid_t) pi.hProcess; /* No need to modify peer process, as we don't change the handle names. However this also means we are not safe, as we inherit too many handles. Should use approach similar to gpgme and glib using a helper process. */ return 0; } /* FIXME: Add some sort of waitpid function that covers GPGME and gpg-agent's use of assuan. */ pid_t __assuan_waitpid (assuan_context_t ctx, pid_t pid, int nowait, int *status, int options) { CloseHandle ((HANDLE) pid); return 0; } int __assuan_socketpair (assuan_context_t ctx, int namespace, int style, int protocol, assuan_fd_t filedes[2]) { gpg_err_set_errno (ENOSYS); return -1; } int __assuan_socket (assuan_context_t ctx, int domain, int type, int proto) { int res; res = socket (domain, type, proto); if (res == -1) gpg_err_set_errno (_assuan_sock_wsa2errno (WSAGetLastError ())); return res; } int __assuan_connect (assuan_context_t ctx, int sock, struct sockaddr *addr, socklen_t length) { int res; res = connect (sock, addr, length); if (res < 0) gpg_err_set_errno (_assuan_sock_wsa2errno (WSAGetLastError ())); return res; } /* The default system hooks for assuan contexts. */ struct assuan_system_hooks _assuan_system_hooks = { ASSUAN_SYSTEM_HOOKS_VERSION, __assuan_usleep, __assuan_pipe, __assuan_close, __assuan_read, __assuan_write, __assuan_recvmsg, __assuan_sendmsg, __assuan_spawn, __assuan_waitpid, __assuan_socketpair, __assuan_socket, __assuan_connect };