diff --git a/src/assuan-buffer.c b/src/assuan-buffer.c index f131a8d..7cb3032 100644 --- a/src/assuan-buffer.c +++ b/src/assuan-buffer.c @@ -1,552 +1,552 @@ /* assuan-buffer.c - read and send data * Copyright (C) 2001-2004, 2006, 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+ */ #include #include #include #include #include #ifdef HAVE_UNISTD_H # include #endif #include #ifdef HAVE_W32_SYSTEM #ifndef HAVE_W32CE_SYSTEM # include #endif #endif #include "assuan-defs.h" /* Extended version of write(2) to guarantee that all bytes are written. Returns 0 on success or -1 and ERRNO on failure. NOTE: This function does not return the number of bytes written, so any error must be treated as fatal for this connection as the state of the receiver is unknown. This works best if blocking is allowed (so EAGAIN cannot occur). */ static int writen (assuan_context_t ctx, const char *buffer, size_t length) { while (length) { ssize_t nwritten = ctx->engine.writefnc (ctx, buffer, length); if (nwritten < 0) { if (errno == EINTR) continue; return -1; /* write error */ } length -= nwritten; buffer += nwritten; } return 0; /* okay */ } /* Read an entire line. Returns 0 on success or -1 and ERRNO on failure. EOF is indictated by setting the integer at address R_EOF. Note: BUF, R_NREAD and R_EOF contain a valid result even if an error is returned. */ static int readline (assuan_context_t ctx, char *buf, size_t buflen, int *r_nread, int *r_eof) { size_t nleft = buflen; char *p; *r_eof = 0; *r_nread = 0; while (nleft > 0) { ssize_t n = ctx->engine.readfnc (ctx, buf, nleft); if (n < 0) { if (errno == EINTR) continue; #ifdef HAVE_W32_SYSTEM if (errno == EPIPE) { /* Under Windows we get EPIPE (actually ECONNRESET) after termination of the client. Assume an EOF. */ *r_eof = 1; break; /* allow incomplete lines */ } #endif /*HAVE_W32_SYSTEM*/ return -1; /* read error */ } else if (!n) { *r_eof = 1; break; /* allow incomplete lines */ } p = buf; nleft -= n; buf += n; *r_nread += n; p = memrchr (p, '\n', n); if (p) break; /* at least one full line available - that's enough for now */ } return 0; } /* Read a line with buffering of partial lines. Function returns an Assuan error. */ gpg_error_t _assuan_read_line (assuan_context_t ctx) { gpg_error_t rc = 0; char *line = ctx->inbound.line; int nread, atticlen; char *endp = 0; if (ctx->inbound.eof) return _assuan_error (ctx, GPG_ERR_EOF); atticlen = ctx->inbound.attic.linelen; if (atticlen) { memcpy (line, ctx->inbound.attic.line, atticlen); ctx->inbound.attic.linelen = 0; endp = memchr (line, '\n', atticlen); if (endp) { /* Found another line in the attic. */ nread = atticlen; atticlen = 0; } else { /* There is pending data but not a full line. */ assert (atticlen < LINELENGTH); rc = readline (ctx, line + atticlen, LINELENGTH - atticlen, &nread, &ctx->inbound.eof); } } else /* No pending data. */ rc = readline (ctx, line, LINELENGTH, &nread, &ctx->inbound.eof); if (rc) { int saved_errno = errno; char buf[100]; snprintf (buf, sizeof buf, "error: %s", strerror (errno)); _assuan_log_control_channel (ctx, 0, buf, NULL, 0, NULL, 0); if (saved_errno == EAGAIN) { /* We have to save a partial line. Due to readline's behaviour, we know that this is not a complete line yet (no newline). So we don't set PENDING to true. */ memcpy (ctx->inbound.attic.line, line, atticlen + nread); ctx->inbound.attic.pending = 0; ctx->inbound.attic.linelen = atticlen + nread; } gpg_err_set_errno (saved_errno); return _assuan_error (ctx, gpg_err_code_from_syserror ()); } if (!nread) { assert (ctx->inbound.eof); _assuan_log_control_channel (ctx, 0, "eof", NULL, 0, NULL, 0); return _assuan_error (ctx, GPG_ERR_EOF); } ctx->inbound.attic.pending = 0; nread += atticlen; if (! endp) endp = memchr (line, '\n', nread); if (endp) { unsigned monitor_result; int n = endp - line + 1; if (n < nread) /* LINE contains more than one line. We copy it to the attic now as handlers are allowed to modify the passed buffer. */ { int len = nread - n; memcpy (ctx->inbound.attic.line, endp + 1, len); ctx->inbound.attic.pending = memrchr (endp + 1, '\n', len) ? 1 : 0; ctx->inbound.attic.linelen = len; } if (endp != line && endp[-1] == '\r') endp --; *endp = 0; ctx->inbound.linelen = endp - line; monitor_result = 0; if (ctx->io_monitor) monitor_result = ctx->io_monitor (ctx, ctx->io_monitor_data, 0, ctx->inbound.line, ctx->inbound.linelen); if (monitor_result & ASSUAN_IO_MONITOR_IGNORE) ctx->inbound.linelen = 0; if ( !(monitor_result & ASSUAN_IO_MONITOR_NOLOG)) _assuan_log_control_channel (ctx, 0, NULL, ctx->inbound.line, ctx->inbound.linelen, NULL, 0); return 0; } else { _assuan_log_control_channel (ctx, 0, "invalid line", NULL, 0, NULL, 0); *line = 0; ctx->inbound.linelen = 0; return _assuan_error (ctx, ctx->inbound.eof ? GPG_ERR_ASS_INCOMPLETE_LINE : GPG_ERR_ASS_LINE_TOO_LONG); } } /* Read the next line from the client or server and return a pointer in *LINE to a buffer holding the line. LINELEN is the length of *LINE. The buffer is valid until the next read operation on it. The caller may modify the buffer. The buffer is invalid (i.e. must not be used) if an error is returned. Returns 0 on success or an assuan error code. See also: assuan_pending_line(). */ gpg_error_t assuan_read_line (assuan_context_t ctx, char **line, size_t *linelen) { gpg_error_t err; if (!ctx) return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE); do { err = _assuan_read_line (ctx); } while (_assuan_error_is_eagain (ctx, err)); *line = ctx->inbound.line; *linelen = ctx->inbound.linelen; return err; } /* Return true if a full line is buffered (i.e. an entire line may be read without any I/O). */ int assuan_pending_line (assuan_context_t ctx) { return ctx && ctx->inbound.attic.pending; } gpg_error_t _assuan_write_line (assuan_context_t ctx, const char *prefix, const char *line, size_t len) { gpg_error_t rc = 0; size_t prefixlen = prefix? strlen (prefix):0; unsigned int monitor_result; /* Make sure that the line is short enough. */ if (len + prefixlen + 2 > ASSUAN_LINELENGTH) { _assuan_log_control_channel (ctx, 1, "supplied line too long - truncated", NULL, 0, NULL, 0); if (prefixlen > 5) prefixlen = 5; if (len > ASSUAN_LINELENGTH - prefixlen - 2) len = ASSUAN_LINELENGTH - prefixlen - 2 - 1; } monitor_result = 0; if (ctx->io_monitor) monitor_result = ctx->io_monitor (ctx, ctx->io_monitor_data, 1, line, len); /* Fixme: we should do some kind of line buffering. */ if (!(monitor_result & ASSUAN_IO_MONITOR_NOLOG)) _assuan_log_control_channel (ctx, 1, NULL, prefixlen? prefix:NULL, prefixlen, line, len); if (prefixlen && !(monitor_result & ASSUAN_IO_MONITOR_IGNORE)) { rc = writen (ctx, prefix, prefixlen); if (rc) rc = _assuan_error (ctx, gpg_err_code_from_syserror ()); } if (!rc && !(monitor_result & ASSUAN_IO_MONITOR_IGNORE)) { rc = writen (ctx, line, len); if (rc) rc = _assuan_error (ctx, gpg_err_code_from_syserror ()); if (!rc) { rc = writen (ctx, "\n", 1); if (rc) rc = _assuan_error (ctx, gpg_err_code_from_syserror ()); } } return rc; } gpg_error_t assuan_write_line (assuan_context_t ctx, const char *line) { size_t len; const char *str; if (! ctx) return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE); /* Make sure that we never take a LF from the user - this might violate the protocol. */ str = strchr (line, '\n'); len = str ? (str - line) : strlen (line); if (str) _assuan_log_control_channel (ctx, 1, "supplied line with LF - truncated", NULL, 0, NULL, 0); return _assuan_write_line (ctx, NULL, line, len); } /* Write out the data in buffer as datalines with line wrapping and percent escaping. This function is used for GNU's custom streams. */ int _assuan_cookie_write_data (void *cookie, const char *buffer, size_t orig_size) { assuan_context_t ctx = cookie; size_t size = orig_size; char *line; size_t linelen; if (ctx->outbound.data.error) return 0; line = ctx->outbound.data.line; linelen = ctx->outbound.data.linelen; line += linelen; while (size) { unsigned int monitor_result; /* Insert data line header. */ if (!linelen) { *line++ = 'D'; *line++ = ' '; linelen += 2; } /* Copy data, keep space for the CRLF and to escape one character. */ while (size && linelen < LINELENGTH-2-2) { if (*buffer == '%' || *buffer == '\r' || *buffer == '\n') { sprintf (line, "%%%02X", *(unsigned char*)buffer); line += 3; linelen += 3; buffer++; } else { *line++ = *buffer++; linelen++; } size--; } monitor_result = 0; if (ctx->io_monitor) monitor_result = ctx->io_monitor (ctx, ctx->io_monitor_data, 1, ctx->outbound.data.line, linelen); if (linelen >= LINELENGTH-2-2) { if (!(monitor_result & ASSUAN_IO_MONITOR_NOLOG)) _assuan_log_control_channel (ctx, 1, NULL, ctx->outbound.data.line, linelen, NULL, 0); *line++ = '\n'; linelen++; if ( !(monitor_result & ASSUAN_IO_MONITOR_IGNORE) && writen (ctx, ctx->outbound.data.line, linelen)) { ctx->outbound.data.error = gpg_err_code_from_syserror (); return 0; } line = ctx->outbound.data.line; linelen = 0; } } ctx->outbound.data.linelen = linelen; return (int) orig_size; } /* Write out any buffered data This function is used for GNU's custom streams */ int _assuan_cookie_write_flush (void *cookie) { assuan_context_t ctx = cookie; char *line; size_t linelen; unsigned int monitor_result; if (ctx->outbound.data.error) return 0; line = ctx->outbound.data.line; linelen = ctx->outbound.data.linelen; line += linelen; monitor_result = 0; if (ctx->io_monitor) monitor_result = ctx->io_monitor (ctx, ctx->io_monitor_data, 1, ctx->outbound.data.line, linelen); if (linelen) { if (!(monitor_result & ASSUAN_IO_MONITOR_NOLOG)) _assuan_log_control_channel (ctx, 1, NULL, ctx->outbound.data.line, linelen, NULL, 0); *line++ = '\n'; linelen++; if (! (monitor_result & ASSUAN_IO_MONITOR_IGNORE) && writen (ctx, ctx->outbound.data.line, linelen)) { ctx->outbound.data.error = gpg_err_code_from_syserror (); return 0; } ctx->outbound.data.linelen = 0; } return 0; } /** * assuan_send_data: * @ctx: An assuan context * @buffer: Data to send or NULL to flush * @length: length of the data to send/ * * This function may be used by the server or the client to send data * lines. The data will be escaped as required by the Assuan protocol * and may get buffered until a line is full. To force sending the * data out @buffer may be passed as NULL (in which case @length must * also be 0); however when used by a client this flush operation does * also send the terminating "END" command to terminate the response on * a INQUIRE response. However, when assuan_transact() is used, this * function takes care of sending END itself. * * If BUFFER is NULL and LENGTH is 1 and we are a client, a "CAN" is * send instead of an "END". * * Return value: 0 on success or an error code **/ gpg_error_t assuan_send_data (assuan_context_t ctx, const void *buffer, size_t length) { if (!ctx) return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE); if (!buffer && length > 1) return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE); if (!buffer) { /* flush what we have */ _assuan_cookie_write_flush (ctx); if (ctx->flags.confidential) wipememory (ctx->outbound.data.line, LINELENGTH); if (ctx->outbound.data.error) return ctx->outbound.data.error; - if (!ctx->is_server) + if (!ctx->flags.is_server) return assuan_write_line (ctx, length == 1? "CAN":"END"); } else { _assuan_cookie_write_data (ctx, buffer, length); if (ctx->outbound.data.error) return ctx->outbound.data.error; } return 0; } gpg_error_t assuan_sendfd (assuan_context_t ctx, assuan_fd_t fd) { /* It is explicitly allowed to use (NULL, -1) as a runtime test to check whether descriptor passing is available. */ if (!ctx && fd == ASSUAN_INVALID_FD) #ifdef USE_DESCRIPTOR_PASSING return 0; #else return _assuan_error (ctx, GPG_ERR_NOT_IMPLEMENTED); #endif if (!ctx) return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE); if (! ctx->engine.sendfd) return set_error (ctx, GPG_ERR_NOT_IMPLEMENTED, "server does not support sending and receiving " "of file descriptors"); return ctx->engine.sendfd (ctx, fd); } gpg_error_t assuan_receivefd (assuan_context_t ctx, assuan_fd_t *fd) { if (!ctx) return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE); if (! ctx->engine.receivefd) return set_error (ctx, GPG_ERR_NOT_IMPLEMENTED, "server does not support sending and receiving " "of file descriptors"); return ctx->engine.receivefd (ctx, fd); } diff --git a/src/assuan-defs.h b/src/assuan-defs.h index 1d1617f..b9a0e8b 100644 --- a/src/assuan-defs.h +++ b/src/assuan-defs.h @@ -1,441 +1,440 @@ /* 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, it's internal flag. */ + /* 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; + unsigned int in_process_next : 1; + unsigned int process_complete : 1; + unsigned int in_command : 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]); 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); #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 *)(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-handler.c b/src/assuan-handler.c index 52dc9bc..a572b62 100644 --- a/src/assuan-handler.c +++ b/src/assuan-handler.c @@ -1,1069 +1,1069 @@ /* assuan-handler.c - dispatch commands * Copyright (C) 2001, 2002, 2003, 2007, 2009, * 2011 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" #define spacep(p) (*(p) == ' ' || *(p) == '\t') #define digitp(a) ((a) >= '0' && (a) <= '9') static int my_strcasecmp (const char *a, const char *b); #define PROCESS_DONE(ctx, rc) \ - ((ctx)->in_process_next ? assuan_process_done ((ctx), (rc)) : (rc)) + ((ctx)->flags.in_process_next ? assuan_process_done ((ctx), (rc)) : (rc)) static gpg_error_t dummy_handler (assuan_context_t ctx, char *line) { return PROCESS_DONE (ctx, set_error (ctx, GPG_ERR_ASSUAN_SERVER_FAULT, "no handler registered")); } static const char std_help_nop[] = "NOP\n" "\n" "No operation. Returns OK without any action."; static gpg_error_t std_handler_nop (assuan_context_t ctx, char *line) { return PROCESS_DONE (ctx, 0); /* okay */ } static const char std_help_cancel[] = "CANCEL\n" "\n" "Run the server's cancel handler if one has been registered."; static gpg_error_t std_handler_cancel (assuan_context_t ctx, char *line) { if (ctx->cancel_notify_fnc) /* Return value ignored. */ ctx->cancel_notify_fnc (ctx, line); return PROCESS_DONE (ctx, set_error (ctx, GPG_ERR_NOT_IMPLEMENTED, NULL)); } static const char std_help_option[] = "OPTION [ [=] ]\n" "\n" "Set option to configure server operation. Leading and\n" "trailing spaces around and are allowed but should be\n" "ignored. For compatibility reasons, may be prefixed with two\n" "dashes. The use of the equal sign is optional but suggested if\n" " is given."; static gpg_error_t std_handler_option (assuan_context_t ctx, char *line) { char *key, *value, *p; for (key=line; spacep (key); key++) ; if (!*key) return PROCESS_DONE (ctx, set_error (ctx, GPG_ERR_ASS_SYNTAX, "argument required")); if (*key == '=') return PROCESS_DONE (ctx, set_error (ctx, GPG_ERR_ASS_SYNTAX, "no option name given")); for (value=key; *value && !spacep (value) && *value != '='; value++) ; if (*value) { if (spacep (value)) *value++ = 0; /* terminate key */ for (; spacep (value); value++) ; if (*value == '=') { *value++ = 0; /* terminate key */ for (; spacep (value); value++) ; if (!*value) return PROCESS_DONE (ctx, set_error (ctx, GPG_ERR_ASS_SYNTAX, "option argument expected")); } if (*value) { for (p = value + strlen(value) - 1; p > value && spacep (p); p--) ; if (p > value) *++p = 0; /* strip trailing spaces */ } } if (*key == '-' && key[1] == '-' && key[2]) key += 2; /* the double dashes are optional */ if (*key == '-') return PROCESS_DONE (ctx, set_error (ctx, GPG_ERR_ASS_SYNTAX, "option should not begin with one dash")); if (ctx->option_handler_fnc) return PROCESS_DONE (ctx, ctx->option_handler_fnc (ctx, key, value)); return PROCESS_DONE (ctx, 0); } static const char std_help_bye[] = "BYE\n" "\n" "Close the connection. The server will reply with OK."; static gpg_error_t std_handler_bye (assuan_context_t ctx, char *line) { if (ctx->bye_notify_fnc) /* Return value ignored. */ ctx->bye_notify_fnc (ctx, line); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); /* pretty simple :-) */ - ctx->process_complete = 1; + ctx->flags.process_complete = 1; return PROCESS_DONE (ctx, 0); } static const char std_help_auth[] = "AUTH\n" "\n" "Reserved for future extensions."; static gpg_error_t std_handler_auth (assuan_context_t ctx, char *line) { return PROCESS_DONE (ctx, set_error (ctx, GPG_ERR_NOT_IMPLEMENTED, NULL)); } static const char std_help_reset[] = "RESET\n" "\n" "Reset the connection but not any existing authentication. The server\n" "should release all resources associated with the connection."; static gpg_error_t std_handler_reset (assuan_context_t ctx, char *line) { gpg_error_t err = 0; if (ctx->reset_notify_fnc) err = ctx->reset_notify_fnc (ctx, line); if (! err) { assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); _assuan_uds_close_fds (ctx); } return PROCESS_DONE (ctx, err); } static const char std_help_help[] = "HELP []\n" "\n" "Lists all commands that the server understands as comment lines on\n" "the status channel. If is given, list detailed help for\n" "that command."; static gpg_error_t std_handler_help (assuan_context_t ctx, char *line) { unsigned int i; char buf[ASSUAN_LINELENGTH]; const char *helpstr; size_t n; n = strcspn (line, " \t\n"); if (!n) { /* Print all commands. If a help string is available and that starts with the command name, print the first line of the help string. */ for (i = 0; i < ctx->cmdtbl_used; i++) { n = strlen (ctx->cmdtbl[i].name); helpstr = ctx->cmdtbl[i].helpstr; if (helpstr && !strncmp (ctx->cmdtbl[i].name, helpstr, n) && (!helpstr[n] || helpstr[n] == '\n' || helpstr[n] == ' ') && (n = strcspn (helpstr, "\n")) ) snprintf (buf, sizeof (buf), "# %.*s", (int)n, helpstr); else snprintf (buf, sizeof (buf), "# %s", ctx->cmdtbl[i].name); buf[ASSUAN_LINELENGTH - 1] = '\0'; assuan_write_line (ctx, buf); } } else { /* Print the help for the given command. */ int c = line[n]; line[n] = 0; for (i=0; ctx->cmdtbl[i].name; i++) if (!my_strcasecmp (line, ctx->cmdtbl[i].name)) break; line[n] = c; if (!ctx->cmdtbl[i].name) return PROCESS_DONE (ctx, set_error (ctx,GPG_ERR_UNKNOWN_COMMAND,NULL)); helpstr = ctx->cmdtbl[i].helpstr; if (!helpstr) return PROCESS_DONE (ctx, set_error (ctx, GPG_ERR_NOT_FOUND, NULL)); do { n = strcspn (helpstr, "\n"); snprintf (buf, sizeof (buf), "# %.*s", (int)n, helpstr); helpstr += n; if (*helpstr == '\n') helpstr++; buf[ASSUAN_LINELENGTH - 1] = '\0'; assuan_write_line (ctx, buf); } while (*helpstr); } return PROCESS_DONE (ctx, 0); } static const char std_help_end[] = "END\n" "\n" "Used by a client to mark the end of raw data."; static gpg_error_t std_handler_end (assuan_context_t ctx, char *line) { return PROCESS_DONE (ctx, set_error (ctx, GPG_ERR_NOT_IMPLEMENTED, NULL)); } gpg_error_t assuan_command_parse_fd (assuan_context_t ctx, char *line, assuan_fd_t *rfd) { char *endp; if ((strncmp (line, "FD", 2) && strncmp (line, "fd", 2)) || (line[2] != '=' && line[2] != '\0' && !spacep(&line[2]))) return set_error (ctx, GPG_ERR_ASS_SYNTAX, "FD[=] expected"); line += 2; if (*line == '=') { line ++; if (!digitp (*line)) return set_error (ctx, GPG_ERR_ASS_SYNTAX, "number required"); #if HAVE_W64_SYSTEM *rfd = (void*)strtoull (line, &endp, 10); #elif HAVE_W32_SYSTEM *rfd = (void*)strtoul (line, &endp, 10); #else *rfd = strtoul (line, &endp, 10); #endif /* Remove that argument so that a notify handler won't see it. */ memset (line, ' ', endp? (endp-line):strlen(line)); if (*rfd == ctx->inbound.fd) return set_error (ctx, GPG_ERR_ASS_PARAMETER, "fd same as inbound fd"); if (*rfd == ctx->outbound.fd) return set_error (ctx, GPG_ERR_ASS_PARAMETER, "fd same as outbound fd"); return 0; } else /* Our peer has sent the file descriptor. */ return assuan_receivefd (ctx, rfd); } static const char std_help_input[] = "INPUT FD[=]\n" "\n" "Used by a client to pass an input file descriptor to the server.\n" "The server opens as a local file descriptor. Without , the\n" "server opens the file descriptor just sent by the client using\n" "assuan_sendfd."; static gpg_error_t std_handler_input (assuan_context_t ctx, char *line) { gpg_error_t rc; assuan_fd_t fd, oldfd; rc = assuan_command_parse_fd (ctx, line, &fd); if (rc) return PROCESS_DONE (ctx, rc); #ifdef HAVE_W32CE_SYSTEM oldfd = fd; fd = _assuan_w32ce_finish_pipe ((int)fd, 0); if (fd == INVALID_HANDLE_VALUE) return PROCESS_DONE (ctx, set_error (ctx, GPG_ERR_ASS_PARAMETER, "rvid conversion failed")); TRACE2 (ctx, ASSUAN_LOG_SYSIO, "std_handler_input", ctx, "turned RVID 0x%x into handle 0x%x", oldfd, fd); #endif if (ctx->input_notify_fnc) { oldfd = ctx->input_fd; ctx->input_fd = fd; rc = ctx->input_notify_fnc (ctx, line); if (rc) ctx->input_fd = oldfd; } else if (!rc) ctx->input_fd = fd; return PROCESS_DONE (ctx, rc); } static const char std_help_output[] = "OUTPUT FD[=]\n" "\n" "Used by a client to pass an output file descriptor to the server.\n" "The server opens as a local file descriptor. Without , the\n" "server opens the file descriptor just sent by the client using\n" "assuan_sendfd."; static gpg_error_t std_handler_output (assuan_context_t ctx, char *line) { gpg_error_t rc; assuan_fd_t fd, oldfd; rc = assuan_command_parse_fd (ctx, line, &fd); if (rc) return PROCESS_DONE (ctx, rc); #ifdef HAVE_W32CE_SYSTEM oldfd = fd; fd = _assuan_w32ce_finish_pipe ((int)fd, 1); if (fd == INVALID_HANDLE_VALUE) return PROCESS_DONE (ctx, set_error (ctx, gpg_err_code_from_syserror (), "rvid conversion failed")); TRACE2 (ctx, ASSUAN_LOG_SYSIO, "std_handler_output", ctx, "turned RVID 0x%x into handle 0x%x", oldfd, fd); #endif if (ctx->output_notify_fnc) { oldfd = ctx->output_fd; ctx->output_fd = fd; rc = ctx->output_notify_fnc (ctx, line); if (rc) ctx->output_fd = oldfd; } else if (!rc) ctx->output_fd = fd; return PROCESS_DONE (ctx, rc); } /* This is a table with the standard commands and handler for them. The table is used to initialize a new context and associate strings with default handlers */ static struct { const char *name; gpg_error_t (*handler)(assuan_context_t, char *line); const char *help; int always; /* always initialize this command */ } std_cmd_table[] = { { "NOP", std_handler_nop, std_help_nop, 1 }, { "CANCEL", std_handler_cancel, std_help_cancel, 1 }, { "OPTION", std_handler_option, std_help_option, 1 }, { "BYE", std_handler_bye, std_help_bye, 1 }, { "AUTH", std_handler_auth, std_help_auth, 1 }, { "RESET", std_handler_reset, std_help_reset, 1 }, { "END", std_handler_end, std_help_end, 1 }, { "HELP", std_handler_help, std_help_help, 1 }, { "INPUT", std_handler_input, std_help_input, 0 }, { "OUTPUT", std_handler_output, std_help_output, 0 }, { } }; /** * assuan_register_command: * @ctx: the server context * @cmd_name: A string with the command name * @handler: The handler function to be called or NULL to use a default * handler. * HELPSTRING * * Register a handler to be used for a given command. Note that * several default handlers are already registered with a new context. * This function however allows to override them. * * Return value: 0 on success or an error code **/ gpg_error_t assuan_register_command (assuan_context_t ctx, const char *cmd_name, assuan_handler_t handler, const char *help_string) { int i, cmd_index = -1; const char *s; if (cmd_name && !*cmd_name) cmd_name = NULL; if (!cmd_name) return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE); if (!handler) { /* find a default handler. */ for (i=0; (s=std_cmd_table[i].name) && strcmp (cmd_name, s); i++) ; if (!s) { /* Try again but case insensitive. */ for (i=0; (s=std_cmd_table[i].name) && my_strcasecmp (cmd_name, s); i++) ; } if (s) handler = std_cmd_table[i].handler; if (!handler) handler = dummy_handler; /* Last resort is the dummy handler. */ } if (!ctx->cmdtbl) { ctx->cmdtbl_size = 50; ctx->cmdtbl = _assuan_calloc (ctx, ctx->cmdtbl_size, sizeof *ctx->cmdtbl); if (!ctx->cmdtbl) return _assuan_error (ctx, gpg_err_code_from_syserror ()); ctx->cmdtbl_used = 0; } else if (ctx->cmdtbl_used >= ctx->cmdtbl_size) { struct cmdtbl_s *x; x = _assuan_realloc (ctx, ctx->cmdtbl, (ctx->cmdtbl_size+10) * sizeof *x); if (!x) return _assuan_error (ctx, gpg_err_code_from_syserror ()); ctx->cmdtbl = x; ctx->cmdtbl_size += 50; } for (i=0; icmdtbl_used; i++) { if (!my_strcasecmp (cmd_name, ctx->cmdtbl[i].name)) { cmd_index = i; break; } } if (cmd_index == -1) cmd_index = ctx->cmdtbl_used++; ctx->cmdtbl[cmd_index].name = cmd_name; ctx->cmdtbl[cmd_index].handler = handler; ctx->cmdtbl[cmd_index].helpstr = help_string; return 0; } /* Return the name of the command currently processed by a handler. The string returned is valid until the next call to an assuan function on the same context. Returns NULL if no handler is executed or the command is not known. */ const char * assuan_get_command_name (assuan_context_t ctx) { return ctx? ctx->current_cmd_name : NULL; } gpg_error_t assuan_register_pre_cmd_notify (assuan_context_t ctx, gpg_error_t (*fnc)(assuan_context_t, const char *cmd)) { if (!ctx) return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE); ctx->pre_cmd_notify_fnc = fnc; return 0; } gpg_error_t assuan_register_post_cmd_notify (assuan_context_t ctx, void (*fnc)(assuan_context_t, gpg_error_t)) { if (!ctx) return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE); ctx->post_cmd_notify_fnc = fnc; return 0; } gpg_error_t assuan_register_bye_notify (assuan_context_t ctx, assuan_handler_t fnc) { if (!ctx) return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE); ctx->bye_notify_fnc = fnc; return 0; } gpg_error_t assuan_register_reset_notify (assuan_context_t ctx, assuan_handler_t fnc) { if (!ctx) return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE); ctx->reset_notify_fnc = fnc; return 0; } gpg_error_t assuan_register_cancel_notify (assuan_context_t ctx, assuan_handler_t fnc) { if (!ctx) return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE); ctx->cancel_notify_fnc = fnc; return 0; } gpg_error_t assuan_register_option_handler (assuan_context_t ctx, gpg_error_t (*fnc)(assuan_context_t, const char*, const char*)) { if (!ctx) return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE); ctx->option_handler_fnc = fnc; return 0; } gpg_error_t assuan_register_input_notify (assuan_context_t ctx, assuan_handler_t fnc) { if (!ctx) return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE); ctx->input_notify_fnc = fnc; return 0; } gpg_error_t assuan_register_output_notify (assuan_context_t ctx, assuan_handler_t fnc) { if (!ctx) return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE); ctx->output_notify_fnc = fnc; return 0; } /* Helper to register the standards commands */ gpg_error_t _assuan_register_std_commands (assuan_context_t ctx) { gpg_error_t rc; int i; for (i = 0; std_cmd_table[i].name; i++) { if (std_cmd_table[i].always) { rc = assuan_register_command (ctx, std_cmd_table[i].name, NULL, NULL); if (rc) return rc; } } return 0; } /* Process the special data lines. The "D " has already been removed from the line. As all handlers this function may modify the line. */ static gpg_error_t handle_data_line (assuan_context_t ctx, char *line, int linelen) { return set_error (ctx, GPG_ERR_NOT_IMPLEMENTED, NULL); } /* like ascii_strcasecmp but assume that B is already uppercase */ static int my_strcasecmp (const char *a, const char *b) { if (a == b) return 0; for (; *a && *b; a++, b++) { if (((*a >= 'a' && *a <= 'z')? (*a&~0x20):*a) != *b) break; } return *a == *b? 0 : (((*a >= 'a' && *a <= 'z')? (*a&~0x20):*a) - *b); } /* Parse the line, break out the command, find it in the command table, remove leading and white spaces from the arguments, call the handler with the argument line and return the error. */ static gpg_error_t dispatch_command (assuan_context_t ctx, char *line, int linelen) { gpg_error_t err; char *p; const char *s; int shift, i; /* Note that as this function is invoked by assuan_process_next as well, we need to hide non-critical errors with PROCESS_DONE. */ if (*line == 'D' && line[1] == ' ') /* divert to special handler */ /* FIXME: Depending on the final implementation of handle_data_line, this may be wrong here. For example, if a user callback is invoked, and that callback is responsible for calling assuan_process_done, then this is wrong. */ return PROCESS_DONE (ctx, handle_data_line (ctx, line+2, linelen-2)); for (p=line; *p && *p != ' ' && *p != '\t'; p++) ; if (p==line) return PROCESS_DONE (ctx, set_error (ctx, GPG_ERR_ASS_SYNTAX, "leading white-space")); if (*p) { /* Skip over leading WS after the keyword */ *p++ = 0; while ( *p == ' ' || *p == '\t') p++; } shift = p - line; for (i=0; (s=ctx->cmdtbl[i].name); i++) { if (!strcmp (line, s)) break; } if (!s) { /* and try case insensitive */ for (i=0; (s=ctx->cmdtbl[i].name); i++) { if (!my_strcasecmp (line, s)) break; } } if (!s) return PROCESS_DONE (ctx, set_error (ctx, GPG_ERR_ASS_UNKNOWN_CMD, NULL)); line += shift; /* linelen -= shift; -- not needed. */ if (ctx->pre_cmd_notify_fnc) { err = ctx->pre_cmd_notify_fnc(ctx, ctx->cmdtbl[i].name); if (err) return PROCESS_DONE(ctx, err); } /* fprintf (stderr, "DBG-assuan: processing %s `%s'\n", s, line); */ ctx->current_cmd_name = ctx->cmdtbl[i].name; err = ctx->cmdtbl[i].handler (ctx, line); ctx->current_cmd_name = NULL; return err; } /* Call this to acknowledge the current command. */ gpg_error_t assuan_process_done (assuan_context_t ctx, gpg_error_t rc) { - if (!ctx->in_command) + if (!ctx->flags.in_command) return _assuan_error (ctx, GPG_ERR_ASS_GENERAL); if (ctx->flags.force_close) - ctx->process_complete = 1; + ctx->flags.process_complete = 1; - ctx->in_command = 0; + ctx->flags.in_command = 0; /* Check for data write errors. */ if (ctx->outbound.data.fp) { /* Flush the data lines. */ fclose (ctx->outbound.data.fp); ctx->outbound.data.fp = NULL; if (!rc && ctx->outbound.data.error) rc = ctx->outbound.data.error; } else { /* Flush any data send without using the data FP. */ assuan_send_data (ctx, NULL, 0); if (!rc && ctx->outbound.data.error) rc = ctx->outbound.data.error; } /* Error handling. */ if (!rc) { - if (ctx->process_complete) + if (ctx->flags.process_complete) { /* No error checking because the peer may have already disconnect. */ assuan_write_line (ctx, "OK closing connection"); ctx->finish_handler (ctx); } else rc = assuan_write_line (ctx, ctx->okay_line ? ctx->okay_line : "OK"); } else { char errline[300]; const char *text = ctx->err_no == rc ? ctx->err_str : NULL; char ebuf[50]; if (ctx->flags.force_close) text = "[closing connection]"; gpg_strerror_r (rc, ebuf, sizeof (ebuf)); snprintf (errline, sizeof errline, "ERR %d %.50s <%.30s>%s%.100s", rc, ebuf, gpg_strsource (rc), text? " - ":"", text?text:""); rc = assuan_write_line (ctx, errline); if (ctx->flags.force_close) ctx->finish_handler (ctx); } if (ctx->post_cmd_notify_fnc) ctx->post_cmd_notify_fnc (ctx, rc); ctx->flags.confidential = 0; if (ctx->okay_line) { _assuan_free (ctx, ctx->okay_line); ctx->okay_line = NULL; } return rc; } static gpg_error_t process_next (assuan_context_t ctx) { gpg_error_t rc; /* What the next thing to do is depends on the current state. However, we will always first read the next line. The client is required to write full lines without blocking long after starting a partial line. */ rc = _assuan_read_line (ctx); if (_assuan_error_is_eagain (ctx, rc)) return 0; if (gpg_err_code (rc) == GPG_ERR_EOF) { - ctx->process_complete = 1; + ctx->flags.process_complete = 1; return 0; } if (rc) return rc; if (*ctx->inbound.line == '#' || !ctx->inbound.linelen) /* Comment lines are ignored. */ return 0; /* Now we have a line that really means something. It could be one of the following things: First, if we are not in a command already, it is the next command to dispatch. Second, if we are in a command, it can only be the response to an INQUIRE reply. */ - if (!ctx->in_command) + if (!ctx->flags.in_command) { - ctx->in_command = 1; + ctx->flags.in_command = 1; ctx->outbound.data.error = 0; ctx->outbound.data.linelen = 0; /* Dispatch command and return reply. */ - ctx->in_process_next = 1; + ctx->flags.in_process_next = 1; rc = dispatch_command (ctx, ctx->inbound.line, ctx->inbound.linelen); - ctx->in_process_next = 0; + ctx->flags.in_process_next = 0; } - else if (ctx->in_inquire) + else if (ctx->flags.in_inquire) { /* FIXME: Pick up the continuation. */ rc = _assuan_inquire_ext_cb (ctx); } else { /* Should not happen. The client is sending data while we are in a command and not waiting for an inquire. We log an error and discard it. */ TRACE0 (ctx, ASSUAN_LOG_DATA, "process_next", ctx, "unexpected client data"); rc = 0; } return rc; } /* This function should be invoked when the assuan connected FD is ready for reading. If the equivalent to EWOULDBLOCK is returned (this should be done by the command handler), assuan_process_next should be invoked the next time the connected FD is readable. Eventually, the caller will finish by invoking assuan_process_done. DONE is set to 1 if the connection has ended. */ gpg_error_t assuan_process_next (assuan_context_t ctx, int *done) { gpg_error_t rc; if (done) *done = 0; - ctx->process_complete = 0; + ctx->flags.process_complete = 0; do { rc = process_next (ctx); } - while (!rc && !ctx->process_complete && assuan_pending_line (ctx)); + while (!rc && !ctx->flags.process_complete && assuan_pending_line (ctx)); if (done) - *done = !!ctx->process_complete; + *done = !!ctx->flags.process_complete; return rc; } static gpg_error_t process_request (assuan_context_t ctx) { gpg_error_t rc; - if (ctx->in_inquire) + if (ctx->flags.in_inquire) return _assuan_error (ctx, GPG_ERR_ASS_NESTED_COMMANDS); do { rc = _assuan_read_line (ctx); } while (_assuan_error_is_eagain (ctx, rc)); if (gpg_err_code (rc) == GPG_ERR_EOF) { - ctx->process_complete = 1; + ctx->flags.process_complete = 1; return 0; } if (rc) return rc; if (*ctx->inbound.line == '#' || !ctx->inbound.linelen) return 0; /* comment line - ignore */ - ctx->in_command = 1; + ctx->flags.in_command = 1; ctx->outbound.data.error = 0; ctx->outbound.data.linelen = 0; /* dispatch command and return reply */ rc = dispatch_command (ctx, ctx->inbound.line, ctx->inbound.linelen); return assuan_process_done (ctx, rc); } /** * assuan_process: * @ctx: assuan context * * This function is used to handle the assuan protocol after a * connection has been established using assuan_accept(). This is the * main protocol handler. * * Return value: 0 on success or an error code if the assuan operation * failed. Note, that no error is returned for operational errors. **/ gpg_error_t assuan_process (assuan_context_t ctx) { gpg_error_t rc; - ctx->process_complete = 0; + ctx->flags.process_complete = 0; do { rc = process_request (ctx); - } while (!rc && !ctx->process_complete); + } while (!rc && !ctx->flags.process_complete); return rc; } /** * assuan_get_active_fds: * @ctx: Assuan context * @what: 0 for read fds, 1 for write fds * @fdarray: Caller supplied array to store the FDs * @fdarraysize: size of that array * * Return all active filedescriptors for the given context. This * function can be used to select on the fds and call * assuan_process_next() if there is an active one. The first fd in * the array is the one used for the command connection. * * Note, that write FDs are not yet supported. * * Return value: number of FDs active and put into @fdarray or -1 on * error which is most likely a too small fdarray. **/ int assuan_get_active_fds (assuan_context_t ctx, int what, assuan_fd_t *fdarray, int fdarraysize) { int n = 0; if (!ctx || fdarraysize < 2 || what < 0 || what > 1) return -1; if (!what) { if (ctx->inbound.fd != ASSUAN_INVALID_FD) fdarray[n++] = ctx->inbound.fd; } else { if (ctx->outbound.fd != ASSUAN_INVALID_FD) fdarray[n++] = ctx->outbound.fd; if (ctx->outbound.data.fp) #if defined(HAVE_W32CE_SYSTEM) fdarray[n++] = (void*)fileno (ctx->outbound.data.fp); #elif defined(HAVE_W32_SYSTEM) fdarray[n++] = (void*)_get_osfhandle (fileno (ctx->outbound.data.fp)); #else fdarray[n++] = fileno (ctx->outbound.data.fp); #endif } return n; } /* Two simple wrappers to make the expected function types match. */ #ifdef HAVE_FUNOPEN static int fun1_cookie_write (void *cookie, const char *buffer, int orig_size) { return _assuan_cookie_write_data (cookie, buffer, orig_size); } #endif /*HAVE_FUNOPEN*/ #ifdef HAVE_FOPENCOOKIE static ssize_t fun2_cookie_write (void *cookie, const char *buffer, size_t orig_size) { return _assuan_cookie_write_data (cookie, buffer, orig_size); } #endif /*HAVE_FOPENCOOKIE*/ /* Return a FP to be used for data output. The FILE pointer is valid until the end of a handler. So a close is not needed. Assuan does all the buffering needed to insert the status line as well as the required line wappping and quoting for data lines. We use GNU's custom streams here. There should be an alternative implementaion for systems w/o a glibc, a simple implementation could use a child process */ FILE * assuan_get_data_fp (assuan_context_t ctx) { #if defined (HAVE_FOPENCOOKIE) || defined (HAVE_FUNOPEN) if (ctx->outbound.data.fp) return ctx->outbound.data.fp; #ifdef HAVE_FUNOPEN ctx->outbound.data.fp = funopen (ctx, 0, fun1_cookie_write, 0, _assuan_cookie_write_flush); #else ctx->outbound.data.fp = funopen (ctx, 0, fun2_cookie_write, 0, _assuan_cookie_write_flush); #endif ctx->outbound.data.error = 0; return ctx->outbound.data.fp; #else gpg_err_set_errno (ENOSYS); return NULL; #endif } /* Set the text used for the next OK response. This string is automatically reset to NULL after the next command. */ gpg_error_t assuan_set_okay_line (assuan_context_t ctx, const char *line) { if (!ctx) return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE); if (!line) { _assuan_free (ctx, ctx->okay_line); ctx->okay_line = NULL; } else { /* FIXME: we need to use gcry_is_secure() to test whether we should allocate the entire line in secure memory */ char *buf = _assuan_malloc (ctx, 3 + strlen(line) + 1); if (!buf) return _assuan_error (ctx, gpg_err_code_from_syserror ()); strcpy (buf, "OK "); strcpy (buf+3, line); _assuan_free (ctx, ctx->okay_line); ctx->okay_line = buf; } return 0; } gpg_error_t assuan_write_status (assuan_context_t ctx, const char *keyword, const char *text) { char buffer[256]; char *helpbuf; size_t n; gpg_error_t ae; if ( !ctx || !keyword) return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE); if (!text) text = ""; n = 2 + strlen (keyword) + 1 + strlen (text) + 1; if (n < sizeof (buffer)) { strcpy (buffer, "S "); strcat (buffer, keyword); if (*text) { strcat (buffer, " "); strcat (buffer, text); } ae = assuan_write_line (ctx, buffer); } else if ( (helpbuf = _assuan_malloc (ctx, n)) ) { strcpy (helpbuf, "S "); strcat (helpbuf, keyword); if (*text) { strcat (helpbuf, " "); strcat (helpbuf, text); } ae = assuan_write_line (ctx, helpbuf); _assuan_free (ctx, helpbuf); } else ae = 0; return ae; } diff --git a/src/assuan-inquire.c b/src/assuan-inquire.c index 9599a77..064c8e9 100644 --- a/src/assuan-inquire.c +++ b/src/assuan-inquire.c @@ -1,421 +1,421 @@ /* assuan-inquire.c - handle inquire stuff * Copyright (C) 2001-2003, 2005, 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 #include "assuan-defs.h" #define digitp(a) ((a) >= '0' && (a) <= '9') #define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \ *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10)) #define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1)) struct membuf { size_t len; size_t size; char *buf; int out_of_core; int too_large; size_t maxlen; }; /* A simple implementation of a dynamic buffer. Use init_membuf() to create a buffer, put_membuf to append bytes and get_membuf to release and return the buffer. Allocation errors are detected but only returned at the final get_membuf(), this helps not to clutter the code with out of core checks. */ static void init_membuf (assuan_context_t ctx, struct membuf *mb, int initiallen, size_t maxlen) { mb->len = 0; mb->size = initiallen; mb->out_of_core = 0; mb->too_large = 0; mb->maxlen = maxlen; /* we need to allocate one byte more for get_membuf */ mb->buf = _assuan_malloc (ctx, initiallen + 1); if (!mb->buf) mb->out_of_core = 1; } static void put_membuf (assuan_context_t ctx, struct membuf *mb, const void *buf, size_t len) { if (mb->out_of_core || mb->too_large) return; if (mb->maxlen && mb->len + len > mb->maxlen) { mb->too_large = 1; return; } if (mb->len + len >= mb->size) { char *p; mb->size += len + 1024; /* we need to allocate one byte more for get_membuf */ p = _assuan_realloc (ctx, mb->buf, mb->size + 1); if (!p) { mb->out_of_core = 1; return; } mb->buf = p; } memcpy (mb->buf + mb->len, buf, len); mb->len += len; } static void * get_membuf (assuan_context_t ctx, struct membuf *mb, size_t *len) { char *p; if (mb->out_of_core || mb->too_large) { _assuan_free (ctx, mb->buf); mb->buf = NULL; return NULL; } mb->buf[mb->len] = 0; /* there is enough space for the hidden eos */ p = mb->buf; *len = mb->len; mb->buf = NULL; mb->out_of_core = 1; /* don't allow a reuse */ return p; } static void free_membuf (assuan_context_t ctx, struct membuf *mb) { _assuan_free (ctx, mb->buf); mb->buf = NULL; } /** * assuan_inquire: * @ctx: An assuan context * @keyword: The keyword used for the inquire * @r_buffer: Returns an allocated buffer * @r_length: Returns the length of this buffer * @maxlen: If not 0, the size limit of the inquired data. * * A server may use this to send an inquire. r_buffer, r_length and * maxlen may all be NULL/0 to indicate that no real data is expected. * The returned buffer is guaranteed to have an extra 0-byte after the * length. Thus it can be used as a string if embedded 0 bytes are * not an issue. * * Return value: 0 on success or an ASSUAN error code **/ gpg_error_t assuan_inquire (assuan_context_t ctx, const char *keyword, unsigned char **r_buffer, size_t *r_length, size_t maxlen) { gpg_error_t rc; struct membuf mb; char cmdbuf[LINELENGTH-10]; /* (10 = strlen ("INQUIRE ")+CR,LF) */ unsigned char *line, *p; int linelen; int nodataexpected; if (r_buffer) *r_buffer = NULL; if (r_length) *r_length = 0; if (!ctx || !keyword || (10 + strlen (keyword) >= sizeof (cmdbuf))) return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE); nodataexpected = !r_buffer && !r_length && !maxlen; if (!nodataexpected && (!r_buffer || !r_length)) return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE); - if (!ctx->is_server) + if (!ctx->flags.is_server) return _assuan_error (ctx, GPG_ERR_ASS_NOT_A_SERVER); - if (ctx->in_inquire) + if (ctx->flags.in_inquire) return _assuan_error (ctx, GPG_ERR_ASS_NESTED_COMMANDS); - ctx->in_inquire = 1; + ctx->flags.in_inquire = 1; if (nodataexpected) memset (&mb, 0, sizeof mb); /* avoid compiler warnings */ else init_membuf (ctx, &mb, maxlen? maxlen:1024, maxlen); strcpy (stpcpy (cmdbuf, "INQUIRE "), keyword); rc = assuan_write_line (ctx, cmdbuf); if (rc) goto out; for (;;) { do { do rc = _assuan_read_line (ctx); while (_assuan_error_is_eagain (ctx, rc)); if (rc) goto out; line = (unsigned char *) ctx->inbound.line; linelen = ctx->inbound.linelen; } while (*line == '#' || !linelen); /* Note: As a convenience for manual testing we allow case insensitive keywords. */ if ((line[0] == 'E'||line[0] == 'e') && (line[1] == 'N' || line[1] == 'n') && (line[2] == 'D' || line[2] == 'd') && (!line[3] || line[3] == ' ')) break; /* END command received*/ if ((line[0] == 'C' || line[0] == 'c') && (line[1] == 'A' || line[1] == 'a') && (line[2] == 'N' || line[2] == 'n')) { rc = _assuan_error (ctx, GPG_ERR_ASS_CANCELED); goto out; } if ((line[0] != 'D' && line[0] != 'd') || line[1] != ' ' || nodataexpected) { rc = _assuan_error (ctx, GPG_ERR_ASS_UNEXPECTED_CMD); goto out; } if (linelen < 3) continue; line += 2; linelen -= 2; if (mb.too_large) continue; /* Need to read up the remaining data. */ p = line; while (linelen) { for (;linelen && *p != '%'; linelen--, p++) ; put_membuf (ctx, &mb, line, p-line); if (linelen > 2) { /* handle escaping */ unsigned char tmp[1]; p++; *tmp = xtoi_2 (p); p += 2; linelen -= 3; put_membuf (ctx, &mb, tmp, 1); } line = p; } } if (!nodataexpected) { if (mb.too_large) rc = _assuan_error (ctx, GPG_ERR_ASS_TOO_MUCH_DATA); else { *r_buffer = get_membuf (ctx, &mb, r_length); if (!*r_buffer) rc = _assuan_error (ctx, gpg_err_code_from_syserror ()); } } out: if (!nodataexpected) { if (ctx->flags.confidential) wipememory (mb.buf, mb.len); free_membuf (ctx, &mb); } if (ctx->flags.confidential) wipememory (ctx->inbound.line, LINELENGTH); - ctx->in_inquire = 0; + ctx->flags.in_inquire = 0; return rc; } void _assuan_inquire_release (assuan_context_t ctx) { - if (ctx->in_inquire) + if (ctx->flags.in_inquire) { if (ctx->inquire_membuf) { free_membuf (ctx, ctx->inquire_membuf); free (ctx->inquire_membuf); } - ctx->in_inquire = 0; + ctx->flags.in_inquire = 0; } } gpg_error_t _assuan_inquire_ext_cb (assuan_context_t ctx) { gpg_error_t rc; unsigned char *line; int linelen; struct membuf *mb; unsigned char *p; line = (unsigned char *) ctx->inbound.line; linelen = ctx->inbound.linelen; mb = ctx->inquire_membuf; if ((line[0] == 'C' || line[0] == 'c') && (line[1] == 'A' || line[1] == 'a') && (line[2] == 'N' || line[2] == 'n')) { rc = _assuan_error (ctx, GPG_ERR_ASS_CANCELED); goto out; } if ((line[0] == 'E'||line[0] == 'e') && (line[1] == 'N' || line[1] == 'n') && (line[2] == 'D' || line[2] == 'd') && (!line[3] || line[3] == ' ')) { rc = 0; goto out; } if ((line[0] != 'D' && line[0] != 'd') || line[1] != ' ' || mb == NULL) { rc = _assuan_error (ctx, GPG_ERR_ASS_UNEXPECTED_CMD); goto out; } if (linelen < 3) return 0; line += 2; linelen -= 2; p = line; while (linelen) { for (;linelen && *p != '%'; linelen--, p++) ; put_membuf (ctx, mb, line, p-line); if (linelen > 2) { /* handle escaping */ unsigned char tmp[1]; p++; *tmp = xtoi_2 (p); p += 2; linelen -= 3; put_membuf (ctx, mb, tmp, 1); } line = p; } if (mb->too_large) { rc = _assuan_error (ctx, GPG_ERR_ASS_TOO_MUCH_DATA); goto out; } return 0; out: { size_t buf_len = 0; unsigned char *buf = NULL; if (mb) { buf = get_membuf (ctx, mb, &buf_len); if (!buf) rc = _assuan_error (ctx, gpg_err_code_from_syserror ()); free_membuf (ctx, mb); free (mb); ctx->inquire_membuf = NULL; } - ctx->in_inquire = 0; + ctx->flags.in_inquire = 0; rc = (ctx->inquire_cb) (ctx->inquire_cb_data, rc, buf, buf_len); } return rc; } /** * assuan_inquire_ext: * @ctx: An assuan context * @keyword: The keyword used for the inquire * @maxlen: If not 0, the size limit of the inquired data. * @cb: A callback handler which is invoked after the operation completed. * @cb_data: A user-provided value passed to the callback handler. * * A server may use this to send an inquire. R_BUFFER, R_LENGTH and * MAXLEN may all be NULL/0 to indicate that no real data is expected. * * Return value: 0 on success or an ASSUAN error code **/ gpg_error_t assuan_inquire_ext (assuan_context_t ctx, const char *keyword, size_t maxlen, gpg_error_t (*cb) (void *cb_data, gpg_error_t rc, unsigned char *buf, size_t len), void *cb_data) { gpg_error_t rc; struct membuf *mb = NULL; char cmdbuf[LINELENGTH-10]; /* (10 = strlen ("INQUIRE ")+CR,LF) */ if (!ctx || !keyword || (10 + strlen (keyword) >= sizeof (cmdbuf))) return _assuan_error (ctx, GPG_ERR_ASS_INV_VALUE); - if (!ctx->is_server) + if (!ctx->flags.is_server) return _assuan_error (ctx, GPG_ERR_ASS_NOT_A_SERVER); - if (ctx->in_inquire) + if (ctx->flags.in_inquire) return _assuan_error (ctx, GPG_ERR_ASS_NESTED_COMMANDS); mb = malloc (sizeof (struct membuf)); if (!mb) return _assuan_error (ctx, gpg_err_code_from_syserror ()); init_membuf (ctx, mb, maxlen ? maxlen : 1024, maxlen); strcpy (stpcpy (cmdbuf, "INQUIRE "), keyword); rc = assuan_write_line (ctx, cmdbuf); if (rc) { free_membuf (ctx, mb); free (mb); return rc; } - ctx->in_inquire = 1; + ctx->flags.in_inquire = 1; /* Set up the continuation. */ ctx->inquire_cb = cb; ctx->inquire_cb_data = cb_data; ctx->inquire_membuf = mb; return 0; } diff --git a/src/assuan-pipe-server.c b/src/assuan-pipe-server.c index c78abcd..db66978 100644 --- a/src/assuan-pipe-server.c +++ b/src/assuan-pipe-server.c @@ -1,135 +1,135 @@ /* assuan-pipe-server.c - Assuan server working over a pipe * Copyright (C) 2001, 2002, 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 #ifdef HAVE_SYS_TYPES_H # include #endif #ifdef HAVE_SYS_STAT_H # include #endif #ifdef HAVE_UNISTD_H # include #endif #ifdef HAVE_W32_SYSTEM # ifdef HAVE_WINSOCK2_H # include # endif # include #ifdef HAVE_FCNTL_H # include #endif #endif #include "assuan-defs.h" #include "debug.h" /* Returns true if atoi(S) denotes a valid socket. */ #ifndef HAVE_W32_SYSTEM static int is_valid_socket (const char *s) { struct stat buf; if ( fstat (atoi (s), &buf ) ) return 0; return S_ISSOCK (buf.st_mode); } #endif /*!HAVE_W32_SYSTEM*/ /* This actually is a int file descriptor (and not assuan_fd_t) as _get_osfhandle is called on W32 systems. */ gpg_error_t assuan_init_pipe_server (assuan_context_t ctx, assuan_fd_t filedes[2]) { const char *s; unsigned long ul; gpg_error_t rc; assuan_fd_t infd = ASSUAN_INVALID_FD; assuan_fd_t outfd = ASSUAN_INVALID_FD; int is_usd = 0; TRACE_BEG (ctx, ASSUAN_LOG_CTX, "assuan_init_pipe_server", ctx); if (filedes) { TRACE_LOG2 ("fd[0]=0x%x, fd[1]=0x%x", filedes[0], filedes[1]); } rc = _assuan_register_std_commands (ctx); if (rc) return TRACE_ERR (rc); #ifdef HAVE_W32_SYSTEM infd = filedes[0]; outfd = filedes[1]; #else s = getenv ("_assuan_connection_fd"); if (s && *s && is_valid_socket (s)) { /* Well, we are called with an bi-directional file descriptor. Prepare for using sendmsg/recvmsg. In this case we ignore the passed file descriptors. */ infd = atoi (s); outfd = atoi (s); is_usd = 1; } else if (filedes && filedes[0] != ASSUAN_INVALID_FD && filedes[1] != ASSUAN_INVALID_FD ) { /* Standard pipe server. */ infd = filedes[0]; outfd = filedes[1]; } else { rc = _assuan_error (ctx, GPG_ERR_ASS_SERVER_START); return TRACE_ERR (rc); } #endif - ctx->is_server = 1; + ctx->flags.is_server = 1; 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->max_accepts = 1; s = getenv ("_assuan_pipe_connect_pid"); if (s && (ul=strtoul (s, NULL, 10)) && ul) ctx->pid = (pid_t)ul; else ctx->pid = (pid_t)-1; ctx->accept_handler = NULL; ctx->finish_handler = _assuan_server_finish; ctx->inbound.fd = infd; ctx->outbound.fd = outfd; if (is_usd) _assuan_init_uds_io (ctx); return TRACE_SUC(); } diff --git a/src/assuan-socket-server.c b/src/assuan-socket-server.c index 8b20718..a8330a8 100644 --- a/src/assuan-socket-server.c +++ b/src/assuan-socket-server.c @@ -1,259 +1,259 @@ /* 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->is_server = 1; + 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; if (flags & ASSUAN_SOCKET_SERVER_FDPASSING) _assuan_init_uds_io (ctx); 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; }