505 lines
12 KiB
C
505 lines
12 KiB
C
/* assuan-domain-connect.c - Assuan unix domain socket based client
|
|
* Copyright (C) 2002, 2003 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, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <sys/types.h>
|
|
#ifndef HAVE_W32_SYSTEM
|
|
#include <sys/socket.h>
|
|
#include <sys/un.h>
|
|
#else
|
|
#include <windows.h>
|
|
#endif
|
|
#if HAVE_SYS_UIO_H
|
|
#include <sys/uio.h>
|
|
#endif
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
|
|
#include "assuan-defs.h"
|
|
|
|
#ifndef PF_LOCAL
|
|
# ifdef PF_UNIX
|
|
# define PF_LOCAL PF_UNIX
|
|
# else
|
|
# define PF_LOCAL AF_UNIX
|
|
# endif
|
|
# ifndef AF_LOCAL
|
|
# define AF_LOCAL AF_UNIX
|
|
# endif
|
|
#endif
|
|
|
|
|
|
static void
|
|
do_deinit (assuan_context_t ctx)
|
|
{
|
|
if (ctx->inbound.fd != -1)
|
|
_assuan_close (ctx->inbound.fd);
|
|
ctx->inbound.fd = -1;
|
|
ctx->outbound.fd = -1;
|
|
|
|
if (ctx->domainbuffer)
|
|
{
|
|
assert (ctx->domainbufferallocated);
|
|
free (ctx->domainbuffer);
|
|
}
|
|
|
|
if (ctx->pendingfds)
|
|
{
|
|
int i;
|
|
|
|
assert (ctx->pendingfdscount > 0);
|
|
for (i = 0; i < ctx->pendingfdscount; i ++)
|
|
_assuan_close (ctx->pendingfds[i]);
|
|
|
|
free (ctx->pendingfds);
|
|
}
|
|
|
|
unlink (ctx->myaddr.sun_path);
|
|
}
|
|
|
|
|
|
/* Read from the socket server. */
|
|
static ssize_t
|
|
domain_reader (assuan_context_t ctx, void *buf, size_t buflen)
|
|
{
|
|
int len = ctx->domainbuffersize;
|
|
|
|
#ifndef HAVE_W32_SYSTEM
|
|
start:
|
|
if (len == 0)
|
|
/* No data is buffered. */
|
|
{
|
|
struct msghdr msg;
|
|
struct iovec iovec;
|
|
struct sockaddr_un sender;
|
|
struct
|
|
{
|
|
struct cmsghdr hdr;
|
|
int fd;
|
|
}
|
|
cmsg;
|
|
|
|
memset (&msg, 0, sizeof (msg));
|
|
|
|
for (;;)
|
|
{
|
|
msg.msg_name = &sender;
|
|
msg.msg_namelen = sizeof (struct sockaddr_un);
|
|
msg.msg_iov = &iovec;
|
|
msg.msg_iovlen = 1;
|
|
iovec.iov_base = ctx->domainbuffer;
|
|
iovec.iov_len = ctx->domainbufferallocated;
|
|
msg.msg_control = &cmsg;
|
|
msg.msg_controllen = sizeof cmsg;
|
|
|
|
/* Peek first: if the buffer we have is too small then it
|
|
will be truncated. */
|
|
len = recvmsg (ctx->inbound.fd, &msg, MSG_PEEK);
|
|
if (len < 0)
|
|
{
|
|
printf ("domain_reader: %m\n");
|
|
return -1;
|
|
}
|
|
|
|
if (strcmp (ctx->serveraddr.sun_path,
|
|
((struct sockaddr_un *) msg.msg_name)->sun_path) != 0)
|
|
{
|
|
/* XXX: Arg. Not from whom we expected! What do we
|
|
want to do? Should we just ignore it? Either way,
|
|
we still need to consume the message. */
|
|
break;
|
|
}
|
|
|
|
if (msg.msg_flags & MSG_TRUNC)
|
|
/* Enlarge the buffer and try again. */
|
|
{
|
|
int size = ctx->domainbufferallocated;
|
|
void *tmp;
|
|
|
|
if (size == 0)
|
|
size = 4 * 1024;
|
|
else
|
|
size *= 2;
|
|
|
|
tmp = malloc (size);
|
|
if (! tmp)
|
|
return -1;
|
|
|
|
free (ctx->domainbuffer);
|
|
ctx->domainbuffer = tmp;
|
|
ctx->domainbufferallocated = size;
|
|
}
|
|
else
|
|
/* We have enough space! */
|
|
break;
|
|
}
|
|
|
|
/* Now we have to actually consume it (remember, we only
|
|
peeked). */
|
|
msg.msg_name = &sender;
|
|
msg.msg_namelen = sizeof (struct sockaddr_un);
|
|
msg.msg_iov = &iovec;
|
|
msg.msg_iovlen = 1;
|
|
iovec.iov_base = ctx->domainbuffer;
|
|
iovec.iov_len = ctx->domainbufferallocated;
|
|
msg.msg_control = &cmsg;
|
|
msg.msg_controllen = sizeof cmsg;
|
|
|
|
if (strcmp (ctx->serveraddr.sun_path,
|
|
((struct sockaddr_un *) msg.msg_name)->sun_path) != 0)
|
|
{
|
|
/* XXX: Arg. Not from whom we expected! What do we want to
|
|
do? Should we just ignore it? We shall do the latter
|
|
for the moment. */
|
|
_assuan_log_printf ("not setup to receive messages from `%s'\n",
|
|
((struct sockaddr_un *) msg.msg_name)->sun_path);
|
|
goto start;
|
|
}
|
|
|
|
len = recvmsg (ctx->inbound.fd, &msg, 0);
|
|
if (len < 0)
|
|
{
|
|
_assuan_log_printf ("domain_reader: %s\n", strerror (errno));
|
|
return -1;
|
|
}
|
|
|
|
ctx->domainbuffersize = len;
|
|
ctx->domainbufferoffset = 0;
|
|
|
|
if (sizeof (cmsg) == msg.msg_controllen)
|
|
/* We received a file descriptor. */
|
|
{
|
|
void *tmp;
|
|
|
|
tmp = realloc (ctx->pendingfds,
|
|
sizeof (int) * (ctx->pendingfdscount + 1));
|
|
if (! tmp)
|
|
{
|
|
_assuan_log_printf ("domain_reader: %s\n", strerror (errno));
|
|
return -1;
|
|
}
|
|
|
|
ctx->pendingfds = tmp;
|
|
ctx->pendingfds[ctx->pendingfdscount++]
|
|
= *(int *) CMSG_DATA (&cmsg.hdr);
|
|
|
|
_assuan_log_printf ("received file descriptor %d from peer\n",
|
|
ctx->pendingfds[ctx->pendingfdscount - 1]);
|
|
}
|
|
|
|
if (len == 0)
|
|
goto start;
|
|
}
|
|
#else
|
|
len = recvfrom (ctx->inbound.fd, buf, buflen, 0, NULL, NULL);
|
|
#endif
|
|
|
|
/* Return some data to the user. */
|
|
|
|
if (len > buflen)
|
|
/* We have more than the user requested. */
|
|
len = buflen;
|
|
|
|
memcpy (buf, ctx->domainbuffer + ctx->domainbufferoffset, len);
|
|
ctx->domainbuffersize -= len;
|
|
assert (ctx->domainbuffersize >= 0);
|
|
ctx->domainbufferoffset += len;
|
|
assert (ctx->domainbufferoffset <= ctx->domainbufferallocated);
|
|
|
|
return len;
|
|
}
|
|
|
|
/* Write to the domain server. */
|
|
static ssize_t
|
|
domain_writer (assuan_context_t ctx, const void *buf, size_t buflen)
|
|
{
|
|
#ifndef HAVE_W32_SYSTEM
|
|
struct msghdr msg;
|
|
struct iovec iovec;
|
|
ssize_t len;
|
|
|
|
memset (&msg, 0, sizeof (msg));
|
|
|
|
msg.msg_name = &ctx->serveraddr;
|
|
msg.msg_namelen = offsetof (struct sockaddr_un, sun_path)
|
|
+ strlen (ctx->serveraddr.sun_path) + 1;
|
|
|
|
msg.msg_iovlen = 1;
|
|
msg.msg_iov = &iovec;
|
|
iovec.iov_base = (void *) buf;
|
|
iovec.iov_len = buflen;
|
|
msg.msg_control = 0;
|
|
msg.msg_controllen = 0;
|
|
|
|
len = sendmsg (ctx->outbound.fd, &msg, 0);
|
|
if (len < 0)
|
|
_assuan_log_printf ("domain_writer: %s\n", strerror (errno));
|
|
#else
|
|
int len;
|
|
|
|
len = sendto (ctx->outbound.fd, buf, buflen, 0,
|
|
(struct sockaddr *)&ctx->serveraddr,
|
|
sizeof (struct sockaddr_in));
|
|
#endif
|
|
return len;
|
|
}
|
|
|
|
static assuan_error_t
|
|
domain_sendfd (assuan_context_t ctx, int fd)
|
|
{
|
|
#ifndef HAVE_W32_SYSTEM
|
|
struct msghdr msg;
|
|
struct
|
|
{
|
|
struct cmsghdr hdr;
|
|
int fd;
|
|
}
|
|
cmsg;
|
|
int len;
|
|
|
|
memset (&msg, 0, sizeof (msg));
|
|
|
|
msg.msg_name = &ctx->serveraddr;
|
|
msg.msg_namelen = offsetof (struct sockaddr_un, sun_path)
|
|
+ strlen (ctx->serveraddr.sun_path) + 1;
|
|
|
|
msg.msg_iovlen = 0;
|
|
msg.msg_iov = 0;
|
|
|
|
cmsg.hdr.cmsg_level = SOL_SOCKET;
|
|
cmsg.hdr.cmsg_type = SCM_RIGHTS;
|
|
cmsg.hdr.cmsg_len = sizeof (cmsg);
|
|
|
|
msg.msg_control = &cmsg;
|
|
msg.msg_controllen = sizeof (cmsg);
|
|
|
|
*(int *) CMSG_DATA (&cmsg.hdr) = fd;
|
|
|
|
len = sendmsg (ctx->outbound.fd, &msg, 0);
|
|
if (len < 0)
|
|
{
|
|
_assuan_log_printf ("domain_sendfd: %s\n", strerror (errno));
|
|
return ASSUAN_General_Error;
|
|
}
|
|
else
|
|
return 0;
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
static assuan_error_t
|
|
domain_receivefd (assuan_context_t ctx, int *fd)
|
|
{
|
|
#ifndef HAVE_W32_SYSTEM
|
|
if (ctx->pendingfds == 0)
|
|
{
|
|
_assuan_log_printf ("no pending file descriptors!\n");
|
|
return ASSUAN_General_Error;
|
|
}
|
|
|
|
*fd = ctx->pendingfds[0];
|
|
if (-- ctx->pendingfdscount == 0)
|
|
{
|
|
free (ctx->pendingfds);
|
|
ctx->pendingfds = 0;
|
|
}
|
|
else
|
|
/* Fix the array. */
|
|
{
|
|
memmove (ctx->pendingfds, ctx->pendingfds + 1,
|
|
ctx->pendingfdscount * sizeof (int));
|
|
ctx->pendingfds = realloc (ctx->pendingfds,
|
|
ctx->pendingfdscount * sizeof (int));
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/* Make a connection to the Unix domain socket NAME and return a new
|
|
Assuan context in CTX. SERVER_PID is currently not used but may
|
|
become handy in the future. */
|
|
assuan_error_t
|
|
_assuan_domain_init (assuan_context_t *r_ctx, int rendezvousfd, pid_t peer)
|
|
{
|
|
static struct assuan_io io = { domain_reader, domain_writer,
|
|
domain_sendfd, domain_receivefd };
|
|
|
|
assuan_error_t err;
|
|
assuan_context_t ctx;
|
|
int fd;
|
|
size_t len;
|
|
int tries;
|
|
|
|
if (!r_ctx)
|
|
return ASSUAN_Invalid_Value;
|
|
*r_ctx = NULL;
|
|
|
|
err = _assuan_new_context (&ctx);
|
|
if (err)
|
|
return err;
|
|
|
|
/* Save it in case we need it later. */
|
|
ctx->pid = peer;
|
|
|
|
/* Override the default (NOP) handlers. */
|
|
ctx->deinit_handler = do_deinit;
|
|
|
|
/* Setup the socket. */
|
|
|
|
fd = _assuan_sock_new (PF_LOCAL, SOCK_DGRAM, 0);
|
|
if (fd == -1)
|
|
{
|
|
_assuan_log_printf ("can't create socket: %s\n", strerror (errno));
|
|
_assuan_release_context (ctx);
|
|
return ASSUAN_General_Error;
|
|
}
|
|
|
|
ctx->inbound.fd = fd;
|
|
ctx->outbound.fd = fd;
|
|
|
|
/* And the io buffers. */
|
|
|
|
ctx->io = &io;
|
|
ctx->domainbuffer = 0;
|
|
ctx->domainbufferoffset = 0;
|
|
ctx->domainbuffersize = 0;
|
|
ctx->domainbufferallocated = 0;
|
|
ctx->pendingfds = 0;
|
|
ctx->pendingfdscount = 0;
|
|
|
|
/* Get usable name and bind to it. */
|
|
|
|
for (tries = 0; tries < TMP_MAX; tries ++)
|
|
{
|
|
char *p;
|
|
char buf[L_tmpnam];
|
|
|
|
/* XXX: L_tmpnam must be shorter than sizeof (sun_path)! */
|
|
assert (L_tmpnam < sizeof (ctx->myaddr.sun_path));
|
|
|
|
/* XXX: W32 tmpnam is broken */
|
|
p = tmpnam (buf);
|
|
if (! p)
|
|
{
|
|
_assuan_log_printf ("cannot determine an appropriate temporary file "
|
|
"name. DoS in progress?\n");
|
|
_assuan_release_context (ctx);
|
|
_assuan_close (fd);
|
|
return ASSUAN_General_Error;
|
|
}
|
|
|
|
memset (&ctx->myaddr, 0, sizeof ctx->myaddr);
|
|
ctx->myaddr.sun_family = AF_LOCAL;
|
|
len = strlen (buf) + 1;
|
|
memcpy (ctx->myaddr.sun_path, buf, len);
|
|
len += offsetof (struct sockaddr_un, sun_path);
|
|
|
|
err = _assuan_sock_bind (fd, (struct sockaddr *) &ctx->myaddr, len);
|
|
if (! err)
|
|
break;
|
|
}
|
|
|
|
if (err)
|
|
{
|
|
_assuan_log_printf ("can't bind to `%s': %s\n", ctx->myaddr.sun_path,
|
|
strerror (errno));
|
|
_assuan_release_context (ctx);
|
|
_assuan_close (fd);
|
|
return ASSUAN_Connect_Failed;
|
|
}
|
|
|
|
/* Rendezvous with our peer. */
|
|
{
|
|
FILE *fp;
|
|
char *p;
|
|
|
|
fp = fdopen (rendezvousfd, "w+");
|
|
if (! fp)
|
|
{
|
|
_assuan_log_printf ("can't open rendezvous port: %s\n", strerror (errno));
|
|
return ASSUAN_Connect_Failed;
|
|
}
|
|
|
|
/* Send our address. */
|
|
fprintf (fp, "%s\n", ctx->myaddr.sun_path);
|
|
fflush (fp);
|
|
|
|
/* And receive our peer's. */
|
|
memset (&ctx->serveraddr, 0, sizeof ctx->serveraddr);
|
|
for (p = ctx->serveraddr.sun_path;
|
|
p < (ctx->serveraddr.sun_path
|
|
+ sizeof ctx->serveraddr.sun_path - 1);
|
|
p ++)
|
|
{
|
|
*p = fgetc (fp);
|
|
if (*p == '\n')
|
|
break;
|
|
}
|
|
*p = '\0';
|
|
fclose (fp);
|
|
|
|
ctx->serveraddr.sun_family = AF_LOCAL;
|
|
}
|
|
|
|
*r_ctx = ctx;
|
|
return 0;
|
|
}
|
|
|
|
assuan_error_t
|
|
assuan_domain_connect (assuan_context_t * r_ctx, int rendezvousfd, pid_t peer)
|
|
{
|
|
assuan_error_t aerr;
|
|
int okay, off;
|
|
|
|
aerr = _assuan_domain_init (r_ctx, rendezvousfd, peer);
|
|
if (aerr)
|
|
return aerr;
|
|
|
|
/* Initial handshake. */
|
|
aerr = _assuan_read_from_server (*r_ctx, &okay, &off);
|
|
if (aerr)
|
|
_assuan_log_printf ("can't connect to server: %s\n",
|
|
assuan_strerror (aerr));
|
|
else if (okay != 1)
|
|
{
|
|
_assuan_log_printf ("can't connect to server: `");
|
|
_assuan_log_sanitized_string ((*r_ctx)->inbound.line);
|
|
fprintf (assuan_get_assuan_log_stream (), "'\n");
|
|
aerr = ASSUAN_Connect_Failed;
|
|
}
|
|
|
|
if (aerr)
|
|
assuan_disconnect (*r_ctx);
|
|
|
|
return aerr;
|
|
}
|