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