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