1/*- 2 * Copyright (c) 2009,2010 The NetBSD Foundation, Inc. 3 * All rights reserved. 4 * 5 * This code is derived from software contributed to The NetBSD Foundation 6 * by Alistair Crooks (agc@NetBSD.org) 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 18 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 20 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 21 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 * POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30#include <sys/types.h> 31#include <sys/param.h> 32#include <sys/socket.h> 33#include <sys/stat.h> 34#include <sys/select.h> 35 36#include <netinet/in.h> 37 38#include <errno.h> 39#include <netdb.h> 40#include <netpgp.h> 41#include <regex.h> 42#include <stdio.h> 43#include <stdlib.h> 44#include <string.h> 45#include <unistd.h> 46 47#include "hkpd.h" 48 49/* make the string have %xx -> %c */ 50static size_t 51frompercent(char *in, size_t insize, char *out, size_t outsize) 52{ 53 size_t outcc; 54 char *next; 55 char *pc; 56 57 outcc = 0; 58 for (pc = in ; (next = strchr(pc, '%')) != NULL ; pc = next + 3) { 59 (void) memcpy(&out[outcc], pc, (size_t)(next - pc)); 60 outcc += (size_t)(next - pc); 61 out[outcc++] = (char)strtol(next + 1, NULL, 16); 62 } 63 (void) memcpy(&out[outcc], pc, insize - (int)(pc - in)); 64 outcc += insize - (int)(pc - in); 65 out[outcc] = 0x0; 66 return outcc; 67} 68 69#define HKP_HTTP_LEVEL "HTTP/1.0" 70#define HKP_NAME "hkpd" 71#define HKP_MIME_GET "application/pgp-keys" 72#define HKP_MIME_INDEX "text/plain" 73#define HKP_MACHREAD "info:1:1\r\n" 74 75#define HKP_SUCCESS 200 76#define HKP_NOT_FOUND 404 77 78/* make into html */ 79static int 80htmlify(char *buf, size_t size, const int code, const int get, const char *title, const char *out, const char *body) 81{ 82 return snprintf(buf, size, 83 "%s %d %s\r\n" 84 "Server: %s/%d\r\n" 85 "Content-type: %s\r\n" 86 "\r\n" 87 "%s" 88 "%s", 89 HKP_HTTP_LEVEL, code, (code == HKP_SUCCESS) ? "OK" : "not found", 90 HKP_NAME, HKPD_VERSION, 91 (get) ? HKP_MIME_GET : HKP_MIME_INDEX, 92 (get || strcmp(out, "mr") != 0) ? "" : HKP_MACHREAD, 93 body); 94} 95 96/* send the response now */ 97static int 98response(int sock, const int code, const char *search, const int get, char *buf, int cc, const char *out) 99{ 100 char outbuf[1024 * 512]; 101 char item[BUFSIZ]; 102 int tot; 103 int wc; 104 int n; 105 106 if (buf == NULL) { 107 (void) snprintf(item, sizeof(item), 108 "Error handling request: No keys found for '%s'\r\n", search); 109 n = htmlify(outbuf, sizeof(outbuf), code, get, 110 "Error handling request\r\n", 111 out, 112 item); 113 } else { 114 (void) snprintf(item, sizeof(item), "Search results for '%s'", search); 115 n = htmlify(outbuf, sizeof(outbuf), code, get, 116 item, 117 out, 118 buf); 119 } 120 for (tot = 0 ; (wc = write(sock, &outbuf[tot], n - tot)) > 0 && tot < n ; tot += wc) { 121 } 122 return 1; 123} 124 125/* get a socket (we'll bind it later) */ 126static int 127hkpd_sock_get(const int fam) 128{ 129 int sock; 130 int on = 1; 131 132 sock = socket((fam == 4) ? AF_INET : AF_INET6, SOCK_STREAM, 0); 133 if (sock < 0) { 134 (void) fprintf(stderr,"hkpd_sock_get: can't get a socket\n"); 135 return -1; 136 } 137 if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, 138 (void *)&on, sizeof(on)) == -1) { 139 (void) fprintf(stderr, 140 "hkpd_sock_get: can't set SO_REUSEADDR\n"); 141 return -1; 142 } 143 if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, 144 (void *)&on, sizeof(on)) == -1) { 145 (void) fprintf(stderr, 146 "hkpd_sock_get: can't set SO_KEEPALIVE\n"); 147 return -1; 148 } 149 return sock; 150} 151 152/**************************************************************************/ 153 154/* get a socket and bind it to the server */ 155int 156hkpd_sock_bind(const char *hostname, const int port, const int fam) 157{ 158 struct addrinfo hints; 159 struct addrinfo *res; 160 char portstr[32]; 161 int sock; 162 int rc = 0; 163 164 (void) memset(&hints, 0, sizeof(hints)); 165 hints.ai_family = (fam == 4) ? PF_INET : PF_INET6; 166 hints.ai_socktype = SOCK_STREAM; 167 (void) snprintf(portstr, sizeof(portstr), "%d", port); 168 /* Attempt connection */ 169#ifdef AI_NUMERICSERV 170 hints.ai_flags = AI_NUMERICSERV; 171#endif 172 if ((rc = getaddrinfo(hostname, portstr, &hints, &res)) != 0) { 173 hints.ai_flags = 0; 174 if ((rc = getaddrinfo(hostname, "hkp", &hints, &res)) != 0) { 175 (void) fprintf(stderr, "getaddrinfo: %s", 176 gai_strerror(rc)); 177 return -1; 178 } 179 } 180 if ((sock = hkpd_sock_get(fam)) < 0) { 181 (void) fprintf(stderr, "hkpd_sock_get failed %d\n", errno); 182 freeaddrinfo(res); 183 return -1; 184 } 185 if ((rc = bind(sock, res->ai_addr, res->ai_addrlen)) < 0) { 186 (void) fprintf(stderr, "bind failed %d\n", errno); 187 freeaddrinfo(res); 188 return -1; 189 } 190 freeaddrinfo(res); 191 if (rc < 0) { 192 (void) fprintf(stderr, "bind() to %s:%d failed (rc %d)\n", 193 hostname, port, rc); 194 } 195 return sock; 196} 197 198/* netpgp key daemon - does not return */ 199int 200hkpd(netpgp_t *netpgp, int sock4, int sock6) 201{ 202 struct sockaddr_in from; 203 regmatch_t searchmatches[10]; 204 regmatch_t opmatches[10]; 205 regmatch_t fmtmatch[3]; 206 socklen_t fromlen; 207 regex_t searchterm; 208 regex_t fmtterm; 209 regex_t opterm; 210 regex_t get; 211 fd_set sockets; 212 char search[BUFSIZ]; 213 char buf[BUFSIZ]; 214 char *cp; 215 char fmt[10]; 216 int newsock; 217 int sock; 218 int code; 219 int ok; 220 int cc; 221 int n; 222 223/* GET /pks/lookup?search=agc%40netbsd.org&op=index&options=mr HTTP/1.1\n */ 224#define HTTPGET "GET /pks/lookup\\?" 225#define OPTERM "op=([a-zA-Z]+)" 226#define SEARCHTERM "search=([^ \t&]+)" 227#define FMT "options=(mr|json)" 228 229 (void) regcomp(&get, HTTPGET, REG_EXTENDED); 230 (void) regcomp(&opterm, OPTERM, REG_EXTENDED); 231 (void) regcomp(&searchterm, SEARCHTERM, REG_EXTENDED); 232 (void) regcomp(&fmtterm, FMT, REG_EXTENDED); 233 if (sock4 >= 0) { 234 listen(sock4, 32); 235 } 236 if (sock6 >= 0) { 237 listen(sock6, 32); 238 } 239 for (;;) { 240 /* find out which socket we have data on */ 241 FD_ZERO(&sockets); 242 if (sock4 >= 0) { 243 FD_SET(sock4, &sockets); 244 } 245 if (sock6 >= 0) { 246 FD_SET(sock6, &sockets); 247 } 248 if (select(32, &sockets, NULL, NULL, NULL) < 0) { 249 (void) fprintf(stderr, "bad select call\n"); 250 continue; 251 } 252 sock = (sock4 >= 0 && FD_ISSET(sock4, &sockets)) ? sock4 : sock6; 253 /* read data from socket */ 254 fromlen = sizeof(from); 255 newsock = accept(sock, (struct sockaddr *) &from, &fromlen); 256 cc = read(newsock, buf, sizeof(buf)); 257 /* parse the request */ 258 ok = 1; 259 if (regexec(&get, buf, 10, opmatches, 0) != 0) { 260 (void) fprintf(stderr, "not a valid get request\n"); 261 ok = 0; 262 } 263 if (ok && regexec(&opterm, buf, 10, opmatches, 0) != 0) { 264 (void) fprintf(stderr, "no operation in request\n"); 265 ok = 0; 266 } 267 if (ok && regexec(&fmtterm, buf, 3, fmtmatch, 0) == 0) { 268 (void) snprintf(fmt, sizeof(fmt), "%.*s", 269 (int)(fmtmatch[1].rm_eo - fmtmatch[1].rm_so), 270 &buf[(int)fmtmatch[1].rm_so]); 271 } else { 272 fmt[0] = 0x0; 273 } 274 if (ok && regexec(&searchterm, buf, 10, searchmatches, 0) != 0) { 275 (void) fprintf(stderr, "no search term in request\n"); 276 ok = 0; 277 } 278 if (!ok) { 279 (void) close(newsock); 280 continue; 281 } 282 /* convert from %2f to / etc */ 283 n = frompercent(&buf[searchmatches[1].rm_so], 284 (int)(searchmatches[1].rm_eo - searchmatches[1].rm_so), 285 search, 286 sizeof(search)); 287 code = HKP_NOT_FOUND; 288 cc = 0; 289 if (strncmp(&buf[opmatches[1].rm_so], "vindex", 6) == 0) { 290 cc = 0; 291 netpgp_setvar(netpgp, "subkey sigs", "yes"); 292 if (strcmp(fmt, "json") == 0) { 293 if (netpgp_match_keys_json(netpgp, &cp, search, "human", 1)) { 294 cc = strlen(cp); 295 code = HKP_SUCCESS; 296 } 297 } else if ((cp = netpgp_get_key(netpgp, search, fmt)) != NULL) { 298 cc = strlen(cp); 299 code = HKP_SUCCESS; 300 } 301 response(newsock, code, search, 0, cp, cc, fmt); 302 netpgp_unsetvar(netpgp, "subkey sigs"); 303 } else if (strncmp(&buf[opmatches[1].rm_so], "index", 5) == 0) { 304 cc = 0; 305 netpgp_unsetvar(netpgp, "subkey sigs"); 306 if (strcmp(fmt, "json") == 0) { 307 if (netpgp_match_keys_json(netpgp, &cp, search, "human", 0)) { 308 cc = strlen(cp); 309 code = HKP_SUCCESS; 310 } 311 } else if ((cp = netpgp_get_key(netpgp, search, fmt)) != NULL) { 312 cc = strlen(cp); 313 code = HKP_SUCCESS; 314 } 315 response(newsock, code, search, 0, cp, cc, fmt); 316 } else if (strncmp(&buf[opmatches[1].rm_so], "get", 3) == 0) { 317 if ((cp = netpgp_export_key(netpgp, search)) != NULL) { 318 cc = strlen(cp); 319 code = HKP_SUCCESS; 320 } 321 response(newsock, code, search, 1, cp, cc, fmt); 322 } 323 free(cp); 324 (void) close(newsock); 325 } 326} 327