* configure.ac: Make the check for funopen fail with just a
[gpgme.git] / assuan / assuan-domain-connect.c
1 /* assuan-domain-connect.c - Assuan unix domain socket based client
2  *      Copyright (C) 2002, 2003 Free Software Foundation, Inc.
3  *
4  * This file is part of Assuan.
5  *
6  * Assuan is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as
8  * published by the Free Software Foundation; either version 2.1 of
9  * the License, or (at your option) any later version.
10  *
11  * Assuan is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA 
19  */
20
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24
25 #include <stdlib.h>
26 #include <stddef.h>
27 #include <stdio.h>
28 #include <errno.h>
29 #include <sys/socket.h>
30 #include <sys/un.h>
31 #include <unistd.h>
32 #include <fcntl.h>
33 #include <string.h>
34 #include <assert.h>
35
36 #include "assuan-defs.h"
37
38 #define LOG(format, args...) \
39         fprintf (assuan_get_assuan_log_stream (), \
40                  assuan_get_assuan_log_prefix (), \
41                  "%s" format , ## args)
42
43
44 static void
45 do_deinit (ASSUAN_CONTEXT ctx)
46 {
47   if (ctx->inbound.fd != -1)
48     close (ctx->inbound.fd);
49   ctx->inbound.fd = -1;
50   ctx->outbound.fd = -1;
51
52   if (ctx->domainbuffer)
53     {
54       assert (ctx->domainbufferallocated);
55       free (ctx->domainbuffer);
56     }
57
58   if (ctx->pendingfds)
59     {
60       int i;
61
62       assert (ctx->pendingfdscount > 0);
63       for (i = 0; i < ctx->pendingfdscount; i ++)
64         close (ctx->pendingfds[i]);
65
66       free (ctx->pendingfds);
67     }
68
69   unlink (ctx->myaddr.sun_path);
70 }
71
72
73 /* Read from the socket server.  */
74 static ssize_t
75 domain_reader (ASSUAN_CONTEXT ctx, void *buf, size_t buflen)
76 {
77   int len = ctx->domainbuffersize;
78
79  start:
80   if (len == 0)
81     /* No data is buffered.  */
82     {
83       struct msghdr msg;
84       struct iovec iovec;
85       struct sockaddr_un sender;
86       struct
87       {
88         struct cmsghdr hdr;
89         int fd;
90       }
91       cmsg;
92
93       memset (&msg, 0, sizeof (msg));
94
95       for (;;)
96         {
97           msg.msg_name = &sender;
98           msg.msg_namelen = sizeof (struct sockaddr_un);
99           msg.msg_iov = &iovec;
100           msg.msg_iovlen = 1;
101           iovec.iov_base = ctx->domainbuffer;
102           iovec.iov_len = ctx->domainbufferallocated;
103           msg.msg_control = &cmsg;
104           msg.msg_controllen = sizeof cmsg;
105
106           /* Peek first: if the buffer we have is too small then it
107              will be truncated.  */
108           len = recvmsg (ctx->inbound.fd, &msg, MSG_PEEK);
109           if (len < 0)
110             {
111               printf ("domain_reader: %m\n");
112               return -1;
113             }
114
115           if (strcmp (ctx->serveraddr.sun_path,
116                       ((struct sockaddr_un *) msg.msg_name)->sun_path) != 0)
117             {
118               /* XXX: Arg.  Not from whom we expected!  What do we
119                  want to do?  Should we just ignore it?  Either way,
120                  we still need to consume the message.  */
121               break;
122             }
123
124           if (msg.msg_flags & MSG_TRUNC)
125             /* Enlarge the buffer and try again.  */
126             {
127               int size = ctx->domainbufferallocated;
128               void *tmp;
129
130               if (size == 0)
131                 size = 4 * 1024;
132               else
133                 size *= 2;
134
135               tmp = malloc (size);
136               if (! tmp)
137                 return -1;
138
139               free (ctx->domainbuffer);
140               ctx->domainbuffer = tmp;
141               ctx->domainbufferallocated = size;
142             }
143           else
144             /* We have enough space!  */
145             break;
146         }
147
148       /* Now we have to actually consume it (remember, we only
149          peeked).  */
150       msg.msg_name = &sender;
151       msg.msg_namelen = sizeof (struct sockaddr_un);
152       msg.msg_iov = &iovec;
153       msg.msg_iovlen = 1;
154       iovec.iov_base = ctx->domainbuffer;
155       iovec.iov_len = ctx->domainbufferallocated;
156       msg.msg_control = &cmsg;
157       msg.msg_controllen = sizeof cmsg;
158
159       if (strcmp (ctx->serveraddr.sun_path,
160                   ((struct sockaddr_un *) msg.msg_name)->sun_path) != 0)
161         {
162           /* XXX: Arg.  Not from whom we expected!  What do we want to
163              do?  Should we just ignore it?  We shall do the latter
164              for the moment.  */
165           LOG ("Not setup to receive messages from: `%s'.",
166                ((struct sockaddr_un *) msg.msg_name)->sun_path);
167           goto start;
168         }
169
170       len = recvmsg (ctx->inbound.fd, &msg, 0);
171       if (len < 0)
172         {
173           LOG ("domain_reader: %s\n", strerror (errno));
174           return -1;
175         }
176
177       ctx->domainbuffersize = len;
178       ctx->domainbufferoffset = 0;
179
180       if (sizeof (cmsg) == msg.msg_controllen)
181         /* We received a file descriptor.  */
182         {
183           void *tmp;
184
185           tmp = realloc (ctx->pendingfds,
186                          sizeof (int) * (ctx->pendingfdscount + 1));
187           if (! tmp)
188             {
189               LOG ("domain_reader: %s\n", strerror (errno));
190               return -1;
191             }
192
193           ctx->pendingfds = tmp;
194           ctx->pendingfds[ctx->pendingfdscount++]
195             = *(int *) CMSG_DATA (&cmsg.hdr);
196
197           LOG ("Received file descriptor %d from peer.\n",
198                ctx->pendingfds[ctx->pendingfdscount - 1]);
199         }
200
201       if (len == 0)
202         goto start;
203     }
204
205   /* Return some data to the user.  */
206
207   if (len > buflen)
208     /* We have more than the user requested.  */
209     len = buflen;
210
211   memcpy (buf, ctx->domainbuffer + ctx->domainbufferoffset, len);
212   ctx->domainbuffersize -= len;
213   assert (ctx->domainbuffersize >= 0);
214   ctx->domainbufferoffset += len;
215   assert (ctx->domainbufferoffset <= ctx->domainbufferallocated);
216
217   return len;
218 }
219
220 /* Write to the domain server.  */
221 static ssize_t
222 domain_writer (ASSUAN_CONTEXT ctx, const void *buf, size_t buflen)
223 {
224   struct msghdr msg;
225   struct iovec iovec;
226   ssize_t len;
227
228   memset (&msg, 0, sizeof (msg));
229
230   msg.msg_name = &ctx->serveraddr;
231   msg.msg_namelen = offsetof (struct sockaddr_un, sun_path)
232     + strlen (ctx->serveraddr.sun_path) + 1;
233
234   msg.msg_iovlen = 1;
235   msg.msg_iov = &iovec;
236   iovec.iov_base = (void *) buf;
237   iovec.iov_len = buflen;
238   msg.msg_control = 0;
239   msg.msg_controllen = 0;
240
241   len = sendmsg (ctx->outbound.fd, &msg, 0);
242   if (len < 0)
243     LOG ("domain_writer: %s\n", strerror (errno));
244
245   return len;
246 }
247
248 static AssuanError
249 domain_sendfd (ASSUAN_CONTEXT ctx, int fd)
250 {
251   struct msghdr msg;
252   struct
253   {
254     struct cmsghdr hdr;
255     int fd;
256   }
257   cmsg;
258   int len;
259
260   memset (&msg, 0, sizeof (msg));
261
262   msg.msg_name = &ctx->serveraddr;
263   msg.msg_namelen = offsetof (struct sockaddr_un, sun_path)
264     + strlen (ctx->serveraddr.sun_path) + 1;
265
266   msg.msg_iovlen = 0;
267   msg.msg_iov = 0;
268
269   cmsg.hdr.cmsg_level = SOL_SOCKET;
270   cmsg.hdr.cmsg_type = SCM_RIGHTS;
271   cmsg.hdr.cmsg_len = sizeof (cmsg);
272
273   msg.msg_control = &cmsg;
274   msg.msg_controllen = sizeof (cmsg);
275
276   *(int *) CMSG_DATA (&cmsg.hdr) = fd;
277
278   len = sendmsg (ctx->outbound.fd, &msg, 0);
279   if (len < 0)
280     {
281       LOG ("domain_sendfd: %s\n", strerror (errno));
282       return ASSUAN_General_Error;
283     }
284   else
285     return 0;
286 }
287
288 static AssuanError
289 domain_receivefd (ASSUAN_CONTEXT ctx, int *fd)
290 {
291   if (ctx->pendingfds == 0)
292     {
293       LOG ("No pending file descriptors!\n");
294       return ASSUAN_General_Error;
295     }
296
297   *fd = ctx->pendingfds[0];
298   if (-- ctx->pendingfdscount == 0)
299     {
300       free (ctx->pendingfds);
301       ctx->pendingfds = 0;
302     }
303   else
304     /* Fix the array.  */
305     {
306       memmove (ctx->pendingfds, ctx->pendingfds + 1,
307                ctx->pendingfdscount * sizeof (int));
308       ctx->pendingfds = realloc (ctx->pendingfds,
309                                  ctx->pendingfdscount * sizeof (int));
310     }
311
312   return 0;
313 }
314
315
316
317 /* Make a connection to the Unix domain socket NAME and return a new
318    Assuan context in CTX.  SERVER_PID is currently not used but may
319    become handy in the future.  */
320 AssuanError
321 _assuan_domain_init (ASSUAN_CONTEXT *r_ctx, int rendezvousfd, pid_t peer)
322 {
323   static struct assuan_io io = { domain_reader, domain_writer,
324                                  domain_sendfd, domain_receivefd };
325
326   AssuanError err;
327   ASSUAN_CONTEXT ctx;
328   int fd;
329   size_t len;
330   int tries;
331
332   if (!r_ctx)
333     return ASSUAN_Invalid_Value;
334   *r_ctx = NULL;
335
336   err = _assuan_new_context (&ctx); 
337   if (err)
338     return err;
339
340   /* Save it in case we need it later.  */
341   ctx->pid = peer;
342
343   /* Override the default (NOP) handlers.  */
344   ctx->deinit_handler = do_deinit;
345
346   /* Setup the socket.  */
347
348   fd = socket (PF_LOCAL, SOCK_DGRAM, 0);
349   if (fd == -1)
350     {
351       LOG ("can't create socket: %s\n", strerror (errno));
352       _assuan_release_context (ctx);
353       return ASSUAN_General_Error;
354     }
355
356   ctx->inbound.fd = fd;
357   ctx->outbound.fd = fd;
358
359   /* And the io buffers.  */
360
361   ctx->io = &io;
362   ctx->domainbuffer = 0;
363   ctx->domainbufferoffset = 0;
364   ctx->domainbuffersize = 0;
365   ctx->domainbufferallocated = 0;
366   ctx->pendingfds = 0;
367   ctx->pendingfdscount = 0;
368
369   /* Get usable name and bind to it.  */
370
371   for (tries = 0; tries < TMP_MAX; tries ++)
372     {
373       char *p;
374       char buf[L_tmpnam];
375
376       /* XXX: L_tmpnam must be shorter than sizeof (sun_path)!  */
377       assert (L_tmpnam < sizeof (ctx->myaddr.sun_path));
378
379       p = tmpnam (buf);
380       if (! p)
381         {
382           LOG ("cannot determine an appropriate temporary file "
383                "name.  DOS in progress?\n");
384           _assuan_release_context (ctx);
385           close (fd);
386           return ASSUAN_General_Error;
387         }
388
389       memset (&ctx->myaddr, 0, sizeof ctx->myaddr);
390       ctx->myaddr.sun_family = AF_LOCAL;
391       len = strlen (buf) + 1;
392       memcpy (ctx->myaddr.sun_path, buf, len);
393       len += offsetof (struct sockaddr_un, sun_path);
394
395       err = bind (fd, (struct sockaddr *) &ctx->myaddr, len);
396       if (! err)
397         break;
398     }
399
400   if (err)
401     {
402       LOG ("can't bind to `%s': %s\n", ctx->myaddr.sun_path,
403            strerror (errno));
404       _assuan_release_context (ctx);
405       close (fd);
406       return ASSUAN_Connect_Failed;
407     }
408
409   /* Rendezvous with our peer.  */
410   {
411     FILE *fp;
412     char *p;
413
414     fp = fdopen (rendezvousfd, "w+");
415     if (! fp)
416       {
417         LOG ("can't open rendezvous port: %s\n", strerror (errno));
418         return ASSUAN_Connect_Failed;
419       }
420
421     /* Send our address.  */
422     fprintf (fp, "%s\n", ctx->myaddr.sun_path);
423     fflush (fp);
424
425     /* And receive our peer's.  */
426     memset (&ctx->serveraddr, 0, sizeof ctx->serveraddr);
427     for (p = ctx->serveraddr.sun_path;
428          p < (ctx->serveraddr.sun_path
429               + sizeof ctx->serveraddr.sun_path - 1);
430          p ++)
431       {
432         *p = fgetc (fp);
433         if (*p == '\n')
434           break;
435       }
436     *p = '\0';
437     fclose (fp);
438
439     ctx->serveraddr.sun_family = AF_LOCAL;
440   }
441
442   *r_ctx = ctx;
443   return 0;
444 }
445
446 AssuanError
447 assuan_domain_connect (ASSUAN_CONTEXT * r_ctx, int rendezvousfd, pid_t peer)
448 {
449   AssuanError aerr;
450   int okay, off;
451
452   aerr = _assuan_domain_init (r_ctx, rendezvousfd, peer);
453   if (aerr)
454     return aerr;
455
456   /* Initial handshake.  */
457   aerr = _assuan_read_from_server (*r_ctx, &okay, &off);
458   if (aerr)
459     LOG ("can't connect to server: %s\n", assuan_strerror (aerr));
460   else if (okay != 1)
461     {
462       LOG ("can't connect to server: `");
463       _assuan_log_sanitized_string ((*r_ctx)->inbound.line);
464       fprintf (assuan_get_assuan_log_stream (), "'\n");
465       aerr = ASSUAN_Connect_Failed;
466     }
467
468   if (aerr)
469     assuan_disconnect (*r_ctx);
470
471   return aerr;
472 }