gpgme/assuan/assuan-domain-connect.c
2004-02-13 13:02:07 +00:00

473 lines
11 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/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <assert.h>
#include "assuan-defs.h"
#define LOG(format, args...) \
fprintf (assuan_get_assuan_log_stream (), \
assuan_get_assuan_log_prefix (), \
"%s" format , ## args)
static void
do_deinit (ASSUAN_CONTEXT ctx)
{
if (ctx->inbound.fd != -1)
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 ++)
close (ctx->pendingfds[i]);
free (ctx->pendingfds);
}
unlink (ctx->myaddr.sun_path);
}
/* Read from the socket server. */
static ssize_t
domain_reader (ASSUAN_CONTEXT ctx, void *buf, size_t buflen)
{
int len = ctx->domainbuffersize;
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. */
LOG ("Not setup to receive messages from: `%s'.",
((struct sockaddr_un *) msg.msg_name)->sun_path);
goto start;
}
len = recvmsg (ctx->inbound.fd, &msg, 0);
if (len < 0)
{
LOG ("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)
{
LOG ("domain_reader: %s\n", strerror (errno));
return -1;
}
ctx->pendingfds = tmp;
ctx->pendingfds[ctx->pendingfdscount++]
= *(int *) CMSG_DATA (&cmsg.hdr);
LOG ("Received file descriptor %d from peer.\n",
ctx->pendingfds[ctx->pendingfdscount - 1]);
}
if (len == 0)
goto start;
}
/* 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 ctx, const void *buf, size_t buflen)
{
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)
LOG ("domain_writer: %s\n", strerror (errno));
return len;
}
static AssuanError
domain_sendfd (ASSUAN_CONTEXT ctx, int fd)
{
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)
{
LOG ("domain_sendfd: %s\n", strerror (errno));
return ASSUAN_General_Error;
}
else
return 0;
}
static AssuanError
domain_receivefd (ASSUAN_CONTEXT ctx, int *fd)
{
if (ctx->pendingfds == 0)
{
LOG ("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));
}
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. */
AssuanError
_assuan_domain_init (ASSUAN_CONTEXT *r_ctx, int rendezvousfd, pid_t peer)
{
static struct assuan_io io = { domain_reader, domain_writer,
domain_sendfd, domain_receivefd };
AssuanError err;
ASSUAN_CONTEXT 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 = socket (PF_LOCAL, SOCK_DGRAM, 0);
if (fd == -1)
{
LOG ("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));
p = tmpnam (buf);
if (! p)
{
LOG ("cannot determine an appropriate temporary file "
"name. DOS in progress?\n");
_assuan_release_context (ctx);
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 = bind (fd, (struct sockaddr *) &ctx->myaddr, len);
if (! err)
break;
}
if (err)
{
LOG ("can't bind to `%s': %s\n", ctx->myaddr.sun_path,
strerror (errno));
_assuan_release_context (ctx);
close (fd);
return ASSUAN_Connect_Failed;
}
/* Rendezvous with our peer. */
{
FILE *fp;
char *p;
fp = fdopen (rendezvousfd, "w+");
if (! fp)
{
LOG ("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;
}
AssuanError
assuan_domain_connect (ASSUAN_CONTEXT * r_ctx, int rendezvousfd, pid_t peer)
{
AssuanError 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)
LOG ("can't connect to server: %s\n", assuan_strerror (aerr));
else if (okay != 1)
{
LOG ("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;
}