/* 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 #endif #include #include #include #include #include #include #include #if HAVE_SYS_UIO_H #include #endif #include #include #include #include #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; }