Various polishing: man page tweaks, --version now does something,
[sgt/agedu] / httpd.c
1 /*
2 * httpd.c: implementation of httpd.h.
3 */
4
5 #define _GNU_SOURCE
6
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <errno.h>
11 #include <assert.h>
12 #include <unistd.h>
13 #include <pwd.h>
14 #include <ctype.h>
15 #include <sys/types.h>
16 #include <sys/wait.h>
17 #include <fcntl.h>
18 #include <sys/socket.h>
19 #include <arpa/inet.h>
20 #include <netinet/in.h>
21 #include <syslog.h>
22
23 #include "agedu.h"
24 #include "alloc.h"
25 #include "html.h"
26 #include "httpd.h"
27
28 /* --- Logic driving what the web server's responses are. --- */
29
30 enum { /* connctx states */
31 READING_REQ_LINE,
32 READING_HEADERS,
33 DONE
34 };
35
36 struct connctx {
37 const void *t;
38 char *data;
39 int datalen, datasize;
40 char *method, *url, *headers, *auth;
41 int state;
42 };
43
44 /*
45 * Called when a new connection arrives on a listening socket.
46 * Returns a connctx for the new connection.
47 */
48 struct connctx *new_connection(const void *t)
49 {
50 struct connctx *cctx = snew(struct connctx);
51 cctx->t = t;
52 cctx->data = NULL;
53 cctx->datalen = cctx->datasize = 0;
54 cctx->state = READING_REQ_LINE;
55 cctx->method = cctx->url = cctx->headers = cctx->auth = NULL;
56 return cctx;
57 }
58
59 void free_connection(struct connctx *cctx)
60 {
61 sfree(cctx->data);
62 sfree(cctx);
63 }
64
65 static char *http_error(char *code, char *errmsg, char *extraheader,
66 char *errtext, ...)
67 {
68 return dupfmt("HTTP/1.1 %s %s\r\n"
69 "Date: %D\r\n"
70 "Server: " PNAME "\r\n"
71 "Connection: close\r\n"
72 "%s"
73 "Content-Type: text/html; charset=US-ASCII\r\n"
74 "\r\n"
75 "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
76 "<HTML><HEAD>\r\n"
77 "<TITLE>%s %s</TITLE>\r\n"
78 "</HEAD><BODY>\r\n"
79 "<H1>%s %s</H1>\r\n"
80 "<P>%s</P>\r\n"
81 "</BODY></HTML>\r\n", code, errmsg,
82 extraheader ? extraheader : "",
83 code, errmsg, code, errmsg, errtext);
84 }
85
86 static char *http_success(char *mimetype, int stuff_cr, char *document)
87 {
88 return dupfmt("HTTP/1.1 200 OK\r\n"
89 "Date: %D\r\n"
90 "Expires: %D\r\n"
91 "Server: " PNAME "\r\n"
92 "Connection: close\r\n"
93 "Content-Type: %s\r\n"
94 "\r\n"
95 "%S", mimetype, stuff_cr, document);
96 }
97
98 /*
99 * Called when data comes in on a connection.
100 *
101 * If this function returns NULL, the platform code continues
102 * reading from the socket. Otherwise, it returns some dynamically
103 * allocated data which the platform code will then write to the
104 * socket before closing it.
105 */
106 char *got_data(struct connctx *ctx, char *data, int length,
107 int magic_access, const char *auth_string,
108 const struct html_config *cfg)
109 {
110 char *line, *p, *q, *r, *z1, *z2, c1, c2;
111 int auth_provided = 0, auth_correct = 0;
112 unsigned long index;
113 char *document, *ret;
114
115 /*
116 * Add the data we've just received to our buffer.
117 */
118 if (ctx->datasize < ctx->datalen + length) {
119 ctx->datasize = (ctx->datalen + length) * 3 / 2 + 4096;
120 ctx->data = sresize(ctx->data, ctx->datasize, char);
121 }
122 memcpy(ctx->data + ctx->datalen, data, length);
123 ctx->datalen += length;
124
125 /*
126 * Gradually process the HTTP request as we receive it.
127 */
128 if (ctx->state == READING_REQ_LINE) {
129 /*
130 * We're waiting for the first line of the input, which
131 * contains the main HTTP request. See if we've got it
132 * yet.
133 */
134
135 line = ctx->data;
136 /*
137 * RFC 2616 section 4.1: `In the interest of robustness,
138 * [...] if the server is reading the protocol stream at
139 * the beginning of a message and receives a CRLF first,
140 * it should ignore the CRLF.'
141 */
142 while (line - ctx->data < ctx->datalen &&
143 (*line == '\r' || *line == '\n'))
144 line++;
145 q = line;
146 while (q - ctx->data < ctx->datalen && *q != '\n')
147 q++;
148 if (q - ctx->data >= ctx->datalen)
149 return NULL; /* not got request line yet */
150
151 /*
152 * We've got the first line of the request. Zero-terminate
153 * and parse it into method, URL and optional HTTP
154 * version.
155 */
156 *q = '\0';
157 ctx->headers = q+1;
158 if (q > line && q[-1] == '\r')
159 *--q = '\0';
160 z1 = z2 = q;
161 c1 = c2 = *q;
162 p = line;
163 while (*p && !isspace((unsigned char)*p)) p++;
164 if (*p) {
165 z1 = p++;
166 c1 = *z1;
167 *z1 = '\0';
168 }
169 while (*p && isspace((unsigned char)*p)) p++;
170 q = p;
171 while (*q && !isspace((unsigned char)*q)) q++;
172 z2 = q++;
173 c2 = *z2;
174 *z2 = '\0';
175 while (*q && isspace((unsigned char)*q)) q++;
176
177 /*
178 * Now `line' points at the method name; p points at the
179 * URL, if any; q points at the HTTP version, if any.
180 */
181
182 /*
183 * There should _be_ a URL, on any request type at all.
184 */
185 if (!*p) {
186 char *ret, *text;
187 /* Restore the request to the way we received it. */
188 *z2 = c2;
189 *z1 = c1;
190 text = dupfmt("<code>" PNAME "</code> received the HTTP request"
191 " \"<code>%h</code>\", which contains no URL.",
192 line);
193 ret = http_error("400", "Bad request", NULL, text);
194 sfree(text);
195 return ret;
196 }
197
198 ctx->method = line;
199 ctx->url = p;
200
201 /*
202 * If there was an HTTP version, we might need to see
203 * headers. Otherwise, the request is done.
204 */
205 if (*q) {
206 ctx->state = READING_HEADERS;
207 } else {
208 ctx->state = DONE;
209 }
210 }
211
212 if (ctx->state == READING_HEADERS) {
213 /*
214 * While we're receiving the HTTP request headers, all we
215 * do is to keep scanning to see if we find two newlines
216 * next to each other.
217 */
218 q = ctx->data + ctx->datalen;
219 for (p = ctx->headers; p < q; p++) {
220 if (*p == '\n' &&
221 ((p+1 < q && p[1] == '\n') ||
222 (p+2 < q && p[1] == '\r' && p[2] == '\n'))) {
223 p[1] = '\0';
224 ctx->state = DONE;
225 break;
226 }
227 }
228 }
229
230 if (ctx->state == DONE) {
231 /*
232 * Now we have the entire HTTP request. Decide what to do
233 * with it.
234 */
235 if (auth_string) {
236 /*
237 * Search the request headers for Authorization.
238 */
239 q = ctx->data + ctx->datalen;
240 for (p = ctx->headers; p < q; p++) {
241 const char *hdr = "Authorization:";
242 int i;
243 for (i = 0; hdr[i]; i++) {
244 if (p >= q || tolower((unsigned char)*p) !=
245 tolower((unsigned char)hdr[i]))
246 break;
247 p++;
248 }
249 if (!hdr[i])
250 break; /* found our header */
251 p = memchr(p, '\n', q - p);
252 if (!p)
253 p = q;
254 }
255 if (p < q) {
256 auth_provided = 1;
257 while (p < q && isspace((unsigned char)*p))
258 p++;
259 r = p;
260 while (p < q && !isspace((unsigned char)*p))
261 p++;
262 if (p < q) {
263 *p++ = '\0';
264 if (!strcasecmp(r, "Basic")) {
265 while (p < q && isspace((unsigned char)*p))
266 p++;
267 r = p;
268 while (p < q && !isspace((unsigned char)*p))
269 p++;
270 if (p < q) {
271 *p++ = '\0';
272 if (!strcmp(r, auth_string))
273 auth_correct = 1;
274 }
275 }
276 }
277 }
278 }
279
280 if (!magic_access && !auth_correct) {
281 if (auth_string && !auth_provided) {
282 ret = http_error("401", "Unauthorized",
283 "WWW-Authenticate: Basic realm=\""PNAME"\"\r",
284 "\nPlease authenticate to view these pages.");
285 } else {
286 ret = http_error("403", "Forbidden", NULL,
287 "This is a restricted-access set of pages.");
288 }
289 } else {
290 p = ctx->url;
291 p += strspn(p, "/?");
292 index = strtoul(p, NULL, 10);
293 document = html_query(ctx->t, index, cfg);
294 if (document) {
295 ret = http_success("text/html", 1, document);
296 sfree(document);
297 } else {
298 ret = http_error("404", "Not Found", NULL,
299 "Pathname index out of range.");
300 }
301 }
302 return ret;
303 } else
304 return NULL;
305 }
306
307 /* --- Platform support for running a web server. --- */
308
309 enum { FD_CLIENT, FD_LISTENER, FD_CONNECTION };
310
311 struct fd {
312 int fd;
313 int type;
314 int deleted;
315 char *wdata;
316 int wdatalen, wdatapos;
317 int magic_access;
318 struct connctx *cctx;
319 };
320
321 struct fd *fds = NULL;
322 int nfds = 0, fdsize = 0;
323
324 struct fd *new_fdstruct(int fd, int type)
325 {
326 struct fd *ret;
327
328 if (nfds >= fdsize) {
329 fdsize = nfds * 3 / 2 + 32;
330 fds = sresize(fds, fdsize, struct fd);
331 }
332
333 ret = &fds[nfds++];
334
335 ret->fd = fd;
336 ret->type = type;
337 ret->wdata = NULL;
338 ret->wdatalen = ret->wdatapos = 0;
339 ret->cctx = NULL;
340 ret->deleted = 0;
341 ret->magic_access = 0;
342
343 return ret;
344 }
345
346 int check_owning_uid(int fd, int flip)
347 {
348 struct sockaddr_in sock, peer;
349 socklen_t addrlen;
350 char linebuf[4096], matchbuf[80];
351 FILE *fp;
352
353 addrlen = sizeof(sock);
354 if (getsockname(fd, (struct sockaddr *)&sock, &addrlen)) {
355 fprintf(stderr, "getsockname: %s\n", strerror(errno));
356 exit(1);
357 }
358 addrlen = sizeof(peer);
359 if (getpeername(fd, (struct sockaddr *)&peer, &addrlen)) {
360 if (errno == ENOTCONN) {
361 peer.sin_addr.s_addr = htonl(0);
362 peer.sin_port = htons(0);
363 } else {
364 fprintf(stderr, "getpeername: %s\n", strerror(errno));
365 exit(1);
366 }
367 }
368
369 if (flip) {
370 struct sockaddr_in tmp = sock;
371 sock = peer;
372 peer = tmp;
373 }
374
375 sprintf(matchbuf, "%08X:%04X %08X:%04X",
376 peer.sin_addr.s_addr, ntohs(peer.sin_port),
377 sock.sin_addr.s_addr, ntohs(sock.sin_port));
378 fp = fopen("/proc/net/tcp", "r");
379 if (fp) {
380 while (fgets(linebuf, sizeof(linebuf), fp)) {
381 if (strlen(linebuf) >= 75 &&
382 !strncmp(linebuf+6, matchbuf, strlen(matchbuf))) {
383 return atoi(linebuf + 75);
384 }
385 }
386 }
387
388 return -1;
389 }
390
391 void check_magic_access(struct fd *fd)
392 {
393 if (check_owning_uid(fd->fd, 0) == getuid())
394 fd->magic_access = 1;
395 }
396
397 static void base64_encode_atom(unsigned char *data, int n, char *out)
398 {
399 static const char base64_chars[] =
400 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
401
402 unsigned word;
403
404 word = data[0] << 16;
405 if (n > 1)
406 word |= data[1] << 8;
407 if (n > 2)
408 word |= data[2];
409 out[0] = base64_chars[(word >> 18) & 0x3F];
410 out[1] = base64_chars[(word >> 12) & 0x3F];
411 if (n > 1)
412 out[2] = base64_chars[(word >> 6) & 0x3F];
413 else
414 out[2] = '=';
415 if (n > 2)
416 out[3] = base64_chars[word & 0x3F];
417 else
418 out[3] = '=';
419 }
420
421 void run_httpd(const void *t, int authmask, const struct httpd_config *dcfg,
422 const struct html_config *incfg)
423 {
424 int fd;
425 int authtype;
426 char *authstring = NULL;
427 unsigned long ipaddr;
428 struct fd *f;
429 struct sockaddr_in addr;
430 socklen_t addrlen;
431 struct html_config cfg = *incfg;
432
433 cfg.format = "%.0lu";
434
435 /*
436 * Establish the listening socket and retrieve its port
437 * number.
438 */
439 fd = socket(PF_INET, SOCK_STREAM, 0);
440 if (fd < 0) {
441 fprintf(stderr, "socket(PF_INET): %s\n", strerror(errno));
442 exit(1);
443 }
444 addr.sin_family = AF_INET;
445 if (!dcfg->address) {
446 srand(0L);
447 ipaddr = 0x7f000000;
448 ipaddr += (1 + rand() % 255) << 16;
449 ipaddr += (1 + rand() % 255) << 8;
450 ipaddr += (1 + rand() % 255);
451 addr.sin_addr.s_addr = htonl(ipaddr);
452 addr.sin_port = htons(0);
453 } else {
454 addr.sin_addr.s_addr = inet_addr(dcfg->address);
455 addr.sin_port = dcfg->port ? htons(dcfg->port) : 80;
456 }
457 addrlen = sizeof(addr);
458 if (bind(fd, (struct sockaddr *)&addr, addrlen) < 0) {
459 fprintf(stderr, "bind: %s\n", strerror(errno));
460 exit(1);
461 }
462 if (listen(fd, 5) < 0) {
463 fprintf(stderr, "listen: %s\n", strerror(errno));
464 exit(1);
465 }
466 addrlen = sizeof(addr);
467 if (getsockname(fd, (struct sockaddr *)&addr, &addrlen)) {
468 fprintf(stderr, "getsockname: %s\n", strerror(errno));
469 exit(1);
470 }
471 if ((authmask & HTTPD_AUTH_MAGIC) &&
472 (check_owning_uid(fd, 1) == getuid())) {
473 authtype = HTTPD_AUTH_MAGIC;
474 if (authmask != HTTPD_AUTH_MAGIC)
475 printf("Using Linux /proc/net magic authentication\n");
476 } else if ((authmask & HTTPD_AUTH_BASIC)) {
477 char username[128], password[128], userpassbuf[259];
478 const char *userpass;
479 const char *rname;
480 unsigned char passbuf[10];
481 int i, j, k, fd;
482
483 authtype = HTTPD_AUTH_BASIC;
484
485 if (authmask != HTTPD_AUTH_BASIC)
486 printf("Using HTTP Basic authentication\n");
487
488 if (dcfg->basicauthdata) {
489 userpass = dcfg->basicauthdata;
490 } else {
491 strcpy(username, PNAME);
492 rname = "/dev/urandom";
493 fd = open(rname, O_RDONLY);
494 if (fd < 0) {
495 int err = errno;
496 rname = "/dev/random";
497 fd = open(rname, O_RDONLY);
498 if (fd < 0) {
499 int err2 = errno;
500 fprintf(stderr, "/dev/urandom: open: %s\n", strerror(err));
501 fprintf(stderr, "/dev/random: open: %s\n", strerror(err2));
502 exit(1);
503 }
504 }
505 for (i = 0; i < 10 ;) {
506 j = read(fd, passbuf + i, 10 - i);
507 if (j <= 0) {
508 fprintf(stderr, "%s: read: %s\n", rname,
509 j < 0 ? strerror(errno) : "unexpected EOF");
510 exit(1);
511 }
512 i += j;
513 }
514 close(fd);
515 for (i = 0; i < 16; i++) {
516 /*
517 * 32 characters out of the 36 alphanumerics gives
518 * me the latitude to discard i,l,o for being too
519 * numeric-looking, and w because it has two too
520 * many syllables and one too many presidential
521 * associations.
522 */
523 static const char chars[32] =
524 "0123456789abcdefghjkmnpqrstuvxyz";
525 int v = 0;
526
527 k = i / 8 * 5;
528 for (j = 0; j < 5; j++)
529 v |= ((passbuf[k+j] >> (i%8)) & 1) << j;
530
531 password[i] = chars[v];
532 }
533 password[i] = '\0';
534
535 sprintf(userpassbuf, "%s:%s", username, password);
536 userpass = userpassbuf;
537
538 printf("Username: %s\nPassword: %s\n", username, password);
539 }
540
541 k = strlen(userpass);
542 authstring = snewn(k * 4 / 3 + 16, char);
543 for (i = j = 0; i < k ;) {
544 int s = k-i < 3 ? k-i : 3;
545 base64_encode_atom((unsigned char *)(userpass+i), s, authstring+j);
546 i += s;
547 j += 4;
548 }
549 authstring[j] = '\0';
550 } else if ((authmask & HTTPD_AUTH_NONE)) {
551 authtype = HTTPD_AUTH_NONE;
552 if (authmask != HTTPD_AUTH_NONE)
553 printf("Web server is unauthenticated\n");
554 } else {
555 fprintf(stderr, PNAME ": authentication method not supported\n");
556 exit(1);
557 }
558 if (!dcfg->address) {
559 if (ntohs(addr.sin_port) == 80) {
560 printf("URL: http://%s/\n", inet_ntoa(addr.sin_addr));
561 } else {
562 printf("URL: http://%s:%d/\n",
563 inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
564 }
565 }
566
567 /*
568 * Now construct an fd structure to hold it.
569 */
570 f = new_fdstruct(fd, FD_LISTENER);
571
572 /*
573 * Read from standard input, and treat EOF as a notification
574 * to exit.
575 */
576 new_fdstruct(0, FD_CLIENT);
577
578 /*
579 * Now we're ready to run our main loop. Keep looping round on
580 * select.
581 */
582 while (1) {
583 fd_set rfds, wfds;
584 int i, j, maxfd, ret;
585
586 #define FD_SET_MAX(fd, set, max) \
587 do { FD_SET((fd),(set)); (max) = ((max)<=(fd)?(fd)+1:(max)); } while(0)
588
589 /*
590 * Loop round the fd list putting fds into our select
591 * sets. Also in this loop we remove any that were marked
592 * as deleted in the previous loop.
593 */
594 FD_ZERO(&rfds);
595 FD_ZERO(&wfds);
596 maxfd = 0;
597 for (i = j = 0; j < nfds; j++) {
598
599 if (fds[j].deleted) {
600 sfree(fds[j].wdata);
601 free_connection(fds[j].cctx);
602 continue;
603 }
604 fds[i] = fds[j];
605
606 switch (fds[i].type) {
607 case FD_CLIENT:
608 case FD_LISTENER:
609 FD_SET_MAX(fds[i].fd, &rfds, maxfd);
610 break;
611 case FD_CONNECTION:
612 /*
613 * Always read from a connection socket. Even
614 * after we've started writing, the peer might
615 * still be sending (e.g. because we shamefully
616 * jumped the gun before waiting for the end of
617 * the HTTP request) and so we should be prepared
618 * to read data and throw it away.
619 */
620 FD_SET_MAX(fds[i].fd, &rfds, maxfd);
621 /*
622 * Also attempt to write, if we have data to write.
623 */
624 if (fds[i].wdatapos < fds[i].wdatalen)
625 FD_SET_MAX(fds[i].fd, &wfds, maxfd);
626 break;
627 }
628
629 i++;
630 }
631 nfds = i;
632
633 ret = select(maxfd, &rfds, &wfds, NULL, NULL);
634 if (ret <= 0) {
635 if (ret < 0 && (errno != EINTR)) {
636 fprintf(stderr, "select: %s", strerror(errno));
637 exit(1);
638 }
639 continue;
640 }
641
642 for (i = 0; i < nfds; i++) {
643 switch (fds[i].type) {
644 case FD_CLIENT:
645 if (FD_ISSET(fds[i].fd, &rfds)) {
646 char buf[4096];
647 int ret = read(fds[i].fd, buf, sizeof(buf));
648 if (ret <= 0) {
649 if (ret < 0) {
650 fprintf(stderr, "standard input: read: %s\n",
651 strerror(errno));
652 exit(1);
653 }
654 return;
655 }
656 }
657 break;
658 case FD_LISTENER:
659 if (FD_ISSET(fds[i].fd, &rfds)) {
660 /*
661 * New connection has come in. Accept it.
662 */
663 struct fd *f;
664 struct sockaddr_in addr;
665 socklen_t addrlen = sizeof(addr);
666 int newfd = accept(fds[i].fd, (struct sockaddr *)&addr,
667 &addrlen);
668 if (newfd < 0)
669 break; /* not sure what happened there */
670
671 f = new_fdstruct(newfd, FD_CONNECTION);
672 f->cctx = new_connection(t);
673 if (authtype == HTTPD_AUTH_MAGIC)
674 check_magic_access(f);
675 }
676 break;
677 case FD_CONNECTION:
678 if (FD_ISSET(fds[i].fd, &rfds)) {
679 /*
680 * There's data to be read.
681 */
682 char readbuf[4096];
683 int ret;
684
685 ret = read(fds[i].fd, readbuf, sizeof(readbuf));
686 if (ret <= 0) {
687 /*
688 * This shouldn't happen in a sensible
689 * HTTP connection, so we abandon the
690 * connection if it does.
691 */
692 close(fds[i].fd);
693 fds[i].deleted = 1;
694 break;
695 } else {
696 if (!fds[i].wdata) {
697 /*
698 * If we haven't got an HTTP response
699 * yet, keep processing data in the
700 * hope of acquiring one.
701 */
702 fds[i].wdata = got_data
703 (fds[i].cctx, readbuf, ret,
704 (authtype == HTTPD_AUTH_NONE ||
705 fds[i].magic_access), authstring, &cfg);
706 if (fds[i].wdata) {
707 fds[i].wdatalen = strlen(fds[i].wdata);
708 fds[i].wdatapos = 0;
709 }
710 } else {
711 /*
712 * Otherwise, just drop our read data
713 * on the floor.
714 */
715 }
716 }
717 }
718 if (FD_ISSET(fds[i].fd, &wfds) &&
719 fds[i].wdatapos < fds[i].wdatalen) {
720 /*
721 * The socket is writable, and we have data to
722 * write. Write it.
723 */
724 int ret = write(fds[i].fd, fds[i].wdata + fds[i].wdatapos,
725 fds[i].wdatalen - fds[i].wdatapos);
726 if (ret <= 0) {
727 /*
728 * Shouldn't happen; abandon the connection.
729 */
730 close(fds[i].fd);
731 fds[i].deleted = 1;
732 break;
733 } else {
734 fds[i].wdatapos += ret;
735 if (fds[i].wdatapos == fds[i].wdatalen) {
736 shutdown(fds[i].fd, SHUT_WR);
737 }
738 }
739 }
740 break;
741 }
742 }
743
744 }
745 }