]> the.earth.li Git - onak.git/blob - keyd.c
Make keyd more robust in the face of socket errors
[onak.git] / keyd.c
1 /*
2  * keyd.c - key retrieval daemon
3  *
4  * Copyright 2004,2011 Jonathan McDowell <noodles@earth.li>
5  *
6  * This program is free software: you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the Free
8  * Software Foundation; version 2 of the License.
9  *
10  * This program is distributed in the hope that it will be useful, but WITHOUT
11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13  * more details.
14  *
15  * You should have received a copy of the GNU General Public License along with
16  * this program; if not, write to the Free Software Foundation, Inc., 51
17  * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  */
19
20 #include <errno.h>
21 #include <fcntl.h>
22 #include <getopt.h>
23 #include <signal.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <sys/select.h>
28 #include <sys/socket.h>
29 #include <sys/types.h>
30 #include <sys/un.h>
31 #include <time.h>
32 #include <unistd.h>
33
34 #include "charfuncs.h"
35 #include "cleanup.h"
36 #include "keyd.h"
37 #include "keydb.h"
38 #include "keyid.h"
39 #include "keystructs.h"
40 #include "log.h"
41 #include "mem.h"
42 #include "onak-conf.h"
43 #include "parsekey.h"
44 #include "version.h"
45
46 /* Maximum number of clients we're prepared to accept at once */
47 #define MAX_CLIENTS 16
48
49 static struct keyd_stats *stats;
50
51 static void daemonize(void)
52 {
53         pid_t pid;
54
55         pid = fork();
56
57         if (pid < 0) {
58                 logthing(LOGTHING_CRITICAL,
59                         "Failed to fork into background: %d (%s)",
60                         errno,
61                         strerror(errno));
62                 exit(EXIT_FAILURE);
63         } else if (pid > 0) {
64                 logthing(LOGTHING_INFO, "Backgrounded as pid %d.", pid);
65                 exit(EXIT_SUCCESS);
66         }
67
68         if (setsid() == -1) {
69                 logthing(LOGTHING_CRITICAL,
70                         "Couldn't set process group leader: %d (%s)",
71                         errno,
72                         strerror(errno));
73                 exit(EXIT_FAILURE);
74         }
75
76         if (!freopen("/dev/null", "r", stdin)) {
77                 logthing(LOGTHING_CRITICAL,
78                         "Couldn't reopen stdin to NULL: %d (%s)",
79                         errno,
80                         strerror(errno));
81                 exit(EXIT_FAILURE);
82         }
83         if (!freopen("/dev/null", "w", stdout)) {
84                 logthing(LOGTHING_CRITICAL,
85                         "Couldn't reopen stdout to NULL: %d (%s)",
86                         errno,
87                         strerror(errno));
88                 exit(EXIT_FAILURE);
89         }
90         if (!freopen("/dev/null", "w", stderr)) {
91                 logthing(LOGTHING_CRITICAL,
92                         "Couldn't reopen stderr to NULL: %d (%s)",
93                         errno,
94                         strerror(errno));
95                 exit(EXIT_FAILURE);
96         }
97
98         return;
99 }
100
101 static bool keyd_write_key(int fd, struct openpgp_publickey *key)
102 {
103         struct openpgp_packet_list *packets = NULL;
104         struct openpgp_packet_list *list_end = NULL;
105         struct buffer_ctx           storebuf;
106         ssize_t written;
107         bool    ok = true;
108
109         storebuf.offset = 0;
110         storebuf.size = 8192;
111         storebuf.buffer = malloc(8192);
112
113         flatten_publickey(key,
114                                 &packets,
115                                 &list_end);
116         write_openpgp_stream(buffer_putchar,
117                                 &storebuf,
118                                 packets);
119         logthing(LOGTHING_TRACE,
120                                 "Sending %d bytes.",
121                                 storebuf.offset);
122         written = write(fd, &storebuf.offset,
123                         sizeof(storebuf.offset));
124         if (written == 0) {
125                 ok = false;
126         } else {
127                 written = write(fd, storebuf.buffer,
128                         storebuf.offset);
129                 if (written != storebuf.offset) {
130                         ok = false;
131                 }
132         }
133
134         free(storebuf.buffer);
135         storebuf.buffer = NULL;
136         storebuf.size = storebuf.offset = 0;
137         free_packet_list(packets);
138         packets = list_end = NULL;
139
140         return (ok);
141 }
142
143 static bool keyd_write_reply(int fd, enum keyd_reply _reply)
144 {
145         uint32_t reply = _reply;
146         ssize_t written;
147         bool ok = true;
148
149         written = write(fd, &reply, sizeof(reply));
150         if (written != sizeof(reply)) {
151                 ok = false;
152         }
153
154         return (ok);
155 }
156
157 static bool keyd_write_size(int fd, size_t size)
158 {
159         ssize_t written;
160         bool ok = true;
161
162         written = write(fd, &size, sizeof(size));
163         if (written != sizeof(size)) {
164                 ok = false;
165         }
166
167         return (ok);
168 }
169
170 static void iteratefunc(void *ctx, struct openpgp_publickey *key)
171 {
172         int      *fd = (int *) ctx;
173         uint64_t  keyid;
174
175         if (key != NULL) {
176                 get_keyid(key, &keyid);
177                 logthing(LOGTHING_TRACE,
178                                 "Iterating over 0x%016" PRIX64 ".",
179                                 keyid);
180
181                 keyd_write_key(*fd, key);
182         }
183
184         return;
185 }
186
187 static int sock_init(const char *sockname)
188 {
189         struct sockaddr_un sock;
190         int                fd = -1;
191         int                ret = -1;
192
193         fd = socket(PF_UNIX, SOCK_STREAM, 0);
194         if (fd != -1) {
195                 ret = fcntl(fd, F_SETFD, FD_CLOEXEC);
196         }
197
198         if (ret != -1) {
199                 sock.sun_family = AF_UNIX;
200                 strncpy(sock.sun_path, sockname, sizeof(sock.sun_path) - 1);
201                 unlink(sockname);
202                 ret = bind(fd, (struct sockaddr *) &sock, sizeof(sock));
203         }
204
205         if (ret != -1) {
206                 ret = listen(fd, 5);
207                 if (ret == -1) {
208                         close(fd);
209                         fd = -1;
210                 }
211         }
212
213         return fd;
214 }
215
216 static int sock_do(struct onak_dbctx *dbctx, int fd)
217 {
218         uint32_t cmd = KEYD_CMD_UNKNOWN;
219         ssize_t  bytes = 0;
220         ssize_t  count = 0;
221         int      ret = 0;
222         uint64_t keyid = 0;
223         char     *search = NULL;
224         struct openpgp_publickey *key = NULL;
225         struct openpgp_packet_list *packets = NULL;
226         struct buffer_ctx storebuf;
227         struct skshash hash;
228         struct openpgp_fingerprint fingerprint;
229
230         /*
231          * Get the command from the client.
232          */
233         bytes = read(fd, &cmd, sizeof(cmd));
234
235         logthing(LOGTHING_DEBUG, "Read %d bytes, command: %d", bytes, cmd);
236
237         if (bytes != sizeof(cmd)) {
238                 ret = 1;
239         }
240         
241         if (ret == 0) {
242                 if (cmd < KEYD_CMD_LAST) {
243                         stats->command_stats[cmd]++;
244                 } else {
245                         stats->command_stats[KEYD_CMD_UNKNOWN]++;
246                 }
247                 switch (cmd) {
248                 case KEYD_CMD_VERSION:
249                         if (!keyd_write_reply(fd, KEYD_REPLY_OK)) {
250                                 ret = 1;
251                         }
252                         if (ret == 0) {
253                                 cmd = sizeof(keyd_version);
254                                 bytes = write(fd, &cmd, sizeof(cmd));
255                                 if (bytes != sizeof(cmd)) {
256                                         ret = 1;
257                                 }
258                         }
259                         if (ret == 0) {
260                                 bytes = write(fd, &keyd_version,
261                                         sizeof(keyd_version));
262                                 if (bytes != sizeof(keyd_version)) {
263                                         ret = 1;
264                                 }
265                         }
266                         break;
267                 case KEYD_CMD_GET_ID:
268                         if (!keyd_write_reply(fd, KEYD_REPLY_OK)) {
269                                 ret = 1;
270                         }
271                         if (ret == 0) {
272                                 bytes = read(fd, &keyid, sizeof(keyid));
273                                 if (bytes != sizeof(keyid)) {
274                                         ret = 1;
275                                 }
276                         }
277                         if (ret == 0) {
278                                 logthing(LOGTHING_INFO,
279                                                 "Fetching 0x%" PRIX64
280                                                 ", result: %d",
281                                                 keyid,
282                                                 dbctx->fetch_key_id(dbctx,
283                                                         keyid,
284                                                         &key, false));
285                                 if (key != NULL) {
286                                         keyd_write_key(fd, key);
287                                         free_publickey(key);
288                                         key = NULL;
289                                 } else {
290                                         if (!keyd_write_size(fd, 0)) {
291                                                 ret = 1;
292                                         }
293                                 }
294                         }
295                         break;
296                 case KEYD_CMD_GET_FP:
297                         if (!keyd_write_reply(fd, KEYD_REPLY_OK)) {
298                                 ret = 1;
299                         }
300                         if (ret == 0) {
301                                 if ((read(fd, &bytes, 1) != 1) ||
302                                                 (bytes > MAX_FINGERPRINT_LEN)) {
303                                         ret = 1;
304                                 } else {
305                                         fingerprint.length = bytes;
306                                         bytes = read(fd, fingerprint.fp,
307                                                 fingerprint.length);
308                                         if (bytes != fingerprint.length) {
309                                                 ret = 1;
310                                         }
311                                 }
312                         }
313                         if (ret == 0) {
314                                 logthing(LOGTHING_INFO,
315                                                 "Fetching by fingerprint"
316                                                 ", result: %d",
317                                                 dbctx->fetch_key_fp(dbctx,
318                                                         &fingerprint,
319                                                         &key, false));
320                                 if (key != NULL) {
321                                         keyd_write_key(fd, key);
322                                         free_publickey(key);
323                                         key = NULL;
324                                 } else {
325                                         if (!keyd_write_size(fd, 0)) {
326                                                 ret = 1;
327                                         }
328                                 }
329                         }
330                         break;
331
332                 case KEYD_CMD_GET_TEXT:
333                         if (!keyd_write_reply(fd, KEYD_REPLY_OK)) {
334                                 ret = 1;
335                         }
336                         if (ret == 0) {
337                                 bytes = read(fd, &count, sizeof(count));
338                                 if (bytes != sizeof(count)) {
339                                         ret = 1;
340                                 }
341                         }
342                         if (ret == 0) {
343                                 search = malloc(count+1);
344                                 bytes = read(fd, search, count);
345                                 if (bytes != count) {
346                                         ret = 1;
347                                         break;
348                                 }
349                                 search[count] = 0;
350                                 logthing(LOGTHING_INFO,
351                                                 "Fetching %s, result: %d",
352                                                 search,
353                                                 dbctx->fetch_key_text(dbctx,
354                                                         search, &key));
355                                 if (key != NULL) {
356                                         keyd_write_key(fd, key);
357                                         free_publickey(key);
358                                         key = NULL;
359                                 } else {
360                                         if (!keyd_write_size(fd, 0)) {
361                                                 ret = 1;
362                                         }
363                                 }
364                                 free(search);
365                         }
366                         break;
367                 case KEYD_CMD_STORE:
368                         if (!keyd_write_reply(fd, KEYD_REPLY_OK)) {
369                                 ret = 1;
370                         }
371                         if (ret == 0) {
372                                 bytes = read(fd, &storebuf.size,
373                                         sizeof(storebuf.size));
374                                 logthing(LOGTHING_TRACE, "Reading %d bytes.",
375                                         storebuf.size);
376                                 if (bytes != sizeof(storebuf.size)) {
377                                         ret = 1;
378                                 }
379                         }
380                         if (ret == 0 && storebuf.size > 0) {
381                                 storebuf.buffer = malloc(storebuf.size);
382                                 storebuf.offset = 0;
383                                 bytes = count = 0;
384                                 while (bytes >= 0 && count < storebuf.size) {
385                                         bytes = read(fd,
386                                                 &storebuf.buffer[count],
387                                                 storebuf.size - count);
388                                         logthing(LOGTHING_TRACE,
389                                                         "Read %d bytes.",
390                                                         bytes);
391                                         count += bytes;
392                                 }
393                                 read_openpgp_stream(buffer_fetchchar,
394                                                 &storebuf,
395                                                 &packets,
396                                                 0);
397                                 parse_keys(packets, &key);
398                                 dbctx->store_key(dbctx, key, false, false);
399                                 free_packet_list(packets);
400                                 packets = NULL;
401                                 free_publickey(key);
402                                 key = NULL;
403                                 free(storebuf.buffer);
404                                 storebuf.buffer = NULL;
405                                 storebuf.size = storebuf.offset = 0;
406                         }
407                         break;
408                 case KEYD_CMD_DELETE:
409                         if (!keyd_write_reply(fd, KEYD_REPLY_OK)) {
410                                 ret = 1;
411                         }
412                         if (ret == 0) {
413                                 bytes = read(fd, &keyid, sizeof(keyid));
414                                 if (bytes != sizeof(keyid)) {
415                                         ret = 1;
416                                 }
417                         }
418                         if (ret == 0) {
419                                 logthing(LOGTHING_INFO,
420                                                 "Deleting 0x%" PRIX64
421                                                 ", result: %d",
422                                                 keyid,
423                                                 dbctx->delete_key(dbctx,
424                                                         keyid, false));
425                         }
426                         break;
427                 case KEYD_CMD_GETFULLKEYID:
428                         if (!keyd_write_reply(fd, KEYD_REPLY_OK)) {
429                                 ret = 1;
430                         }
431                         if (ret == 0) {
432                                 bytes = read(fd, &keyid, sizeof(keyid));
433                                 if (bytes != sizeof(keyid)) {
434                                         ret = 1;
435                                 }
436                         }
437                         if (ret == 0) {
438                                 keyid = dbctx->getfullkeyid(dbctx, keyid);
439                                 cmd = sizeof(keyid);
440                                 bytes = write(fd, &cmd, sizeof(cmd));
441                                 if (bytes != sizeof(cmd)) {
442                                         ret = 1;
443                                 }
444                         }
445                         if (ret == 0) {
446                                 bytes = write(fd, &keyid, sizeof(keyid));
447                                 if (bytes != sizeof(keyid)) {
448                                         ret = 1;
449                                 }
450                         }
451                         break;
452                 case KEYD_CMD_KEYITER:
453                         if (!keyd_write_reply(fd, KEYD_REPLY_OK)) {
454                                 ret = 1;
455                         }
456                         if (ret == 0) {
457                                 dbctx->iterate_keys(dbctx, iteratefunc,
458                                         &fd);
459                                 if (!keyd_write_size(fd, 0)) {
460                                         ret = 1;
461                                 }
462                         }
463                         break;
464                 case KEYD_CMD_CLOSE:
465                         /* We're going to close the FD even if this fails */
466                         (void) keyd_write_reply(fd, KEYD_REPLY_OK);
467                         ret = 1;
468                         break;
469                 case KEYD_CMD_QUIT:
470                         /* We're going to quit even if this fails */
471                         (void) keyd_write_reply(fd, KEYD_REPLY_OK);
472                         logthing(LOGTHING_NOTICE,
473                                 "Exiting due to quit request.");
474                         ret = 1;
475                         trytocleanup();
476                         break;
477                 case KEYD_CMD_STATS:
478                         if (!keyd_write_reply(fd, KEYD_REPLY_OK)) {
479                                 ret = 1;
480                         }
481                         if (ret == 0) {
482                                 cmd = sizeof(*stats);
483                                 bytes = write(fd, &cmd, sizeof(cmd));
484                                 if (bytes != sizeof(cmd)) {
485                                         ret = 1;
486                                 }
487                         }
488                         if (ret == 0) {
489                                 bytes = write(fd, stats, sizeof(*stats));
490                                 if (bytes != sizeof(*stats)) {
491                                         ret = 1;
492                                 }
493                         }
494                         break;
495                 case KEYD_CMD_GET_SKSHASH:
496                         if (!keyd_write_reply(fd, KEYD_REPLY_OK)) {
497                                 ret = 1;
498                         }
499                         if (ret == 0) {
500                                 bytes = read(fd, hash.hash, sizeof(hash.hash));
501                                 if (bytes != sizeof(hash.hash)) {
502                                         ret = 1;
503                                 }
504                         }
505                         if (ret == 0) {
506                                 logthing(LOGTHING_INFO,
507                                                 "Fetching by hash"
508                                                 ", result: %d",
509                                                 dbctx->fetch_key_skshash(dbctx,
510                                                         &hash, &key));
511                                 if (key != NULL) {
512                                         keyd_write_key(fd, key);
513                                         free_publickey(key);
514                                         key = NULL;
515                                 } else {
516                                         if (!keyd_write_size(fd, 0)) {
517                                                 ret = 1;
518                                         }
519                                 }
520                         }
521                         break;
522
523                 default:
524                         logthing(LOGTHING_ERROR, "Got unknown command: %d",
525                                         cmd);
526                         if (!keyd_write_reply(fd, KEYD_REPLY_UNKNOWN_CMD)) {
527                                 ret = 1;
528                         }
529                 }
530         }
531
532         return(ret);
533 }
534
535 static int sock_close(int fd)
536 {
537         shutdown(fd, SHUT_RDWR);
538         return close(fd);
539 }
540
541 static int sock_accept(int fd)
542 {
543         struct sockaddr_un sock;
544         socklen_t          socklen;
545         int    srv = -1;
546         int    ret = -1;
547
548         socklen = sizeof(sock);
549         srv = accept(fd, (struct sockaddr *) &sock, &socklen);
550         if (srv != -1) {
551                 ret = fcntl(srv, F_SETFD, FD_CLOEXEC);
552         }
553
554         if (ret != -1) {
555                 stats->connects++;
556         }
557
558         return (srv);
559 }
560
561 static void usage(void)
562 {
563         puts("keyd " ONAK_VERSION " - backend key serving daemon for the "
564                 "onak PGP keyserver.\n");
565         puts("Usage:\n");
566         puts("\tkeyd [options]\n");
567         puts("\tOptions:\n:");
568         puts("-c <file> - use <file> as the config file");
569         puts("-f        - run in the foreground");
570         puts("-h        - show this help text");
571         exit(EXIT_FAILURE);
572 }
573
574 int main(int argc, char *argv[])
575 {
576         int fd = -1, maxfd, i, clients[MAX_CLIENTS];
577         fd_set rfds;
578         char sockname[1024];
579         char *configfile = NULL;
580         bool foreground = false;
581         int optchar;
582         struct onak_dbctx *dbctx;
583
584         while ((optchar = getopt(argc, argv, "c:fh")) != -1 ) {
585                 switch (optchar) {
586                 case 'c':
587                         if (configfile != NULL) {
588                                 free(configfile);
589                         }
590                         configfile = strdup(optarg);
591                         break;
592                 case 'f':
593                         foreground = true;
594                         break;
595                 case 'h':
596                 default:
597                         usage();
598                         break;
599                 }
600         }
601
602         readconfig(configfile);
603         free(configfile);
604         configfile = NULL;
605         initlogthing("keyd", config.logfile);
606         config.use_keyd = false;
607
608         if (!foreground) {
609                 daemonize();
610         }
611
612         catchsignals();
613         signal(SIGPIPE, SIG_IGN);
614
615
616         stats = calloc(1, sizeof(*stats));
617         if (!stats) {
618                 logthing(LOGTHING_ERROR,
619                         "Couldn't allocate memory for stats structure.");
620                 exit(EXIT_FAILURE);
621         }
622         stats->started = time(NULL);
623
624         snprintf(sockname, 1023, "%s/%s", config.db_dir, KEYD_SOCKET);
625         fd = sock_init(sockname);
626
627         if (fd != -1) {
628                 FD_ZERO(&rfds);
629                 FD_SET(fd, &rfds);
630                 maxfd = fd;
631                 memset(clients, -1, sizeof (clients));
632
633                 dbctx = config.dbinit(false);
634
635                 logthing(LOGTHING_NOTICE, "Accepting connections.");
636                 while (!cleanup() && select(maxfd + 1, &rfds, NULL, NULL, NULL) != -1) {
637                         /*
638                          * Deal with existing clients first; if we're at our
639                          * connection limit then processing them might free
640                          * things up and let us accept the next client below.
641                          */
642                         for (i = 0; i < MAX_CLIENTS; i++) {
643                                 if (clients[i] != -1 &&
644                                                 FD_ISSET(clients[i], &rfds)) {
645                                         logthing(LOGTHING_DEBUG,
646                                                 "Handling connection for client %d.", i);
647                                         if (sock_do(dbctx, clients[i])) {
648                                                 sock_close(clients[i]);
649                                                 clients[i] = -1;
650                                                 logthing(LOGTHING_DEBUG,
651                                                         "Closed connection for client %d.", i);
652                                         }
653                                 }
654                         }
655                         /*
656                          * Check if we have a new incoming connection to accept.
657                          */
658                         if (FD_ISSET(fd, &rfds)) {
659                                 for (i = 0; i < MAX_CLIENTS; i++) {
660                                         if (clients[i] == -1) {
661                                                 break;
662                                         }
663                                 }
664                                 if (i < MAX_CLIENTS) {
665                                         logthing(LOGTHING_INFO,
666                                                 "Accepted connection %d.", i);
667                                         clients[i] = sock_accept(fd);
668                                 }
669                         }
670                         FD_ZERO(&rfds);
671                         FD_SET(fd, &rfds);
672                         maxfd = fd;
673                         for (i = 0; i < MAX_CLIENTS; i++) {
674                                 if (clients[i] != -1) {
675                                         FD_SET(clients[i], &rfds);
676                                         maxfd = (maxfd > clients[i]) ?
677                                                         maxfd : clients[i];
678                                 }
679                         }
680                 }
681                 dbctx->cleanupdb(dbctx);
682                 sock_close(fd);
683                 unlink(sockname);
684         }
685
686         free(stats);
687
688         cleanuplogthing();
689         cleanupconfig();
690
691         return(EXIT_SUCCESS);
692 }