2 * httpd.c: implementation of httpd.h.
15 #include <sys/types.h>
18 #include <sys/socket.h>
19 #include <arpa/inet.h>
20 #include <netinet/in.h>
27 /* --- Logic driving what the web server's responses are. --- */
29 enum { /* connctx states */
38 int datalen
, datasize
;
39 char *method
, *url
, *headers
, *auth
;
44 * Called when a new connection arrives on a listening socket.
45 * Returns a connctx for the new connection.
47 struct connctx
*new_connection(const void *t
)
49 struct connctx
*cctx
= snew(struct connctx
);
52 cctx
->datalen
= cctx
->datasize
= 0;
53 cctx
->state
= READING_REQ_LINE
;
54 cctx
->method
= cctx
->url
= cctx
->headers
= cctx
->auth
= NULL
;
58 void free_connection(struct connctx
*cctx
)
64 static char *http_error(char *code
, char *errmsg
, char *extraheader
,
67 return dupfmt("HTTP/1.1 %s %s\r\n"
70 "Connection: close\r\n"
72 "Content-Type: text/html; charset=US-ASCII\r\n"
74 "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
76 "<TITLE>%s %s</TITLE>\r\n"
80 "</BODY></HTML>\r\n", code
, errmsg
,
81 extraheader ? extraheader
: "",
82 code
, errmsg
, code
, errmsg
, errtext
);
85 static char *http_success(char *mimetype
, int stuff_cr
, char *document
)
87 return dupfmt("HTTP/1.1 200 OK\r\n"
91 "Connection: close\r\n"
92 "Content-Type: %s\r\n"
94 "%S", mimetype
, stuff_cr
, document
);
98 * Called when data comes in on a connection.
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.
105 char *got_data(struct connctx
*ctx
, char *data
, int length
,
106 int magic_access
, const char *auth_string
)
108 char *line
, *p
, *q
, *r
, *z1
, *z2
, c1
, c2
;
109 int auth_provided
= 0, auth_correct
= 0;
111 char *document
, *ret
;
114 * Add the data we've just received to our buffer.
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);
120 memcpy(ctx
->data
+ ctx
->datalen
, data
, length
);
121 ctx
->datalen
+= length
;
124 * Gradually process the HTTP request as we receive it.
126 if (ctx
->state
== READING_REQ_LINE
) {
128 * We're waiting for the first line of the input, which
129 * contains the main HTTP request. See if we've got it
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.'
140 while (line
- ctx
->data
< ctx
->datalen
&&
141 (*line
== '\r' || *line
== '\n'))
144 while (q
- ctx
->data
< ctx
->datalen
&& *q
!= '\n')
146 if (q
- ctx
->data
>= ctx
->datalen
)
147 return NULL
; /* not got request line yet */
150 * We've got the first line of the request. Zero-terminate
151 * and parse it into method, URL and optional HTTP
156 if (q
> line
&& q
[-1] == '\r')
161 while (*p
&& !isspace((unsigned char)*p
)) p
++;
167 while (*p
&& isspace((unsigned char)*p
)) p
++;
169 while (*q
&& !isspace((unsigned char)*q
)) q
++;
173 while (*q
&& isspace((unsigned char)*q
)) q
++;
176 * Now `line' points at the method name; p points at the
177 * URL, if any; q points at the HTTP version, if any.
181 * There should _be_ a URL, on any request type at all.
185 /* Restore the request to the way we received it. */
188 text
= dupfmt("<code>agedu</code> received the HTTP request"
189 " \"<code>%h</code>\", which contains no URL.",
191 ret
= http_error("400", "Bad request", NULL
, text
);
200 * If there was an HTTP version, we might need to see
201 * headers. Otherwise, the request is done.
204 ctx
->state
= READING_HEADERS
;
210 if (ctx
->state
== READING_HEADERS
) {
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.
216 q
= ctx
->data
+ ctx
->datalen
;
217 for (p
= ctx
->headers
; p
< q
; p
++) {
219 ((p
+1 < q
&& p
[1] == '\n') ||
220 (p
+2 < q
&& p
[1] == '\r' && p
[2] == '\n'))) {
228 if (ctx
->state
== DONE
) {
230 * Now we have the entire HTTP request. Decide what to do
235 * Search the request headers for Authorization.
237 q
= ctx
->data
+ ctx
->datalen
;
238 for (p
= ctx
->headers
; p
< q
; p
++) {
239 const char *hdr
= "Authorization:";
241 for (i
= 0; hdr
[i
]; i
++) {
242 if (p
>= q
|| tolower((unsigned char)*p
) !=
243 tolower((unsigned char)hdr
[i
]))
248 break; /* found our header */
249 p
= memchr(p
, '\n', q
- p
);
255 while (p
< q
&& isspace((unsigned char)*p
))
258 while (p
< q
&& !isspace((unsigned char)*p
))
262 if (!strcasecmp(r
, "Basic")) {
263 while (p
< q
&& isspace((unsigned char)*p
))
266 while (p
< q
&& !isspace((unsigned char)*p
))
270 if (!strcmp(r
, auth_string
))
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.");
284 ret
= http_error("403", "Forbidden", NULL
,
285 "This is a restricted-access set of pages.");
289 p
+= strspn(p
, "/?");
290 index
= strtoul(p
, NULL
, 10);
291 document
= html_query(ctx
->t
, index
, "%lu");
293 ret
= http_success("text/html", 1, document
);
296 ret
= http_error("404", "Not Found", NULL
,
297 "Pathname index out of range.");
305 /* --- Platform support for running a web server. --- */
307 enum { FD_CLIENT
, FD_LISTENER
, FD_CONNECTION
};
314 int wdatalen
, wdatapos
;
316 struct connctx
*cctx
;
319 struct fd
*fds
= NULL
;
320 int nfds
= 0, fdsize
= 0;
322 struct fd
*new_fdstruct(int fd
, int type
)
326 if (nfds
>= fdsize
) {
327 fdsize
= nfds
* 3 / 2 + 32;
328 fds
= sresize(fds
, fdsize
, struct fd
);
336 ret
->wdatalen
= ret
->wdatapos
= 0;
339 ret
->magic_access
= 0;
344 int check_owning_uid(int fd
, int flip
)
346 struct sockaddr_in sock
, peer
;
348 char linebuf
[4096], matchbuf
[80];
351 addrlen
= sizeof(sock
);
352 if (getsockname(fd
, (struct sockaddr
*)&sock
, &addrlen
)) {
353 fprintf(stderr
, "getsockname: %s\n", strerror(errno
));
356 addrlen
= sizeof(peer
);
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);
362 fprintf(stderr
, "getpeername: %s\n", strerror(errno
));
368 struct sockaddr_in tmp
= sock
;
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");
378 while (fgets(linebuf
, sizeof(linebuf
), fp
)) {
379 if (strlen(linebuf
) >= 75 &&
380 !strncmp(linebuf
+6, matchbuf
, strlen(matchbuf
))) {
381 return atoi(linebuf
+ 75);
389 void check_magic_access(struct fd
*fd
)
391 if (check_owning_uid(fd
->fd
, 0) == getuid())
392 fd
->magic_access
= 1;
395 static void base64_encode_atom(unsigned char *data
, int n
, char *out
)
397 static const char base64_chars
[] =
398 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
402 word
= data
[0] << 16;
404 word
|= data
[1] << 8;
407 out
[0] = base64_chars
[(word
>> 18) & 0x3F];
408 out
[1] = base64_chars
[(word
>> 12) & 0x3F];
410 out
[2] = base64_chars
[(word
>> 6) & 0x3F];
414 out
[3] = base64_chars
[word
& 0x3F];
419 void run_httpd(const void *t
, int authmask
)
423 char *authstring
= NULL
, authbuf
[512];
424 unsigned long ipaddr
;
426 struct sockaddr_in addr
;
430 * Establish the listening socket and retrieve its port
433 fd
= socket(PF_INET
, SOCK_STREAM
, 0);
435 fprintf(stderr
, "socket(PF_INET): %s\n", strerror(errno
));
438 addr
.sin_family
= AF_INET
;
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
));
451 if (listen(fd
, 5) < 0) {
452 fprintf(stderr
, "listen: %s\n", strerror(errno
));
455 addrlen
= sizeof(addr
);
456 if (getsockname(fd
, (struct sockaddr
*)&addr
, &addrlen
)) {
457 fprintf(stderr
, "getsockname: %s\n", strerror(errno
));
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];
467 unsigned char passbuf
[10];
470 authtype
= HTTPD_AUTH_BASIC
;
472 sprintf(username
, "agedu");
473 rname
= "/dev/urandom";
474 fd
= open(rname
, O_RDONLY
);
477 rname
= "/dev/random";
478 fd
= open(rname
, O_RDONLY
);
481 fprintf(stderr
, "/dev/urandom: open: %s\n", strerror(err
));
482 fprintf(stderr
, "/dev/random: open: %s\n", strerror(err2
));
486 for (i
= 0; i
< 10 ;) {
487 j
= read(fd
, passbuf
+ i
, 10 - i
);
489 fprintf(stderr
, "%s: read: %s\n", rname
,
490 j
< 0 ?
strerror(errno
) : "unexpected EOF");
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";
505 for (j
= 0; j
< 5; j
++)
506 v
|= ((passbuf
[k
+j
] >> (i
%8)) & 1) << j
;
508 password
[i
] = chars
[v
];
512 printf("Using HTTP Basic authentication\nUsername: %s\nPassword: %s\n",
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
);
523 authstring
= authbuf
;
525 authtype
= HTTPD_AUTH_NONE
;
526 printf("Web server is unauthenticated\n");
528 printf("URL: http://%s:%d/\n",
529 inet_ntoa(addr
.sin_addr
), ntohs(addr
.sin_port
));
532 * Now construct an fd structure to hold it.
534 f
= new_fdstruct(fd
, FD_LISTENER
);
537 * Read from standard input, and treat EOF as a notification
540 new_fdstruct(0, FD_CLIENT
);
543 * Now we're ready to run our main loop. Keep looping round on
548 int i
, j
, maxfd
, ret
;
550 #define FD_SET_MAX(fd, set, max) \
551 do { FD_SET((fd),(set)); (max) = ((max)<=(fd)?(fd)+1:(max)); } while(0)
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.
561 for (i
= j
= 0; j
< nfds
; j
++) {
563 if (fds
[j
].deleted
) {
565 free_connection(fds
[j
].cctx
);
570 switch (fds
[i
].type
) {
573 FD_SET_MAX(fds
[i
].fd
, &rfds
, maxfd
);
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.
584 FD_SET_MAX(fds
[i
].fd
, &rfds
, maxfd
);
586 * Also attempt to write, if we have data to write.
588 if (fds
[i
].wdatapos
< fds
[i
].wdatalen
)
589 FD_SET_MAX(fds
[i
].fd
, &wfds
, maxfd
);
597 ret
= select(maxfd
, &rfds
, &wfds
, NULL
, NULL
);
599 if (ret
< 0 && (errno
!= EINTR
)) {
600 fprintf(stderr
, "select: %s", strerror(errno
));
606 for (i
= 0; i
< nfds
; i
++) {
607 switch (fds
[i
].type
) {
609 if (FD_ISSET(fds
[i
].fd
, &rfds
)) {
611 int ret
= read(fds
[i
].fd
, buf
, sizeof(buf
));
614 fprintf(stderr
, "standard input: read: %s\n",
623 if (FD_ISSET(fds
[i
].fd
, &rfds
)) {
625 * New connection has come in. Accept it.
628 struct sockaddr_in addr
;
629 socklen_t addrlen
= sizeof(addr
);
630 int newfd
= accept(fds
[i
].fd
, (struct sockaddr
*)&addr
,
633 break; /* not sure what happened there */
635 f
= new_fdstruct(newfd
, FD_CONNECTION
);
636 f
->cctx
= new_connection(t
);
637 if (authtype
== HTTPD_AUTH_MAGIC
)
638 check_magic_access(f
);
642 if (FD_ISSET(fds
[i
].fd
, &rfds
)) {
644 * There's data to be read.
649 ret
= read(fds
[i
].fd
, readbuf
, sizeof(readbuf
));
652 * This shouldn't happen in a sensible
653 * HTTP connection, so we abandon the
654 * connection if it does.
662 * If we haven't got an HTTP response
663 * yet, keep processing data in the
664 * hope of acquiring one.
666 fds
[i
].wdata
= got_data
667 (fds
[i
].cctx
, readbuf
, ret
,
668 (authtype
== HTTPD_AUTH_NONE
||
669 fds
[i
].magic_access
), authstring
);
671 fds
[i
].wdatalen
= strlen(fds
[i
].wdata
);
676 * Otherwise, just drop our read data
682 if (FD_ISSET(fds
[i
].fd
, &wfds
) &&
683 fds
[i
].wdatapos
< fds
[i
].wdatalen
) {
685 * The socket is writable, and we have data to
688 int ret
= write(fds
[i
].fd
, fds
[i
].wdata
+ fds
[i
].wdatapos
,
689 fds
[i
].wdatalen
- fds
[i
].wdatapos
);
692 * Shouldn't happen; abandon the connection.
698 fds
[i
].wdatapos
+= ret
;
699 if (fds
[i
].wdatapos
== fds
[i
].wdatalen
) {
700 shutdown(fds
[i
].fd
, SHUT_WR
);