diff --git a/src/assuan-handler.c b/src/assuan-handler.c
index 2299fcd..52dc9bc 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))
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;
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 regsitered with a new context.
+ * 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)
return _assuan_error (ctx, GPG_ERR_ASS_GENERAL);
if (ctx->flags.force_close)
ctx->process_complete = 1;
ctx->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)
{
/* 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;
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)
{
ctx->in_command = 1;
ctx->outbound.data.error = 0;
ctx->outbound.data.linelen = 0;
/* Dispatch command and return reply. */
ctx->in_process_next = 1;
rc = dispatch_command (ctx, ctx->inbound.line, ctx->inbound.linelen);
ctx->in_process_next = 0;
}
else if (ctx->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;
do
{
rc = process_next (ctx);
}
while (!rc && !ctx->process_complete && assuan_pending_line (ctx));
if (done)
*done = !!ctx->process_complete;
return rc;
}
static gpg_error_t
process_request (assuan_context_t ctx)
{
gpg_error_t rc;
if (ctx->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;
return 0;
}
if (rc)
return rc;
if (*ctx->inbound.line == '#' || !ctx->inbound.linelen)
return 0; /* comment line - ignore */
ctx->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;
do {
rc = process_request (ctx);
} while (!rc && !ctx->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-socket.c b/src/assuan-socket.c
index bffe480..9a24f1a 100644
--- a/src/assuan-socket.c
+++ b/src/assuan-socket.c
@@ -1,1536 +1,1532 @@
/* assuan-socket.c - Socket wrapper
* Copyright (C) 2004, 2005, 2009 Free Software Foundation, Inc.
* Copyright (C) 2001-2015 g10 Code GmbH
*
* This file is part of Assuan.
*
* Assuan is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* Assuan is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, see .
* SPDX-License-Identifier: LGPL-2.1+
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#ifdef HAVE_W32_SYSTEM
# define WIN32_LEAN_AND_MEAN
# include
# include
#ifndef HAVE_W32CE_SYSTEM
# include
#endif
#else
# include
# include
# include
# include
#endif
#include
#ifdef HAVE_SYS_STAT_H
# include
#endif
#ifdef HAVE_FCNTL_H
#include
#endif
#include
#include "assuan-defs.h"
#include "debug.h"
/* Hacks for Slowaris. */
#ifndef PF_LOCAL
# ifdef PF_UNIX
# define PF_LOCAL PF_UNIX
# else
# define PF_LOCAL AF_UNIX
# endif
#endif
#ifndef AF_LOCAL
# define AF_LOCAL AF_UNIX
#endif
#ifdef HAVE_W32_SYSTEM
#ifndef S_IRUSR
# define S_IRUSR 0
# define S_IWUSR 0
#endif
#ifndef S_IRGRP
# define S_IRGRP 0
# define S_IWGRP 0
#endif
#ifndef ENOTSUP
#define ENOTSUP 129
#endif
#ifndef EPROTO
#define EPROTO 134
#endif
#ifndef EPROTONOSUPPORT
#define EPROTONOSUPPORT 135
#endif
#ifndef ENETDOWN
#define ENETDOWN 116
#endif
#ifndef ENETUNREACH
#define ENETUNREACH 118
#endif
#ifndef EHOSTUNREACH
#define EHOSTUNREACH 110
#endif
#ifndef ECONNREFUSED
#define ECONNREFUSED 107
#endif
#ifndef ETIMEDOUT
#define ETIMEDOUT 138
#endif
#endif
#ifndef ENAMETOOLONG
# define ENAMETOOLONG EINVAL
#endif
#ifndef SUN_LEN
# define SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path) \
+ strlen ((ptr)->sun_path))
#endif
#ifndef INADDR_LOOPBACK
# define INADDR_LOOPBACK ((in_addr_t) 0x7f000001) /* 127.0.0.1. */
#endif
/* The standard SOCKS and TOR port. */
#define SOCKS_PORT 1080
#define TOR_PORT 9050
#define TOR_PORT2 9150 /* The Tor browser is listening there. */
/* In the future, we can allow access to sock_ctx, if that context's
hook functions need to be overridden. There can only be one global
assuan_sock_* user (one library or one application) with this
convenience interface, if non-standard hook functions are
needed. */
static assuan_context_t sock_ctx;
/* This global flag can be set using assuan_sock_set_flag to enable
TOR or SOCKS mode for all sockets. It may not be reset. The value
is the port to be used. */
static unsigned short tor_mode;
#ifdef HAVE_W32_SYSTEM
/* A table of active Cygwin connections. This is only used for
listening socket which should be only a few. We do not enter
sockets after a connect into this table. */
static assuan_fd_t cygwin_fdtable[16];
/* A critical section to guard access to the table of Cygwin
connections. */
static CRITICAL_SECTION cygwin_fdtable_cs;
/* Return true if SOCKFD is listed as Cygwin socket. */
static int
is_cygwin_fd (assuan_fd_t sockfd)
{
int ret = 0;
int i;
EnterCriticalSection (&cygwin_fdtable_cs);
for (i=0; i < DIM(cygwin_fdtable); i++)
{
if (cygwin_fdtable[i] == sockfd)
{
ret = 1;
break;
}
}
LeaveCriticalSection (&cygwin_fdtable_cs);
return ret;
}
/* Insert SOCKFD into the table of Cygwin sockets. Return 0 on
success or -1 on error. */
static int
insert_cygwin_fd (assuan_fd_t sockfd)
{
int ret = 0;
int mark = -1;
int i;
EnterCriticalSection (&cygwin_fdtable_cs);
for (i=0; i < DIM(cygwin_fdtable); i++)
{
if (cygwin_fdtable[i] == sockfd)
goto leave; /* Already in table. */
else if (cygwin_fdtable[i] == ASSUAN_INVALID_FD)
mark = i;
}
if (mark == -1)
{
gpg_err_set_errno (EMFILE);
ret = -1;
}
else
cygwin_fdtable[mark] = sockfd;
leave:
LeaveCriticalSection (&cygwin_fdtable_cs);
return ret;
}
/* Delete SOCKFD from the table of Cygwin sockets. */
static void
delete_cygwin_fd (assuan_fd_t sockfd)
{
int i;
EnterCriticalSection (&cygwin_fdtable_cs);
for (i=0; i < DIM(cygwin_fdtable); i++)
{
if (cygwin_fdtable[i] == sockfd)
{
cygwin_fdtable[i] = ASSUAN_INVALID_FD;
break;
}
}
LeaveCriticalSection (&cygwin_fdtable_cs);
return;
}
-#ifdef HAVE_W32CE_SYSTEM
static wchar_t *
utf8_to_wchar (const char *string)
{
int n;
size_t nbytes;
wchar_t *result;
if (!string)
return NULL;
n = MultiByteToWideChar (CP_UTF8, 0, string, -1, NULL, 0);
if (n < 0)
return NULL;
nbytes = (size_t)(n+1) * sizeof(*result);
if (nbytes / sizeof(*result) != (n+1))
{
SetLastError (ERROR_INVALID_PARAMETER);
return NULL;
}
result = malloc (nbytes);
if (!result)
return NULL;
n = MultiByteToWideChar (CP_UTF8, 0, string, -1, result, n);
if (n < 0)
{
n = GetLastError ();
free (result);
result = NULL;
SetLastError (n);
}
return result;
}
static HANDLE
MyCreateFile (LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwSharedMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile)
{
wchar_t *filename;
HANDLE result;
int err;
filename = utf8_to_wchar (lpFileName);
if (!filename)
return INVALID_HANDLE_VALUE;
result = CreateFileW (filename, dwDesiredAccess, dwSharedMode,
lpSecurityAttributes, dwCreationDisposition,
dwFlagsAndAttributes, hTemplateFile);
err = GetLastError ();
free (filename);
SetLastError (err);
return result;
}
static int
MyDeleteFile (LPCSTR lpFileName)
{
wchar_t *filename;
int result, err;
filename = utf8_to_wchar (lpFileName);
if (!filename)
return 0;
result = DeleteFileW (filename);
err = GetLastError ();
free (filename);
SetLastError (err);
return result;
}
-#else /*!HAVE_W32CE_SYSTEM*/
-#define MyCreateFile CreateFileA
-#define MyDeleteFile DeleteFileA
-#endif /*!HAVE_W32CE_SYSTEM*/
+
int
_assuan_sock_wsa2errno (int err)
{
switch (err)
{
case WSAENOTSOCK:
return EINVAL;
case WSAEWOULDBLOCK:
return EAGAIN;
case ERROR_BROKEN_PIPE:
return EPIPE;
case WSANOTINITIALISED:
return ENOSYS;
case WSAECONNREFUSED:
return ECONNREFUSED;
default:
return EIO;
}
}
/* W32: Fill BUFFER with LENGTH bytes of random. Returns -1 on
failure, 0 on success. Sets errno on failure. */
static int
get_nonce (char *buffer, size_t nbytes)
{
HCRYPTPROV prov;
int ret = -1;
if (!CryptAcquireContext (&prov, NULL, NULL, PROV_RSA_FULL,
(CRYPT_VERIFYCONTEXT|CRYPT_SILENT)) )
gpg_err_set_errno (ENODEV);
else
{
if (!CryptGenRandom (prov, nbytes, (unsigned char *) buffer))
gpg_err_set_errno (ENODEV);
else
ret = 0;
CryptReleaseContext (prov, 0);
}
return ret;
}
/* W32: The buffer for NONCE needs to be at least 16 bytes. Returns 0
on success and sets errno on failure. If FNAME has a Cygwin socket
descriptor True is stored at CYGWIN. */
static int
read_port_and_nonce (const char *fname, unsigned short *port, char *nonce,
int *cygwin)
{
- FILE *fp;
+ estream_t fp;
char buffer[50], *p;
size_t nread;
int aval;
*cygwin = 0;
- fp = fopen (fname, "rb");
+ fp = gpgrt_fopen (fname, "rb");
if (!fp)
return -1;
- nread = fread (buffer, 1, sizeof buffer - 1, fp);
- fclose (fp);
+ nread = gpgrt_fread (buffer, 1, sizeof buffer - 1, fp);
+ gpgrt_fclose (fp);
if (!nread)
{
gpg_err_set_errno (ENOENT);
return -1;
}
buffer[nread] = 0;
if (!strncmp (buffer, "!", 10))
{
/* This is the Cygwin compatible socket emulation. The format
* of the file is:
*
* "!%u %c %08x-%08x-%08x-%08x\x00"
*
* %d for port number, %c for kind of socket (s for STREAM), and
* we have 16-byte random bytes for nonce. We only support
* stream mode.
*/
unsigned int u0;
int narr[4];
if (sscanf (buffer+10, "%u s %08x-%08x-%08x-%08x",
&u0, narr+0, narr+1, narr+2, narr+3) != 5
|| u0 < 1 || u0 > 65535)
{
gpg_err_set_errno (EINVAL);
return -1;
}
*port = u0;
memcpy (nonce, narr, 16);
*cygwin = 1;
}
else
{
/* This is our own socket emulation. */
aval = atoi (buffer);
if (aval < 1 || aval > 65535)
{
gpg_err_set_errno (EINVAL);
return -1;
}
*port = (unsigned int)aval;
for (p=buffer; nread && *p != '\n'; p++, nread--)
;
if (*p != '\n' || nread != 17)
{
gpg_err_set_errno (EINVAL);
return -1;
}
p++; nread--;
memcpy (nonce, p, 16);
}
return 0;
}
#endif /*HAVE_W32_SYSTEM*/
#ifndef HAVE_W32_SYSTEM
/* Find a redirected socket name for fname and return a malloced setup
filled sockaddr. If this does not work out NULL is returned and
ERRNO is set. If the file seems to be a redirect True is stored at
R_REDIRECT. Note that this function uses the standard malloc and
not the assuan wrapped one. The format of the file is:
%Assuan%
socket=NAME
where NAME is the actual socket to use. No white spaces are
allowed, both lines must be terminated by a single LF, extra lines
are not allowed. Environment variables are interpreted in NAME if
given in "${VAR} notation; no escape characters are defined, if
"${" shall be used verbatim, you need to use an environment
variable with that content.
The use of an absolute NAME is strongly suggested. The length of
the file is limited to 511 bytes which is more than sufficient for
that common value of 107 for sun_path. */
static struct sockaddr_un *
eval_redirection (const char *fname, int *r_redirect)
{
FILE *fp;
char buffer[512], *name;
size_t n;
struct sockaddr_un *addr;
char *p, *pend;
const char *s;
*r_redirect = 0;
fp = fopen (fname, "rb");
if (!fp)
return NULL;
n = fread (buffer, 1, sizeof buffer - 1, fp);
fclose (fp);
if (!n)
{
gpg_err_set_errno (ENOENT);
return NULL;
}
buffer[n] = 0;
/* Check that it is a redirection file. We also check that the
first byte of the name is not a LF because that would lead to an
zero length name. */
if (n < 17 || buffer[n-1] != '\n'
|| memcmp (buffer, "%Assuan%\nsocket=", 16)
|| buffer[16] == '\n')
{
gpg_err_set_errno (EINVAL);
return NULL;
}
buffer[n-1] = 0;
name = buffer + 16;
*r_redirect = 1;
addr = calloc (1, sizeof *addr);
if (!addr)
return NULL;
addr->sun_family = AF_LOCAL;
n = 0;
for (p=name; *p; p++)
{
if (*p == '$' && p[1] == '{')
{
p += 2;
pend = strchr (p, '}');
if (!pend)
{
free (addr);
gpg_err_set_errno (EINVAL);
return NULL;
}
*pend = 0;
if (*p && (s = getenv (p)))
{
for (; *s; s++)
{
if (n < sizeof addr->sun_path - 1)
addr->sun_path[n++] = *s;
else
{
free (addr);
gpg_err_set_errno (ENAMETOOLONG);
return NULL;
}
}
}
p = pend;
}
else if (*p == '\n')
break; /* Be nice and stop at the first LF. */
else if (n < sizeof addr->sun_path - 1)
addr->sun_path[n++] = *p;
else
{
free (addr);
gpg_err_set_errno (ENAMETOOLONG);
return NULL;
}
}
return addr;
}
#endif /*!HAVE_W32_SYSTEM*/
/* Return a new socket. Note that under W32 we consider a socket the
same as an System Handle; all functions using such a handle know
about this dual use and act accordingly. */
assuan_fd_t
_assuan_sock_new (assuan_context_t ctx, int domain, int type, int proto)
{
#ifdef HAVE_W32_SYSTEM
assuan_fd_t res;
if (domain == AF_UNIX || domain == AF_LOCAL)
domain = AF_INET;
res = SOCKET2HANDLE(_assuan_socket (ctx, domain, type, proto));
return res;
#else
return _assuan_socket (ctx, domain, type, proto);
#endif
}
int
_assuan_sock_set_flag (assuan_context_t ctx, assuan_fd_t sockfd,
const char *name, int value)
{
(void)ctx;
if (!strcmp (name, "cygwin"))
{
#ifdef HAVE_W32_SYSTEM
if (!value)
delete_cygwin_fd (sockfd);
else if (insert_cygwin_fd (sockfd))
return -1;
#else
/* Setting the Cygwin flag on non-Windows is ignored. */
#endif
}
else if (!strcmp (name, "tor-mode") || !strcmp (name, "socks"))
{
/* If SOCKFD is ASSUAN_INVALID_FD this controls global flag to
switch AF_INET and AF_INET6 into TOR mode by using a SOCKS5
proxy on localhost:9050. It may only be switched on and this
needs to be done before any new threads are started. Once
TOR mode has been enabled, TOR mode can be disabled for a
specific socket by using SOCKFD with a VALUE of 0. */
if (sockfd == ASSUAN_INVALID_FD)
{
if (tor_mode && !value)
{
gpg_err_set_errno (EPERM);
return -1; /* Clearing the global flag is not allowed. */
}
else if (value)
{
if (*name == 's')
tor_mode = SOCKS_PORT;
else
tor_mode = TOR_PORT;
}
}
else if (tor_mode && sockfd != ASSUAN_INVALID_FD)
{
/* Fixme: Disable/enable tormode for the given context. */
}
else
{
gpg_err_set_errno (EINVAL);
return -1;
}
}
else
{
gpg_err_set_errno (EINVAL);
return -1;
}
return 0;
}
int
_assuan_sock_get_flag (assuan_context_t ctx, assuan_fd_t sockfd,
const char *name, int *r_value)
{
(void)ctx;
if (!strcmp (name, "cygwin"))
{
#ifdef HAVE_W32_SYSTEM
*r_value = is_cygwin_fd (sockfd);
#else
*r_value = 0;
#endif
}
else if (!strcmp (name, "tor-mode"))
{
/* FIXME: Find tor-mode for the given socket. */
*r_value = tor_mode == TOR_PORT;
}
else if (!strcmp (name, "socks"))
{
*r_value = tor_mode == SOCKS_PORT;
}
else
{
gpg_err_set_errno (EINVAL);
return -1;
}
return 0;
}
/* Read NBYTES from SOCKFD into BUFFER. Return 0 on success. Handle
EAGAIN and EINTR. */
static int
do_readn (assuan_context_t ctx, assuan_fd_t sockfd,
void *buffer, size_t nbytes)
{
char *p = buffer;
ssize_t n;
while (nbytes)
{
n = _assuan_read (ctx, sockfd, p, nbytes);
if (n < 0 && errno == EINTR)
;
else if (n < 0 && errno == EAGAIN)
_assuan_usleep (ctx, 100000); /* 100ms */
else if (n < 0)
return -1;
else if (!n)
{
gpg_err_set_errno (EIO);
return -1;
}
else
{
p += n;
nbytes -= n;
}
}
return 0;
}
/* Write NBYTES from BUFFER to SOCKFD. Return 0 on success; on error
return -1 and set ERRNO. */
static int
do_writen (assuan_context_t ctx, assuan_fd_t sockfd,
const void *buffer, size_t nbytes)
{
int ret;
ret = _assuan_write (ctx, sockfd, buffer, nbytes);
if (ret >= 0 && ret != nbytes)
{
gpg_err_set_errno (EIO);
ret = -1;
}
else if (ret >= 0)
ret = 0;
return ret;
}
#define TIMEOUT_NOT_WAITING_SOCKS5_FOREVER 1 /* in second(s) */
/* Connect using the SOCKS5 protocol. */
static int
socks5_connect (assuan_context_t ctx, assuan_fd_t sock,
unsigned short socksport,
const char *credentials,
const char *hostname, unsigned short hostport,
struct sockaddr *addr, socklen_t length)
{
int ret;
/* struct sockaddr_in6 proxyaddr_in6; */
struct sockaddr_in proxyaddr_in;
struct sockaddr *proxyaddr;
size_t proxyaddrlen;
union {
struct sockaddr *addr;
struct sockaddr_in *addr_in;
struct sockaddr_in6 *addr_in6;
} addru;
unsigned char buffer[22+512]; /* The extra 512 gives enough space
for username/password or the
hostname. */
size_t buflen, hostnamelen;
int method;
fd_set fds;
struct timeval tv = { TIMEOUT_NOT_WAITING_SOCKS5_FOREVER, 0 };
addru.addr = addr;
FD_ZERO (&fds);
FD_SET (HANDLE2SOCKET (sock), &fds);
/* memset (&proxyaddr_in6, 0, sizeof proxyaddr_in6); */
memset (&proxyaddr_in, 0, sizeof proxyaddr_in);
/* Either HOSTNAME or ADDR may be given. */
if (hostname && addr)
{
gpg_err_set_errno (EINVAL);
return -1;
}
/* If a hostname is given it must fit into our buffer and it must be
less than 256 so that its length can be encoded in one byte. */
hostnamelen = hostname? strlen (hostname) : 0;
if (hostnamelen > 255)
{
gpg_err_set_errno (ENAMETOOLONG);
return -1;
}
/* Connect to local host. */
/* Fixme: First try to use IPv6 but note that
_assuan_sock_connect_byname created the socket with AF_INET. */
proxyaddr_in.sin_family = AF_INET;
proxyaddr_in.sin_port = htons (socksport);
proxyaddr_in.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
proxyaddr = (struct sockaddr *)&proxyaddr_in;
proxyaddrlen = sizeof proxyaddr_in;
ret = _assuan_connect (ctx, HANDLE2SOCKET (sock), proxyaddr, proxyaddrlen);
if (ret && socksport == TOR_PORT && errno == ECONNREFUSED)
{
/* Standard Tor port failed - try the Tor browser port. */
proxyaddr_in.sin_port = htons (TOR_PORT2);
ret = _assuan_connect (ctx, HANDLE2SOCKET (sock),
proxyaddr, proxyaddrlen);
}
/* If we get an EINPROGRESS here the caller is trying to do a
* non-blocking connect (e.g. for custom time out handling) which
* fails here. The easiest fix would be to allow the client to tell
* us the timeout value and we do the timeout handling later on in the
* Socks protocol. */
if (ret)
return ret;
buffer[0] = 5; /* RFC-1928 VER field. */
buffer[1] = 1; /* NMETHODS */
if (credentials)
method = 2; /* Method: username/password authentication. */
else
method = 0; /* Method: No authentication required. */
buffer[2] = method;
/* Negotiate method. */
ret = do_writen (ctx, sock, buffer, 3);
if (ret)
return ret;
/* There may be a different service at the port, which doesn't
respond. Not to be bothred by such a service. */
/* FIXME: Since the process may block on select, it should be
npth_select to release thread scheduling if nPth is enabled.
Ideally, select is better to be in the system hooks. However, it
is considered OK to use select directly; Normal use case is three
steps: detect SOCKS5 service before nPth use, configure nPth
system hooks, and then use socks5_connect. For the first call,
select indeed blocks, but it's only single thread. For
succeeding calls, this select should soon return successfully.
*/
ret = select (HANDLE2SOCKET (sock)+1, &fds, NULL, NULL, &tv);
if (!ret)
{
gpg_err_set_errno (ETIMEDOUT);
return -1;
}
ret = do_readn (ctx, sock, buffer, 2);
if (ret)
return ret;
if (buffer[0] != 5 || buffer[1] != method )
{
/* Socks server returned wrong version or does not support our
requested method. */
gpg_err_set_errno (ENOTSUP); /* Fixme: Is there a better errno? */
return -1;
}
if (credentials)
{
const char *password;
int ulen, plen;
password = strchr (credentials, ':');
if (!password)
{
gpg_err_set_errno (EINVAL); /* No password given. */
return -1;
}
ulen = password - credentials;
password++;
plen = strlen (password);
if (!ulen || ulen > 255 || !plen || plen > 255)
{
gpg_err_set_errno (EINVAL);
return -1;
}
buffer[0] = 1; /* VER of the sub-negotiation. */
buffer[1] = ulen;
buflen = 2;
memcpy (buffer+buflen, credentials, ulen);
buflen += ulen;
buffer[buflen++] = plen;
memcpy (buffer+buflen, password, plen);
buflen += plen;
ret = do_writen (ctx, sock, buffer, buflen);
wipememory (buffer, buflen);
if (ret)
return ret;
ret = do_readn (ctx, sock, buffer, 2);
if (ret)
return ret;
if (buffer[0] != 1)
{
/* SOCKS server returned wrong version. */
gpg_err_set_errno (EPROTONOSUPPORT);
return -1;
}
if (buffer[1])
{
/* SOCKS server denied access. */
gpg_err_set_errno (EACCES);
return -1;
}
}
if (hostname && !*hostname && !hostport)
{
/* Empty hostname given. Stop right here to allow the caller to
do the actual proxy request. */
return 0;
}
/* Send request details (rfc-1928, 4). */
buffer[0] = 5; /* VER */
buffer[1] = 1; /* CMD = CONNECT */
buffer[2] = 0; /* RSV */
if (hostname)
{
buffer[3] = 3; /* ATYP = DOMAINNAME */
buflen = 4;
buffer[buflen++] = hostnamelen;
memcpy (buffer+buflen, hostname, hostnamelen);
buflen += hostnamelen;
buffer[buflen++] = (hostport >> 8); /* DST.PORT */
buffer[buflen++] = hostport;
}
else if (addr->sa_family == AF_INET6)
{
buffer[3] = 4; /* ATYP = IPv6 */
memcpy (buffer+ 4, &addru.addr_in6->sin6_addr.s6_addr, 16); /* DST.ADDR */
memcpy (buffer+20, &addru.addr_in6->sin6_port, 2); /* DST.PORT */
buflen = 22;
}
else
{
buffer[3] = 1; /* ATYP = IPv4 */
memcpy (buffer+4, &addru.addr_in->sin_addr.s_addr, 4); /* DST.ADDR */
memcpy (buffer+8, &addru.addr_in->sin_port, 2); /* DST.PORT */
buflen = 10;
}
ret = do_writen (ctx, sock, buffer, buflen);
if (ret)
return ret;
ret = do_readn (ctx, sock, buffer, 10 /* Length for IPv4 */);
if (ret)
return ret;
if (buffer[0] != 5 || buffer[2] != 0 )
{
/* Socks server returned wrong version or the reserved field is
not zero. */
gpg_err_set_errno (EPROTONOSUPPORT);
return -1;
}
if (buffer[1])
{
switch (buffer[1])
{
case 0x01: /* General SOCKS server failure. */
gpg_err_set_errno (ENETDOWN);
break;
case 0x02: /* Connection not allowed by ruleset. */
gpg_err_set_errno (EACCES);
break;
case 0x03: /* Network unreachable */
gpg_err_set_errno (ENETUNREACH);
break;
case 0x04: /* Host unreachable */
gpg_err_set_errno (EHOSTUNREACH);
break;
case 0x05: /* Connection refused */
gpg_err_set_errno (ECONNREFUSED);
break;
case 0x06: /* TTL expired */
gpg_err_set_errno (ETIMEDOUT);
break;
case 0x08: /* Address type not supported */
gpg_err_set_errno (EPROTONOSUPPORT);
break;
case 0x07: /* Command not supported */
default:
gpg_err_set_errno (ENOTSUP); /* Fixme: Is there a better errno? */
}
return -1;
}
if (buffer[3] == 4)
{
/* ATYP indicates a v6 address. We need to read the remaining
12 bytes. */
ret = do_readn (ctx, sock, buffer+10, 12);
if (ret)
return ret;
}
/* FIXME: We have not way to store the actual address used by the
server. */
return 0;
}
/* Return true if SOCKS shall be used. This is the case if tor_mode
is enabled and the desired address is not the loopback
address. */
static int
use_socks (struct sockaddr *addr)
{
union {
struct sockaddr *addr;
struct sockaddr_in *addr_in;
struct sockaddr_in6 *addr_in6;
} addru;
addru.addr = addr;
if (!tor_mode)
return 0;
else if (addr->sa_family == AF_INET6)
{
const unsigned char *s;
int i;
s = (unsigned char *)&addru.addr_in6->sin6_addr.s6_addr;
if (s[15] != 1)
return 1; /* Last octet is not 1 - not the loopback address. */
for (i=0; i < 15; i++, s++)
if (*s)
return 1; /* Non-zero octet found - not the loopback address. */
return 0; /* This is the loopback address. */
}
else if (addr->sa_family == AF_INET)
{
if (*(unsigned char*)&addru.addr_in->sin_addr.s_addr == 127)
return 0; /* Loopback (127.0.0.0/8) */
return 1;
}
else
return 0;
}
int
_assuan_sock_connect (assuan_context_t ctx, assuan_fd_t sockfd,
struct sockaddr *addr, int addrlen)
{
#ifdef HAVE_W32_SYSTEM
if (addr->sa_family == AF_LOCAL || addr->sa_family == AF_UNIX)
{
struct sockaddr_in myaddr;
struct sockaddr_un *unaddr;
unsigned short port;
char nonce[16];
int cygwin;
int ret;
unaddr = (struct sockaddr_un *)addr;
if (read_port_and_nonce (unaddr->sun_path, &port, nonce, &cygwin))
return -1;
myaddr.sin_family = AF_INET;
myaddr.sin_port = htons (port);
myaddr.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
/* Set return values. */
unaddr->sun_family = myaddr.sin_family;
unaddr->sun_port = myaddr.sin_port;
unaddr->sun_addr.s_addr = myaddr.sin_addr.s_addr;
ret = _assuan_connect (ctx, HANDLE2SOCKET(sockfd),
(struct sockaddr *)&myaddr, sizeof myaddr);
if (!ret)
{
/* Send the nonce. */
ret = do_writen (ctx, sockfd, nonce, 16);
if (!ret && cygwin)
{
char buffer[16];
/* The client sends the nonce back - not useful. We do
a dummy read. */
ret = do_readn (ctx, sockfd, buffer, 16);
if (!ret)
{
/* Send our credentials. */
int n = getpid ();
memcpy (buffer, &n, 4);
memset (buffer+4, 0, 4); /* uid = gid = 0 */
ret = do_writen (ctx, sockfd, buffer, 8);
if (!ret)
{
/* Receive credentials. We don't need them. */
ret = do_readn (ctx, sockfd, buffer, 8);
}
}
}
}
return ret;
}
else if (use_socks (addr))
{
return socks5_connect (ctx, sockfd, tor_mode,
NULL, NULL, 0, addr, addrlen);
}
else
{
return _assuan_connect (ctx, HANDLE2SOCKET (sockfd), addr, addrlen);
}
#else
# if HAVE_STAT
if (addr->sa_family == AF_LOCAL || addr->sa_family == AF_UNIX)
{
struct sockaddr_un *unaddr;
struct stat statbuf;
int redirect, res;
unaddr = (struct sockaddr_un *)addr;
if (!stat (unaddr->sun_path, &statbuf)
&& !S_ISSOCK (statbuf.st_mode)
&& S_ISREG (statbuf.st_mode))
{
/* The given socket file is not a socket but a regular file.
We use the content of that file to redirect to another
socket file. This can be used to use sockets on file
systems which do not support sockets or if for example a
home directory is shared by several machines. */
unaddr = eval_redirection (unaddr->sun_path, &redirect);
if (unaddr)
{
res = _assuan_connect (ctx, sockfd, (struct sockaddr *)unaddr,
SUN_LEN (unaddr));
free (unaddr);
return res;
}
if (redirect)
return -1;
/* Continue using the standard connect. */
}
}
# endif /*HAVE_STAT*/
if (use_socks (addr))
{
return socks5_connect (ctx, sockfd, tor_mode,
NULL, NULL, 0, addr, addrlen);
}
else
{
return _assuan_connect (ctx, sockfd, addr, addrlen);
}
#endif
}
/* Connect to HOST specified as host name on PORT. The current
implementation requires that either the flags ASSUAN_SOCK_SOCKS or
ASSUAN_SOCK_TOR are given in FLAGS. On success a new socket is
returned; on error ASSUAN_INVALID_FD is returned and ERRNO set. If
CREDENTIALS is not NULL, it is a string used for password based
authentication. Username and password are separated by a colon.
RESERVED must be 0. By passing HOST and PORT as 0 the function can
be used to check for proxy availability: If the proxy is available
a socket will be returned which the caller should then close. */
assuan_fd_t
_assuan_sock_connect_byname (assuan_context_t ctx, const char *host,
unsigned short port, int reserved,
const char *credentials, unsigned int flags)
{
assuan_fd_t fd;
unsigned short socksport;
if ((flags & ASSUAN_SOCK_TOR))
socksport = TOR_PORT;
else if ((flags & ASSUAN_SOCK_SOCKS))
socksport = SOCKS_PORT;
else
{
gpg_err_set_errno (ENOTSUP);
return ASSUAN_INVALID_FD;
}
if (host && !*host)
{
/* Error out early on an empty host name. See below. */
gpg_err_set_errno (EINVAL);
return ASSUAN_INVALID_FD;
}
fd = _assuan_sock_new (ctx, AF_INET, SOCK_STREAM, 0);
if (fd == ASSUAN_INVALID_FD)
return fd;
/* For HOST being NULL we pass an empty string which indicates to
socks5_connect to stop midway during the proxy negotiation. Note
that we can't pass NULL directly as this indicates IP address
mode to the called function. */
if (socks5_connect (ctx, fd, socksport,
credentials, host? host:"", port, NULL, 0))
{
int save_errno = errno;
assuan_sock_close (fd);
gpg_err_set_errno (save_errno);
return ASSUAN_INVALID_FD;
}
return fd;
}
int
_assuan_sock_bind (assuan_context_t ctx, assuan_fd_t sockfd,
struct sockaddr *addr, int addrlen)
{
#ifdef HAVE_W32_SYSTEM
if (addr->sa_family == AF_LOCAL || addr->sa_family == AF_UNIX)
{
struct sockaddr_in myaddr;
struct sockaddr_un *unaddr;
HANDLE filehd;
int len = sizeof myaddr;
int rc;
union {
char data[16];
int aint[4];
} nonce;
char tmpbuf[50+16];
DWORD nwritten;
if (get_nonce (nonce.data, 16))
return -1;
unaddr = (struct sockaddr_un *)addr;
myaddr.sin_port = 0;
myaddr.sin_family = AF_INET;
myaddr.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
filehd = MyCreateFile (unaddr->sun_path,
GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
CREATE_NEW,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (filehd == INVALID_HANDLE_VALUE)
{
if (GetLastError () == ERROR_FILE_EXISTS)
gpg_err_set_errno (EADDRINUSE);
return -1;
}
rc = bind (HANDLE2SOCKET (sockfd), (struct sockaddr *)&myaddr, len);
if (!rc)
rc = getsockname (HANDLE2SOCKET (sockfd),
(struct sockaddr *)&myaddr, &len);
if (rc)
{
int save_e = errno;
CloseHandle (filehd);
MyDeleteFile (unaddr->sun_path);
gpg_err_set_errno (save_e);
return rc;
}
if (is_cygwin_fd (sockfd))
{
snprintf (tmpbuf, sizeof tmpbuf,
"!%d s %08x-%08x-%08x-%08x",
ntohs (myaddr.sin_port),
nonce.aint[0], nonce.aint[1], nonce.aint[2], nonce.aint[3]);
len = strlen (tmpbuf) + 1;
}
else
{
snprintf (tmpbuf, sizeof tmpbuf-16, "%d\n", ntohs (myaddr.sin_port));
len = strlen (tmpbuf);
memcpy (tmpbuf+len, nonce.data,16);
len += 16;
}
if (!WriteFile (filehd, tmpbuf, len, &nwritten, NULL))
{
CloseHandle (filehd);
MyDeleteFile (unaddr->sun_path);
gpg_err_set_errno (EIO);
return -1;
}
CloseHandle (filehd);
return 0;
}
else
{
int res = bind (HANDLE2SOCKET(sockfd), addr, addrlen);
if (res < 0)
gpg_err_set_errno ( _assuan_sock_wsa2errno (WSAGetLastError ()));
return res;
}
#else
return bind (sockfd, addr, addrlen);
#endif
}
/* Setup the ADDR structure for a Unix domain socket with the socket
name FNAME. If this is a redirected socket and R_REDIRECTED is not
NULL, it will be setup for the real socket. Returns 0 on success
and stores 1 at R_REDIRECTED if it is a redirected socket. On
error -1 is returned and ERRNO will be set. */
int
_assuan_sock_set_sockaddr_un (const char *fname, struct sockaddr *addr,
int *r_redirected)
{
struct sockaddr_un *unaddr = (struct sockaddr_un *)addr;
#if !defined(HAVE_W32_SYSTEM) && defined(HAVE_STAT)
struct stat statbuf;
#endif
if (r_redirected)
*r_redirected = 0;
#if !defined(HAVE_W32_SYSTEM) && defined(HAVE_STAT)
if (r_redirected
&& !stat (fname, &statbuf)
&& !S_ISSOCK (statbuf.st_mode)
&& S_ISREG (statbuf.st_mode))
{
/* The given socket file is not a socket but a regular file. We
use the content of that file to redirect to another socket
file. This can be used to use sockets on file systems which
do not support sockets or if for example a home directory is
shared by several machines. */
struct sockaddr_un *unaddr_new;
int redirect;
unaddr_new = eval_redirection (fname, &redirect);
if (unaddr_new)
{
memcpy (unaddr, unaddr_new, sizeof *unaddr);
free (unaddr_new);
*r_redirected = 1;
return 0;
}
if (redirect)
{
*r_redirected = 1;
return -1; /* Error. */
}
/* Fallback to standard setup. */
}
#endif /*!HAVE_W32_SYSTEM && HAVE_STAT*/
if (strlen (fname)+1 >= sizeof unaddr->sun_path)
{
gpg_err_set_errno (ENAMETOOLONG);
return -1;
}
memset (unaddr, 0, sizeof *unaddr);
unaddr->sun_family = AF_LOCAL;
strncpy (unaddr->sun_path, fname, sizeof unaddr->sun_path - 1);
unaddr->sun_path[sizeof unaddr->sun_path - 1] = 0;
return 0;
}
int
_assuan_sock_get_nonce (assuan_context_t ctx, struct sockaddr *addr,
int addrlen, assuan_sock_nonce_t *nonce)
{
#ifdef HAVE_W32_SYSTEM
if (addr->sa_family == AF_LOCAL || addr->sa_family == AF_UNIX)
{
struct sockaddr_un *unaddr;
unsigned short port;
int dummy;
if (sizeof nonce->nonce != 16)
{
gpg_err_set_errno (EINVAL);
return -1;
}
nonce->length = 16;
unaddr = (struct sockaddr_un *)addr;
if (read_port_and_nonce (unaddr->sun_path, &port, nonce->nonce, &dummy))
return -1;
}
else
{
nonce->length = 42; /* Arbitrary value to detect unitialized nonce. */
nonce->nonce[0] = 42;
}
#else
(void)addr;
(void)addrlen;
nonce->length = 0;
#endif
return 0;
}
int
_assuan_sock_check_nonce (assuan_context_t ctx, assuan_fd_t fd,
assuan_sock_nonce_t *nonce)
{
#ifdef HAVE_W32_SYSTEM
char buffer[16];
int n;
if (sizeof nonce->nonce != 16)
{
gpg_err_set_errno (EINVAL);
return -1;
}
if (nonce->length == 42 && nonce->nonce[0] == 42)
return 0; /* Not a Unix domain socket. */
if (nonce->length != 16)
{
gpg_err_set_errno (EINVAL);
return -1;
}
if (do_readn (ctx, fd, buffer, 16))
return -1;
if (memcmp (buffer, nonce->nonce, 16))
{
gpg_err_set_errno (EACCES);
return -1;
}
if (is_cygwin_fd (fd))
{
/* Send the nonce back to the client. */
if (do_writen (ctx, fd, buffer, 16))
return -1;
/* Read the credentials. Cygwin uses the
struct ucred { pid_t pid; uid_t uid; gid_t gid; };
with pid_t being an int (4 bytes) and uid_t and gid_t being
shorts (2 bytes). Thus we need to read 8 bytes. However we
we ignore the values because they are not kernel controlled. */
if (do_readn (ctx, fd, buffer, 8))
return -1;
/* Send our credentials: We use the uid and gid we received but
our own pid. */
n = getpid ();
memcpy (buffer, &n, 4);
if (do_writen (ctx, fd, buffer, 8))
return -1;
}
#else
(void)fd;
(void)nonce;
#endif
return 0;
}
/* Public API. */
gpg_error_t
assuan_sock_init ()
{
gpg_error_t err;
#ifdef HAVE_W32_SYSTEM
WSADATA wsadat;
#endif
if (sock_ctx != NULL)
return 0;
#ifdef HAVE_W32_SYSTEM
InitializeCriticalSection (&cygwin_fdtable_cs);
#endif
err = assuan_new (&sock_ctx);
#ifdef HAVE_W32_SYSTEM
if (! err)
WSAStartup (0x202, &wsadat);
#endif
return err;
}
void
assuan_sock_deinit ()
{
if (sock_ctx == NULL)
return;
#ifdef HAVE_W32_SYSTEM
WSACleanup ();
#endif
assuan_release (sock_ctx);
sock_ctx = NULL;
#ifdef HAVE_W32_SYSTEM
DeleteCriticalSection (&cygwin_fdtable_cs);
#endif
}
int
assuan_sock_close (assuan_fd_t fd)
{
#ifdef HAVE_W32_SYSTEM
if (fd != ASSUAN_INVALID_FD)
delete_cygwin_fd (fd);
#endif
return _assuan_close (sock_ctx, fd);
}
assuan_fd_t
assuan_sock_new (int domain, int type, int proto)
{
return _assuan_sock_new (sock_ctx, domain, type, proto);
}
int
assuan_sock_set_flag (assuan_fd_t sockfd, const char *name, int value)
{
return _assuan_sock_set_flag (sock_ctx, sockfd, name, value);
}
int
assuan_sock_get_flag (assuan_fd_t sockfd, const char *name, int *r_value)
{
return _assuan_sock_get_flag (sock_ctx, sockfd, name, r_value);
}
int
assuan_sock_connect (assuan_fd_t sockfd, struct sockaddr *addr, int addrlen)
{
return _assuan_sock_connect (sock_ctx, sockfd, addr, addrlen);
}
assuan_fd_t
assuan_sock_connect_byname (const char *host, unsigned short port,
int reserved, const char *credentials,
unsigned int flags)
{
return _assuan_sock_connect_byname (sock_ctx,
host, port, reserved, credentials, flags);
}
int
assuan_sock_bind (assuan_fd_t sockfd, struct sockaddr *addr, int addrlen)
{
return _assuan_sock_bind (sock_ctx, sockfd, addr, addrlen);
}
int
assuan_sock_set_sockaddr_un (const char *fname, struct sockaddr *addr,
int *r_redirected)
{
return _assuan_sock_set_sockaddr_un (fname, addr, r_redirected);
}
int
assuan_sock_get_nonce (struct sockaddr *addr, int addrlen,
assuan_sock_nonce_t *nonce)
{
return _assuan_sock_get_nonce (sock_ctx, addr, addrlen, nonce);
}
int
assuan_sock_check_nonce (assuan_fd_t fd, assuan_sock_nonce_t *nonce)
{
return _assuan_sock_check_nonce (sock_ctx, fd, nonce);
}
void
assuan_sock_set_system_hooks (assuan_system_hooks_t system_hooks)
{
if (sock_ctx)
_assuan_system_hooks_copy (&sock_ctx->system, system_hooks);
}