X-Git-Url: https://the.earth.li/gitweb/?p=onak.git;a=blobdiff_plain;f=keyd.c;h=68f72370b5e04390ccbbc1515f242a1e7e124b49;hp=c802264fb332882ef523173a5ddd8c9a12da6270;hb=adc800dbc424a1e246dd4a82a0c2e88eeda25531;hpb=7f88fa1553a361d5d70a87eeeb7a37e1f3476cc8 diff --git a/keyd.c b/keyd.c index c802264..68f7237 100644 --- a/keyd.c +++ b/keyd.c @@ -1,14 +1,27 @@ /* * keyd.c - key retrieval daemon * - * Jonathan McDowell + * Copyright 2004,2011 Jonathan McDowell * - * Copyright 2004 Project Purple + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; version 2 of the License. + * + * This program 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 General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . */ #include #include #include +#include +#include +#include #include #include #include @@ -16,8 +29,15 @@ #include #include #include +#include #include +#include "config.h" + +#ifdef HAVE_SYSTEMD +#include +#endif + #include "charfuncs.h" #include "cleanup.h" #include "keyd.h" @@ -28,8 +48,18 @@ #include "mem.h" #include "onak-conf.h" #include "parsekey.h" +#include "version.h" + +/* Maximum number of clients we're prepared to accept at once */ +#define MAX_CLIENTS 16 + +#ifdef HAVE_SYSTEMD +static bool using_socket_activation = false; +#endif -void daemonize(void) +static struct keyd_stats *stats; + +static void daemonize(void) { pid_t pid; @@ -46,86 +76,178 @@ void daemonize(void) exit(EXIT_SUCCESS); } - pid = setsid(); + if (setsid() == -1) { + logthing(LOGTHING_CRITICAL, + "Couldn't set process group leader: %d (%s)", + errno, + strerror(errno)); + exit(EXIT_FAILURE); + } - freopen("/dev/null", "r", stdin); - freopen("/dev/null", "w", stdout); - freopen("/dev/null", "w", stderr); + if (!freopen("/dev/null", "r", stdin)) { + logthing(LOGTHING_CRITICAL, + "Couldn't reopen stdin to NULL: %d (%s)", + errno, + strerror(errno)); + exit(EXIT_FAILURE); + } + if (!freopen("/dev/null", "w", stdout)) { + logthing(LOGTHING_CRITICAL, + "Couldn't reopen stdout to NULL: %d (%s)", + errno, + strerror(errno)); + exit(EXIT_FAILURE); + } + if (!freopen("/dev/null", "w", stderr)) { + logthing(LOGTHING_CRITICAL, + "Couldn't reopen stderr to NULL: %d (%s)", + errno, + strerror(errno)); + exit(EXIT_FAILURE); + } return; } -void iteratefunc(void *ctx, struct openpgp_publickey *key) +static bool keyd_write_key(int fd, struct openpgp_publickey *key) { struct openpgp_packet_list *packets = NULL; struct openpgp_packet_list *list_end = NULL; struct buffer_ctx storebuf; - int ret = 0; - int *fd = (int *) ctx; + ssize_t written; + bool ok = true; - if (key != NULL) { - storebuf.offset = 0; - storebuf.size = 8192; - storebuf.buffer = malloc(8192); - - logthing(LOGTHING_TRACE, - "Iterating over 0x%016llX.", - get_keyid(key)); + storebuf.offset = 0; + storebuf.size = 8192; + storebuf.buffer = malloc(8192); - flatten_publickey(key, + flatten_publickey(key, &packets, &list_end); - write_openpgp_stream(buffer_putchar, + write_openpgp_stream(buffer_putchar, &storebuf, packets); - logthing(LOGTHING_TRACE, + logthing(LOGTHING_TRACE, "Sending %d bytes.", storebuf.offset); - ret = write(*fd, &storebuf.offset, + written = write(fd, &storebuf.offset, sizeof(storebuf.offset)); - if (ret != 0) { - write(*fd, storebuf.buffer, - storebuf.offset); + if (written == 0) { + ok = false; + } else { + written = write(fd, storebuf.buffer, + storebuf.offset); + if (written != storebuf.offset) { + ok = false; } + } + + free(storebuf.buffer); + storebuf.buffer = NULL; + storebuf.size = storebuf.offset = 0; + free_packet_list(packets); + packets = list_end = NULL; + + return (ok); +} + +static bool keyd_write_reply(int fd, enum keyd_reply _reply) +{ + uint32_t reply = _reply; + ssize_t written; + bool ok = true; + + written = write(fd, &reply, sizeof(reply)); + if (written != sizeof(reply)) { + ok = false; + } + + return (ok); +} - free(storebuf.buffer); - storebuf.buffer = NULL; - storebuf.size = storebuf.offset = 0; - free_packet_list(packets); - packets = list_end = NULL; +static bool keyd_write_size(int fd, size_t size) +{ + ssize_t written; + bool ok = true; + + written = write(fd, &size, sizeof(size)); + if (written != sizeof(size)) { + ok = false; + } + + return (ok); +} + +static void iteratefunc(void *ctx, struct openpgp_publickey *key) +{ + int *fd = (int *) ctx; + uint64_t keyid; + + if (key != NULL) { + get_keyid(key, &keyid); + logthing(LOGTHING_TRACE, + "Iterating over 0x%016" PRIX64 ".", + keyid); + + keyd_write_key(*fd, key); } return; } -int sock_init(const char *sockname) +static int sock_init(const char *sockname) { struct sockaddr_un sock; int fd = -1; int ret = -1; +#ifdef HAVE_SYSTEMD + int n; + + n = sd_listen_fds(0); + if (n > 1) { + logthing(LOGTHING_ERROR, + "Too many file descriptors received from systemd."); + } else if (n == 1) { + fd = SD_LISTEN_FDS_START + 0; + if (sd_is_socket_unix(fd, SOCK_STREAM, 1, NULL, 0) <= 0) { + logthing(LOGTHING_ERROR, + "systemd passed an invalid socket."); + fd = -1; + } + using_socket_activation = true; + } else { +#endif + fd = socket(PF_UNIX, SOCK_STREAM, 0); + if (fd != -1) { + ret = fcntl(fd, F_SETFD, FD_CLOEXEC); + } - fd = socket(PF_UNIX, SOCK_STREAM, 0); - if (fd != -1) { - ret = fcntl(fd, F_SETFD, 1); - } + if (ret != -1) { + sock.sun_family = AF_UNIX; + strncpy(sock.sun_path, sockname, + sizeof(sock.sun_path) - 1); + unlink(sockname); + ret = bind(fd, (struct sockaddr *) &sock, + sizeof(sock)); + } - if (ret != -1) { - sock.sun_family = AF_UNIX; - strncpy(sock.sun_path, sockname, sizeof(sock.sun_path) - 1); - unlink(sockname); - ret = bind(fd, (struct sockaddr *) &sock, sizeof(sock)); + if (ret != -1) { + ret = listen(fd, 5); + if (ret == -1) { + close(fd); + fd = -1; + } + } +#ifdef HAVE_SYSTEMD } +#endif - if (ret != -1) { - ret = listen(fd, 5); - } - return fd; } -int sock_do(int fd) +static int sock_do(struct onak_dbctx *dbctx, int fd) { - int cmd = KEYD_CMD_UNKNOWN; + uint32_t cmd = KEYD_CMD_UNKNOWN; ssize_t bytes = 0; ssize_t count = 0; int ret = 0; @@ -133,8 +255,9 @@ int sock_do(int fd) char *search = NULL; struct openpgp_publickey *key = NULL; struct openpgp_packet_list *packets = NULL; - struct openpgp_packet_list *list_end = NULL; struct buffer_ctx storebuf; + struct skshash hash; + struct openpgp_fingerprint fingerprint; /* * Get the command from the client. @@ -148,118 +271,149 @@ int sock_do(int fd) } if (ret == 0) { + if (cmd < KEYD_CMD_LAST) { + stats->command_stats[cmd]++; + } else { + stats->command_stats[KEYD_CMD_UNKNOWN]++; + } switch (cmd) { case KEYD_CMD_VERSION: - cmd = KEYD_REPLY_OK; - write(fd, &cmd, sizeof(cmd)); - write(fd, &keyd_version, sizeof(keyd_version)); + if (!keyd_write_reply(fd, KEYD_REPLY_OK)) { + ret = 1; + } + if (ret == 0) { + cmd = sizeof(keyd_version); + bytes = write(fd, &cmd, sizeof(cmd)); + if (bytes != sizeof(cmd)) { + ret = 1; + } + } + if (ret == 0) { + bytes = write(fd, &keyd_version, + sizeof(keyd_version)); + if (bytes != sizeof(keyd_version)) { + ret = 1; + } + } break; - case KEYD_CMD_GET: - cmd = KEYD_REPLY_OK; - write(fd, &cmd, sizeof(cmd)); - bytes = read(fd, &keyid, sizeof(keyid)); - if (bytes != sizeof(keyid)) { + case KEYD_CMD_GET_ID: + if (!keyd_write_reply(fd, KEYD_REPLY_OK)) { ret = 1; } - storebuf.offset = 0; + if (ret == 0) { + bytes = read(fd, &keyid, sizeof(keyid)); + if (bytes != sizeof(keyid)) { + ret = 1; + } + } if (ret == 0) { logthing(LOGTHING_INFO, - "Fetching 0x%llX, result: %d", + "Fetching 0x%" PRIX64 + ", result: %d", keyid, - config.dbbackend-> - fetch_key(keyid, &key, false)); + dbctx->fetch_key_id(dbctx, + keyid, + &key, false)); if (key != NULL) { - storebuf.size = 8192; - storebuf.buffer = malloc(8192); - - flatten_publickey(key, - &packets, - &list_end); - write_openpgp_stream(buffer_putchar, - &storebuf, - packets); - logthing(LOGTHING_TRACE, - "Sending %d bytes.", - storebuf.offset); - write(fd, &storebuf.offset, - sizeof(storebuf.offset)); - write(fd, storebuf.buffer, - storebuf.offset); - - free(storebuf.buffer); - storebuf.buffer = NULL; - storebuf.size = storebuf.offset = 0; - free_packet_list(packets); - packets = list_end = NULL; + keyd_write_key(fd, key); free_publickey(key); key = NULL; } else { - write(fd, &storebuf.offset, - sizeof(storebuf.offset)); + if (!keyd_write_size(fd, 0)) { + ret = 1; + } } } break; - case KEYD_CMD_GETTEXT: - cmd = KEYD_REPLY_OK; - write(fd, &cmd, sizeof(cmd)); - bytes = read(fd, &count, sizeof(count)); - if (bytes != sizeof(count)) { + case KEYD_CMD_GET_FP: + if (!keyd_write_reply(fd, KEYD_REPLY_OK)) { ret = 1; } - storebuf.offset = 0; + if (ret == 0) { + if ((read(fd, &bytes, 1) != 1) || + (bytes > MAX_FINGERPRINT_LEN)) { + ret = 1; + } else { + fingerprint.length = bytes; + bytes = read(fd, fingerprint.fp, + fingerprint.length); + if (bytes != fingerprint.length) { + ret = 1; + } + } + } + if (ret == 0) { + logthing(LOGTHING_INFO, + "Fetching by fingerprint" + ", result: %d", + dbctx->fetch_key_fp(dbctx, + &fingerprint, + &key, false)); + if (key != NULL) { + keyd_write_key(fd, key); + free_publickey(key); + key = NULL; + } else { + if (!keyd_write_size(fd, 0)) { + ret = 1; + } + } + } + break; + + case KEYD_CMD_GET_TEXT: + if (!keyd_write_reply(fd, KEYD_REPLY_OK)) { + ret = 1; + } + if (ret == 0) { + bytes = read(fd, &count, sizeof(count)); + if (bytes != sizeof(count)) { + ret = 1; + } + } if (ret == 0) { search = malloc(count+1); - read(fd, search, count); + bytes = read(fd, search, count); + if (bytes != count) { + ret = 1; + free(search); + break; + } search[count] = 0; logthing(LOGTHING_INFO, "Fetching %s, result: %d", search, - config.dbbackend-> - fetch_key_text(search, &key)); + dbctx->fetch_key_text(dbctx, + search, &key)); if (key != NULL) { - storebuf.size = 8192; - storebuf.buffer = malloc(8192); - - flatten_publickey(key, - &packets, - &list_end); - write_openpgp_stream(buffer_putchar, - &storebuf, - packets); - logthing(LOGTHING_TRACE, - "Sending %d bytes.", - storebuf.offset); - write(fd, &storebuf.offset, - sizeof(storebuf.offset)); - write(fd, storebuf.buffer, - storebuf.offset); - - free(storebuf.buffer); - storebuf.buffer = NULL; - storebuf.size = storebuf.offset = 0; - free_packet_list(packets); - packets = list_end = NULL; + keyd_write_key(fd, key); free_publickey(key); key = NULL; } else { - write(fd, &storebuf.offset, - sizeof(storebuf.offset)); + if (!keyd_write_size(fd, 0)) { + ret = 1; + } } + free(search); } break; case KEYD_CMD_STORE: - cmd = KEYD_REPLY_OK; - write(fd, &cmd, sizeof(cmd)); - storebuf.offset = 0; - bytes = read(fd, &storebuf.size, + case KEYD_CMD_UPDATE: + if (!keyd_write_reply(fd, KEYD_REPLY_OK)) { + ret = 1; + } + if (ret == 0) { + bytes = read(fd, &storebuf.size, sizeof(storebuf.size)); - logthing(LOGTHING_TRACE, "Reading %d bytes.", + logthing(LOGTHING_TRACE, "Reading %d bytes.", storebuf.size); - if (bytes != sizeof(storebuf.size)) { - ret = 1; + if (bytes != sizeof(storebuf.size)) { + ret = 1; + } } if (ret == 0 && storebuf.size > 0) { storebuf.buffer = malloc(storebuf.size); + storebuf.offset = 0; bytes = count = 0; while (bytes >= 0 && count < storebuf.size) { bytes = read(fd, @@ -275,7 +429,8 @@ int sock_do(int fd) &packets, 0); parse_keys(packets, &key); - config.dbbackend->store_key(key, false, false); + dbctx->store_key(dbctx, key, false, + (cmd == KEYD_CMD_UPDATE)); free_packet_list(packets); packets = NULL; free_publickey(key); @@ -286,64 +441,139 @@ int sock_do(int fd) } break; case KEYD_CMD_DELETE: - cmd = KEYD_REPLY_OK; - write(fd, &cmd, sizeof(cmd)); - bytes = read(fd, &keyid, sizeof(keyid)); - if (bytes != sizeof(keyid)) { + if (!keyd_write_reply(fd, KEYD_REPLY_OK)) { ret = 1; } + if (ret == 0) { + bytes = read(fd, &keyid, sizeof(keyid)); + if (bytes != sizeof(keyid)) { + ret = 1; + } + } if (ret == 0) { logthing(LOGTHING_INFO, - "Deleting 0x%llX, result: %d", + "Deleting 0x%" PRIX64 + ", result: %d", keyid, - config.dbbackend->delete_key( + dbctx->delete_key(dbctx, keyid, false)); } break; case KEYD_CMD_GETFULLKEYID: - cmd = KEYD_REPLY_OK; - write(fd, &cmd, sizeof(cmd)); - bytes = read(fd, &keyid, sizeof(keyid)); - if (bytes != sizeof(keyid)) { + if (!keyd_write_reply(fd, KEYD_REPLY_OK)) { ret = 1; } if (ret == 0) { - keyid = config.dbbackend->getfullkeyid(keyid); - write(fd, &keyid, sizeof(keyid)); + bytes = read(fd, &keyid, sizeof(keyid)); + if (bytes != sizeof(keyid)) { + ret = 1; + } + } + if (ret == 0) { + keyid = dbctx->getfullkeyid(dbctx, keyid); + cmd = sizeof(keyid); + bytes = write(fd, &cmd, sizeof(cmd)); + if (bytes != sizeof(cmd)) { + ret = 1; + } + } + if (ret == 0) { + bytes = write(fd, &keyid, sizeof(keyid)); + if (bytes != sizeof(keyid)) { + ret = 1; + } } break; case KEYD_CMD_KEYITER: - cmd = KEYD_REPLY_OK; - write(fd, &cmd, sizeof(cmd)); - config.dbbackend->iterate_keys(iteratefunc, + if (!keyd_write_reply(fd, KEYD_REPLY_OK)) { + ret = 1; + } + if (ret == 0) { + dbctx->iterate_keys(dbctx, iteratefunc, &fd); - bytes = 0; - write(fd, &bytes, sizeof(bytes)); + if (!keyd_write_size(fd, 0)) { + ret = 1; + } + } break; case KEYD_CMD_CLOSE: + /* We're going to close the FD even if this fails */ + (void) keyd_write_reply(fd, KEYD_REPLY_OK); ret = 1; break; case KEYD_CMD_QUIT: + /* We're going to quit even if this fails */ + (void) keyd_write_reply(fd, KEYD_REPLY_OK); + logthing(LOGTHING_NOTICE, + "Exiting due to quit request."); + ret = 1; trytocleanup(); break; + case KEYD_CMD_STATS: + if (!keyd_write_reply(fd, KEYD_REPLY_OK)) { + ret = 1; + } + if (ret == 0) { + cmd = sizeof(*stats); + bytes = write(fd, &cmd, sizeof(cmd)); + if (bytes != sizeof(cmd)) { + ret = 1; + } + } + if (ret == 0) { + bytes = write(fd, stats, sizeof(*stats)); + if (bytes != sizeof(*stats)) { + ret = 1; + } + } + break; + case KEYD_CMD_GET_SKSHASH: + if (!keyd_write_reply(fd, KEYD_REPLY_OK)) { + ret = 1; + } + if (ret == 0) { + bytes = read(fd, hash.hash, sizeof(hash.hash)); + if (bytes != sizeof(hash.hash)) { + ret = 1; + } + } + if (ret == 0) { + logthing(LOGTHING_INFO, + "Fetching by hash" + ", result: %d", + dbctx->fetch_key_skshash(dbctx, + &hash, &key)); + if (key != NULL) { + keyd_write_key(fd, key); + free_publickey(key); + key = NULL; + } else { + if (!keyd_write_size(fd, 0)) { + ret = 1; + } + } + } + break; + default: logthing(LOGTHING_ERROR, "Got unknown command: %d", cmd); - cmd = KEYD_REPLY_UNKNOWN_CMD; - write(fd, &cmd, sizeof(cmd)); + if (!keyd_write_reply(fd, KEYD_REPLY_UNKNOWN_CMD)) { + ret = 1; + } } } return(ret); } -int sock_close(int fd) +static int sock_close(int fd) { shutdown(fd, SHUT_RDWR); return close(fd); } -int sock_accept(int fd) +static int sock_accept(int fd) { struct sockaddr_un sock; socklen_t socklen; @@ -353,34 +583,54 @@ int sock_accept(int fd) socklen = sizeof(sock); srv = accept(fd, (struct sockaddr *) &sock, &socklen); if (srv != -1) { - ret = fcntl(srv, F_SETFD, 1); + ret = fcntl(srv, F_SETFD, FD_CLOEXEC); } if (ret != -1) { - while (!sock_do(srv)) ; - sock_close(srv); + stats->connects++; } - return 1; + return (srv); +} + +static void usage(void) +{ + puts("keyd " ONAK_VERSION " - backend key serving daemon for the " + "onak PGP keyserver.\n"); + puts("Usage:\n"); + puts("\tkeyd [options]\n"); + puts("\tOptions:\n:"); + puts("-c - use as the config file"); + puts("-f - run in the foreground"); + puts("-h - show this help text"); + exit(EXIT_FAILURE); } int main(int argc, char *argv[]) { - int fd = -1; - fd_set rfds; - char sockname[1024]; + int fd = -1, maxfd, i, clients[MAX_CLIENTS]; + fd_set rfds = { 0 }; /* Avoid scan-build false report for FD_SET */ + char sockname[100]; char *configfile = NULL; bool foreground = false; int optchar; + struct onak_dbctx *dbctx; - while ((optchar = getopt(argc, argv, "c:f")) != -1 ) { + while ((optchar = getopt(argc, argv, "c:fh")) != -1 ) { switch (optchar) { case 'c': + if (configfile != NULL) { + free(configfile); + } configfile = strdup(optarg); break; case 'f': foreground = true; break; + case 'h': + default: + usage(); + break; } } @@ -395,27 +645,88 @@ int main(int argc, char *argv[]) } catchsignals(); - - snprintf(sockname, 1023, "%s/%s", config.db_dir, KEYD_SOCKET); + signal(SIGPIPE, SIG_IGN); + + + stats = calloc(1, sizeof(*stats)); + if (!stats) { + logthing(LOGTHING_ERROR, + "Couldn't allocate memory for stats structure."); + exit(EXIT_FAILURE); + } + stats->started = time(NULL); + + snprintf(sockname, sizeof(sockname) - 1, "%s/%s", + config.sock_dir, KEYD_SOCKET); fd = sock_init(sockname); if (fd != -1) { FD_ZERO(&rfds); FD_SET(fd, &rfds); + maxfd = fd; + memset(clients, -1, sizeof (clients)); - config.dbbackend->initdb(false); + dbctx = config.dbinit(config.backend, false); logthing(LOGTHING_NOTICE, "Accepting connections."); - while (!cleanup() && select(fd + 1, &rfds, NULL, NULL, NULL) != -1) { - logthing(LOGTHING_INFO, "Accepted connection."); - sock_accept(fd); + while (!cleanup() && select(maxfd + 1, &rfds, NULL, NULL, NULL) != -1) { + /* + * Deal with existing clients first; if we're at our + * connection limit then processing them might free + * things up and let us accept the next client below. + */ + for (i = 0; i < MAX_CLIENTS; i++) { + if (clients[i] != -1 && + FD_ISSET(clients[i], &rfds)) { + logthing(LOGTHING_DEBUG, + "Handling connection for client %d.", i); + if (sock_do(dbctx, clients[i])) { + sock_close(clients[i]); + clients[i] = -1; + logthing(LOGTHING_DEBUG, + "Closed connection for client %d.", i); + } + } + } + /* + * Check if we have a new incoming connection to accept. + */ + if (FD_ISSET(fd, &rfds)) { + for (i = 0; i < MAX_CLIENTS; i++) { + if (clients[i] == -1) { + break; + } + } + if (i < MAX_CLIENTS) { + logthing(LOGTHING_INFO, + "Accepted connection %d.", i); + clients[i] = sock_accept(fd); + } + } + FD_ZERO(&rfds); FD_SET(fd, &rfds); + maxfd = fd; + for (i = 0; i < MAX_CLIENTS; i++) { + if (clients[i] != -1) { + FD_SET(clients[i], &rfds); + maxfd = (maxfd > clients[i]) ? + maxfd : clients[i]; + } + } } - config.dbbackend->cleanupdb(); - sock_close(fd); - unlink(sockname); + dbctx->cleanupdb(dbctx); +#ifdef HAVE_SYSTEMD + if (!using_socket_activation) { +#endif + sock_close(fd); + unlink(sockname); +#ifdef HAVE_SYSTEMD + } +#endif } + free(stats); + cleanuplogthing(); cleanupconfig();