1 /* zxid_httpd - small zxid enabled HTTP server derived from mini_httpd-1.19
2  * Copyright (c) 2013-2015 Synergetics NV (sampo@synergetics.be)
3  * All Rights Reserverd.
4  * New bugs are mine, do not bother Jef with them. --Sampo */
5 /* mini_httpd - small HTTP server
6 **
7 ** Copyright � 1999,2000 by Jef Poskanzer <jef@acme.com>.
8 ** All rights reserved.
9 **
10 ** Redistribution and use in source and binary forms, with or without
11 ** modification, are permitted provided that the following conditions
12 ** are met:
13 ** 1. Redistributions of source code must retain the above copyright
14 **    notice, this list of conditions and the following disclaimer.
15 ** 2. Redistributions in binary form must reproduce the above copyright
16 **    notice, this list of conditions and the following disclaimer in the
17 **    documentation and/or other materials provided with the distribution.
18 **
19 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20 ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 ** ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23 ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 ** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 ** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 ** SUCH DAMAGE.
30 */
31 
32 #include <zx/platform.h>
33 #include <zx/errmac.h>
34 #include <zx/zxid.h>
35 #include <zx/zxidutil.h>
36 #include <zx/zxidpriv.h>
37 #include <zx/c/zxidvers.h>
38 
39 #ifdef MINGW
40 #define _POSIX
41 #define __USE_MINGW_ALARM
42 #include <process.h>
43 #endif
44 
45 zxid_ses* zxid_mini_httpd_filter(zxid_conf* cf, const char* method, const char* uri_path, const char* qs, const char* cookie_hdr);
46 void zxid_mini_httpd_wsp_response(zxid_conf* cf, zxid_ses* ses, int rfd, char** response, size_t* response_size, size_t* response_len, int br_ix);
47 int zxid_pool2env(zxid_conf* cf, zxid_ses* ses, char** envp, int envn, int max_envn, const char* uri_path, const char* qs);
48 zxid_ses* zxid_mini_httpd_step_up(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses, const char* uri_path, const char* cookie_hdr);
49 
50 zxid_conf* zxid_cf;      /* ZXID enable flag and config string, zero initialized per POSIX */
51 zxid_ses* zxid_session;  /* Non-null if SSO or session from cookie or WSP validate */
52 int zxid_is_wsp;         /* Flag to trigger WSP response decoration. */
53 char* zxid_conf_str = 0;
54 char server_port_buf[32];
55 char http_host_buf[256];
56 char script_name_buf[256];
57 
58 #define SERVER_SOFTWARE "zxid_httpd/" ZXID_REL " (based on mini_httpd/1.19 19dec2003)"
59 #define SERVER_URL "http://zxid.org/"
60 
61 #include <unistd.h>
62 #include <stdlib.h>
63 #include <stdarg.h>
64 #include <stdio.h>
65 #include <string.h>
66 #include <ctype.h>
67 #include <limits.h>
68 #include <errno.h>
69 #include <sys/param.h>
70 #include <sys/types.h>
71 #include <sys/stat.h>
72 #include <fcntl.h>
73 #include <dirent.h>
74 #include <time.h>
75 #include <signal.h>
76 #ifndef MINGW
77 #include <sys/mman.h>
78 #include <syslog.h>
79 #include <pwd.h>
80 #include <grp.h>
81 #include <netinet/in.h>
82 #include <netinet/tcp.h>
83 #include <arpa/inet.h>
84 #include <sys/wait.h>
85 #include <sys/socket.h>
86 #include <netdb.h>
87 #endif
88 
89 #include "port.h"
90 extern time_t tdate_parse( char* str );
91 
92 #ifdef HAVE_SENDFILE
93 # ifdef HAVE_LINUX_SENDFILE
94 #  include <sys/sendfile.h>
95 # else /* HAVE_LINUX_SENDFILE */
96 #  include <sys/uio.h>
97 # endif /* HAVE_LINUX_SENDFILE */
98 #endif /* HAVE_SENDFILE */
99 
100 #include <openssl/ssl.h>
101 #include <openssl/err.h>
102 #include <openssl/des.h>
103 #define crypt(pw,salt) DES_crypt((pw),(salt))
104 
105 #if defined(AF_INET6) && defined(IN6_IS_ADDR_V4MAPPED)
106 #define USE_IPV6
107 #endif
108 
109 #ifndef SHUT_WR
110 #define SHUT_WR 1
111 #endif
112 
113 #ifndef SIZE_T_MAX
114 #define SIZE_T_MAX 2147483647L
115 #endif
116 
117 #ifndef HAVE_INT64T
118 typedef long long int64_t;
119 #endif
120 
121 #ifndef ERR_DIR
122 #define ERR_DIR "errors"
123 #endif /* ERR_DIR */
124 #ifndef DEFAULT_HTTP_PORT
125 #define DEFAULT_HTTP_PORT 80
126 #endif /* DEFAULT_HTTP_PORT */
127 #ifndef DEFAULT_HTTPS_PORT
128 #define DEFAULT_HTTPS_PORT 443
129 #endif /* DEFAULT_HTTPS_PORT */
130 #ifndef DEFAULT_USER
131 #define DEFAULT_USER "nobody"
132 #endif /* DEFAULT_USER */
133 #ifndef CGI_NICE
134 #define CGI_NICE 10
135 #endif /* CGI_NICE */
136 #ifndef CGI_PATH
137 #define CGI_PATH "/usr/local/bin:/usr/ucb:/bin:/usr/bin"
138 #endif /* CGI_PATH */
139 #ifndef CGI_LD_LIBRARY_PATH
140 #define CGI_LD_LIBRARY_PATH "/usr/local/lib:/usr/lib"
141 #endif /* CGI_LD_LIBRARY_PATH */
142 #ifndef AUTH_FILE
143 #define AUTH_FILE ".htpasswd"
144 #endif /* AUTH_FILE */
145 #ifndef READ_TIMEOUT
146 #define READ_TIMEOUT 60
147 #endif /* READ_TIMEOUT */
148 #ifndef WRITE_TIMEOUT
149 #define WRITE_TIMEOUT 300
150 #endif /* WRITE_TIMEOUT */
151 #ifndef MINI_DEFAULT_CHARSET
152 #define MINI_DEFAULT_CHARSET "iso-8859-1"
153 #endif /* MINI_DEFAULT_CHARSET */
154 
155 #define METHOD_UNKNOWN 0
156 #define METHOD_GET 1
157 #define METHOD_HEAD 2
158 #define METHOD_POST 3
159 
160 /* A multi-family sockaddr. */
161 typedef union {
162   struct sockaddr sa;
163   struct sockaddr_in sa_in;
164 #ifdef USE_IPV6
165   struct sockaddr_in6 sa_in6;
166   struct sockaddr_storage sa_stor;
167 #endif /* USE_IPV6 */
168 } usockaddr;
169 
170 static char* argv0;
171 static unsigned short port;
172 static char* dir;
173 static char* data_dir;
174 static int do_chroot;
175 static int vhost;
176 static char* user;
177 static char* cgi_pattern;
178 static char* url_pattern;
179 static int no_empty_referers;
180 static char* local_pattern;
181 static char* hostname;
182 static char hostname_buf[256];
183 static char* logfile;
184 static char* pidfile;
185 static char* charset;
186 static char* p3p;
187 static int max_age;
188 static int read_timeout = READ_TIMEOUT;
189 static int write_timeout = WRITE_TIMEOUT;
190 static FILE* logfp;
191 static int listen_fd;
192 static int do_ssl;
193 static char* certfile;
194 static char* cipher;
195 static SSL_CTX* ssl_ctx;
196 static char cwd[MAXPATHLEN];
197 static int got_hup;
198 
199 /* Request variables. */
200 static int conn_fd;
201 static SSL* ssl;
202 static usockaddr client_addr;
203 char* request;
204 size_t request_size, request_len, request_idx;
205 static char* method;
206 char* path;
207 static char* file;
208 static char* pathinfo;  /* the stuff after a file in filesystem */
209 struct stat sb;         /* single threaded so we can share stat buffer */
210 static char* query;
211 static char* protocol;
212 static int status;
213 static off_t bytes;
214 static char* req_hostname;
215 
216 char* authorization;
217 size_t content_length;
218 static char* content_type;
219 static char* cookie;
220 static char* host;
221 static time_t if_modified_since;
222 static char* referer;
223 static char* useragent;
224 static char* paos;
225 static char* range;
226 
227 char* remoteuser;
228 
229 /* Forwards. */
230 static int initialize_listen_socket(usockaddr* usaP);
231 static void handle_request(void);
232 static void de_dotdot(char* file);
233 static int get_pathinfo(void);
234 static void do_file(void);
235 static void do_dir(void);
236 static void do_cgi(void);
237 static void cgi_interpose_input(int wfd);
238 static void cgi_interpose_output(int rfd, int parse_headers);
239 static char** make_argp(void);
240 static char** make_envp(void);
241 static void auth_check(char* dirname);
242 static char* virtual_file(char* file);
243 void send_error_and_exit(int s, char* title, char* extra_header, char* text);
244 void add_headers(int s, char* title, char* extra_header, char* me, char* mt, off_t b, time_t mod);
245 static void start_request(void);
246 void add_to_request(char* str, size_t len);
247 static char* get_request_line(void);
248 static void start_response(void);
249 static void send_via_write(int fd, off_t size, off_t start);
250 static void make_log_entry(void);
251 static void check_referer(void);
252 
253 /* ------------- Error and syslog ----------- */
254 
255 /* Called by:  add_password, main x18 */
usage(void)256 static void usage(void) {
257   (void) fprintf(stderr, "usage:  %s [-S certfile] [-Y cipher] [-p port] [-d dir] [-dd data_dir] [-c cgipat] [-u user] [-h hostname] [-r] [-v] [-l logfile] [-i pidfile] [-T charset] [-P P3P] [-M maxage] [-RT read_timeout_secs] [-WT write_timeout_secs] [-zx CONF]\n", argv0);
258   exit(1);
259 }
260 
261 /* Called by:  e_malloc, e_realloc, e_strdup */
die_oom()262 static void die_oom() {
263   syslog(LOG_CRIT, "out of memory");
264   (void) fprintf(stderr, "%s: out of memory\n", argv0);
265   exit(1);
266 }
267 
268 /* Called by:  main x11, re_open_logfile */
die_perror(const char * what)269 static void die_perror(const char* what) {
270 #ifdef MINGW
271   ERR("WSAGetLastError=%d", WSAGetLastError());
272 #endif
273   perror(what);
274   syslog(LOG_CRIT, "%s - %m", what);
275   exit(1);
276 }
277 
278 /* Called by:  initialize_listen_socket x4 */
ret_crit_perror(const char * what)279 static int ret_crit_perror(const char* what) {
280   perror(what);
281   syslog(LOG_CRIT, "%s - %m", what);
282   return -1;
283 }
284 
285 /* ------------- Memory alloc utils ----------- */
286 
287 /* Called by:  add_to_buf, build_env, really_check_referer */
e_malloc(size_t size)288 static void* e_malloc(size_t size) {
289   void* ptr = malloc(size);
290   if (!ptr) die_oom();
291   return ptr;
292 }
293 
294 /* Called by:  add_to_buf, build_env */
e_realloc(void * optr,size_t size)295 static void* e_realloc(void* optr, size_t size) {
296   void* ptr = realloc(optr, size);
297   if (!ptr) die_oom();
298   return ptr;
299 }
300 
301 /* Called by:  build_env, do_cgi */
e_strdup(char * ostr)302 static char* e_strdup(char* ostr) {
303   char* str = strdup(ostr);
304   if (!str) die_oom();
305   return str;
306 }
307 
308 /* ------------- decode ----------- */
309 
310 /* Called by:  strdecode x2 */
hexit(char c)311 static int hexit(char c) {
312   if (c >= '0' && c <= '9')
313     return c - '0';
314   if (c >= 'a' && c <= 'f')
315     return c - 'a' + 10;
316   if (c >= 'A' && c <= 'F')
317     return c - 'A' + 10;
318   return 0;           /* shouldn't happen, we're guarded by isxdigit() */
319 }
320 
321 /* Copies and decodes a string.  It's ok for from and to to be the same string. */
322 /* Called by:  handle_request, make_argp x2 */
strdecode(char * to,char * from)323 static void strdecode(char* to, char* from) {
324   for (; *from != '\0'; ++to, ++from) {
325     if (from[0] == '%' && isxdigit(from[1]) && isxdigit(from[2]))
326       {
327 	*to = hexit(from[1]) * 16 + hexit(from[2]);
328 	from += 2;
329       }
330     else
331       *to = *from;
332   }
333   *to = '\0';
334 }
335 
336 /* Called by:  auth_check */
b64_decode(const char * str,unsigned char * space,int size)337 static int b64_decode(const char* str, unsigned char* space, int size) {
338   unsigned char* q;
339   int len = strlen(str);
340   if (SIMPLE_BASE64_PESSIMISTIC_DECODE_LEN(len)>size) {
341     ERR("Decode might exceed the buffer: estimated=%d available size=%d",SIMPLE_BASE64_PESSIMISTIC_DECODE_LEN(len),size);
342     return 0;
343   }
344   q = (unsigned char*)unbase64_raw(str, str+len, (char*)space, zx_std_index_64);
345   return q-space;
346 }
347 
348 #ifdef HAVE_SCANDIR
349 /* Called by:  file_details */
str_copy_and_url_encode(char * to,size_t tosize,const char * from)350 static void str_copy_and_url_encode(char* to, size_t tosize, const char* from) {
351   int tolen;
352 
353   for (tolen = 0; *from != '\0' && tolen + 4 < tosize; ++from) {
354     if (isalnum(*from) || strchr("/_.-~", *from)) {
355       *to = *from;
356       ++to;
357       ++tolen;
358     } else {
359       (void) sprintf(to, "%%%02x", (int) *from & 0xff);
360       to += 3;
361       tolen += 3;
362     }
363   }
364   *to = '\0';
365 }
366 
367 /* Called by:  do_dir */
file_details(const char * dir,const char * name)368 static char* file_details(const char* dir, const char* name) {
369   char f_time[20];
370   static char encname[1000];
371   static char buf[2000];
372   struct group* grp;
373 
374   (void) snprintf(buf, sizeof(buf), "%s/%s", dir, name);
375   if (lstat(buf, &sb) < 0)
376     return "???";
377 #if 0
378   (void) strftime(f_time, sizeof(f_time), "%d%b%Y %H:%M", localtime(&sb.st_mtime));
379   str_copy_and_url_encode(encname, sizeof(encname), name);
380   (void) snprintf(buf, sizeof(buf), "<A HREF=\"%s\">%-32.32s</A>    %15s %14lld\n",
381 		  encname, name, f_time, (long long int)sb.st_size);
382 #else
383   (void) strftime(f_time, sizeof(f_time), "%Y%m%d-%H:%Mz", gmtime(&sb.st_mtime));
384   str_copy_and_url_encode(encname, sizeof(encname), name);
385   grp = getgrgid(sb.st_gid);
386   (void) snprintf(buf, sizeof(buf), "%15s %c%c %-8.8s %14lld <A HREF=\"%s\">%s</A>\n",
387 		  f_time,
388 		  sb.st_mode & S_IRGRP ? 'g' : '-',
389 		  sb.st_mode & S_IROTH ? 'o' : '-',
390 		  grp?grp->gr_name:"?",
391 		  (long long int)sb.st_size, encname, name);
392 #endif
393   return buf;
394 }
395 
396 #endif /* HAVE_SCANDIR */
397 
398 /* ------------- Read Write Utils ----------- */
399 
400 /* Called by:  cgi_interpose_input, handle_request, zxid_mini_httpd_read_post */
conn_read(char * buf,size_t size)401 ssize_t conn_read(char* buf, size_t size) {
402   DD("size=%d", size);
403   if (do_ssl)
404     size = SSL_read(ssl, buf, size);
405   else
406     size = read(conn_fd, buf, size);
407   DD("got size=%d buf(%.*s)", size, MIN(size, 100), buf);
408   return size;
409 }
410 
411 /* Called by:  cgi_interpose_output x6, send_response, send_via_write x2, zxid_mini_httpd_wsp_response x2 */
conn_write(char * buf,size_t size)412 ssize_t conn_write(char* buf, size_t size) {
413   DD("size=%d buf(%.*s)", size, MIN(size, 100), buf);
414   if (do_ssl)
415     size = SSL_write(ssl, buf, size);
416   else
417     size = write(conn_fd, buf, size);
418   DD("wrote size=%d", size);
419   return size;
420 }
421 
422 #ifdef HAVE_SENDFILE
423 /* Called by:  do_file */
conn_sendfile(int fd,size_t nbytes)424 static int conn_sendfile(int fd, size_t nbytes) {
425 #ifdef HAVE_LINUX_SENDFILE
426   return sendfile(conn_fd, fd, 0, nbytes);
427 #else /* HAVE_LINUX_SENDFILE */
428   return sendfile(fd, conn_fd, 0, nbytes, (struct sf_hdtr*) 0, (off_t*) 0, 0);
429 #endif /* HAVE_LINUX_SENDFILE */
430 }
431 #endif /* HAVE_SENDFILE */
432 
433 /* ------------- Buffer manipulation ----------- */
434 
435 /* Called by:  add_to_request, add_to_response, cgi_interpose_output x2, do_dir x4, zxid_mini_httpd_wsp_response */
add_to_buf(char ** bufP,size_t * bufsizeP,size_t * buflenP,char * str,size_t len)436 void add_to_buf(char** bufP, size_t* bufsizeP, size_t* buflenP, char* str, size_t len) {
437   if (!*bufsizeP) {
438     *bufsizeP = len + 500;
439     *buflenP = 0;
440     *bufP = (char*) e_malloc(*bufsizeP);
441   } else if (*buflenP + len >= *bufsizeP) {
442     *bufsizeP = *buflenP + len + 500;
443     *bufP = (char*) e_realloc((void*) *bufP, *bufsizeP);
444   }
445   (void) memmove(&((*bufP)[*buflenP]), str, len);
446   *buflenP += len;
447   (*bufP)[*buflenP] = '\0';
448 }
449 
450 
451 /* Called by:  handle_request */
start_request(void)452 static void start_request(void) {
453   request_size = 0;
454   request_idx = 0;
455 }
456 
457 /* Called by:  handle_request, zxid_mini_httpd_read_post */
add_to_request(char * str,size_t len)458 void add_to_request(char* str, size_t len) {
459   add_to_buf(&request, &request_size, &request_len, str, len);
460 }
461 
462 /* Called by:  handle_request x2 */
get_request_line(void)463 static char* get_request_line(void) {
464   int i;
465   char c;
466 
467   for (i = request_idx; request_idx < request_len; ++request_idx) {
468     c = request[request_idx];
469     if (c == '\012' || c == '\015')
470       {
471 	request[request_idx] = '\0';
472 	++request_idx;
473 	if (c == '\015' && request_idx < request_len &&
474 	    request[request_idx] == '\012')
475 	  {
476 	    request[request_idx] = '\0';
477 	    ++request_idx;
478 	  }
479 	return &(request[i]);
480       }
481   }
482   return 0;
483 }
484 
485 static char* response;
486 static size_t response_size, response_len;
487 
488 /* Called by:  add_headers */
start_response(void)489 static void start_response(void) {
490   response_size = 0;
491 }
492 
493 /* Called by:  add_headers x14, do_dir, send_error_and_exit x5, send_error_file, zxid_mini_httpd_sso */
add_to_response(char * str,size_t len)494 void add_to_response(char* str, size_t len) {
495   add_to_buf(&response, &response_size, &response_len, str, len);
496 }
497 
498 /* Called by:  do_dir, do_file x2, send_error_and_exit, zxid_mini_httpd_sso */
send_response(void)499 void send_response(void) {
500   (void) conn_write(response, response_len);
501 }
502 
send_via_read_write(int fd,off_t size)503 static void send_via_read_write(int fd, off_t size) {
504   char buf[32*1024];
505   ssize_t r, r2;
506 
507   for (;;) {
508     r = read(fd, buf, sizeof(buf));
509     if (r < 0 && (errno == EINTR || errno == EAGAIN)) {
510       sleep(1);
511       continue;
512     }
513     if (r <= 0)
514       return;
515     for (;;) {
516       r2 = conn_write(buf, r);
517       if (r2 < 0 && (errno == EINTR || errno == EAGAIN)) {
518 	sleep(1);
519 	continue;
520       }
521       if (r2 != r)
522 	return;
523       break;
524     }
525   }
526 }
527 
528 /*() Send file to connection.
529  * Called if sendfile(2) is not available or SSL is used.
530  * This function uses mmap(2) optimization for sending
531  * plain files. If Range causes offset (start), then mmap(2)
532  * is not used (even if by accident start would fall on page boundary). */
533 
534 /* Called by:  do_file x2 */
send_via_write(int fd,off_t size,off_t start)535 static void send_via_write(int fd, off_t size, off_t start) {
536 #ifndef MINGW
537   if (size <= SIZE_T_MAX && !start) {
538     size_t size_size = (size_t) size;
539     void* ptr = mmap(0, size_size, PROT_READ, MAP_PRIVATE, fd, 0);
540     if (ptr != (void*) -1) {
541 #ifdef MADV_SEQUENTIAL
542       /* If we have madvise, might as well call it.  Although sequential
543       ** access is probably already the default. */
544       (void) madvise(ptr, size_size, MADV_SEQUENTIAL|MADV_WILLNEED);
545 #endif /* MADV_SEQUENTIAL */
546       (void) conn_write(ptr, size_size);
547       (void) munmap(ptr, size_size);
548     }
549   } else
550 #endif
551     /* mmap can't deal with files larger than 2GB. */
552     send_via_read_write(fd, size);
553 }
554 
555 /* ------------- name resolution & misc I/O tweaking ----------- */
556 
557 /* Called by:  initialize_listen_socket */
sockaddr_check(usockaddr * usaP)558 static int sockaddr_check(usockaddr* usaP) {
559   switch (usaP->sa.sa_family) {
560   case AF_INET: return 1;
561 #ifdef USE_IPV6
562   case AF_INET6: return 1;
563 #endif /* USE_IPV6 */
564   default:
565     return 0;
566   }
567 }
568 
569 /* Called by:  initialize_listen_socket, ntoa */
sockaddr_len(usockaddr * usaP)570 static size_t sockaddr_len(usockaddr* usaP) {
571   switch (usaP->sa.sa_family) {
572   case AF_INET: return sizeof(struct sockaddr_in);
573 #ifdef USE_IPV6
574   case AF_INET6: return sizeof(struct sockaddr_in6);
575 #endif /* USE_IPV6 */
576   default:
577     return 0;	/* shouldn't happen */
578   }
579 }
580 
581 /* Called by:  main */
lookup_hostname(usockaddr * usa4P,size_t sa4_len,int * gotv4P,usockaddr * usa6P,size_t sa6_len,int * gotv6P)582 static void lookup_hostname(usockaddr* usa4P, size_t sa4_len, int* gotv4P, usockaddr* usa6P, size_t sa6_len, int* gotv6P) {
583   (void) memset(usa4P, 0, sa4_len);
584   usa4P->sa.sa_family = AF_INET;
585   usa4P->sa_in.sin_addr.s_addr = htonl(INADDR_ANY);
586   usa4P->sa_in.sin_port = htons(port);
587   *gotv4P = 1;
588   *gotv6P = 0; /* *** how do you bind INADDR_ANY for IP6? */
589 }
590 
591 /* Called by:  auth_check, check_referer, do_dir, do_file x2, handle_read_timeout, handle_write_timeout, make_envp, make_log_entry, virtual_file */
ntoa(usockaddr * usaP)592 static char* ntoa(usockaddr* usaP) {
593 #ifdef USE_IPV6
594   static char str[200];
595   if (getnameinfo(&usaP->sa, sockaddr_len(usaP), str, sizeof(str), 0, 0, NI_NUMERICHOST)) {
596     str[0] = '?';
597     str[1] = '\0';
598   } else if (IN6_IS_ADDR_V4MAPPED(&usaP->sa_in6.sin6_addr) && !strncmp(str, "::ffff:", 7))
599     (void) strcpy(str, &str[7]);     /* Elide IPv6ish prefix for IPv4 addresses. */
600 
601   return str;
602 #else /* USE_IPV6 */
603   return inet_ntoa(usaP->sa_in.sin_addr);
604 #endif /* USE_IPV6 */
605 }
606 
607 /* Called by:  main x2 */
initialize_listen_socket(usockaddr * usaP)608 static int initialize_listen_socket(usockaddr* usaP)
609 {
610   int listen_fd, i=1;
611 
612   if (!sockaddr_check(usaP)) {
613     syslog(LOG_ERR, "unknown sockaddr family on listen socket - %d", usaP->sa.sa_family);
614     (void) fprintf(stderr, "%s: unknown sockaddr family on listen socket - %d\n",
615 		   argv0, usaP->sa.sa_family);
616     return -1;
617   }
618 
619   D("bind addr(%s) family=%d", ntoa(usaP), usaP->sa.sa_family);
620   listen_fd = socket(usaP->sa.sa_family, SOCK_STREAM, 6 /* tcp */);
621   if (listen_fd < 0) return ret_crit_perror("socket");
622   (void) fcntl(listen_fd, F_SETFD, 1);  /* close on exec (FD_CLOEXEC) */
623   if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (void*) &i, sizeof(i)) < 0)
624     return ret_crit_perror("setsockopt SO_REUSEADDR");
625   if (bind(listen_fd, &usaP->sa, sockaddr_len(usaP)) < 0) return ret_crit_perror("bind");
626   if (listen(listen_fd, 1024) < 0) return ret_crit_perror("listen");
627 
628 #ifdef HAVE_ACCEPT_FILTERS
629   {
630     struct accept_filter_arg af;
631     (void) bzero(&af, sizeof(af));
632     (void) strcpy(af.af_name, ACCEPT_FILTER_NAME);
633     (void) setsockopt(listen_fd, SOL_SOCKET, SO_ACCEPTFILTER, (char*) &af, sizeof(af));
634   }
635 #endif /* HAVE_ACCEPT_FILTERS */
636   return listen_fd;
637 }
638 
639 /* Set NDELAY mode on a socket. */
640 /* Called by:  post_post_garbage_hack */
set_ndelay(int fd)641 static void set_ndelay(int fd) {
642   int flags, newflags;
643 
644   flags = fcntl(fd, F_GETFL, 0);
645   if (flags != -1) {
646     newflags = flags | (int) O_NDELAY;
647     if (newflags != flags)
648       (void) fcntl(fd, F_SETFL, newflags);
649   }
650 }
651 
652 /* Clear NDELAY mode on a socket. */
653 /* Called by:  post_post_garbage_hack */
clear_ndelay(int fd)654 static void clear_ndelay(int fd) {
655   int flags, newflags;
656 
657   flags = fcntl(fd, F_GETFL, 0);
658   if (flags != -1) {
659     newflags = flags & ~ (int) O_NDELAY;
660     if (newflags != flags)
661       (void) fcntl(fd, F_SETFL, newflags);
662   }
663 }
664 
665 /* Special hack to deal with broken browsers that send a LF or CRLF
666 ** after POST data, causing TCP resets - we just read and discard up
667 ** to 2 bytes.  Unfortunately this doesn't fix the problem for CGIs
668 ** which avoid the interposer process due to their POST data being
669 ** short.  Creating an interposer process for all POST CGIs is
670 ** unacceptably expensive. */
671 /* Called by:  cgi_interpose_input */
post_post_garbage_hack(void)672 static void post_post_garbage_hack(void) {
673   char buf[2];
674 
675   if (do_ssl)
676     /* We don't need to do this for SSL, since the garbage has
677     ** already been read.  Probably. */
678     return;
679 
680   set_ndelay(conn_fd);
681   (void)read(conn_fd, buf, sizeof(buf));
682   clear_ndelay(conn_fd);
683 }
684 
685 /* ------------- mime ----------- */
686 
687 struct mime_entry {
688   const char* ext;
689   const char* val;
690 };
691 /* Keep tables in most likely first order*/
692 static const struct mime_entry enc_tab[] = {
693 { "gz", "gzip" },
694 { "Z", "compress" },
695 { "uu", "x-uuencode" },
696 };
697 static const int n_enc_tab = sizeof(enc_tab) / sizeof(*enc_tab);
698 static const struct mime_entry typ_tab[] = {
699 { "gif",  "image/gif" },
700 { "png",  "image/png" },
701 { "jpg",  "image/jpeg" },
702 { "js",   "application/x-javascript" },
703 { "css",  "text/css" },
704 { "html", "text/html; charset=%s" },
705 { "htm",  "text/html; charset=%s" },
706 { "pdf",  "application/pdf" },
707 { "ico",  "image/x-icon" },  /* image/vnd.microsoft.icon does not work in some versions of IE */
708 { "jpeg", "image/jpeg" },
709 { "jfif", "image/jpeg" },
710 { "jpe", "image/jpeg" },
711 { "pbm", "image/x-portable-bitmap" },
712 { "pgm", "image/x-portable-graymap" },
713 { "pnm", "image/x-portable-anymap" },
714 { "ppm", "image/x-portable-pixmap" },
715 { "xpm", "image/x-xpixmap" },
716 { "svg", "image/svg+xml" },
717 { "svgz", "image/svg+xml" },
718 { "swf", "application/x-shockwave-flash" },
719 { "xht", "application/xhtml+xml" },
720 { "xhtml", "application/xhtml+xml" },
721 { "xml", "text/xml" },
722 { "xsl", "text/xml" },
723 { "tif", "image/tiff" },
724 { "tiff", "image/tiff" },
725 { "vrml", "model/vrml" },
726 { "rss", "application/rss+xml" },
727 { "snd", "audio/basic" },
728 { "wav", "audio/x-wav" },
729 { "wmv", "video/x-ms-wmv" },
730 { "avi", "video/x-msvideo" },
731 { "mp2", "audio/mpeg" },
732 { "mp3", "audio/mpeg" },
733 { "mp4", "video/mp4" },
734 { "mpe", "video/mpeg" },
735 { "mpeg", "video/mpeg" },
736 { "mpg", "video/mpeg" },
737 { "mpga", "audio/mpeg" },
738 { "ogg", "application/x-ogg" },
739 { "mid", "audio/midi" },
740 { "midi", "audio/midi" },
741 { "mime", "message/rfc822" },
742 { "mov", "video/quicktime" },
743 { "movie", "video/x-sgi-movie" },
744 { "class", "application/x-java-vm" },
745 { "a",   "application/octet-stream" },
746 { "lib", "application/octet-stream" },
747 { "so",  "application/octet-stream" },
748 { "o",   "application/octet-stream" },
749 { "obj", "application/octet-stream" },
750 { "bin", "application/octet-stream" },
751 { "dll", "application/octet-stream" },
752 { "exe", "application/octet-stream" },
753 { "lha", "application/octet-stream" },
754 { "lzh", "application/octet-stream" },
755 { "tgz", "application/x-tar" },
756 { "tar", "application/x-tar" },
757 { "jar", "application/x-java-archive" },
758 { "zip", "application/zip" },
759 { "doc", "application/msword" },
760 { "docx", "application/msword" },
761 { "ppt",  "application/vnd.ms-powerpoint" },
762 { "pptx", "application/vnd.ms-powerpoint" },
763 { "xls", "application/vnd.ms-excel" },
764 { "xlsx", "application/vnd.ms-excel" },
765 { "crl", "application/x-pkcs7-crl" },
766 { "crt", "application/x-x509-ca-cert" },
767   /*#include "mime_types.h"*/
768 };
769 static const int n_typ_tab = sizeof(typ_tab) / sizeof(*typ_tab);
770 
771 /* Figure out MIME encodings and type based on the filename.  Multiple
772 ** encodings are separated by commas, and are listed in the order in
773 ** which they were applied to the file.
774 */
775 /* Called by:  do_file */
figure_mime(char * name,char * me,size_t me_size)776 static const char* figure_mime(char* name, char* me, size_t me_size)
777 {
778   char* prev_dot;
779   char* dot;
780   char* ext;
781   int me_indexes[10];
782   int n_me_indexes;
783   size_t ext_len, me_len;
784   int i, mei, len;
785   const char* mime_type = "text/plain; charset=%s";
786 
787   /* Peel off encoding extensions until there aren't any more. */
788   n_me_indexes = 0;
789   for (prev_dot = name + strlen(name); ; prev_dot = dot) {
790     for (dot = prev_dot - 1; dot >= name && *dot != '.'; --dot) ;
791     if (dot < name) {
792       /* No dot found.  No more encoding extensions, and no type extension either. */
793       goto done;
794     }
795     ext = dot + 1;
796     ext_len = prev_dot - ext;
797     /* Search the encodings table.  Linear search is fine here, there are only a few entries. */
798     for (i = 0; i < n_enc_tab; ++i) {
799       /* name == file is nul terminated and ext is either nul or . terminated.
800        * It is safe to do strncasecmp() */
801       if (!strncasecmp(enc_tab[i].ext, ext, ext_len) && !enc_tab[i].ext[ext_len]) {
802 	if (n_me_indexes < sizeof(me_indexes)/sizeof(*me_indexes)) {
803 	  me_indexes[n_me_indexes] = i;
804 	  ++n_me_indexes;
805 	}
806 	goto continue_prevdot;
807       }
808     }
809     break;  /* No encoding extension found.  Break and look for a type extension. */
810 
811   continue_prevdot: ;
812   }
813 
814   /* Linear search, with most common assumed to be first. ext and ext_len from enc search. */
815   for (i = 0; i < n_typ_tab; ++i) {
816     /* name == file is nul terminated and ext is either nul or . terminated.
817      * It is safe to do strncasecmp() */
818     if (!strncasecmp(typ_tab[i].ext, ext, ext_len) && !typ_tab[i].ext[ext_len]) {
819       mime_type = typ_tab[i].val;
820       goto done;
821     }
822   }
823 
824  done:
825 
826   /* The last thing we do is actually generate the mime-encoding header in buffer me */
827   me[0] = '\0';
828   me_len = 0;
829   for (i = n_me_indexes - 1; i >= 0; --i) {
830     mei = me_indexes[i];
831     len = strlen(enc_tab[mei].val);  /* was enc_tab[mei].val_len */
832     if (me_len + len + 1 < me_size) {
833       if (me[0] != '\0')
834 	me[me_len++] = ',';
835       (void) strcpy(me+me_len, enc_tab[mei].val);
836       me_len += len;
837     }
838   }
839 
840   return mime_type;
841 }
842 
843 /* ------------- signal handling ----------- */
844 
845 /* Called by: */
handle_sigterm(int sig)846 static void handle_sigterm(int sig) {
847   /* Don't need to set up the handler again, since it's a one-shot. */
848 
849   syslog(LOG_NOTICE, "exiting due to signal %d", sig);
850   (void) fprintf(stderr, "%s: exiting due to signal %d\n", argv0, sig);
851   closelog();
852   exit(1);
853 }
854 
855 /* SIGHUP says to re-open the log file. */
856 /* Called by: */
handle_sighup(int sig)857 static void handle_sighup(int sig) {
858   const int oerrno = errno;
859 
860 #ifndef HAVE_SIGSET
861   /* Set up handler again. */
862   (void) signal(SIGHUP, handle_sighup);
863 #endif /* ! HAVE_SIGSET */
864 
865   /* Just set a flag that we got the signal. */
866   got_hup = 1;
867 
868   /* Restore previous errno. */
869   errno = oerrno;
870 }
871 
872 #ifndef MINGW
873 /* Called by: */
handle_sigchld(int sig)874 static void handle_sigchld(int sig) {
875   const int oerrno = errno;
876   pid_t pid;
877   int status;
878 
879 #ifndef HAVE_SIGSET
880   /* Set up handler again. */
881   (void) signal(SIGCHLD, handle_sigchld);
882 #endif /* ! HAVE_SIGSET */
883 
884   /* Reap defunct children until there aren't any more. */
885   for (;;) {
886 #ifdef HAVE_WAITPID
887     pid = waitpid((pid_t) -1, &status, WNOHANG);
888 #else /* HAVE_WAITPID */
889     pid = wait3(&status, WNOHANG, (struct rusage*) 0);
890 #endif /* HAVE_WAITPID */
891     if ((int) pid == 0)		/* none left */
892       break;
893     if ((int) pid < 0)
894       {
895 	if (errno == EINTR || errno == EAGAIN)
896 	  continue;
897 	/* ECHILD shouldn't happen with the WNOHANG option,
898 	** but with some kernels it does anyway.  Ignore it.
899 	*/
900 	if (errno != ECHILD) {
901 	  perror("child wait");
902 	  syslog(LOG_ERR, "child wait - %m");
903 	}
904 	break;
905       }
906   }
907 
908   /* Restore previous errno. */
909   errno = oerrno;
910 }
911 #endif
912 
913 /* Called by:  main x2 */
re_open_logfile(void)914 static void re_open_logfile(void) {
915   if (logfp != (FILE*) 0) {
916     (void) fclose(logfp);
917     logfp = (FILE*) 0;
918   }
919   if (logfile) {
920     syslog(LOG_NOTICE, "(re)opening logfile");
921     logfp = fopen(logfile, "a");
922     if (logfp == (FILE*) 0) die_perror(logfile);
923   }
924 }
925 
926 /* Called by: */
handle_read_timeout(int sig)927 static void handle_read_timeout(int sig) {
928   syslog(LOG_INFO, "%.80s connection timed out reading", ntoa(&client_addr));
929   send_error_and_exit(408, "Request Timeout", "",
930 		      "No request appeared within a reasonable time period.");
931 }
932 
933 /* Called by: */
handle_write_timeout(int sig)934 static void handle_write_timeout(int sig) {
935   syslog(LOG_INFO, "%.80s connection timed out writing", ntoa(&client_addr));
936   exit(1);
937 }
938 
939 /* Called by:  main */
init_catch_sigs()940 static void init_catch_sigs() {
941 #ifdef HAVE_SIGSET
942   (void) sigset(SIGTERM, handle_sigterm);
943   (void) sigset(SIGINT, handle_sigterm);
944   (void) sigset(SIGUSR1, handle_sigterm);
945   (void) sigset(SIGHUP, handle_sighup);
946   (void) sigset(SIGCHLD, handle_sigchld);
947   (void) sigset(SIGPIPE, SIG_IGN);
948 #else /* HAVE_SIGSET */
949   (void) signal(SIGTERM, handle_sigterm);
950   (void) signal(SIGINT, handle_sigterm);
951 #ifndef MINGW
952   (void) signal(SIGUSR1, handle_sigterm);
953   (void) signal(SIGCHLD, handle_sigchld);
954 #endif
955   (void) signal(SIGHUP, handle_sighup);
956   (void) signal(SIGPIPE, SIG_IGN);
957 #endif /* HAVE_SIGSET */
958   got_hup = 0;
959 }
960 
961 /* =================== M A I N =================== */
962 
963 /* Called by: */
main(int argc,char ** av)964 int main(int argc, char** av)
965 {
966   struct passwd* pwd;
967   uid_t uid = 32767;
968   gid_t gid = 32767;
969   usockaddr host_addr4;
970   usockaddr host_addr6;
971   int gotv4, gotv6;
972   usockaddr usa;
973   socklen_t sz;
974   int an, r;
975   char* cp;
976 
977   argv0 = av[0];
978   port = 0;
979   dir = 0;
980   data_dir = 0;
981   do_chroot = 0;
982   vhost = 0;
983   cgi_pattern = 0;
984   url_pattern = 0;
985   no_empty_referers = 0;
986   local_pattern = 0;
987   charset = MINI_DEFAULT_CHARSET;
988   p3p = 0;
989   max_age = -1;
990   user = DEFAULT_USER;
991   hostname = 0;
992   logfile = 0;
993   pidfile = 0;
994   logfp = 0;
995   do_ssl = 0;
996   cipher = 0;
997   memset(&usa, 0, sizeof(usa));  /* *** avoid valgrind complaints */
998 
999   /* Parse args. */
1000   for (an = 1; an < argc && av[an][0] == '-'; ++an) {
1001 #ifdef MINGW
1002     if (!strcmp(av[an], "-child")) {
1003       /* Child process to handle request. */
1004       client_addr = usa;
1005       handle_request();
1006       exit(0);
1007     }
1008     if (!strcmp(av[an], "-cgiin-child")) {
1009       /* Child process to handle cgi input: shuffle input from conn_fd to write end of pipeA */
1010       conn_fd = atoll(av[an+1]);
1011       cgi_interpose_input(atoll(av[an+3]));
1012     }
1013     if (!strcmp(av[an], "-cgiout-child")) {
1014       /* Child process to handle cgi output: shuffle output from read end of pipeB to conn_fd */
1015       conn_fd = atoll(av[an+1]);
1016       cgi_interpose_output(atoll(av[an+2]), 1);
1017     }
1018 #endif
1019     if (!strcmp(av[an], "-V")) {
1020       (void) printf("%s\n", SERVER_SOFTWARE);
1021       exit(0);
1022     }
1023     if (!strcmp(av[an], "-D")) ++errmac_debug; /* zxid_httpd runs always in -D mode */
1024     else if (!strcmp(av[an], "-S") && an + 1 < argc)  { ++an; certfile = av[an]; do_ssl = 1; }
1025     else if (!strcmp(av[an], "-Y") && an + 1 < argc)  { ++an; cipher = av[an]; }
1026     else if (!strcmp(av[an], "-zx") && an + 1 < argc) { ++an; zxid_conf_str = av[an]; }
1027     else if (!strcmp(av[an], "-RT") && an + 1 < argc) { ++an; read_timeout = atoi(av[an]); }
1028     else if (!strcmp(av[an], "-WT") && an + 1 < argc) { ++an; write_timeout = atoi(av[an]); }
1029     else if (!strcmp(av[an], "-p") && an + 1 < argc)  { ++an; port =(unsigned short)atoi(av[an]); }
1030     else if (!strcmp(av[an], "-d") && an + 1 < argc)  { ++an; dir = av[an]; }
1031     else if (!strcmp(av[an], "-dd") && an + 1 < argc) { ++an; data_dir = av[an]; }
1032     else if (!strcmp(av[an], "-c") && an + 1 < argc)  { ++an; cgi_pattern = av[an]; }
1033     else if (!strcmp(av[an], "-u") && an + 1 < argc)  { ++an; user = av[an]; }
1034     else if (!strcmp(av[an], "-h") && an + 1 < argc)  { ++an; hostname = av[an]; }
1035     else if (!strcmp(av[an], "-r")) do_chroot = 1;
1036     else if (!strcmp(av[an], "-v")) vhost = 1;
1037     else if (!strcmp(av[an], "-l") && an + 1 < argc)  { ++an; logfile = av[an]; }
1038     else if (!strcmp(av[an], "-i") && an + 1 < argc)  { ++an; pidfile = av[an]; }
1039     else if (!strcmp(av[an], "-T") && an + 1 < argc)  { ++an; charset = av[an]; }
1040     else if (!strcmp(av[an], "-P") && an + 1 < argc)  { ++an; p3p = av[an]; }
1041     else if (!strcmp(av[an], "-M") && an + 1 < argc)  { ++an; max_age = atoi(av[an]); }
1042     else usage();
1043   }
1044   if (an != argc) usage();
1045 
1046   cp = strrchr(argv0, '/');
1047   if (cp)
1048     ++cp;
1049   else
1050     cp = argv0;
1051   openlog(cp, LOG_NDELAY|LOG_PID, LOG_DAEMON);
1052 
1053   if (!port) {
1054     if (do_ssl)
1055       port = DEFAULT_HTTPS_PORT;
1056     else
1057       port = DEFAULT_HTTP_PORT;
1058   }
1059   snprintf(server_port_buf, sizeof(server_port_buf), "SERVER_PORT=%d", port);
1060   putenv(server_port_buf);
1061 
1062 #ifndef MINGW
1063   /* If we're root and we're going to become another user, get the uid/gid now. */
1064   if (!getuid()) {
1065     pwd = getpwnam(user);
1066     if (pwd == (struct passwd*) 0) {
1067       syslog(LOG_CRIT, "unknown user - '%s'", user);
1068       (void) fprintf(stderr, "%s: unknown user - '%s'\n", argv0, user);
1069       exit(1);
1070     }
1071     uid = pwd->pw_uid;
1072     gid = pwd->pw_gid;
1073   }
1074 #endif
1075 
1076   /* Log file. */
1077   if (logfile) {
1078     re_open_logfile();
1079     if (logfile[0] != '/') {
1080       syslog(LOG_WARNING, "logfile is not an absolute path, you may not be able to re-open it");
1081       (void) fprintf(stderr, "%s: logfile is not an absolute path, you may not be able to re-open it\n", argv0);
1082     }
1083 #ifndef MINGW
1084     if (!getuid()) {
1085       /* If we are root then we chown the log file to the user we'll
1086       ** be switching to.
1087       */
1088       if (fchown(fileno(logfp), uid, gid) < 0) {
1089 	perror("fchown logfile");
1090 	syslog(LOG_WARNING, "fchown logfile - %m");
1091       }
1092     }
1093 #endif
1094   }
1095 
1096   /* Look up hostname. */
1097   lookup_hostname(&host_addr4, sizeof(host_addr4), &gotv4,
1098 		  &host_addr6, sizeof(host_addr6), &gotv6);
1099   if (!hostname) {
1100     (void) gethostname(hostname_buf, sizeof(hostname_buf));
1101     hostname_buf[sizeof(hostname_buf)-1]=0;
1102     hostname = hostname_buf;
1103   }
1104   if (! (gotv4 || gotv6)) {
1105     syslog(LOG_CRIT, "can't find any valid address");
1106     (void) fprintf(stderr, "%s: can't find any valid address\n", argv0);
1107     exit(1);
1108   }
1109 
1110   /* Initialize listen sockets.  Try v6 first because of a Linux peculiarity;
1111   ** like some other systems, it has magical v6 sockets that also listen for
1112   ** v4, but in Linux if you bind a v4 socket first then the v6 bind fails. */
1113   if (gotv6)
1114     listen_fd = initialize_listen_socket(&host_addr6);
1115   else if (gotv4)
1116     listen_fd = initialize_listen_socket(&host_addr4);
1117   else
1118     listen_fd = -1;
1119   /* If we didn't get any valid sockets, fail. */
1120   if (listen_fd == -1) {
1121     D("gotv4=%d gotv6=%d  ip4(%s) ip6(%s)", gotv4, gotv6, ntoa(&host_addr4), ntoa(&host_addr6));
1122     die_perror("listen(2): can't bind to any address");
1123   }
1124 
1125   if (do_ssl)	{
1126     SSL_load_error_strings();
1127     SSLeay_add_ssl_algorithms();
1128     ssl_ctx = SSL_CTX_new(SSLv23_server_method());
1129     if (certfile[0] != '\0')
1130       if (!SSL_CTX_use_certificate_file(ssl_ctx, certfile, SSL_FILETYPE_PEM) ||
1131 	  !SSL_CTX_use_PrivateKey_file(ssl_ctx, certfile, SSL_FILETYPE_PEM) ||
1132 	  !SSL_CTX_check_private_key(ssl_ctx)) {
1133 	ERR_print_errors_fp(stderr);
1134 	exit(1);
1135       }
1136     if (cipher) {
1137       if (!SSL_CTX_set_cipher_list(ssl_ctx, cipher)) {
1138 	ERR_print_errors_fp(stderr);
1139 	exit(1);
1140       }
1141     }
1142   }
1143 
1144 #ifdef HAVE_SETSID
1145   /* Even if we don't daemonize, we still want to disown our parent
1146   ** process.
1147   */
1148   (void) setsid();
1149 #endif /* HAVE_SETSID */
1150 
1151   if (pidfile) {
1152     /* Write the PID file. */
1153     FILE* pidfp = fopen(pidfile, "w");
1154     if (pidfp == (FILE*) 0) die_perror(pidfile);
1155     (void) fprintf(pidfp, "%d\n", (int) getpid());
1156     (void) fclose(pidfp);
1157   }
1158 
1159 #ifndef MINGW
1160   /* If we're root, start becoming someone else. */
1161   if (!getuid()) {
1162     /* Set aux groups to null. */
1163     if (setgroups(0, (gid_t*) 0) < 0) die_perror("setgroups");
1164     /* Set primary group. */
1165     if (setgid(gid) < 0) die_perror("setgid");
1166     /* Try setting aux groups correctly - not critical if this fails. */
1167     if (initgroups(user, gid) < 0) {
1168       perror("initgroups");
1169       syslog(LOG_ERR, "initgroups - %m");
1170     }
1171 #ifdef HAVE_SETLOGIN
1172     /* Set login name. */
1173     (void) setlogin(user);
1174 #endif /* HAVE_SETLOGIN */
1175   }
1176 #endif /* !MINGW */
1177 
1178   /* Switch directories if requested. */
1179   if (dir) {
1180     if (chdir(dir) < 0) die_perror("chdir");
1181   }
1182 
1183   /* Get current directory. */
1184   (void) getcwd(cwd, sizeof(cwd) - 1);
1185   if (cwd[strlen(cwd) - 1] != '/')
1186     (void) strcat(cwd, "/");
1187 
1188 #ifndef MINGW
1189   /* Chroot if requested. */
1190   if (do_chroot) {
1191     if (chroot(cwd) < 0) die_perror("chroot");
1192     /* If we are logging and the logfile's pathname begins with the
1193     ** chroot tree's pathname, then elide the chroot pathname so
1194     ** that the logfile pathname still works from inside the chroot tree. */
1195     if (logfile)
1196       if (!strncmp(logfile, cwd, strlen(cwd))) {
1197 	(void) strcpy(logfile, &logfile[strlen(cwd) - 1]);
1198 	/* (We already guaranteed that cwd ends with a slash, so leaving
1199 	** that slash in logfile makes it an absolute pathname within
1200 	** the chroot tree.) */
1201       } else {
1202 	syslog(LOG_WARNING, "logfile is not within the chroot tree, you will not be able to re-open it");
1203 	(void) fprintf(stderr, "%s: logfile is not within the chroot tree, you will not be able to re-open it\n", argv0);
1204       }
1205     (void) strcpy(cwd, "/");
1206     /* Always chdir to / after a chroot. */
1207     if (chdir(cwd) < 0) die_perror("chroot chdir");
1208   }
1209 #endif
1210 
1211   /* Switch directories again if requested. */
1212   if (data_dir) {
1213     if (chdir(data_dir) < 0) die_perror("data_dir chdir");
1214   }
1215 
1216 #ifndef MINGW
1217   /* If we're root, become someone else. */
1218   if (!getuid()) {
1219     /* Set uid. */
1220     if (setuid(uid) < 0) die_perror("setuid");
1221     /* Check for unnecessary security exposure. */
1222     if (! do_chroot) {
1223       syslog(LOG_WARNING,
1224 	     "started as root without requesting chroot(), warning only");
1225       (void)fprintf(stderr, "%s: started as root without chroot(), warning only\n", argv0);
1226     }
1227   }
1228 #endif
1229 
1230   init_catch_sigs();
1231 
1232   syslog(LOG_NOTICE, "%.80s starting on %.80s, port %d", SERVER_SOFTWARE, hostname?hostname:"0.0.0.0", (int) port);
1233 
1234   /* ----- Main loop ----- */
1235   for (;;) {
1236     if (got_hup) {           /* Do we need to re-open the log file? */
1237       re_open_logfile();
1238       got_hup = 0;
1239     }
1240 
1241     sz = sizeof(usa);        /* Accept the new connection (blocks). */
1242     conn_fd = accept(listen_fd, &usa.sa, &sz);
1243     if (conn_fd < 0) {
1244       if (errno == EINTR || errno == EAGAIN)
1245 	continue;	/* try again */
1246 #ifdef EPROTO
1247       if (errno == EPROTO)
1248 	continue;	/* try again */
1249 #endif
1250       die_perror("accept");
1251     }
1252 
1253     /* Fork a sub-process to handle the connection, see handle_request() */
1254 #ifdef MINGW
1255     /* *** determine whole path. For now we assume working directory contains mini_httpd */
1256     r = spawnlp(P_NOWAIT, ".", argv0, "-child");
1257     /* Parent comes here. child is processed where option -child is processed. */
1258     if (r) {
1259       perror("spawnlp");
1260       ERR("spawn failed to create subprocess to handle connection. r=%d errno=%d %s",r,errno,STRERROR(errno));
1261       exit(1);
1262     }
1263     close(conn_fd);
1264 #else
1265     r = fork();
1266     if (r < 0) die_perror("fork");
1267     if (!r) {
1268       /* Child process. */
1269       DD("child for handle_request() conn_fd=%d", conn_fd);
1270       client_addr = usa;
1271       if (listen_fd != -1)
1272 	(void) close(listen_fd);
1273       handle_request();
1274       exit(0);
1275     }
1276     (void) close(conn_fd);
1277 #endif
1278   }
1279 }
1280 
1281 /*() This runs in a child process, and exits when done, so cleanup is not needed. */
1282 /* Called by:  main x2 */
handle_request(void)1283 static void handle_request(void)
1284 {
1285   char* method_str;
1286   char* line;
1287   char* cp;
1288   int ret, file_len, i;
1289   const char* index_names[] = {
1290     "index.html", "index.htm", "index.xhtml", "index.xht", "Default.htm",
1291     "index.cgi" };
1292   char cwdbuf[1024];
1293 
1294   /* Set up the timeout for reading. */
1295 #ifdef HAVE_SIGSET
1296   (void) sigset(SIGALRM, handle_read_timeout);
1297 #else /* HAVE_SIGSET */
1298   (void) signal(SIGALRM, handle_read_timeout);
1299 #endif /* HAVE_SIGSET */
1300   (void) alarm(read_timeout);
1301 
1302   /* Initialize the request variables. */
1303   remoteuser = 0;
1304   method = "UNKNOWN";
1305   path = 0;
1306   file = 0;
1307   pathinfo = 0;
1308   query = "";
1309   protocol = 0;
1310   status = 0;
1311   bytes = -1;
1312   req_hostname = 0;
1313 
1314   authorization = 0;
1315   content_type = 0;
1316   content_length = -1;
1317   cookie = 0;
1318   host = 0;
1319   if_modified_since = (time_t) -1;
1320   referer = "";
1321   useragent = "";
1322   paos = "";
1323 
1324 #ifdef TCP_NOPUSH
1325   /* Set the TCP_NOPUSH socket option, to try and avoid the 0.2 second
1326   ** delay between sending the headers and sending the data.  A better
1327   ** solution is writev() (as used in thttpd), or send the headers with
1328   ** send(MSG_MORE) (only available in Linux so far). */
1329   i = 1;
1330   (void) setsockopt(conn_fd, IPPROTO_TCP, TCP_NOPUSH, (void*) &i, sizeof(i));
1331 #endif /* TCP_NOPUSH */
1332 
1333   if (do_ssl) {
1334     ssl = SSL_new(ssl_ctx);
1335     SSL_set_fd(ssl, conn_fd);
1336     if (!SSL_accept(ssl)) {
1337       ERR_print_errors_fp(stderr);
1338       exit(1);
1339     }
1340   }
1341 
1342   /* Read in the request. */
1343   start_request();
1344   for (;;) {
1345     char buf[10000];
1346     int got = conn_read(buf, sizeof(buf));
1347     if (got < 0 && (errno == EINTR || errno == EAGAIN))
1348       continue;
1349     if (got <= 0)
1350       break;
1351     (void) alarm(read_timeout);
1352     add_to_request(buf, got);
1353     if (strstr(request, "\015\012\015\012") || strstr(request, "\012\012"))
1354       break;  /* Empty line ending headers detected. */
1355   }
1356 
1357   /* Parse the first line of the request. */
1358   method_str = get_request_line();
1359   if (!method_str) send_error_and_exit(400, "Bad Request", "", "Can't parse request. 1");
1360   path = strpbrk(method_str, " \t\012\015");
1361   if (!path)       send_error_and_exit(400, "Bad Request", "", "Can't parse request. 2");
1362   *path++ = '\0';
1363   path += strspn(path, " \t\012\015");
1364   protocol = strpbrk(path, " \t\012\015");
1365   if (!protocol)   send_error_and_exit(400, "Bad Request", "", "Can't parse request. 3");
1366   *protocol++ = '\0';
1367   protocol += strspn(protocol, " \t\012\015");
1368   query = strchr(path, '?');
1369   if (!query)
1370     query = "";
1371   else
1372     *query++ = '\0';
1373 
1374   /* Parse the rest of the request headers. */
1375   while (line = get_request_line()) {
1376     if (line[0] == '\0')
1377       break;
1378     else if (!strncasecmp(line, "Authorization:", 14)) {
1379       cp = &line[14];
1380       cp += strspn(cp, " \t");
1381       authorization = cp;
1382     } else if (!strncasecmp(line, "Content-Length:", 15)) {
1383       cp = &line[15];
1384       cp += strspn(cp, " \t");
1385       content_length = atol(cp);
1386     } else if (!strncasecmp(line, "Content-Type:", 13)) {
1387       cp = &line[13];
1388       cp += strspn(cp, " \t");
1389       content_type = cp;
1390     } else if (!strncasecmp(line, "Cookie:", 7)) {
1391       cp = &line[7];
1392       cp += strspn(cp, " \t");
1393       cookie = cp;
1394     } else if (!strncasecmp(line, "Host:", 5)) {
1395       cp = &line[5];
1396       cp += strspn(cp, " \t");
1397       host = cp;
1398       if (strchr(host, '/') || host[0] == '.')
1399 	send_error_and_exit(400, "Bad Request", "", "Can't parse request.");
1400     } else if (!strncasecmp(line, "If-Modified-Since:", 18)) {
1401       cp = &line[18];
1402       cp += strspn(cp, " \t");
1403       if_modified_since = tdate_parse(cp);
1404     } else if (!strncasecmp(line, "Referer:", 8)) {
1405       cp = &line[8];
1406       cp += strspn(cp, " \t");
1407       referer = cp;
1408     } else if (!strncasecmp(line, "User-Agent:", 11)) {
1409       cp = &line[11];
1410       cp += strspn(cp, " \t");
1411       useragent = cp;
1412     } else if (!strncasecmp(line, "Range:", 6)) {
1413       cp = &line[11];
1414       cp += strspn(cp, " \t");
1415       range = cp;
1416     } else if (!strncasecmp(line, "PAOS:", 5)) {
1417       cp = &line[11];
1418       cp += strspn(cp, " \t");
1419       paos = cp;
1420     }
1421   }
1422 
1423   if (     !strcasecmp(method_str, "GET"))  method = "GET";
1424   else if (!strcasecmp(method_str, "HEAD")) method = "HEAD";
1425   else if (!strcasecmp(method_str, "POST")) method = "POST";
1426   else
1427     send_error_and_exit(501, "Not Implemented", "", "That method is not implemented.");
1428 
1429   strdecode(path, path);
1430   if (path[0] != '/')
1431     send_error_and_exit(400, "Bad Request", "", "Bad filename.");
1432   file = &(path[1]);
1433   de_dotdot(file);
1434   if (file[0] == '\0')
1435     file = "./";
1436   if (file[0] == '/' ||
1437       (file[0] == '.' && file[1] == '.' &&
1438        (file[2] == '\0' || file[2] == '/')))
1439     send_error_and_exit(400, "Bad Request", "", "Illegal filename.");
1440   if (vhost)
1441     file = virtual_file(file);
1442 
1443   /* Set up the timeout for writing. */
1444 #ifdef HAVE_SIGSET
1445   (void) sigset(SIGALRM, handle_write_timeout);
1446 #else /* HAVE_SIGSET */
1447   (void) signal(SIGALRM, handle_write_timeout);
1448 #endif /* HAVE_SIGSET */
1449   (void) alarm(write_timeout);
1450 
1451   if (zxid_conf_str) {
1452     /* We recreate the configuration every time. This is to allow
1453      * features such as virtual hosting (VPATH and VURL) to work. */
1454     snprintf(http_host_buf, sizeof(http_host_buf), "HTTP_HOST=%s",
1455 	     host?host:(req_hostname?req_hostname:(hostname?hostname:"UNKNOWN_HOST")));
1456     putenv(http_host_buf);
1457     snprintf(script_name_buf, sizeof(script_name_buf), "SCRIPT_NAME=%s", path);
1458     putenv(script_name_buf);
1459     strcpy(errmac_instance, CC_GREENY("\tminizx"));
1460     zxid_cf = zxid_new_conf_to_cf(zxid_conf_str);
1461 
1462     /* Since the filter may read rest of the post data, request buffer
1463      * may be reallocated, thus invalidating old pointers. Make copies
1464      * to stay safe. */
1465     protocol = strdup(protocol);
1466     path = strdup(path);
1467     file = strdup(file);
1468     query = strdup(query);
1469     if (authorization) authorization = strdup(authorization);
1470     if (content_type)  content_type  = strdup(content_type);
1471     if (cookie)    cookie = strdup(cookie);
1472     if (host)      host = strdup(host);
1473     if (referer)   referer = strdup(referer);
1474     if (useragent) useragent = strdup(useragent);
1475     if (paos)      paos = strdup(paos);
1476     zxid_session = zxid_mini_httpd_filter(zxid_cf, method, path, query, cookie);
1477   }
1478 
1479   ret = stat(file, &sb);
1480   D("handle request stat(%s)=%d st_mode=%o cwd(%s)", file, ret, sb.st_mode, getcwd(cwdbuf, sizeof(cwdbuf)));
1481   if (ret < 0)
1482     ret = get_pathinfo();
1483   if (ret < 0)
1484     send_error_and_exit(404, "Not Found", "", "File not found. 1");
1485   file_len = strlen(file);
1486   if (! S_ISDIR(sb.st_mode)) {
1487     /* Not a directory. */
1488     for (; file[file_len - 1] == '/'; --file_len)
1489       file[file_len - 1] = '\0';
1490     do_file();  /* also handles CGI */
1491   } else {
1492     char idx[10000];
1493 
1494     /* The filename is a directory.  Is it missing the trailing slash? */
1495     if (file[file_len - 1] != '/' && !pathinfo) {
1496       char location[10000];
1497       if (query[0] != '\0')
1498 	(void) snprintf(location, sizeof(location), "Location: %s/?%s", path, query);
1499       else
1500 	(void) snprintf(location, sizeof(location), "Location: %s/", path);
1501       send_error_and_exit(302, "Found", location, "Directories must end with a slash.");
1502     }
1503 
1504     /* Check for an index file. */
1505     for (i = 0; i < sizeof(index_names) / sizeof(char*); ++i) {
1506       (void) snprintf(idx, sizeof(idx), "%s%s", file, index_names[i]);
1507       if (stat(idx, &sb) >= 0) {
1508 	file = idx;
1509 	do_file();
1510 	goto got_one;
1511       }
1512     }
1513 
1514     /* Nope, no index file, so it's an actual directory request. */
1515     do_dir();
1516 
1517   got_one: ;
1518   }
1519 
1520   SSL_free(ssl);
1521 }
1522 
1523 /* Called by:  handle_request */
de_dotdot(char * file)1524 static void de_dotdot(char* file)
1525 {
1526   char* cp;
1527   char* cp2;
1528   int l;
1529 
1530   /* Collapse any multiple / sequences. */
1531   while (cp = strstr(file, "//")) {
1532     for (cp2 = cp + 2; *cp2 == '/'; ++cp2)
1533       continue;
1534     (void) strcpy(cp + 1, cp2);
1535   }
1536 
1537   /* Remove leading ./ and any /./ sequences. */
1538   while (!strncmp(file, "./", 2))
1539     (void) strcpy(file, file + 2);
1540   while (cp = strstr(file, "/./"))
1541     (void) strcpy(cp, cp + 2);
1542 
1543   /* Alternate between removing leading ../ and removing xxx/../ */
1544   for (;;) {
1545     while (!strncmp(file, "../", 3))
1546       (void) strcpy(file, file + 3);
1547     if (!(cp = strstr(file, "/../")))
1548       break;
1549     for (cp2 = cp - 1; cp2 >= file && *cp2 != '/'; --cp2)
1550       continue;
1551     (void) strcpy(cp2 + 1, cp + 4);
1552   }
1553 
1554   /* Also elide any xxx/.. at the end. */
1555   while ((l = strlen(file)) > 3 && !strcmp((cp = file + l - 3), "/..")) {
1556     for (cp2 = cp - 1; cp2 >= file && *cp2 != '/'; --cp2)
1557       continue;
1558     if (cp2 < file)
1559       break;
1560     *cp2 = '\0';
1561   }
1562 }
1563 
1564 /*() Walk file name buffer backwards to extract the longest
1565  * prefix that corresponds to stat'able file. Anything
1566  * beyond this is considered PATH_INFO. */
1567 
1568 /* Called by:  handle_request */
get_pathinfo(void)1569 static int get_pathinfo(void) {
1570   int r;
1571   pathinfo = file+strlen(file);
1572   for (;;) {
1573     do {
1574       --pathinfo;
1575       if (pathinfo <= file) {
1576 	pathinfo = 0;
1577 	return -1;      /* exhausted file without finding slash or pathinfo */
1578       }
1579     } while (*pathinfo != '/');
1580     *pathinfo = '\0';   /* nul terminate file */
1581     r = stat(file, &sb);
1582     if (r >= 0) {
1583       ++pathinfo;       /* pathinfo is the part of the path after matching file */
1584       return r;
1585     } else
1586       *pathinfo = '/';  /* restore slash */
1587   }
1588 }
1589 
1590 /* Called by:  handle_request x2 */
do_file(void)1591 static void do_file(void) {
1592   char buf[10000];
1593   char mime_encodings[500];
1594   const char* mime_type;
1595   char fixed_mime_type[500];
1596   char* cp;
1597   int fd,len, rstart=-1, rend=-1;
1598 
1599   /* Check authorization for this directory. */
1600   (void) strncpy(buf, file, sizeof(buf));
1601   cp = strrchr(buf, '/');
1602   if (!cp)
1603     (void) strcpy(buf, ".");
1604   else
1605     *cp = '\0';
1606   auth_check(buf);
1607 
1608   /* Check if the filename is the AUTH_FILE itself - that's verboten. */
1609   len = strlen(file);
1610   if (len >= sizeof(AUTH_FILE)-1
1611       &&(!strcmp(file, AUTH_FILE) ||
1612 	 (!strcmp(&(file[len - sizeof(AUTH_FILE) + 1]), AUTH_FILE) &&
1613 	  file[len - sizeof(AUTH_FILE)] == '/'))) {
1614     syslog(LOG_NOTICE, "%.80s URL \"%.80s\" tried to retrieve an auth file",
1615 	   ntoa(&client_addr), path);
1616     send_error_and_exit(403, "Forbidden", "", "File is protected. 1");
1617   }
1618 
1619   check_referer();
1620 
1621   if (cgi_pattern && zx_match(cgi_pattern, file)) {  /* Is it CGI? */
1622     do_cgi();
1623     return;
1624   }
1625   if (pathinfo)
1626     send_error_and_exit(404, "Not Found", "", "File not found. 2");
1627 
1628   fd = open(file, O_RDONLY);
1629   if (fd < 0) {
1630     syslog(LOG_INFO, "%.80s File \"%.80s\" is protected", ntoa(&client_addr), path);
1631     send_error_and_exit(403, "Forbidden", "", "File is protected. 2");
1632   }
1633   mime_type = figure_mime(file, mime_encodings, sizeof(mime_encodings));
1634   (void) snprintf(fixed_mime_type, sizeof(fixed_mime_type), mime_type, charset);
1635   if (if_modified_since != (time_t) -1 &&
1636       if_modified_since >= sb.st_mtime) {
1637     add_headers(304, "Not Modified", "", mime_encodings, fixed_mime_type,
1638 		(off_t) -1, sb.st_mtime);
1639     send_response();
1640     return;
1641   }
1642   if (range) {
1643     sscanf(range, " bytes=%d-%d", &rstart, &rend);
1644     if (rstart > sb.st_size)
1645       send_error_and_exit(416, "Request Range Not Satisfiable", "", "Start of Range is beyond end of file");
1646     if (ONE_OF_2(rend, 0, -1) || rend >= sb.st_size)
1647       rend = sb.st_size-1;
1648     if (rstart == -1)
1649       rstart = sb.st_size - rend;
1650     if (rstart == 0 && rend == sb.st_size-1) {
1651       D("Range %d-%d includes whole file", rstart, rend);
1652       add_headers(200, "Ok", "", mime_encodings, fixed_mime_type, sb.st_size, sb.st_mtime);
1653     } else {
1654       D("206 Content-Range %d-%d/%d", rstart, rend, (int)sb.st_size);
1655       sprintf(buf, "Content-Range: %d-%d/%lld", rstart, rend, (long long int)sb.st_size);
1656       add_headers(206, "Partial Content", buf, mime_encodings, fixed_mime_type,
1657 		  rend-rstart+1, sb.st_mtime);
1658       lseek(fd, rstart, SEEK_SET);
1659     }
1660   } else {
1661     rstart = 0;
1662     rend = sb.st_size-1;
1663     add_headers(200, "Ok", "", mime_encodings, fixed_mime_type, sb.st_size, sb.st_mtime);
1664   }
1665   send_response();
1666   if (*method == 'H' /* HEAD */)
1667     return;
1668 
1669   if (sb.st_size > 0) {	/* ignore zero-length files */
1670 #ifdef HAVE_SENDFILE
1671     if (do_ssl)
1672       send_via_write(fd, rend-rstart+1, rstart);
1673     else
1674       (void) conn_sendfile(fd, rend-rstart+1);
1675 #else
1676     send_via_write(fd, rend-rstart+1, rstart);
1677 #endif
1678   }
1679 
1680   (void) close(fd);
1681 }
1682 
1683 
1684 /* Called by:  handle_request */
do_dir(void)1685 static void do_dir(void)
1686 {
1687   char buf[10000];
1688   size_t buflen;
1689   char* contents;
1690   size_t contents_size, contents_len;
1691 #ifdef HAVE_SCANDIR
1692   int n, i;
1693   struct dirent **dl;
1694   char* name_info;
1695 #else /* HAVE_SCANDIR */
1696   char command[10000];
1697   FILE* fp;
1698 #endif /* HAVE_SCANDIR */
1699 
1700   if (pathinfo)
1701     send_error_and_exit(404, "Not Found", "", "File not found. 3");
1702 
1703   auth_check(file);
1704   check_referer();
1705 
1706 #ifdef HAVE_SCANDIR
1707   n = scandir(file, &dl, NULL, alphasort);
1708   if (n < 0) {
1709     syslog(LOG_INFO, "%.80s Directory \"%.80s\" is protected", ntoa(&client_addr), path);
1710     send_error_and_exit(403, "Forbidden", "", "Directory is protected.");
1711   }
1712 #endif /* HAVE_SCANDIR */
1713 
1714   contents_size = 0;
1715   buflen = snprintf(buf, sizeof(buf), "<TITLE>Index of %s</TITLE>\n\
1716 <BODY BGCOLOR=\"#99cc99\" TEXT=\"#000000\" LINK=\"#2020ff\" VLINK=\"#4040cc\">\n\
1717 <H4>Index of %s</H4>\n\
1718 <PRE>\n", file, file);
1719   add_to_buf(&contents, &contents_size, &contents_len, buf, buflen);
1720 
1721 #ifdef HAVE_SCANDIR
1722   for (i = 0; i < n; ++i) {
1723     name_info = file_details(file, dl[i]->d_name);
1724     add_to_buf(&contents, &contents_size, &contents_len, name_info, strlen(name_info));
1725   }
1726 #else /* HAVE_SCANDIR */
1727       /* Magic HTML ls command! */
1728   if (!strchr(file, '\'')) {
1729     (void) snprintf(command, sizeof(command),
1730 		    "ls -lgF '%s' | tail +2 | sed -e 's/^\\([^ ][^ ]*\\)\\( *[^ ][^ ]*  *[^ ][^ ]*  *[^ ][^ ]*\\)\\( *[^ ][^ ]*\\)  *\\([^ ][^ ]*  *[^ ][^ ]*  *[^ ][^ ]*\\)  *\\(.*\\)$/\\1 \\3  \\4  |\\5/' -e '/ -> /!s,|\\([^*]*\\)$,|<A HREF=\"\\1\">\\1</A>,' -e '/ -> /!s,|\\(.*\\)\\([*]\\)$,|<A HREF=\"\\1\">\\1</A>\\2,' -e '/ -> /s,|\\([^@]*\\)\\(@* -> \\),|<A HREF=\"\\1\">\\1</A>\\2,' -e 's/|//'",
1731 		    file);
1732     fp = popen(command, "r");
1733     for (;;) {
1734       size_t r;
1735       r = fread(buf, 1, sizeof(buf), fp);
1736       if (!r)
1737 	break;
1738       add_to_buf(&contents, &contents_size, &contents_len, buf, r);
1739     }
1740     (void) pclose(fp);
1741   }
1742 #endif /* HAVE_SCANDIR */
1743 
1744   buflen = snprintf(buf, sizeof(buf), "</PRE>\n<HR>\n<ADDRESS><A HREF=\"%s\">%s</A></ADDRESS>\n",
1745 		    SERVER_URL, SERVER_SOFTWARE);
1746   add_to_buf(&contents, &contents_size, &contents_len, buf, buflen);
1747 
1748   add_headers(200, "Ok", "", "", "text/html; charset=%s", contents_len, sb.st_mtime);
1749   if (*method != 'H' /*HEAD*/)
1750     add_to_response(contents, contents_len);
1751   send_response();
1752 }
1753 
1754 /* Called by:  do_cgi x2 */
pipe_and_fork(int * p,const char * next_step_flag)1755 static int pipe_and_fork(int* p, const char* next_step_flag) {
1756   int ret;
1757 
1758   if (pipe(p) < 0) {
1759     perror("pipe");
1760     syslog(LOG_CRIT, "pipe - %m");
1761     send_error_and_exit(500, "Internal Error","","Something unexpected went wrong making a pipe.");
1762   }
1763 
1764 #ifdef MINGW
1765   /* *** how to pass global variables and other processing context across the spawn? need to construct complicated environment. */
1766   /* *** determine whole path. for now we assume working directory contains mini_httpd */
1767   {
1768     char conn_fd_buf[32];
1769     char rfd_buf[32];
1770     char wfd_buf[32];
1771     snprintf(conn_fd_buf, sizeof(conn_fd_buf), "%lld", (long long)conn_fd);
1772     conn_fd_buf[sizeof(conn_fd_buf)-1] = 0;
1773     snprintf(rfd_buf, sizeof(rfd_buf), "%lld", (long long)p[1]);
1774     rfd_buf[sizeof(rfd_buf)-1] = 0;
1775     snprintf(wfd_buf, sizeof(wfd_buf), "%lld", (long long)p[1]);
1776     wfd_buf[sizeof(wfd_buf)-1] = 0;
1777     D("spawing interposer wfd=%d conn_fd=%d", p[1], conn_fd);
1778     ret = spawnlp(P_NOWAIT, ".", argv0, next_step_flag, conn_fd_buf, rfd_buf, wfd_buf);
1779   }
1780   /* Parent comes here. child is processed where option -cgiin-child is processed. */
1781   if (ret) {
1782     perror("spawnlp");
1783     ERR("spawn failed to create subprocess (%s) to handle connection. ret=%d errno=%d %s", argv0, ret, errno, STRERROR(errno));
1784     send_error_and_exit(500, "Internal Error", "", "Something unexpected went wrong spawning an interposer.");
1785   }
1786   return 1; /* indicate parent, the child is handled by reinvokcation with command line flag. */
1787 #else
1788   ret = fork();
1789   if (ret < 0) {
1790     syslog(LOG_CRIT, "fork - %m");
1791     perror("fork");
1792     send_error_and_exit(500, "Internal Error", "", "Something unexpected went wrong forking an interposer.");
1793   }
1794   return ret;
1795 #endif
1796 }
1797 
1798 /* Called by:  do_file */
do_cgi(void)1799 static void do_cgi(void) {
1800   char** argp;
1801   char** envp;
1802   int parse_headers;
1803   char* cgi_binary;
1804   char* directory;
1805   int p[2];
1806 
1807   if (*method != 'G' && *method != 'P')
1808     send_error_and_exit(501, "Not Implemented", "", "That method is not implemented for CGI.");
1809 
1810   D("stdin_fd=%d stdout_fd=%d stderr_fd=%d conn_fd=%d", fileno(stdin), fileno(stdout), fileno(stderr), conn_fd);
1811   /* If the socket happens to be using one of the stdin/stdout/stderr
1812   ** descriptors, move it to another descriptor so that the dup2 calls
1813   ** below don't screw things up.  We arbitrarily pick fd 3 - if there
1814   ** was already something on it, we clobber it, but that doesn't matter
1815   ** since at this point the only fd of interest is the connection.
1816   ** All others will be closed on exec. */
1817   if (conn_fd == fileno(stdin) || conn_fd == fileno(stdout) || conn_fd == fileno(stderr)) {
1818     int newfd = dup2(conn_fd, fileno(stderr) + 1);
1819     if (newfd >= 0)
1820       conn_fd = newfd;
1821     /* If the dup2 fails, shrug.  We'll just take our chances. Shouldn't happen though. */
1822   }
1823 
1824   /* Set up stdin.  For POSTs we may have to set up a pipe from an
1825   ** interposer process, depending on if we've read some of the data
1826   ** into our buffer.  We also have to do this for all SSL CGIs. */
1827   if ((*method == 'P' && request_len > request_idx) || do_ssl) {
1828     DD("about to fork interpose_input p0=%d p1=%d", p[0], p[1]);
1829     if (!pipe_and_fork(p,"-cgiin-child")) {
1830       /* Child: Interposer process. */
1831       (void) close(p[0]);        /* the read end will be stdin of the CGI script */
1832       cgi_interpose_input(p[1]); /* shuffle input from conn_fd to write end of the pipe */
1833     }
1834     /* parent (the future CGI script) *** we should write captured POST input to the child */
1835     (void) close(p[1]);
1836     if (p[0] != fileno(stdin)) {           /* wire read end to be CGI stdin (if not already) */
1837       (void) dup2(p[0], fileno(stdin));
1838       (void) close(p[0]);
1839     }
1840   } else {
1841     if (conn_fd != fileno(stdin))          /* Otherwise, the request socket is stdin. */
1842       (void) dup2(conn_fd, fileno(stdin));
1843   }
1844 
1845   envp = make_envp();
1846   argp = make_argp();
1847 
1848   /* Set up stdout/stderr.  For SSL, or if we're doing CGI header parsing,
1849   ** we need an output interposer too.  */
1850   if (!strncmp(argp[0], "nph-", 4))
1851     parse_headers = 0;
1852   else
1853     parse_headers = 1;
1854   if (parse_headers || do_ssl) {
1855     DD("about to fork interpose_output p0=%d p1=%d", p[0], p[1]);
1856     if (!pipe_and_fork(p,"-cgiout-child")) {
1857       /* Child: Interposer process. */
1858       (void) close(p[1]);        /* the write end will be stdout of the CGI script */
1859       cgi_interpose_output(p[0], parse_headers); /* shuffle output from read end to conn_fd */
1860     }
1861     DD("Parent %d", p[0]);
1862     (void) close(p[0]);  /* parent (the future CGI): assign stdout to write end */
1863     if (p[1] != fileno(stdout))
1864       (void) dup2(p[1], fileno(stdout));
1865     //if (p[1] != STDERR_FILENO)            // perhaps we do not want to capture stderr
1866     //  (void) dup2(p[1], STDERR_FILENO);
1867     if (p[1] != fileno(stdout) && p[1] != fileno(stderr))
1868       (void) close(p[1]);
1869   } else {
1870     if (conn_fd != fileno(stdout))
1871       (void) dup2(conn_fd, fileno(stdout)); /* Otherwise, the request socket is stdout/stderr. */
1872     //if (conn_fd != STDERR_FILENO)        // perhaps we do not want to capture stderr
1873     //  (void) dup2(conn_fd, STDERR_FILENO);
1874   }
1875 
1876   /* At this point we would like to set conn_fd to be close-on-exec.
1877   ** Unfortunately there seems to be a Linux problem here - if we
1878   ** do this close-on-exec in Linux, the socket stays open but stderr
1879   ** gets closed - the last fd duped from the socket.  What a mess.
1880   ** So we'll just leave the socket as is, which under other OSs means
1881   ** an extra file descriptor gets passed to the child process.  Since
1882   ** the child probably already has that file open via stdin stdout
1883   ** and/or stderr, this is not a problem. */
1884   /* (void) fcntl(conn_fd, F_SETFD, 1); */
1885 
1886   if (logfp)
1887     (void) fclose(logfp);  /* Close the log file. */
1888   closelog();              /* Close syslog. */
1889   (void) nice(CGI_NICE);
1890 
1891   /* Split the program into directory and binary, so we can chdir()
1892   ** to the program's own directory.  This isn't in the CGI 1.1
1893   ** spec, but it's what other HTTP servers do. */
1894   directory = e_strdup(file);
1895   cgi_binary = strrchr(directory, '/');
1896   if (!cgi_binary)
1897     cgi_binary = file;
1898   else {
1899     *cgi_binary++ = '\0';
1900     (void) chdir(directory);	/* ignore errors */
1901   }
1902 
1903   /* Default behavior for SIGPIPE. */
1904 #ifdef HAVE_SIGSET
1905   (void) sigset(SIGPIPE, SIG_DFL);
1906 #else /* HAVE_SIGSET */
1907   (void) signal(SIGPIPE, SIG_DFL);
1908 #endif /* HAVE_SIGSET */
1909 
1910   DD("about to exec CGI(%s)", cgi_binary);
1911   (void) execve(cgi_binary, argp, envp);  /* Run the CGI script. */
1912   send_error_and_exit(500, "Internal Error", "", "Something unexpected went wrong launching a CGI program. Bad nonexistent path to CGI? No execute permission?");
1913 }
1914 
1915 /* This routine is used only for POST requests.  It reads the data
1916 ** from the request and sends it to the child process.  The only reason
1917 ** we need to do it this way instead of just letting the child read
1918 ** directly is that we have already read part of the data into our
1919 ** buffer.
1920 **
1921 ** Oh, and it's also used for all SSL CGIs.
1922 */
1923 /* Called by:  do_cgi, main */
cgi_interpose_input(int wfd)1924 static void cgi_interpose_input(int wfd)
1925 {
1926   size_t cnt;
1927   ssize_t r2;
1928   ssize_t got = 0;
1929   char buf[1024];
1930 
1931   cnt = request_len - request_idx;
1932   D("write wfd=%d buffered post cnt=%d content_length=%d", (int)wfd, (int)cnt,(int)content_length);
1933   if (cnt > 0) {
1934     // *** MINGW problem: after spawn the read buffer global is no longer available
1935     if ((r2 = write(wfd, request+request_idx, cnt)) != cnt)
1936       exit(0);
1937   }
1938   while ((int)cnt < (int)content_length) {  /* without the cast 0 < -1 seems to be true */
1939     got = conn_read(buf, MIN(sizeof(buf), content_length - cnt));
1940     if (got < 0 && (errno == EINTR || errno == EAGAIN)) {
1941       sleep(1);
1942       continue;
1943     }
1944     if (got <= 0)
1945       exit(0);
1946     for (;;) {
1947       DD("writing input wfd=%d cnt=%d got=%d", wfd, cnt, got);
1948       r2 = write(wfd, buf, got);
1949       DD("got r2=%d", r2);
1950       if (r2 < 0 && (errno == EINTR || errno == EAGAIN)) {
1951 	sleep(1);
1952 	continue;
1953       }
1954       if (r2 != got)
1955 	exit(0);
1956       break;
1957     }
1958     cnt += got;
1959   }
1960   D("done got=%d", (int)got);
1961   post_post_garbage_hack();
1962   exit(0);
1963 }
1964 
1965 /* This routine is used for parsed-header CGIs and for all SSL CGIs. */
1966 /* Called by:  do_cgi, main */
cgi_interpose_output(int rfd,int parse_headers)1967 static void cgi_interpose_output(int rfd, int parse_headers)
1968 {
1969   ssize_t got, r2;
1970   char buf[4096];
1971 
1972   D("ph=%d, rfd=%d conn_fd=%d", parse_headers, rfd, conn_fd);
1973   if (!parse_headers) {
1974     /* If we're not parsing headers, write out the default status line
1975     ** and proceed to the echo phase. */
1976     char http_head[] = "HTTP/1.0 200 OK\015\012";
1977     (void) conn_write(http_head, sizeof(http_head));
1978   } else {
1979     /* Header parsing.  The idea here is that the CGI can return special
1980     ** headers such as "Status:" and "Location:" which change the return
1981     ** status of the response.  Since the return status has to be the very
1982     ** first line written out, we have to accumulate all the headers
1983     ** and check for the special ones before writing the status.  Then
1984     ** we write out the saved headers and proceed to echo the rest of
1985     ** the response. */
1986     size_t headers_size, headers_len;
1987     char* headers;
1988     char* br;
1989     int status, buflen;
1990     char* title;
1991     char* cp;
1992 
1993     /* Slurp in all headers. */
1994     headers_size = 0;  /* 0 = force allocation */
1995     add_to_buf(&headers, &headers_size, &headers_len, 0, 0);
1996     for (;;) {
1997       DD("read rfd=%d", rfd);
1998       got = read(rfd, buf, sizeof(buf));
1999       DD("got=%d (%.*s)", got, MIN(got, 100), buf);
2000       if (got < 0 && (errno == EINTR || errno == EAGAIN)) {
2001 	sleep(1);
2002 	continue;
2003       }
2004       if (got <= 0) {
2005 	br = &(headers[headers_len]);
2006 	break;
2007       }
2008       add_to_buf(&headers, &headers_size, &headers_len, buf, got);
2009       if ((br = strstr(headers, "\015\012\015\012")) ||
2010 	  (br = strstr(headers, "\012\012")))
2011 	break;
2012     }
2013 
2014     if (headers[0] == '\0')    /* If there were no headers, bail. */
2015       goto done;
2016 
2017     status = 200;
2018     if ((cp = strstr(headers, "Status:")) && cp < br &&	(cp == headers || *(cp-1) == '\012')) {
2019       cp += 7;
2020       cp += strspn(cp, " \t");
2021       status = atoi(cp);
2022     }
2023     if ((cp = strstr(headers, "Location:")) && cp < br && (cp == headers || *(cp-1) == '\012'))
2024       status = 302;
2025 
2026     /* Write the status line. */
2027     switch (status) {
2028     case 200: title = "OK"; break;
2029     case 302: title = "Found"; break;
2030     case 304: title = "Not Modified"; break;
2031     case 400: title = "Bad Request"; break;
2032     case 401: title = "Unauthorized"; break;
2033     case 403: title = "Forbidden"; break;
2034     case 404: title = "Not Found"; break;
2035     case 408: title = "Request Timeout"; break;
2036     case 500: title = "Internal Error"; break;
2037     case 501: title = "Not Implemented"; break;
2038     case 503: title = "Service Temporarily Overloaded"; break;
2039     default:  title = "Something"; break;
2040     }
2041     buflen = snprintf(buf, sizeof(buf), "HTTP/1.0 %d %s\015\012", status, title);
2042     (void) conn_write(buf, buflen);
2043 
2044     // *** MINGW: recreate zxid_cf and zxid_session
2045     if (zxid_cf && zxid_session) {
2046       if (zxid_is_wsp) {
2047 	zxid_mini_httpd_wsp_response(zxid_cf, zxid_session, rfd,
2048 				     &headers, &headers_size, &headers_len, br-headers);
2049 	goto done;
2050       } else {
2051 	if (zxid_session->setcookie) {
2052 	  buflen = snprintf(buf, sizeof(buf), "Set-Cookie: %s\015\012",zxid_session->setcookie);
2053 	  conn_write(buf, buflen);
2054 	}
2055 	if (zxid_session->setptmcookie) {
2056 	  buflen = snprintf(buf, sizeof(buf), "Set-Cookie: %s\015\012",zxid_session->setptmcookie);
2057 	  conn_write(buf, buflen);
2058 	}
2059       }
2060     }
2061     /* Write the saved headers (and any beginning of payload). */
2062     (void) conn_write(headers, headers_len);
2063   }
2064 
2065   /* Echo the rest of the output. */
2066   for (;;) {
2067     DD("read rfd=%d", rfd);
2068     got = read(rfd, buf, sizeof(buf));
2069     DD("got=%d (%.*s)", got, MIN(got, 100), buf);
2070     if (got < 0 && (errno == EINTR || errno == EAGAIN)) {
2071       sleep(1);
2072       continue;
2073     }
2074     if (got <= 0)
2075       goto done;
2076     for (;;) {
2077       r2 = conn_write(buf, got);
2078       if (r2 < 0 && (errno == EINTR || errno == EAGAIN)) {
2079 	sleep(1);
2080 	continue;
2081       }
2082       if (r2 != got)
2083 	goto done;
2084       break;
2085     }
2086   }
2087  done:
2088   D("done conn_fd=%d", conn_fd);
2089   shutdown(conn_fd, SHUT_WR);
2090   exit(0);
2091 }
2092 
2093 /* Set up CGI argument vector.  We don't have to worry about freeing
2094 ** stuff since we're a sub-process.  This gets done after make_envp() because
2095 ** we scribble on query. */
2096 /* Called by:  do_cgi */
make_argp(void)2097 static char** make_argp(void)
2098 {
2099   char** argp;
2100   int an;
2101   char* cp1;
2102   char* cp2;
2103 
2104   /* By allocating an arg slot for every character in the query, plus
2105   ** one for the filename and one for the NULL, we are guaranteed to
2106   ** have enough.  We could actually use strlen/2.  */
2107   argp = (char**) malloc((strlen(query) + 2) * sizeof(char*));
2108   if (!argp) die_oom();
2109 
2110   argp[0] = strrchr(file, '/');
2111   if (argp[0])
2112     ++argp[0];
2113   else
2114     argp[0] = file;
2115 
2116   an = 1;
2117   /* According to the CGI spec at http://hoohoo.ncsa.uiuc.edu/cgi/cl.html,
2118   ** "The server should search the query information for a non-encoded =
2119   ** character to determine if the command line is to be used, if it finds
2120   ** one, the command line is not to be used."  */
2121   if (!strchr(query, '=')) {
2122     for (cp1 = cp2 = query; *cp2 != '\0'; ++cp2) {
2123       if (*cp2 == '+') {
2124 	*cp2 = '\0';
2125 	strdecode(cp1, cp1);
2126 	argp[an++] = cp1;
2127 	cp1 = cp2 + 1;
2128       }
2129     }
2130     if (cp2 != cp1) {
2131       strdecode(cp1, cp1);
2132       argp[an++] = cp1;
2133     }
2134   }
2135 
2136   argp[an] = 0;
2137   return argp;
2138 }
2139 
2140 /* Called by:  make_envp x23 */
build_env(char * fmt,char * arg)2141 static char* build_env(char* fmt, char* arg)
2142 {
2143   char* cp;
2144   int size;
2145   static char* buf;
2146   static int maxbuf = 0;
2147 
2148   size = strlen(fmt) + strlen(arg);
2149   if (size > maxbuf) {
2150     if (maxbuf == 0) {
2151       maxbuf = MAX(200, size + 100);
2152       buf = (char*) e_malloc(maxbuf);
2153     } else {
2154       maxbuf = MAX(maxbuf * 2, size * 5 / 4);
2155       buf = (char*) e_realloc((void*) buf, maxbuf);
2156     }
2157   }
2158   (void) snprintf(buf, maxbuf, fmt, arg);
2159   cp = e_strdup(buf);
2160   return cp;
2161 }
2162 
2163 /* Set up CGI environment variables. Be real careful here to avoid
2164 ** letting malicious clients overrun a buffer.  We don't have
2165 ** to worry about freeing stuff since we're a sub-process. */
2166 /* Called by:  do_cgi */
make_envp(void)2167 static char** make_envp(void)
2168 {
2169   static char* envp[50+200];
2170   int envn;
2171   char* cp;
2172   char buf[256];
2173 
2174   envn = 0;
2175   envp[envn++] = build_env("PATH=%s", CGI_PATH);
2176   envp[envn++] = build_env("LD_LIBRARY_PATH=%s", CGI_LD_LIBRARY_PATH);
2177   envp[envn++] = build_env("SERVER_SOFTWARE=%s", SERVER_SOFTWARE);
2178   if (! vhost)
2179     cp = hostname;
2180   else
2181     cp = req_hostname;	/* already computed by virtual_file() */
2182   if (cp) envp[envn++] = build_env("SERVER_NAME=%s", cp);
2183   envp[envn++] = "GATEWAY_INTERFACE=CGI/1.1";
2184   envp[envn++] = "SERVER_PROTOCOL=HTTP/1.0";
2185   (void) snprintf(buf, sizeof(buf), "%d", (int) port);
2186   envp[envn++] = build_env("SERVER_PORT=%s", buf);
2187   envp[envn++] = build_env("REQUEST_METHOD=%s",  method);
2188   envp[envn++] = build_env("SCRIPT_NAME=%s", path);
2189   if (pathinfo) {
2190     envp[envn++] = build_env("PATH_INFO=/%s", pathinfo);
2191     (void) snprintf(buf, sizeof(buf), "%s%s", cwd, pathinfo);
2192     envp[envn++] = build_env("PATH_TRANSLATED=%s", buf);
2193   }
2194   if (query[0] != '\0')
2195     envp[envn++] = build_env("QUERY_STRING=%s", query);
2196   envp[envn++] = build_env("REMOTE_ADDR=%s", ntoa(&client_addr));
2197   if (referer[0] != '\0')           envp[envn++] = build_env("HTTP_REFERER=%s", referer);
2198   if (useragent[0] != '\0')         envp[envn++] = build_env("HTTP_USER_AGENT=%s", useragent);
2199   if (cookie)                       envp[envn++] = build_env("HTTP_COOKIE=%s", cookie);
2200   if (host)                         envp[envn++] = build_env("HTTP_HOST=%s", host);
2201   if (content_type)                 envp[envn++] = build_env("CONTENT_TYPE=%s", content_type);
2202   if (content_length != -1) {
2203     (void) snprintf(buf, sizeof(buf), "%lu", (unsigned long) content_length);
2204     envp[envn++] = build_env("CONTENT_LENGTH=%s", buf);
2205   }
2206   if (authorization)                {
2207     envp[envn++] = build_env("AUTH_TYPE=%s", "Basic");                 /* Of dubious value */
2208     envp[envn++] = build_env("HTTP_AUTHORIZATION=%s", authorization);  /* Allow CGI to see it */
2209   }
2210   if (cp = getenv("TZ"))            envp[envn++] = build_env("TZ=%s", cp);
2211   if (cp = getenv("MALLOC_CHECK_")) envp[envn++] = build_env("MALLOC_CHECK_=%s", cp);
2212   if (paos[0] != '\0')              envp[envn++] = build_env("HTTP_PAOS=%s", paos);
2213   if (cp = getenv("ZXID_PRE_CONF")) envp[envn++] = build_env("ZXID_PRE_CONF=%s", cp);
2214   if (cp = getenv("ZXID_CONF"))     envp[envn++] = build_env("ZXID_CONF=%s", cp);
2215   if (zxid_session)
2216     envn = zxid_pool2env(zxid_cf, zxid_session, envp, envn, sizeof(envp)/sizeof(char*), path, query);
2217   if (remoteuser != 0)
2218     envp[envn++] = build_env("REMOTE_USER=%s", remoteuser);
2219 
2220   envp[envn] = 0;
2221   return envp;
2222 }
2223 
2224 /*() Start a response by rendering typical headers
2225  *
2226  * s:: status code (e.g. 200=OK)
2227  * title:: status code explanation, e.g. "OK"
2228  * extra_header:: One or more fully formated headers, or null for none.
2229  * me:: MIME Encoding, if any, for Content-Encoding header
2230  * mt:: MIME type for Content-Type header
2231  * byt:: bytes for Content-Length
2232  * mod:: Last-Modified time
2233  */
2234 
2235 /* Called by:  do_dir, do_file x2, send_error_and_exit, zxid_mini_httpd_sso */
add_headers(int s,char * title,char * extra_header,char * me,char * mt,off_t byt,time_t mod)2236 void add_headers(int s, char* title, char* extra_header, char* me, char* mt, off_t byt, time_t mod)
2237 {
2238   time_t now, expires;
2239   char timebuf[100];
2240   char buf[10000];
2241   int buflen;
2242   int s100;
2243   const char* rfc1123_fmt = "%a, %d %b %Y %H:%M:%S GMT";
2244 
2245   D("status=%d %s", s, title);
2246   status = s;
2247   bytes = byt;
2248   make_log_entry();
2249   start_response();
2250   buflen = snprintf(buf, sizeof(buf), "%s %d %s\015\012", protocol, status, title);
2251   add_to_response(buf, buflen);
2252   buflen = snprintf(buf, sizeof(buf), "Server: %s\015\012", SERVER_SOFTWARE);
2253   add_to_response(buf, buflen);
2254   now = time((time_t*) 0);
2255   (void) strftime(timebuf, sizeof(timebuf), rfc1123_fmt, gmtime(&now));
2256   buflen = snprintf(buf, sizeof(buf), "Date: %s\015\012", timebuf);
2257   add_to_response(buf, buflen);
2258   s100 = status / 100;
2259   if (s100 != 2 && s100 != 3) {
2260     buflen = snprintf(buf, sizeof(buf), "Cache-Control: no-cache,no-store\015\012");
2261     add_to_response(buf, buflen);
2262   }
2263   if (extra_header != 0 && extra_header[0] != '\0') {
2264     buflen = strlen(extra_header);
2265     for (; buflen > 0 && ONE_OF_2(extra_header[buflen], '\015', '\012');
2266 	 --buflen) ; /* eliminate trailing CRLFs, e.g. from zxid_simple() */
2267     buflen = snprintf(buf, sizeof(buf), "%.*s\015\012", buflen, extra_header);
2268     add_to_response(buf, buflen);
2269   }
2270   if (me != 0 && me[0] != '\0') {
2271     buflen = snprintf(buf, sizeof(buf), "Content-Encoding: %s\015\012", me);
2272     add_to_response(buf, buflen);
2273   }
2274   if (mt != 0 && mt[0] != '\0') {
2275     buflen = snprintf(buf, sizeof(buf), "Content-Type: %s\015\012", mt);
2276     add_to_response(buf, buflen);
2277   }
2278   if (bytes >= 0) {
2279     buflen = snprintf(buf, sizeof(buf), "Content-Length: %lld\015\012", (long long int) bytes);
2280     add_to_response(buf, buflen);
2281   }
2282   if (p3p != 0 && p3p[0] != '\0') {
2283     buflen = snprintf(buf, sizeof(buf), "P3P: %s\015\012", p3p);
2284     add_to_response(buf, buflen);
2285   }
2286   if (max_age >= 0) {
2287     expires = now + max_age;
2288     (void) strftime(timebuf, sizeof(timebuf), rfc1123_fmt, gmtime(&expires));
2289     buflen = snprintf(buf, sizeof(buf),
2290 		      "Cache-Control: max-age=%d\015\012Expires: %s\015\012", max_age, timebuf);
2291     add_to_response(buf, buflen);
2292   }
2293   if (mod != (time_t) -1) {
2294     (void) strftime(timebuf, sizeof(timebuf), rfc1123_fmt, gmtime(&mod));
2295     buflen = snprintf(buf, sizeof(buf), "Last-Modified: %s\015\012", timebuf);
2296     add_to_response(buf, buflen);
2297   }
2298   D("zxid_cf=%p zxid_session=%p", zxid_cf, zxid_session);
2299   if (zxid_cf && zxid_session) {
2300     if (zxid_is_wsp) {
2301       /* Nothing to add, not even likely to occur */
2302       D("zxid_is_wsp=%d", zxid_is_wsp);
2303     } else {
2304       if (zxid_session->setcookie) {
2305 	buflen = snprintf(buf, sizeof(buf), "Set-Cookie: %s\015\012", zxid_session->setcookie);
2306 	D("set-cookie(%.*s)", buflen, buf);
2307 	add_to_response(buf, buflen);
2308       }
2309       if (zxid_session->setptmcookie) {
2310 	buflen = snprintf(buf, sizeof(buf), "Set-Cookie: %s\015\012", zxid_session->setptmcookie);
2311 	D("set-cookie(%.*s)", buflen, buf);
2312 	add_to_response(buf, buflen);
2313       }
2314     }
2315   }
2316   buflen = snprintf(buf, sizeof(buf), "Connection: close\015\012\015\012");
2317   add_to_response(buf, buflen);
2318 }
2319 
2320 /* Called by:  handle_request */
virtual_file(char * file)2321 static char* virtual_file(char* file) {
2322   char* cp;
2323   static char vfile[10000];
2324 
2325   /* Use the request's hostname, or fall back on the IP address. */
2326   if (host != 0)
2327     req_hostname = host;
2328   else
2329     {
2330       usockaddr usa;
2331       socklen_t sz = sizeof(usa);
2332       if (getsockname(conn_fd, &usa.sa, &sz) < 0)
2333 	req_hostname = "UNKNOWN_HOST";
2334       else
2335 	req_hostname = ntoa(&usa);
2336     }
2337   /* Pound it to lower case. */
2338   for (cp = req_hostname; *cp != '\0'; ++cp)
2339     if (isupper(*cp))
2340       *cp = tolower(*cp);
2341   (void) snprintf(vfile, sizeof(vfile), "%s/%s", req_hostname, file);
2342   return vfile;
2343 }
2344 
2345 /* Called by:  send_error_and_exit x2 */
send_error_file(char * filename)2346 static int send_error_file(char* filename) {
2347   FILE* fp;
2348   char buf[1000];
2349   size_t r;
2350 
2351   fp = fopen(filename, "r");
2352   if (!fp)
2353     return 0;
2354   for (;;) {
2355     r = fread(buf, 1, sizeof(buf), fp);
2356     if (!r)
2357       break;
2358     add_to_response(buf, r);
2359   }
2360   (void) fclose(fp);
2361   return 1;
2362 }
2363 
2364 /* Called by:  auth_check, check_referer, do_cgi x2, do_dir x2, do_file x3, handle_read_timeout, handle_request x9, pipe_and_fork x3, send_authenticate, zxid_mini_httpd_sso x3, zxid_mini_httpd_wsp x2 */
send_error_and_exit(int err_code,char * title,char * extra_header,char * text)2365 void send_error_and_exit(int err_code, char* title, char* extra_header, char* text) {
2366   char buf[4000];
2367   int buflen;
2368 
2369   add_headers(err_code, title, extra_header, "", "text/html; charset=%s", (off_t) -1, (time_t) -1);
2370 
2371   if (vhost && req_hostname) {
2372     /* Try virtual-host custom error page. */
2373     (void) snprintf(buf, sizeof(buf), "%s/%s/err%d.html", req_hostname, ERR_DIR, err_code);
2374     if (send_error_file(buf))
2375       exit(1);
2376   }
2377 
2378   /* Try server-wide custom error page. */
2379   (void) snprintf(buf, sizeof(buf), "%s/err%d.html", ERR_DIR, err_code);
2380   if (send_error_file(buf))
2381     exit(1);
2382 
2383   /* Send built-in error page. */
2384   buflen = snprintf(buf, sizeof(buf), "<TITLE>%d %s</TITLE><BODY BGCOLOR=\"#cc9999\" TEXT=\"#000000\" LINK=\"#2020ff\" VLINK=\"#4040cc\">\n<H4>%d %s</H4>\n%s\n",err_code,title,err_code,title,text);
2385   add_to_response(buf, buflen);
2386 
2387   if (zx_match("**MSIE**", useragent)) {
2388     int n;
2389     buflen = snprintf(buf, sizeof(buf), "<!--\n");
2390     add_to_response(buf, buflen);
2391     for (n = 0; n < 6; ++n)
2392       {
2393 	buflen = snprintf(buf, sizeof(buf), "Padding so that MSIE deigns to show this error instead of its own canned one.\n");
2394 	add_to_response(buf, buflen);
2395       }
2396     buflen = snprintf(buf, sizeof(buf), "-->\n");
2397     add_to_response(buf, buflen);
2398   }
2399 
2400   buflen = snprintf(buf, sizeof(buf), "<HR>\n<ADDRESS><A HREF=\"%s\">%s</A></ADDRESS>\n",
2401 		    SERVER_URL, SERVER_SOFTWARE);
2402   add_to_response(buf, buflen);
2403   send_response();
2404   SSL_free(ssl);
2405   exit(1);
2406 }
2407 
2408 /* Called by:  auth_check x5 */
send_authenticate(char * realm)2409 static void send_authenticate(char* realm) {
2410   char header[1000];
2411   (void) snprintf(header, sizeof(header), "WWW-Authenticate: Basic realm=\"%s\"", realm);
2412   send_error_and_exit(401, "Unauthorized", header, "Authorization required.");
2413 }
2414 
2415 /*() Check that file (or CGI or directory) access is permitted.
2416  * First, if UNIX_GROUP_AZ_MAP has been configured, the current
2417  * user's attributes, typically role or o (organization), are checked
2418  * against the group. This is typically used with SSO.
2419  * Second, the .htaccess file in the directory, if any, is checked (i.e. HTTP
2420  * Basic Auth with usename and password).
2421  */
2422 /* Called by:  do_dir, do_file */
auth_check(char * dirname)2423 static void auth_check(char* dirname)
2424 {
2425   char authpath[10000];
2426   char authinfo[500];
2427   char* authpass;
2428   char* colon;
2429   static char line[10000];
2430   int len;
2431   FILE* fp;
2432   char* cryp;
2433   zxid_cgi cgi;
2434 
2435   if (zxid_cf && zxid_cf->unix_grp_az_map) {
2436     D("Checking unix_grp_az_map st_mode=%o", sb.st_mode);
2437     /* The stat buffer has already been filled by caller */
2438     if (sb.st_mode & S_IROTH)
2439       return;    /* Ok. World readable file, directory, or executable. */
2440     if (sb.st_mode & S_IRGRP) {
2441       D("HERE2 st_mode=%o", sb.st_mode);
2442       if (zxid_unix_grp_az_check(zxid_cf, zxid_session, sb.st_gid))
2443 	return;  /* Permit */
2444       if (!zxid_session || !zxid_session->nid || !zxid_session->nid[0]) {
2445 	D("HERE3 st_mode=%o", sb.st_mode);
2446 	/* User had not logged in yet */
2447 	ZERO(&cgi, sizeof(zxid_cgi));
2448 	cgi.op = 'E';
2449 	cgi.uri_path = path;
2450 	zxid_mini_httpd_step_up(zxid_cf, &cgi, 0, path, 0);
2451 	exit(0);
2452       }
2453       D("HERE8 st_mode=%o", sb.st_mode);
2454     }
2455     send_error_and_exit(403, "Forbidden", "", "File is protected. 3");
2456   }
2457 
2458   if (dirname[strlen(dirname) - 1] == '/')
2459     (void) snprintf(authpath, sizeof(authpath), "%s%s", dirname, AUTH_FILE);
2460   else
2461     (void) snprintf(authpath, sizeof(authpath), "%s/%s", dirname, AUTH_FILE);
2462   if (stat(authpath, &sb) < 0)  /* Does this directory have an auth file? */
2463     return;                     /* Nope, let the request go through. */
2464   if (!authorization)           /* Does this request contain authorization info? */
2465     send_authenticate(dirname); /* Nope, return a 401 Unauthorized. */
2466 
2467   if (strncmp(authorization, "Basic ", 6))  /* Basic authorization info? */
2468     send_authenticate(dirname);
2469 
2470   len = b64_decode(&(authorization[6]), (unsigned char*) authinfo, sizeof(authinfo) - 1);
2471   authinfo[len] = '\0';
2472   authpass = strchr(authinfo, ':');  /* Split into user and password. */
2473   if (!authpass)
2474     send_authenticate(dirname);      /* No colon?  Bogus auth info. */
2475   *authpass++ = '\0';
2476   colon = strchr(authpass, ':');     /* If there are more fields, cut them off. */
2477   if (colon)
2478     *colon = '\0';
2479 
2480   fp = fopen(authpath, "r");    /* Open the password file. */
2481   if (fp == (FILE*) 0) {
2482     syslog(LOG_ERR, "%.80s auth file %.80s could not be opened - %m",ntoa(&client_addr),authpath);
2483     send_error_and_exit(403, "Forbidden", "", "File is protected. 4");
2484   }
2485 
2486   while (fgets(line, sizeof(line), fp)) {
2487     len = strlen(line);
2488     if (line[len - 1] == '\n')
2489       line[len - 1] = '\0';     /* Nuke newline. */
2490     cryp = strchr(line, ':');   /* Split into user and encrypted password. */
2491     if (!cryp)
2492       continue;
2493     *cryp++ = '\0';
2494     if (!strcmp(line, authinfo)) {   /* Is this the right user? */
2495       (void) fclose(fp);
2496       if (!strcmp(crypt(authpass, cryp), cryp)) { /* Yes, So is the password right? */
2497 	remoteuser = line;
2498 	return; /* Ok! */
2499       } else /* Wrong password */
2500 	send_authenticate(dirname);
2501     }
2502   }
2503 
2504   (void) fclose(fp);
2505   send_authenticate(dirname);   /* Didn't find that user.  Access denied. */
2506 }
2507 
2508 /* Returns 1 if ok to serve the url, 0 if not. */
2509 /* Called by:  check_referer */
really_check_referer(void)2510 static int really_check_referer(void)
2511 {
2512   char* cp1;
2513   char* cp2;
2514   char* cp3;
2515   char* refhost;
2516   char *lp;
2517 
2518   /* Check for an empty referer. */
2519   if (!referer || !*referer || !(cp1 = strstr(referer, "//"))) {
2520     /* Disallow if we require a referer and the url matches. */
2521     if (no_empty_referers && zx_match(url_pattern, path))
2522       return 0;
2523     /* Otherwise ok. */
2524     return 1;
2525   }
2526 
2527   /* Extract referer host. */
2528   cp1 += 2;
2529   for (cp2 = cp1; *cp2 != '/' && *cp2 != ':' && *cp2 != '\0'; ++cp2)
2530     continue;
2531   refhost = (char*) e_malloc(cp2 - cp1 + 1);
2532   for (cp3 = refhost; cp1 < cp2; ++cp1, ++cp3)
2533     if (isupper(*cp1))
2534       *cp3 = tolower(*cp1);
2535     else
2536       *cp3 = *cp1;
2537   *cp3 = '\0';
2538 
2539   /* Local pattern? */
2540   if (local_pattern)
2541     lp = local_pattern;
2542   else {
2543     /* No local pattern.  What's our hostname? */
2544     if (!vhost) {
2545       /* Not vhosting, use the server name. */
2546       lp = hostname;
2547       if (!lp)
2548 	return 1; /* Couldn't figure out local hostname - give up. */
2549     } else {
2550       /* We are vhosting, use the hostname on this connection. */
2551       lp = req_hostname;
2552       if (!lp)
2553 	/* Oops, no hostname.  Maybe it's an old browser that
2554 	 * doesn't send a Host: header.  We could figure out
2555 	 * the default hostname for this IP address, but it's
2556 	 * not worth it for the few requests like this. */
2557 	return 1;
2558     }
2559   }
2560 
2561   /* If the referer host doesn't match the local host pattern, and
2562   ** the URL does match the url pattern, it's an illegal reference. */
2563   if (! zx_match(lp, refhost) && zx_match(url_pattern, path))
2564     return 0;
2565   /* Otherwise ok. */
2566   return 1;
2567 }
2568 
2569 /* Returns if it's ok to serve the url, otherwise generates an error and exits. */
2570 /* Called by:  do_dir, do_file */
check_referer(void)2571 static void check_referer(void)
2572 {
2573   char* cp;
2574   if (!url_pattern)
2575     return;  /*Not doing referer checking at all. */
2576 
2577   if (really_check_referer())
2578     return; /* Ok */
2579 
2580   /* Lose. */
2581   if (!(cp = vhost && req_hostname ? req_hostname : hostname))
2582     cp = "";
2583   syslog(LOG_INFO, "%.80s non-local referer \"%.80s%.80s\" \"%.80s\"",
2584 	 ntoa(&client_addr), cp, path, referer);
2585   send_error_and_exit(403, "Forbidden", "", "You must supply a local referer.");
2586 }
2587 
2588 /* Called by:  add_headers */
make_log_entry(void)2589 static void make_log_entry(void)
2590 {
2591   char url[500];
2592   char bytes_str[40];
2593   time_t now;
2594   char date[100];
2595 
2596   if (!logfp)
2597     return;  /* no logging */
2598 
2599   /* If we're vhosting, prepend the hostname to the url. Separate files are not supported. */
2600   if (vhost)
2601     (void) snprintf(url, sizeof(url), "/%s%s", req_hostname?req_hostname:hostname, path?path:"");
2602   else
2603     (void) snprintf(url, sizeof(url), "%s", path?path:"");
2604   if (bytes >= 0)
2605     (void) snprintf(bytes_str, sizeof(bytes_str), "%lld", (long long int)bytes);
2606   else
2607     (void) strcpy(bytes_str, "-");
2608   now = time(0);
2609   (void) strftime(date, sizeof(date), "%d/%b/%Y:%H:%M:%S +0000", gmtime(&now)); /* always gmt */
2610   (void) fprintf(logfp, "%.80s - %.80s [%s] \"%.80s %.200s %.80s\" %d %s \"%.200s\" \"%.200s\"\n",
2611 		 ntoa(&client_addr), remoteuser?remoteuser:"-", date, method,
2612 		 url, protocol?protocol:"UNKNOWN", status, bytes_str, referer, useragent);
2613   (void) fflush(logfp);
2614 }
2615 
2616 /* EOF */
2617