diff --git a/src/assuan-defs.h b/src/assuan-defs.h index de2a6d4..68bd983 100644 --- a/src/assuan-defs.h +++ b/src/assuan-defs.h @@ -1,434 +1,435 @@ /* 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; /* From here, we have internal flags, not defined by assuan_flag_t. */ unsigned int is_socket : 1; unsigned int is_server : 1; /* Set if this is context belongs to a server */ unsigned int in_inquire : 1; /* Server: inside assuan_inquire */ unsigned int in_process_next : 1; unsigned int process_complete : 1; unsigned int in_command : 1; unsigned int in_inq_cb : 1; /* Client: inquire callback is active */ unsigned int confidential_inquiry : 1; /* Client: inquiry is confidential */ } 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; /* 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]); assuan_fd_t _assuan_socket (assuan_context_t ctx, int namespace, int style, int protocol); int _assuan_connect (assuan_context_t ctx, assuan_fd_t 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); gpg_error_t w32_fdpass_send (assuan_context_t ctx, assuan_fd_t fd); +gpg_error_t w32_fdpass_recv (assuan_context_t ctx, assuan_fd_t *fd); #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); /* 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 *)(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); void _assuan_pre_syscall (void); void _assuan_post_syscall (void); #endif /*ASSUAN_DEFS_H*/ diff --git a/src/assuan-socket-server.c b/src/assuan-socket-server.c index a8330a8..f5fe038 100644 --- a/src/assuan-socket-server.c +++ b/src/assuan-socket-server.c @@ -1,259 +1,263 @@ /* assuan-socket-server.c - Assuan socket based server * Copyright (C) 2002, 2007, 2009 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 #ifdef HAVE_UNISTD_H # include #endif #ifdef HAVE_SYS_TYPES_H # include #endif #ifdef HAVE_W32_SYSTEM # ifdef HAVE_WINSOCK2_H # include # endif # include # if HAVE_SYS_SOCKET_H # include # elif HAVE_WS2TCPIP_H # include # endif #else # include # include #endif #ifdef HAVE_SYS_UCRED_H #include #endif #ifdef HAVE_UCRED_H #include #endif #include "debug.h" #include "assuan-defs.h" static gpg_error_t accept_connection_bottom (assuan_context_t ctx) { assuan_fd_t fd = ctx->connected_fd; TRACE (ctx, ASSUAN_LOG_SYSIO, "accept_connection_bottom", ctx); ctx->peercred_valid = 0; #ifdef SO_PEERCRED { #ifdef HAVE_STRUCT_SOCKPEERCRED_PID struct sockpeercred cr; /* OpenBSD */ #else struct ucred cr; /* GNU/Linux */ #endif socklen_t cl = sizeof cr; if (!getsockopt (fd, SOL_SOCKET, SO_PEERCRED, &cr, &cl)) { ctx->peercred_valid = 1; ctx->peercred.pid = cr.pid; ctx->peercred.uid = cr.uid; ctx->peercred.gid = cr.gid; } } #elif defined (LOCAL_PEERPID) { /* macOS */ socklen_t len = sizeof (pid_t); if (!getsockopt (fd, SOL_LOCAL, LOCAL_PEERPID, &ctx->peercred.pid, &len)) { ctx->peercred_valid = 1; #if defined (LOCAL_PEERCRED) { struct xucred cr; len = sizeof (struct xucred); if (!getsockopt (fd, SOL_LOCAL, LOCAL_PEERCRED, &cr, &len)) { ctx->peercred.uid = cr.cr_uid; ctx->peercred.gid = cr.cr_gid; } } #endif } } #elif defined (LOCAL_PEEREID) { /* NetBSD */ struct unpcbid unp; socklen_t unpl = sizeof unp; if (getsockopt (fd, 0, LOCAL_PEEREID, &unp, &unpl) != -1) { ctx->peercred_valid = 1; ctx->peercred.pid = unp.unp_pid; ctx->peercred.uid = unp.unp_euid; ctx->peercred.gid = unp.unp_egid; } } #elif defined (HAVE_GETPEERUCRED) { /* Solaris */ ucred_t *ucred = NULL; if (getpeerucred (fd, &ucred) != -1) { ctx->peercred_valid = 1; ctx->peercred.pid = ucred_getpid (ucred); ctx->peercred.uid = ucred_geteuid (ucred); ctx->peercred.gid = ucred_getegid (ucred); ucred_free (ucred); } } #elif defined(HAVE_GETPEEREID) { /* FreeBSD */ if (getpeereid (fd, &ctx->peercred.uid, &ctx->peercred.gid) != -1) { ctx->peercred_valid = 1; ctx->peercred.pid = ASSUAN_INVALID_PID; } } #endif /* This overrides any already set PID if the function returns a valid one. */ if (ctx->peercred_valid && ctx->peercred.pid != ASSUAN_INVALID_PID) ctx->pid = ctx->peercred.pid; ctx->inbound.fd = fd; ctx->inbound.eof = 0; ctx->inbound.linelen = 0; ctx->inbound.attic.linelen = 0; ctx->inbound.attic.pending = 0; ctx->outbound.fd = fd; ctx->outbound.data.linelen = 0; ctx->outbound.data.error = 0; ctx->flags.confidential = 0; return 0; } static gpg_error_t accept_connection (assuan_context_t ctx) { assuan_fd_t fd; struct sockaddr_un clnt_addr; socklen_t len = sizeof clnt_addr; TRACE1 (ctx, ASSUAN_LOG_SYSIO, "accept_connection", ctx, "listen_fd=0x%x", ctx->listen_fd); fd = SOCKET2HANDLE(accept (HANDLE2SOCKET(ctx->listen_fd), (struct sockaddr*)&clnt_addr, &len )); if (fd == ASSUAN_INVALID_FD) { return _assuan_error (ctx, gpg_err_code_from_syserror ()); } TRACE1 (ctx, ASSUAN_LOG_SYSIO, "accept_connection", ctx, "fd->0x%x", fd); if (_assuan_sock_check_nonce (ctx, fd, &ctx->listen_nonce)) { _assuan_close (ctx, fd); return _assuan_error (ctx, GPG_ERR_ASS_ACCEPT_FAILED); } ctx->connected_fd = fd; return accept_connection_bottom (ctx); } /* Flag bits: 0 - use sendmsg/recvmsg to allow descriptor passing 1 - FD has already been accepted. */ gpg_error_t assuan_init_socket_server (assuan_context_t ctx, assuan_fd_t fd, unsigned int flags) { gpg_error_t rc; TRACE_BEG2 (ctx, ASSUAN_LOG_CTX, "assuan_init_socket_server", ctx, "fd=0x%x, flags=0x%x", fd, flags); ctx->flags.is_socket = 1; rc = _assuan_register_std_commands (ctx); if (rc) return TRACE_ERR (rc); ctx->engine.release = _assuan_server_release; ctx->engine.readfnc = _assuan_simple_read; ctx->engine.writefnc = _assuan_simple_write; ctx->engine.sendfd = NULL; ctx->engine.receivefd = NULL; ctx->flags.is_server = 1; if (flags & ASSUAN_SOCKET_SERVER_ACCEPTED) /* We want a second accept to indicate EOF. */ ctx->max_accepts = 1; else ctx->max_accepts = -1; ctx->input_fd = ASSUAN_INVALID_FD; ctx->output_fd = ASSUAN_INVALID_FD; ctx->inbound.fd = ASSUAN_INVALID_FD; ctx->outbound.fd = ASSUAN_INVALID_FD; if (flags & ASSUAN_SOCKET_SERVER_ACCEPTED) { ctx->listen_fd = ASSUAN_INVALID_FD; ctx->connected_fd = fd; } else { ctx->listen_fd = fd; ctx->connected_fd = ASSUAN_INVALID_FD; } ctx->accept_handler = ((flags & ASSUAN_SOCKET_SERVER_ACCEPTED) ? accept_connection_bottom : accept_connection); ctx->finish_handler = _assuan_server_finish; +#ifdef HAVE_W32_SYSTEM + ctx->engine.receivefd = w32_fdpass_recv; +#else if (flags & ASSUAN_SOCKET_SERVER_FDPASSING) _assuan_init_uds_io (ctx); +#endif rc = _assuan_register_std_commands (ctx); if (rc) _assuan_reset (ctx); return TRACE_ERR (rc); } /* Save a copy of NONCE in context CTX. This should be used to register the server's nonce with an context established by assuan_init_socket_server. */ void assuan_set_sock_nonce (assuan_context_t ctx, assuan_sock_nonce_t *nonce) { if (ctx && nonce) ctx->listen_nonce = *nonce; } diff --git a/src/system-w32.c b/src/system-w32.c index 9694481..f1f2716 100644 --- a/src/system-w32.c +++ b/src/system-w32.c @@ -1,642 +1,771 @@ /* 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; if (ctx->flags.is_socket) { rc = closesocket (HANDLE2SOCKET(fd)); if (rc) gpg_err_set_errno ( _assuan_sock_wsa2errno (WSAGetLastError ()) ); } else { rc = CloseHandle (fd); if (rc) /* FIXME. */ gpg_err_set_errno (EIO); } return rc; } +/* To encode/decode file HANDLE, we use FDPASS_FORMAT */ +#define FDPASS_FORMAT "%p" + +static gpg_error_t +get_file_handle (int fd, int server_pid, HANDLE *r_handle) +{ + HANDLE prochandle, handle, newhandle; + + handle = (void *)_get_osfhandle (fd); + + prochandle = OpenProcess (PROCESS_DUP_HANDLE, FALSE, server_pid); + if (!prochandle) + return gpg_error (GPG_ERR_ASS_PARAMETER);/*FIXME: error*/ + + if (!DuplicateHandle (GetCurrentProcess (), handle, prochandle, &newhandle, + 0, TRUE, DUPLICATE_SAME_ACCESS)) + { + CloseHandle (prochandle); + return gpg_error (GPG_ERR_ASS_PARAMETER);/*FIXME: error*/ + } + CloseHandle (prochandle); + *r_handle = newhandle; + return 0; +} + + +/* + * On Windows, we can consider two different ways about what FD means: + * + * (1) POSIX fd + * (2) Windows file HANDLE + * + * In assuan, we use assuan_fd_t for socket/pipe. + * + * Here, it refers file object. + * + * If we started the design and implementation now, it would be more + * natural (easy to understand, no confusion) to use the "int" type + * and POSIX fd here. Considering the purpose of sending fd (the file + * object to share), it is better portability-wise, even though use of + * POSIX fd on Windows requires emulation layer. (These days, in + * GnuPG, we use gpgrt's estream for file assces and gpgrt_fileno for + * POSIX fd.) + * + * That is: + * + * - assuan_sendfd/assuan_recvfd sends/receives POSIX fd + * - assuan_get_input_fd/assuan_get_output_fd returns POSIX fd + * + * However, those APIs now uses assuan_fd_t. That's troublesome or it + * allows confusion about the semantics of the APIs. + * + * Perhaps, avoiding API/ABI breaks, we would need introducing new APIs: + * + * - assuan_sendFD/assuan_recvFD sends/receives POSIX fd + * - assuan_get_input_FD/assuan_get_output_FD returns POSIX fd + * + * For this experiment, we don't care about API/ABI breaks, for now. + * And use POSIX fd here. + * + * We use sending MSG_OOB, but it only allows a single-byte in TCP. + * So, it is used to notify other end for fdpassing. + */ gpg_error_t w32_fdpass_send (assuan_context_t ctx, assuan_fd_t fd) { char fdpass_msg[256]; size_t msglen; int res; + int fd0; /* POSIX fd */ + intptr_t fd_converted_to_integer; + HANDLE file_handle; + gpg_error_t err; + + fd_converted_to_integer = (intptr_t)fd; + fd0 = (int)fd_converted_to_integer; /* Bit pattern is possibly truncated. */ + + err = get_file_handle (fd0, ctx->pid, &file_handle); + if (err) + return err; + + res = snprintf (fdpass_msg, sizeof (fdpass_msg), FDPASS_FORMAT, file_handle); + if (res < 0) + { + CloseHandle (file_handle); + return gpg_error (GPG_ERR_ASS_PARAMETER);/*FIXME: error*/ + } - /* not yet implemented. */ - (void)fd; - msglen = 6; - strcpy (fdpass_msg, "hello!"); - res = send (HANDLE2SOCKET (ctx->outbound.fd), fdpass_msg, msglen, MSG_OOB); + msglen = (size_t)res + 1; /* Including NUL. */ + + res = send (HANDLE2SOCKET (ctx->outbound.fd), "!", 1, MSG_OOB); + res = send (HANDLE2SOCKET (ctx->outbound.fd), fdpass_msg, msglen, 0); return 0; } static int -process_fdpass_msg (const char *fdpass_msg, size_t msglen, assuan_fd_t *r_fd) +process_fdpass_msg (const char *fdpass_msg, size_t msglen, int *r_fd) { - fprintf (stderr, "process_fdpass_msg: %s\n", fdpass_msg); - *r_fd = NULL; - /* not implemented yet, but pretending as if it's successful. */ + void *file_handle; + int res; + int fd; + + *r_fd = -1; + + res = sscanf (fdpass_msg, FDPASS_FORMAT, &file_handle); + if (res != 1) + return -1; + + fd = _open_osfhandle ((intptr_t)file_handle, _O_RDWR); + if (fd < 0) + { + CloseHandle (file_handle); + return -1; + } + + *r_fd = fd; + return 0; +} + +gpg_error_t +w32_fdpass_recv (assuan_context_t ctx, assuan_fd_t *fd) +{ + int i; + + if (!ctx->uds.pendingfdscount) + { + TRACE0 (ctx, ASSUAN_LOG_SYSIO, "w32_receivefd", ctx, + "no pending file descriptors"); + return _assuan_error (ctx, GPG_ERR_ASS_GENERAL); + } + + *fd = ctx->uds.pendingfds[0]; + for (i=1; i < ctx->uds.pendingfdscount; i++) + ctx->uds.pendingfds[i-1] = ctx->uds.pendingfds[i]; + ctx->uds.pendingfdscount--; + return 0; } ssize_t __assuan_read (assuan_context_t ctx, assuan_fd_t fd, void *buffer, size_t size) { int res; int ec = 0; if (ctx->flags.is_socket) { fd_set fds; int tries = 3; - struct timeval timeout; + fd_set efds; + try_again: - timeout.tv_sec = 0; - timeout.tv_usec = 0; FD_ZERO (&fds); FD_SET (HANDLE2SOCKET (fd), &fds); - res = select (0, NULL, NULL, &fds, &timeout); + FD_ZERO (&efds); + FD_SET (HANDLE2SOCKET (fd), &efds); + res = select (0, &fds, NULL, &efds, NULL); if (res < 0) { gpg_err_set_errno (EIO); return -1; } - else if (res) + else if (FD_ISSET (HANDLE2SOCKET (fd), &efds)) { - assuan_fd_t fd_recv; + int fd_recv; char fdpass_msg[256]; + /* the message of ! */ res = recv (HANDLE2SOCKET (fd), fdpass_msg, sizeof (fdpass_msg), MSG_OOB); if (res < 0) { gpg_err_set_errno (EIO); return -1; } + + /* the body of message */ + res = recv (HANDLE2SOCKET (fd), fdpass_msg, sizeof (fdpass_msg), 0); + if (res < 0) + { + gpg_err_set_errno (EIO); + return -1; + } + res = process_fdpass_msg (fdpass_msg, res, &fd_recv); - if (res) - ctx->uds.pendingfds[ctx->uds.pendingfdscount++] = fd_recv; - else + if (res < 0) { gpg_err_set_errno (EIO); return -1; } - } - again: - ec = 0; - res = recv (HANDLE2SOCKET (fd), buffer, size, 0); - if (res == -1) - ec = WSAGetLastError (); - if (ec == WSAEWOULDBLOCK && tries--) + ctx->uds.pendingfds[ctx->uds.pendingfdscount++] = (assuan_fd_t)fd_recv; + goto try_again; + } + else { - /* 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_ZERO (&fds); - FD_SET (HANDLE2SOCKET (fd), &fds); - select (0, &fds, NULL, NULL, NULL); - goto again; + 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_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 (ctx->flags.is_socket) { 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. */ }; 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. Take care: CreateProcessW may modify wpgmname */ /* _assuan_log_printf ("CreateProcess, path=`%s' cmdline=`%s'\n", */ /* name, cmdline); */ 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: %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; } assuan_fd_t __assuan_socket (assuan_context_t ctx, int domain, int type, int proto) { assuan_fd_t res; res = SOCKET2HANDLE (socket (domain, type, proto)); if (res == SOCKET2HANDLE (INVALID_SOCKET)) gpg_err_set_errno (_assuan_sock_wsa2errno (WSAGetLastError ())); return res; } int __assuan_connect (assuan_context_t ctx, assuan_fd_t sock, struct sockaddr *addr, socklen_t length) { int res; res = connect (HANDLE2SOCKET (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 = { 0, __assuan_usleep, __assuan_pipe, __assuan_close, __assuan_read, __assuan_write, __assuan_recvmsg, __assuan_sendmsg, __assuan_spawn, __assuan_waitpid, __assuan_socketpair, __assuan_socket, __assuan_connect };