kuhttpd/kuhttpd.c

2279 lines
58 KiB
C
Raw Normal View History

2013-06-28 18:48:22 +02:00
/**
* @PROJECT Kagera uHTTP Daemon
* @COPYRIGHT See COPYING in the top level directory
* @FILE kuhttpd.c
* @PURPOSE HTTP Server
* @DEVELOPERS Eric Bishop <eric@gargoyle-router.com>
* Rafal Kupiec <belliash@asiotec.eu.org>
* Jef Poskanzer <jef@acme.com>
*/
#include <unistd.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <limits.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <time.h>
#include <pwd.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <ctype.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <dirent.h>
#include <cyassl/openssl/ssl.h>
#include <cyassl/openssl/err.h>
#include <cyassl/error.h>
#ifdef CROSS_BUILD
#include <cyassl/cyassl_error.h>
#endif
#include "kuhttpd.h"
#include "dateparse.h"
#include "match.h"
#include "mime_enc.h"
#include "mime_typ.h"
static void add_headers(int s, char* title, char* extra_header, char* me, char* mt, off_t b, time_t mod) {
time_t now;
char timebuf[100];
char buf[10000];
int buflen;
const char* rfc1123_fmt = "%a, %d %b %Y %H:%M:%S GMT";
status = s;
bytes = b;
make_log_entry();
start_response();
buflen = snprintf(buf, sizeof(buf), "%s %d %s\015\012", protocol, status, title);
add_to_response(buf, buflen);
buflen = snprintf(buf, sizeof(buf), "Server: %s\015\012", SERVER_SOFTWARE);
add_to_response(buf, buflen);
now = time((time_t*) 0);
(void) strftime(timebuf, sizeof(timebuf), rfc1123_fmt, gmtime(&now));
buflen = snprintf(buf, sizeof(buf), "Date: %s\015\012", timebuf);
add_to_response(buf, buflen);
buflen = snprintf(buf, sizeof(buf), "Expires: %s\015\012", timebuf);
add_to_response(buf, buflen);
if(mod != (time_t) -1) {
(void) strftime(timebuf, sizeof(timebuf), rfc1123_fmt, gmtime(&mod));
buflen = snprintf(buf, sizeof(buf), "Last-Modified: %s\015\012", timebuf);
add_to_response(buf, buflen);
}
if(extra_header != (char*) 0 && extra_header[0] != '\0') {
buflen = snprintf(buf, sizeof(buf), "%s\015\012", extra_header);
add_to_response(buf, buflen);
}
if(me != (char*) 0 && me[0] != '\0') {
buflen = snprintf(buf, sizeof(buf), "Content-Encoding: %s\015\012", me);
add_to_response(buf, buflen);
}
if(mt != (char*) 0 && mt[0] != '\0') {
buflen = snprintf(buf, sizeof(buf), "Content-Type: %s\015\012", mt);
add_to_response(buf, buflen);
}
if(bytes >= 0) {
buflen = snprintf(buf, sizeof(buf), "Content-Length: %lld\015\012", (long long int) bytes);
add_to_response(buf, buflen);
}
if(p3p != (char*) 0 && p3p[0] != '\0') {
buflen = snprintf(buf, sizeof(buf), "P3P: %s\015\012", p3p);
add_to_response(buf, buflen);
}
buflen = snprintf(buf, sizeof(buf), "\015\012");
add_to_response(buf, buflen);
}
static void add_to_buf(char** bufP, size_t* bufsizeP, size_t* buflenP, char* str, size_t len) {
if(*bufsizeP == 0) {
*bufsizeP = len + 500;
*buflenP = 0;
*bufP = (char*) e_malloc(*bufsizeP);
} else if(*buflenP + len >= *bufsizeP) {
*bufsizeP = *buflenP + len + 500;
*bufP = (char*) e_realloc((void*) *bufP, *bufsizeP);
}
(void) memmove(&((*bufP)[*buflenP]), str, len);
*buflenP += len;
(*bufP)[*buflenP] = '\0';
}
static void add_to_request(char* str, size_t len) {
add_to_buf(&request, &request_size, &request_len, str, len);
}
static void add_to_response(char* str, size_t len) {
add_to_buf(&response, &response_size, &response_len, str, len);
}
static void auth_check(char* dirname, int is_ssl) {
char authpath[10000];
char realmName[500];
struct stat sb;
char authinfo[500];
char* authpass;
char* colon;
static char line[10000];
int l;
FILE* fp;
char* cryp;
if(dirname[strlen(dirname) - 1] == '/') {
(void) snprintf(authpath, sizeof(authpath), "%s%s", dirname, AUTH_FILE);
} else {
(void) snprintf(authpath, sizeof(authpath), "%s/%s", dirname, AUTH_FILE);
}
if(stat(authpath, &sb) < 0) {
if(defaultRealmName == NULL || defaultRealmPasswordFile == NULL) {
return;
} else {
snprintf(authpath, sizeof(authpath), "%s", defaultRealmPasswordFile);
}
}
if(strcmp(authpath, defaultRealmPasswordFile) == 0) {
snprintf(realmName, sizeof(realmName), "%s", defaultRealmName);
} else {
snprintf(realmName, sizeof(realmName), "%s", dirname);
}
if(authorization == (char*) 0) {
send_authenticate(realmName, is_ssl);
}
if(strncmp(authorization, "Basic ", 6) != 0) {
send_authenticate(realmName, is_ssl);
}
l = b64_decode(&(authorization[6]), (unsigned char*) authinfo, sizeof(authinfo) - 1);
authinfo[l] = '\0';
authpass = strchr(authinfo, ':');
if(authpass == (char*) 0) {
send_authenticate(realmName, is_ssl);
}
*authpass++ = '\0';
colon = strchr(authpass, ':');
if(colon != (char*) 0) {
*colon = '\0';
}
fp = fopen(authpath, "r");
if(fp == (FILE*) 0) {
syslog(LOG_ERR, "%.80s auth file %.80s could not be opened - %m", ntoa(&client_addr), authpath);
send_error(403, "Forbidden", "", "File is protected.", is_ssl);
}
while(fgets(line, sizeof(line), fp) != (char*) 0) {
l = strlen(line);
if(line[l - 1] == '\n') {
line[l - 1] = '\0';
}
cryp = strchr(line, ':');
if(cryp == (char*) 0) {
continue;
}
*cryp++ = '\0';
if(strcmp(line, authinfo) == 0) {
(void) fclose(fp);
if(strcmp(crypt(authpass, cryp), cryp) == 0) {
remoteuser = line;
return;
} else {
send_authenticate(realmName, is_ssl);
}
}
}
(void) fclose(fp);
send_authenticate(realmName, is_ssl);
}
static int b64_decode(const char* str, unsigned char* space, int size) {
const char* cp;
int space_idx, phase;
int d, prev_d = 0;
unsigned char c;
space_idx = 0;
phase = 0;
for(cp = str; *cp != '\0'; ++cp) {
d = b64_decode_table[(int) *cp];
if(d != -1) {
switch(phase) {
case 0:
++phase;
break;
case 1:
c = ((prev_d << 2) | ((d & 0x30) >> 4));
if(space_idx < size) {
space[space_idx++] = c;
}
++phase;
break;
case 2:
c = (((prev_d & 0xf) << 4) | ((d & 0x3c) >> 2));
if(space_idx < size) {
space[space_idx++] = c;
}
++phase;
break;
case 3:
c = (((prev_d & 0x03) << 6) | d);
if(space_idx < size) {
space[space_idx++] = c;
}
phase = 0;
break;
}
prev_d = d;
}
}
return space_idx;
}
static char* build_env(char* fmt, char* arg) {
char* cp;
int size;
static char* buf;
static int maxbuf = 0;
size = strlen(fmt) + strlen(arg);
if(size > maxbuf) {
if(maxbuf == 0) {
maxbuf = MAX(200, size + 100);
buf = (char*) e_malloc(maxbuf);
} else {
maxbuf = MAX(maxbuf * 2, size * 5 / 4);
buf = (char*) e_realloc((void*) buf, maxbuf);
}
}
(void) snprintf(buf, maxbuf, fmt, arg);
cp = e_strdup(buf);
return cp;
}
static void cgi_interpose_input(int wfd, int is_ssl) {
size_t c;
ssize_t r, r2;
char buf[1024];
c = request_len - request_idx;
if(c > 0) {
if(write(wfd, &(request[request_idx]), c) != c) {
return;
}
}
while(c < content_length) {
r = my_read(buf, MIN( sizeof(buf), content_length - c), is_ssl);
if(r < 0 && (errno == EINTR || errno == EAGAIN)) {
sleep(1);
continue;
}
if(r <= 0) {
return;
}
for(;;) {
r2 = write(wfd, buf, r);
if(r2 < 0 && (errno == EINTR || errno == EAGAIN)) {
sleep(1);
continue;
}
if(r2 != r) {
return;
}
break;
}
c += r;
}
post_post_garbage_hack(is_ssl);
}
static void cgi_interpose_output(int rfd, int parse_headers, int is_ssl) {
ssize_t r, r2;
char buf[1024];
if(!parse_headers) {
char http_head[] = "HTTP/1.0 200 OK\015\012";
(void) my_write(http_head, sizeof(http_head), is_ssl);
} else {
size_t headers_size, headers_len;
char* headers;
char* br;
int status;
char* title;
char* cp;
headers_size = 0;
add_to_buf(&headers, &headers_size, &headers_len, (char*) 0, 0);
for(;;) {
r = read(rfd, buf, sizeof(buf));
if(r < 0 && (errno == EINTR || errno == EAGAIN)) {
sleep(1);
continue;
}
if(r <= 0) {
br = &(headers[headers_len]);
break;
}
add_to_buf(&headers, &headers_size, &headers_len, buf, r);
if((br = strstr(headers, "\015\012\015\012")) != (char*) 0 || (br = strstr(headers, "\012\012")) != (char*) 0) {
break;
}
}
if(headers[0] == '\0') {
return;
}
status = 200;
if((cp = strstr(headers, "Status:")) != (char*) 0 && cp < br && (cp == headers || *(cp-1) == '\012')) {
cp += 7;
cp += strspn(cp, " \t");
status = atoi(cp);
}
if((cp = strstr(headers, "Location:")) != (char*) 0 && cp < br && (cp == headers || *(cp-1) == '\012')) {
status = 302;
}
switch(status) {
case 200:
title = "OK";
break;
case 302:
title = "Found";
break;
case 304:
title = "Not Modified";
break;
case 400:
title = "Bad Request";
break;
case 401:
title = "Unauthorized";
break;
case 403:
title = "Forbidden";
break;
case 404:
title = "Not Found";
break;
case 408:
title = "Request Timeout";
break;
case 500:
title = "Internal Error";
break;
case 501:
title = "Not Implemented";
break;
case 503:
title = "Service Temporarily Overloaded";
break;
default:
title = "Something";
break;
}
(void) snprintf(buf, sizeof(buf), "HTTP/1.0 %d %s\015\012", status, title);
(void) my_write(buf, strlen(buf), is_ssl);
if(strstr(headers, "Server:") == NULL) {
char line[200];
sprintf(line, "Server: %s\015\012", SERVER_SOFTWARE);
(void) my_write(line, strlen(line), is_ssl);
}
if(strstr(headers, "Date:") == NULL) {
char line[200];
char timebuf[100];
time_t now = time((time_t*) 0);
const char* rfc1123_fmt = "%a, %d %b %Y %H:%M:%S GMT";
(void) strftime(timebuf, sizeof(timebuf), rfc1123_fmt, gmtime(&now));
sprintf(line, "Date: %s\015\012", timebuf);
(void) my_write(line, strlen(line), is_ssl);
if(strstr(headers, "Expires:") == NULL) {
sprintf(line, "Expires: %s\015\012", timebuf);
(void) my_write(line, strlen(line), is_ssl);
}
} else if(strstr(headers, "Expires:") == NULL) {
char* line = "Expires: Thu, 01 Jan 1970 00:00:00 GMT\015\012";
(void) my_write(line, strlen(line), is_ssl);
}
(void) my_write(headers, headers_len, is_ssl);
}
for(;;) {
r = read(rfd, buf, sizeof(buf));
if(r < 0 && (errno == EINTR || errno == EAGAIN)) {
sleep(1);
continue;
}
if(r <= 0) {
goto done;
}
for(;;) {
r2 = my_write(buf, r, is_ssl);
if(r2 < 0 && (errno == EINTR || errno == EAGAIN)) {
sleep(1);
continue;
}
if(r2 != r) {
goto done;
}
break;
}
}
done:
shutdown(conn_fd, SHUT_WR);
}
static void check_referer(int is_ssl) {
char* cp;
if(url_pattern == (char*) 0) {
return;
}
if(really_check_referer()) {
return;
}
cp = hostname;
if(cp == (char*) 0) {
cp = "";
}
syslog(LOG_INFO, "%.80s non-local referer \"%.80s%.80s\" \"%.80s\"", ntoa(&client_addr), cp, path, referer);
send_error(403, "Forbidden", "", "You must supply a local referer.", is_ssl);
}
static void clear_ndelay(int fd) {
int flags, newflags;
flags = fcntl(fd, F_GETFL, 0);
if(flags != -1) {
newflags = flags & ~ (int) O_NDELAY;
if(newflags != flags) {
(void) fcntl(fd, F_SETFL, newflags);
}
}
}
static void de_dotdot(char* file) {
char* cp;
char* cp2;
int l;
while((cp = strstr(file, "//")) != (char*) 0) {
for(cp2 = cp + 2; *cp2 == '/'; ++cp2) {
continue;
}
(void) strcpy(cp + 1, cp2);
}
while(strncmp(file, "./", 2) == 0) {
(void) strcpy(file, file + 2);
}
while((cp = strstr( file, "/./")) != (char*) 0) {
(void) strcpy(cp, cp + 2);
}
for(;;) {
while(strncmp(file, "../", 3) == 0) {
(void) strcpy( file, file + 3 );
}
cp = strstr(file, "/../");
if(cp == (char*) 0) {
break;
}
for(cp2 = cp - 1; cp2 >= file && *cp2 != '/'; --cp2) {
continue;
}
(void) strcpy(cp2 + 1, cp + 4);
}
while((l = strlen(file)) > 3 && strcmp((cp = file + l - 3), "/..") == 0) {
for(cp2 = cp - 1; cp2 >= file && *cp2 != '/'; --cp2) {
continue;
}
if(cp2 < file) {
break;
}
*cp2 = '\0';
}
}
static void do_cgi(int is_ssl, unsigned short conn_port) {
char** argp;
char** envp;
int parse_headers;
char* binary;
char* directory;
if(method != METHOD_GET && method != METHOD_POST) {
send_error(501, "Not Implemented", "", "That method is not implemented for CGI.", is_ssl);
}
if(conn_fd == STDIN_FILENO || conn_fd == STDOUT_FILENO || conn_fd == STDERR_FILENO) {
int newfd = dup2(conn_fd, STDERR_FILENO + 1);
if(newfd >= 0) {
conn_fd = newfd;
}
}
envp = make_envp(is_ssl, conn_port);
argp = make_argp();
if((method == METHOD_POST && request_len > request_idx) || is_ssl) {
int p[2];
int r;
if(pipe(p) < 0) {
send_error(500, "Internal Error", "", "Something unexpected went wrong making a pipe.", is_ssl);
}
r = fork();
if(r < 0) {
send_error(500, "Internal Error", "", "Something unexpected went wrong forking an interposer.", is_ssl);
}
if(r == 0) {
(void) close(p[0]);
cgi_interpose_input(p[1], is_ssl);
exit(0);
}
(void) close(p[1]);
if(p[0] != STDIN_FILENO) {
(void) dup2(p[0], STDIN_FILENO);
(void) close(p[0]);
}
} else {
if(conn_fd != STDIN_FILENO) {
(void) dup2(conn_fd, STDIN_FILENO);
}
}
if(strncmp(argp[0], "nph-", 4) == 0) {
parse_headers = 0;
} else {
parse_headers = 1;
}
if(parse_headers || is_ssl) {
int p[2];
int r;
if(pipe(p) < 0) {
send_error(500, "Internal Error", "", "Something unexpected went wrong making a pipe.", is_ssl);
}
r = fork();
if(r < 0) {
send_error(500, "Internal Error", "", "Something unexpected went wrong forking an interposer.", is_ssl);
}
if(r == 0) {
(void) close(p[1]);
cgi_interpose_output(p[0], parse_headers, is_ssl);
exit(0);
}
(void) close(p[0]);
if(p[1] != STDOUT_FILENO) {
(void) dup2(p[1], STDOUT_FILENO);
}
if(p[1] != STDERR_FILENO) {
(void) dup2(p[1], STDERR_FILENO);
}
if(p[1] != STDOUT_FILENO && p[1] != STDERR_FILENO) {
(void) close(p[1]);
}
} else {
if(conn_fd != STDOUT_FILENO) {
(void) dup2(conn_fd, STDOUT_FILENO);
}
if(conn_fd != STDERR_FILENO) {
(void) dup2(conn_fd, STDERR_FILENO);
}
}
if(logfp != (FILE*) 0) {
(void) fclose(logfp);
}
closelog();
if(nice(CGI_NICE) < 0) { ; }
directory = e_strdup(file);
binary = strrchr(directory, '/');
if(binary == (char*) 0) {
binary = file;
} else {
*binary++ = '\0';
if(chdir(directory) < 0) { ; }
}
(void) signal(SIGPIPE, SIG_DFL);
(void) execve(binary, argp, envp);
send_error(500, "Internal Error", "", "Something unexpected went wrong running a CGI program.", is_ssl);
}
static void do_dir(int is_ssl) {
char buf[10000];
size_t buflen;
char* contents;
size_t contents_size, contents_len;
int n, i;
struct dirent **dl;
char* name_info;
if(pathinfo != (char*) 0) {
send_error(404, "Not Found", "", "File not found.", is_ssl);
}
auth_check(file, is_ssl);
check_referer(is_ssl);
n = scandir(file, &dl, NULL, alphasort);
if(n < 0) {
syslog(LOG_INFO, "%.80s Directory \"%.80s\" is protected", ntoa(&client_addr), path);
send_error(403, "Forbidden", "", "Directory is protected.", is_ssl);
}
contents_size = 0;
buflen = snprintf(buf, sizeof(buf), "<html>\n<head><title>Index of %s</title></head>\n<body bgcolor=\"#FFFFFF\">\n<h1>Index of %s</h1><hr><pre>\n", file, file);
add_to_buf(&contents, &contents_size, &contents_len, buf, buflen);
for(i = 0; i < n; ++i) {
if(strcmp(dl[i]->d_name, ".") == 0) {
continue;
}
name_info = file_details(file, dl[i]->d_name);
add_to_buf(&contents, &contents_size, &contents_len, name_info, strlen(name_info));
}
buflen = snprintf(buf, sizeof(buf), "</pre><hr>\n%s\n</body>\n</html>\n", SERVER_SOFTWARE);
add_to_buf(&contents, &contents_size, &contents_len, buf, buflen);
add_headers(200, "Ok", "", "", "text/html; charset=%s", contents_len, sb.st_mtime);
if(method != METHOD_HEAD) {
add_to_response(contents, contents_len);
}
send_response(is_ssl);
}
static void do_file(int is_ssl, unsigned short conn_port) {
char buf[10000];
char mime_encodings[500];
const char* mime_type;
char fixed_mime_type[500];
char* cp;
int fd;
(void) strncpy(buf, file, sizeof(buf));
cp = strrchr(buf, '/');
if(cp == (char*) 0) {
(void) strcpy(buf, ".");
} else {
*cp = '\0';
}
auth_check(buf, is_ssl);
if(strcmp(file, AUTH_FILE) == 0 || (strcmp(&(file[strlen(file) - sizeof(AUTH_FILE) + 1]), AUTH_FILE) == 0 && file[strlen(file) - sizeof(AUTH_FILE)] == '/')) {
syslog(LOG_NOTICE, "%.80s URL \"%.80s\" tried to retrieve an auth file", ntoa(&client_addr), path);
send_error(403, "Forbidden", "", "File is protected.", is_ssl);
}
if(defaultRealmName != NULL && defaultRealmPasswordFile != NULL) {
if(strcmp(file, defaultRealmPasswordFile) == 0 || strcmp(&(file[strlen(file) - sizeof(defaultRealmPasswordFile) + 1]), defaultRealmPasswordFile) == 0 && file[strlen(file) - sizeof(defaultRealmPasswordFile)] == '/') {
syslog(LOG_NOTICE, "%.80s URL \"%.80s\" tried to retrieve an auth file", ntoa(&client_addr), path);
send_error(403, "Forbidden", "", "File is protected.", is_ssl);
}
}
check_referer(is_ssl);
if(cgi_pattern != (char*) 0 && match(cgi_pattern, file)) {
do_cgi(is_ssl, conn_port);
return;
} else if(pathinfo != (char*) 0) {
send_error(404, "Not Found", "", "File not found.", is_ssl);
}
fd = open(file, O_RDONLY);
if(fd < 0) {
syslog(LOG_INFO, "%.80s File \"%.80s\" is protected", ntoa(&client_addr), path);
send_error(403, "Forbidden", "", "File is protected.", is_ssl);
}
mime_type = figure_mime(file, mime_encodings, sizeof(mime_encodings));
(void) snprintf(fixed_mime_type, sizeof(fixed_mime_type), mime_type, charset);
if(if_modified_since != (time_t) -1 && if_modified_since >= sb.st_mtime) {
add_headers(304, "Not Modified", "", mime_encodings, fixed_mime_type, (off_t) -1, sb.st_mtime);
send_response(is_ssl);
return;
}
add_headers(200, "Ok", "", mime_encodings, fixed_mime_type, sb.st_size, sb.st_mtime);
send_response(is_ssl);
if(method == METHOD_HEAD) {
return;
}
if(sb.st_size > 0) {
send_via_write(fd, sb.st_size, is_ssl);
}
(void) close(fd);
}
static void* e_malloc(size_t size) {
void* ptr;
ptr = malloc(size);
if(ptr == (void*) 0) {
syslog(LOG_CRIT, "out of memory");
(void) fprintf(stderr, "%s: out of memory\n", argv0);
exit(1);
}
return ptr;
}
static void* e_realloc(void* optr, size_t size) {
void* ptr;
ptr = realloc(optr, size);
if(ptr == (void*) 0) {
syslog(LOG_CRIT, "out of memory");
(void) fprintf(stderr, "%s: out of memory\n", argv0);
exit(1);
}
return ptr;
}
static char* e_strdup(char* ostr) {
char* str;
str = strdup(ostr);
if(str == (char*) 0) {
syslog(LOG_CRIT, "out of memory copying a string");
(void) fprintf(stderr, "%s: out of memory copying a string\n", argv0);
exit(1);
}
return str;
}
static int ext_compare(struct mime_entry* a, struct mime_entry* b) {
return strcmp(a->ext, b->ext);
}
static const char* figure_mime(char* name, char* me, size_t me_size) {
char* prev_dot;
char* dot;
char* ext;
int me_indexes[100], n_me_indexes;
size_t ext_len, me_len;
int i, top, bot, mid;
int r;
const char* default_type = "application/octet-stream ";
const char* type;
n_me_indexes = 0;
for(prev_dot = &name[strlen(name)]; ; prev_dot = dot) {
for(dot = prev_dot - 1; dot >= name && *dot != '.'; --dot);
if(dot < name) {
type = default_type;
goto done;
}
ext = dot + 1;
ext_len = prev_dot - ext;
for(i = 0; i < n_enc_tab; ++i) {
if(ext_len == enc_tab[i].ext_len && strncasecmp(ext, enc_tab[i].ext, ext_len) == 0) {
if(n_me_indexes < sizeof(me_indexes)/sizeof(*me_indexes)) {
me_indexes[n_me_indexes] = i;
++n_me_indexes;
}
goto next;
}
}
break;
next: ;
}
top = n_typ_tab - 1;
bot = 0;
while(top >= bot) {
mid = (top + bot) / 2;
r = strncasecmp(ext, typ_tab[mid].ext, ext_len);
if(r < 0) {
top = mid - 1;
} else if(r > 0) {
bot = mid + 1;
} else if(ext_len < typ_tab[mid].ext_len) {
top = mid - 1;
} else if(ext_len > typ_tab[mid].ext_len) {
bot = mid + 1;
} else {
type = typ_tab[mid].val;
goto done;
}
}
type = default_type;
done:
me[0] = '\0';
me_len = 0;
for(i = n_me_indexes - 1; i >= 0; --i) {
if(me_len + enc_tab[me_indexes[i]].val_len + 1 < me_size) {
if(me[0] != '\0') {
(void) strcpy(&me[me_len], ",");
++me_len;
}
(void) strcpy(&me[me_len], enc_tab[me_indexes[i]].val);
me_len += enc_tab[me_indexes[i]].val_len;
}
}
return type;
}
static char* file_details(const char* dir, const char* name) {
struct stat sb;
char f_time[20];
static char encname[1000];
static char buf[2000];
(void) snprintf(buf, sizeof(buf), "%s/%s", dir, name);
if(lstat(buf, &sb) < 0)
return "???";
(void) strftime(f_time, sizeof(f_time), "%d-%b-%Y %H:%M", localtime(&sb.st_mtime));
strencode(encname, sizeof(encname), name);
(void) snprintf(buf, sizeof(buf), "<a href=\"%s\">%-50s</a> %15s %14lld\n", encname, name, f_time, (long long int) sb.st_size);
return buf;
}
static char* get_method_str(int m) {
switch(m) {
case METHOD_GET:
return "GET";
case METHOD_HEAD:
return "HEAD";
case METHOD_POST:
return "POST";
default:
return "UNKNOWN";
}
}
static int get_pathinfo(void) {
int r;
pathinfo = &file[strlen(file)];
for(;;) {
do {
--pathinfo;
if(pathinfo <= file) {
pathinfo = (char*) 0;
return -1;
}
} while(*pathinfo != '/');
*pathinfo = '\0';
r = stat(file, &sb);
if(r >= 0) {
++pathinfo;
return r;
} else {
*pathinfo = '/';
}
}
}
static char* get_request_line(void) {
int i;
char c;
for(i = request_idx; request_idx < request_len; ++request_idx) {
c = request[request_idx];
if(c == '\012' || c == '\015') {
request[request_idx] = '\0';
++request_idx;
if(c == '\015' && request_idx < request_len && request[request_idx] == '\012') {
request[request_idx] = '\0';
++request_idx;
}
return &(request[i]);
}
}
return (char*) 0;
}
static void handle_read_timeout(int sig, int is_ssl) {
syslog(LOG_INFO, "%.80s connection timed out reading", ntoa(&client_addr));
send_error(408, "Request Timeout", "", "No request appeared within a reasonable time period.", is_ssl);
}
static void handle_read_timeout_sig(int sig) {
handle_read_timeout(0,0);
}
static void handle_request(int is_ssl, unsigned short conn_port) {
char* method_str;
char* line;
char* cp;
int r, file_len, i;
const char* index_names[] = {"default.html", "default.htm", "index.html", "index.htm", "index.cgi", "index.sh" };
(void) signal(SIGALRM, handle_read_timeout_sig);
(void) alarm(READ_TIMEOUT);
remoteuser = (char*) 0;
method = METHOD_UNKNOWN;
path = (char*) 0;
file = (char*) 0;
pathinfo = (char*) 0;
query = "";
protocol = (char*) 0;
status = 0;
bytes = -1;
authorization = (char*) 0;
content_type = (char*) 0;
content_length = -1;
cookie = (char*) 0;
host = (char*) 0;
if_modified_since = (time_t) -1;
referer = "";
useragent = "";
#ifdef TCP_NOPUSH
r = 1;
(void) setsockopt(conn_fd, IPPROTO_TCP, TCP_NOPUSH, (void*) &r, sizeof(r));
#endif
if(is_ssl) {
ssl = SSL_new(ssl_ctx);
SSL_set_fd(ssl, conn_fd);
int accept_ret = SSL_accept(ssl);
if(accept_ret <= 0) {
int e = SSL_get_error(ssl, accept_ret);
if(e != VERSION_ERROR) {
syslog(LOG_CRIT, "error: can't initialize ssl connection, error = %d\n", e);
}
exit(1);
}
}
start_request();
for(;;) {
char buf[10000];
int r = my_read( buf, sizeof(buf), is_ssl );
if(r < 0 && (errno == EINTR || errno == EAGAIN)) {
continue;
}
if(r <= 0) {
break;
}
(void) alarm(READ_TIMEOUT);
add_to_request(buf, r);
if(strstr(request, "\015\012\015\012") != (char*) 0 || strstr(request, "\012\012") != (char*) 0) {
break;
}
}
method_str = get_request_line();
if(method_str == (char*) 0) {
send_error(400, "Bad Request", "", "Can't parse request.", is_ssl);
}
path = strpbrk(method_str, " \t\012\015");
if(path == (char*) 0) {
send_error(400, "Bad Request", "", "Can't parse request.", is_ssl);
}
*path++ = '\0';
path += strspn(path, " \t\012\015");
protocol = strpbrk(path, " \t\012\015");
if(protocol == (char*) 0) {
send_error(400, "Bad Request", "", "Can't parse request.", is_ssl);
}
*protocol++ = '\0';
protocol += strspn(protocol, " \t\012\015");
query = strchr(path, '?');
if(query == (char*) 0) {
query = "";
} else {
*query++ = '\0';
}
while((line = get_request_line()) != (char*) 0) {
if(line[0] == '\0') {
break;
} else if(strncasecmp(line, "Authorization:", 14) == 0) {
cp = &line[14];
cp += strspn(cp, " \t");
authorization = cp;
} else if(strncasecmp(line, "Content-Length:", 15) == 0) {
cp = &line[15];
cp += strspn(cp, " \t");
content_length = atol(cp);
} else if(strncasecmp(line, "Content-Type:", 13) == 0) {
cp = &line[13];
cp += strspn(cp, " \t");
content_type = cp;
} else if(strncasecmp(line, "Cookie:", 7) == 0) {
cp = &line[7];
cp += strspn(cp, " \t");
cookie = cp;
} else if(strncasecmp(line, "Host:", 5) == 0) {
cp = &line[5];
cp += strspn(cp, " \t");
host = cp;
if(strchr(host, '/') != (char*) 0 || host[0] == '.') {
send_error(400, "Bad Request", "", "Can't parse request.", is_ssl);
}
} else if(strncasecmp(line, "If-Modified-Since:", 18) == 0) {
cp = &line[18];
cp += strspn(cp, " \t");
if_modified_since = dateparse(cp);
} else if(strncasecmp(line, "Referer:", 8) == 0) {
cp = &line[8];
cp += strspn(cp, " \t");
referer = cp;
} else if(strncasecmp(line, "User-Agent:", 11) == 0) {
cp = &line[11];
cp += strspn(cp, " \t");
useragent = cp;
}
}
if(strcasecmp(method_str, get_method_str(METHOD_GET)) == 0) {
method = METHOD_GET;
} else if(strcasecmp(method_str, get_method_str(METHOD_HEAD)) == 0) {
method = METHOD_HEAD;
} else if(strcasecmp(method_str, get_method_str(METHOD_POST)) == 0) {
method = METHOD_POST;
} else {
send_error(501, "Not Implemented", "", "That method is not implemented.", is_ssl);
}
strdecode(path, path);
if(path[0] != '/') {
send_error(400, "Bad Request", "", "Bad filename.", is_ssl);
}
file = &(path[1]);
de_dotdot(file);
if(file[0] == '\0') {
file = "./";
}
if(file[0] == '/' || (file[0] == '.' && file[1] == '.' && (file[2] == '\0' || file[2] == '/'))) {
send_error(400, "Bad Request", "", "Illegal filename.", is_ssl);
}
(void) signal(SIGALRM, handle_write_timeout);
(void) alarm(WRITE_TIMEOUT);
r = stat(file, &sb);
if(r < 0) {
r = get_pathinfo();
}
if(r < 0) {
if(pageNotFoundFile != NULL && host != NULL) {
send_redirect("", host, pageNotFoundFile, is_ssl);
} else {
send_error(404, "Not Found", "", "File not found.", is_ssl);
}
}
file_len = strlen( file );
if(!S_ISDIR(sb.st_mode)) {
while(file[file_len - 1] == '/') {
file[file_len - 1] = '\0';
--file_len;
}
do_file(is_ssl, conn_port);
} else {
char idx[10000];
unsigned char found_index = 0;
if(file[file_len - 1] != '/' && pathinfo == (char*) 0) {
char location[10000];
if(query[0] != '\0') {
(void) snprintf(location, sizeof(location), "Location: %s/?%s", path, query);
} else {
(void) snprintf(location, sizeof(location), "Location: %s/", path);
send_error(302, "Found", location, "Directories must end with a slash.", is_ssl);
}
}
if(defaultPageFile != NULL) {
(void) snprintf(idx, sizeof(idx), "%s%s", file, defaultPageFile);
if(stat( idx, &sb ) >= 0) {
file = idx;
do_file(is_ssl, conn_port);
found_index = 1;
}
}
for(i = 0; i < (sizeof(index_names) / sizeof(char*)) && found_index == 0; ++i) {
(void) snprintf(idx, sizeof(idx), "%s%s", file, index_names[i]);
if(stat(idx, &sb) >= 0) {
file = idx;
do_file(is_ssl, conn_port);
found_index = 1;
}
}
if(found_index == 0) {
if(allowDirectoryListing == 0) {
if(pageNotFoundFile != NULL && host != NULL) {
send_redirect("", host, pageNotFoundFile, is_ssl);
} else {
send_error(404, "Not Found", "", "File not found.", is_ssl);
}
} else {
do_dir(is_ssl);
}
}
}
if(is_ssl) {
SSL_free(ssl);
}
}
static void handle_sigchld(int sig) {
const int oerrno = errno;
pid_t pid;
int status;
(void) signal(SIGCHLD, handle_sigchld);
for(;;) {
pid = waitpid((pid_t) -1, &status, WNOHANG);
if((int) pid == 0) {
break;
}
if((int) pid < 0) {
if(errno == EINTR || errno == EAGAIN) {
continue;
}
if(errno != ECHILD) {
syslog(LOG_ERR, "child wait - %m");
perror("child wait");
}
break;
}
}
errno = oerrno;
}
static void handle_sighup(int sig) {
const int oerrno = errno;
(void) signal(SIGHUP, handle_sighup);
got_hup = 1;
errno = oerrno;
}
static void handle_sigterm(int sig) {
syslog(LOG_NOTICE, "exiting due to signal %d", sig);
(void) fprintf(stderr, "%s: exiting due to signal %d\n", argv0, sig);
closelog();
exit(1);
}
static void handle_write_timeout(int sig) {
syslog(LOG_INFO, "%.80s connection timed out writing", ntoa(&client_addr));
exit(1);
}
static int hexit(char c) {
if(c >= '0' && c <= '9') {
return c - '0';
}
if(c >= 'a' && c <= 'f') {
return c - 'a' + 10;
}
if(c >= 'A' && c <= 'F') {
return c - 'A' + 10;
}
return 0;
}
static void init_mime(void) {
int i;
qsort(enc_tab, n_enc_tab, sizeof(*enc_tab), (int(*)(const void*, const void*)) ext_compare);
qsort(typ_tab, n_typ_tab, sizeof(*typ_tab), (int(*)(const void*, const void*)) ext_compare);
for(i = 0; i < n_enc_tab; ++i) {
enc_tab[i].ext_len = strlen(enc_tab[i].ext);
enc_tab[i].val_len = strlen(enc_tab[i].val);
}
for(i = 0; i < n_typ_tab; ++i) {
typ_tab[i].ext_len = strlen(typ_tab[i].ext);
typ_tab[i].val_len = strlen(typ_tab[i].val);
}
}
static int initialize_listen_socket(usockaddr* usaP) {
int listen_fd;
int i;
if(!sockaddr_check(usaP)) {
syslog(LOG_ERR, "unknown sockaddr family on listen socket - %d", usaP->sa.sa_family);
(void) fprintf(stderr, "%s: unknown sockaddr family on listen socket - %d\n", argv0, usaP->sa.sa_family);
return -1;
}
listen_fd = socket(usaP->sa.sa_family, SOCK_STREAM, 0);
if(listen_fd < 0) {
if(usaP->sa.sa_family == AF_INET6 && (errno == ENOPROTOOPT || errno == EPROTONOSUPPORT || errno == ESOCKTNOSUPPORT || EPFNOSUPPORT || EAFNOSUPPORT)) {
/* IPv6 not compiled into kernel, no big deal, don't print errors */
} else {
syslog(LOG_CRIT, "socket %.80s - %m", ntoa(usaP));
perror("socket");
}
return -1;
}
(void) fcntl(listen_fd, F_SETFD, 1);
i = 1;
if(setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (void*) &i, sizeof(i)) < 0) {
syslog(LOG_CRIT, "setsockopt SO_REUSEADDR - %m");
perror("setsockopt SO_REUSEADDR");
return -1;
}
if(bind(listen_fd, &usaP->sa, sockaddr_len(usaP)) < 0) {
syslog(LOG_CRIT, "bind %.80s - %m", ntoa(usaP));
perror("bind");
return -1;
}
if(listen(listen_fd, 1024) < 0) {
syslog(LOG_CRIT, "listen - %m");
perror("listen");
return -1;
}
return listen_fd;
}
static void lookup_hostname(usockaddr* usa4P, usockaddr* usa4sP, size_t sa4_len, int* gotv4P, int* gotv4sP, usockaddr* usa6P, usockaddr* usa6sP, size_t sa6_len, int* gotv6P, int* gotv6sP) {
int port_index;
unsigned short port_list[4];
port_list[0] = port;
port_list[1] = sslPort;
port_list[2] = 0;
*gotv6P = 0;
*gotv6sP = 0;
*gotv4P = 0;
*gotv4sP = 0;
for(port_index=0; port_list[port_index] != 0; port_index++) {
struct addrinfo hints;
char portstr[10];
int gaierr;
struct addrinfo* ai;
struct addrinfo* ai2;
struct addrinfo* aiv6;
struct addrinfo* aiv4;
(void) memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_flags = AI_PASSIVE;
hints.ai_socktype = SOCK_STREAM;
(void) snprintf(portstr, sizeof(portstr), "%d", (int) port_list[port_index]);
if((gaierr = getaddrinfo(hostname, portstr, &hints, &ai)) != 0) {
syslog(LOG_CRIT, "getaddrinfo %.80s - %s", hostname, gai_strerror(gaierr));
(void) fprintf(stderr, "%s: getaddrinfo %.80s - %s\n", argv0, hostname, gai_strerror(gaierr));
exit(1);
}
aiv6 = (struct addrinfo*) 0;
aiv4 = (struct addrinfo*) 0;
for(ai2 = ai; ai2 != (struct addrinfo*) 0; ai2 = ai2->ai_next) {
switch(ai2->ai_family) {
case AF_INET6:
if(aiv6 == (struct addrinfo*) 0) {
aiv6 = ai2;
}
break;
case AF_INET:
if(aiv4 == (struct addrinfo*) 0) {
aiv4 = ai2;
}
break;
}
}
if(aiv6 != (struct addrinfo*) 0) {
if(sa6_len < aiv6->ai_addrlen) {
syslog(LOG_CRIT, "%.80s - sockaddr too small (%lu < %lu)", hostname, (unsigned long) sa6_len, (unsigned long) aiv6->ai_addrlen);
(void) fprintf(stderr, "%s: %.80s - sockaddr too small (%lu < %lu)\n", argv0, hostname, (unsigned long) sa6_len, (unsigned long) aiv6->ai_addrlen);
exit(1);
}
if(port_index==0) {
*gotv6P = 1;
(void) memset(usa6P, 0, sa6_len);
(void) memmove(usa6P, aiv6->ai_addr, aiv6->ai_addrlen);
} else {
*gotv6sP = 1;
(void) memset(usa6sP, 0, sa6_len);
(void) memmove(usa6sP, aiv6->ai_addr, aiv6->ai_addrlen);
}
}
if(aiv4 != (struct addrinfo*) 0) {
if(sa4_len < aiv4->ai_addrlen) {
syslog(LOG_CRIT, "%.80s - sockaddr too small (%lu < %lu)", hostname, (unsigned long) sa4_len, (unsigned long) aiv4->ai_addrlen);
(void) fprintf(stderr, "%s: %.80s - sockaddr too small (%lu < %lu)\n", argv0, hostname, (unsigned long) sa4_len, (unsigned long) aiv4->ai_addrlen);
exit(1);
}
if(port_index == 0) {
*gotv4P = 1;
(void) memset(usa4P, 0, sa4_len);
(void) memmove(usa4P, aiv4->ai_addr, aiv4->ai_addrlen);
} else {
*gotv4sP = 1;
(void) memset(usa4sP, 0, sa4_len);
(void) memmove(usa4sP, aiv4->ai_addr, aiv4->ai_addrlen);
}
*gotv4P = 1;
}
freeaddrinfo(ai);
}
}
int main(int argc, char** argv) {
int argn;
struct passwd* pwd;
uid_t uid = 32767;
gid_t gid = 32767;
usockaddr host_addr4;
usockaddr host_addr6;
int gotv4, gotv4s, gotv6, gotv6s;
fd_set lfdset;
int maxfd;
usockaddr usa;
int sz, r;
char* cp;
usockaddr host_addr4s;
usockaddr host_addr6s;
argv0 = argv[0];
debug = 0;
port = 0;
dir = (char*) 0;
data_dir = (char*) 0;
cgi_pattern = DEFAULT_CGI_PATTERN;
url_pattern = (char*) 0;
no_empty_referers = 0;
local_pattern = (char*) 0;
charset = DEFAULT_CHARSET;
language = DEFAULT_LANGUAGE;
p3p = (char*) 0;
max_age = -1;
2013-08-13 12:08:39 +02:00
user = NULL;
2013-06-28 18:48:22 +02:00
hostname = (char*) 0;
logfile = (char*) 0;
pidfile = (char*) 0;
logfp = (FILE*) 0;
do_ssl = 0;
certfile = DEFAULT_CERTFILE;
cipher = (char*) 0;
defaultRealmName = NULL;
defaultRealmPasswordFile = NULL;
defaultPageFile = NULL;
pageNotFoundFile = NULL;
allowDirectoryListing = 1;
sslPort = 0;
argn = 1;
while(argn < argc && argv[argn][0] == '-') {
if(strcmp( argv[argn], "-V" ) == 0) {
(void) printf("%s\n", SERVER_SOFTWARE);
exit(0);
} else if(strcmp(argv[argn], "-C") == 0 && argn + 1 < argc) {
++argn;
read_config(argv[argn]);
} else if(strcmp(argv[argn], "-D") == 0) {
debug = 1;
} else if(strcmp(argv[argn], "-S") == 0) {
do_ssl = 1;
} else if(strcmp(argv[argn], "-E") == 0 && argn + 1 < argc) {
++argn;
certfile = argv[argn];
} else if(strcmp(argv[argn], "-Y") == 0 && argn + 1 < argc) {
++argn;
cipher = argv[argn];
} else if(strcmp(argv[argn], "-p") == 0 && argn + 1 < argc) {
++argn;
port = (unsigned short) atoi(argv[argn]);
} else if(strcmp(argv[argn], "-d") == 0 && argn + 1 < argc) {
++argn;
dir = argv[argn];
} else if(strcmp(argv[argn], "-dd") == 0 && argn + 1 < argc) {
++argn;
data_dir = argv[argn];
} else if(strcmp(argv[argn], "-c") == 0 && argn + 1 < argc) {
++argn;
cgi_pattern = argv[argn];
} else if(strcmp(argv[argn], "-u") == 0 && argn + 1 < argc) {
++argn;
user = argv[argn];
} else if(strcmp(argv[argn], "-h") == 0 && argn + 1 < argc) {
++argn;
hostname = argv[argn];
} else if(strcmp(argv[argn], "-l") == 0 && argn + 1 < argc) {
++argn;
logfile = argv[argn];
} else if(strcmp(argv[argn], "-i") == 0 && argn + 1 < argc) {
++argn;
pidfile = argv[argn];
} else if(strcmp(argv[argn], "-T") == 0 && argn + 1 < argc) {
++argn;
charset = argv[argn];
} else if(strcmp(argv[argn], "-L") == 0 && argn + 1 < argc) {
++argn;
language = argv[argn];
} else if(strcmp(argv[argn], "-P") == 0 && argn + 1 < argc) {
++argn;
p3p = argv[argn];
} else if(strcmp(argv[argn], "-M") == 0 && argn + 1 < argc) {
++argn;
max_age = atoi(argv[argn]);
} else if(strcmp(argv[argn], "-DRN") == 0 && argn + 1 < argc) {
++argn;
defaultRealmName = argv[argn];
} else if(strcmp(argv[argn], "-DRP") == 0 && argn + 1 < argc) {
++argn;
defaultRealmPasswordFile = argv[argn];
} else if(strcmp(argv[argn], "-DPF") == 0 && argn + 1 < argc) {
++argn;
defaultPageFile = argv[argn];
} else if(strcmp(argv[argn], "-PNF") == 0 && argn + 1 < argc) {
++argn;
pageNotFoundFile = argv[argn];
} else if(strcmp(argv[argn], "-ADL") == 0 && argn + 1 < argc) {
++argn;
if(strcmp(argv[argn], "1") == 0 || strcmp(argv[argn], "true") == 0 || strcmp(argv[argn], "TRUE") == 0 || strcmp(argv[argn], "yes") == 0 || strcmp(argv[argn], "YES") == 0) {
allowDirectoryListing = 1;
} else {
allowDirectoryListing = 0;
}
} else if(strcmp(argv[argn], "-SP") == 0 && argn + 1 < argc) {
++argn;
sslPort = (unsigned short) atoi(argv[argn]);
} else {
usage();
}
++argn;
}
if(argn != argc) {
usage();
}
cp = strrchr(argv0, '/');
if(cp != (char*) 0) {
++cp;
} else {
cp = argv0;
}
openlog(cp, LOG_NDELAY|LOG_PID, LOG_DAEMON);
if(port == 0) {
if(sslPort != 0 && do_ssl) {
port = sslPort;
sslPort = 0;
}
if(do_ssl) {
port = DEFAULT_HTTPS_PORT;
} else {
port = DEFAULT_HTTP_PORT;
}
}
2013-08-13 12:08:39 +02:00
if(getuid() == 0 && user != NULL) {
2013-06-28 18:48:22 +02:00
pwd = getpwnam(user);
if(pwd == (struct passwd*) 0) {
syslog(LOG_CRIT, "unknown user - '%s'", user);
(void) fprintf(stderr, "%s: unknown user - '%s'\n", argv0, user);
exit(1);
}
uid = pwd->pw_uid;
gid = pwd->pw_gid;
}
if(logfile != (char*) 0) {
logfp = fopen(logfile, "a");
if(logfp == (FILE*) 0) {
syslog(LOG_CRIT, "%s - %m", logfile);
perror(logfile);
exit(1);
}
if(logfile[0] != '/') {
syslog(LOG_WARNING, "logfile is not an absolute path, you may not be able to re-open it");
(void) fprintf(stderr, "%s: logfile is not an absolute path, you may not be able to re-open it\n", argv0);
}
if(getuid() == 0) {
if(fchown(fileno(logfp), uid, gid) < 0) {
syslog(LOG_WARNING, "fchown logfile - %m");
perror("fchown logfile");
}
}
}
lookup_hostname(&host_addr4, &host_addr4s, sizeof(struct sockaddr_in), &gotv4, &gotv4s, &host_addr6, &host_addr6s, sizeof(struct sockaddr_in6), &gotv6, &gotv6s);
if(hostname == (char*) 0) {
(void) gethostname(hostname_buf, sizeof(hostname_buf));
hostname = hostname_buf;
}
if(!( gotv4 || gotv4s || gotv6 || gotv6s)) {
syslog(LOG_CRIT, "can't find any valid address");
(void) fprintf(stderr, "%s: can't find any valid address\n", argv0);
exit(1);
}
listen6_fd = -1;
listen6s_fd = -1;
listen4_fd = -1;
listen4s_fd = -1;
if(gotv6) {
listen6_fd = initialize_listen_socket(&host_addr6);
}
if(gotv6s) {
listen6s_fd = initialize_listen_socket(&host_addr6s);
}
if(gotv4) {
listen4_fd = initialize_listen_socket(&host_addr4);
}
if(gotv4s) {
listen4s_fd = initialize_listen_socket(&host_addr4s);
}
if(listen4_fd == -1 && listen6_fd == -1 && listen4s_fd == -1 && listen6s_fd == -1) {
syslog(LOG_CRIT, "can't bind to any address");
(void) fprintf(stderr, "%s: can't bind to any address\n", argv0);
exit(1);
}
if(do_ssl) {
ssl_ctx = SSL_CTX_new(TLSv1_server_method());
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, 0);
if(certfile[0] != '\0') {
if(SSL_CTX_use_certificate_file(ssl_ctx, certfile, SSL_FILETYPE_PEM) != SSL_SUCCESS || SSL_CTX_use_PrivateKey_file(ssl_ctx, certfile, SSL_FILETYPE_PEM) != SSL_SUCCESS) {
syslog(LOG_CRIT, "can't load certificate and/or private key\n");
exit(1);
}
}
if(cipher != (char*) 0) {
if(SSL_CTX_set_cipher_list(ssl_ctx, cipher) <= 0) {
syslog(LOG_CRIT, "can't load certificate and/or private key\n");
exit(1);
}
}
}
if(!debug) {
if(daemon(1, 0) < 0) {
syslog(LOG_CRIT, "daemon - %m");
perror("daemon");
exit(1);
}
} else {
(void) setsid();
}
if(pidfile != (char*) 0) {
FILE* pidfp = fopen(pidfile, "w");
if(pidfp == (FILE*) 0) {
syslog(LOG_CRIT, "%s - %m", pidfile);
perror(pidfile);
exit(1);
}
(void) fprintf(pidfp, "%d\n", (int) getpid());
(void) fclose(pidfp);
}
tzset();
2013-08-13 12:08:39 +02:00
if(getuid() == 0 && user != NULL) {
2013-06-28 18:48:22 +02:00
if(setgroups(0, (gid_t*) 0) < 0) {
syslog(LOG_CRIT, "setgroups - %m");
perror("setgroups");
exit(1);
}
if(setgid(gid) < 0) {
syslog(LOG_CRIT, "setgid - %m");
perror("setgid");
exit(1);
}
if(initgroups(user, gid ) < 0) {
syslog(LOG_ERR, "initgroups - %m");
perror("initgroups");
}
}
if(dir != (char*) 0) {
if(chdir(dir) < 0) {
syslog(LOG_CRIT, "chdir - %m");
perror("chdir");
exit(1);
}
}
if(getcwd(cwd, sizeof(cwd) - 1) == NULL) { ; }
if(cwd[strlen(cwd) - 1] != '/') {
(void) strcat( cwd, "/" );
}
if(data_dir != (char*) 0) {
if(chdir(data_dir) < 0) {
syslog(LOG_CRIT, "data_dir chdir - %m");
perror("data_dir chdir");
exit(1);
}
}
2013-08-13 12:08:39 +02:00
if(getuid() == 0 && user != NULL) {
2013-06-28 18:48:22 +02:00
if(setuid(uid) < 0) {
syslog(LOG_CRIT, "setuid - %m");
perror("setuid");
exit(1);
}
}
(void) signal(SIGTERM, handle_sigterm);
(void) signal(SIGINT, handle_sigterm);
(void) signal(SIGUSR1, handle_sigterm);
(void) signal(SIGHUP, handle_sighup);
(void) signal(SIGCHLD, handle_sigchld);
(void) signal(SIGPIPE, SIG_IGN);
got_hup = 0;
init_mime();
if(hostname == (char*) 0) {
syslog(LOG_NOTICE, "%.80s starting on port %d", SERVER_SOFTWARE, (int) port);
} else {
syslog(LOG_NOTICE, "%.80s starting on %.80s, port %d", SERVER_SOFTWARE, hostname, (int) port);
}
for(;;) {
int is_ssl;
unsigned short conn_port;
if(got_hup) {
reopen_logfile();
got_hup = 0;
}
FD_ZERO(&lfdset);
maxfd = -1;
if(listen4_fd != -1) {
FD_SET(listen4_fd, &lfdset);
if(listen4_fd > maxfd) {
maxfd = listen4_fd;
}
}
if(listen4s_fd != -1) {
FD_SET(listen4s_fd, &lfdset);
if(listen4s_fd > maxfd) {
maxfd = listen4s_fd;
}
}
if(listen6_fd != -1) {
FD_SET(listen6_fd, &lfdset);
if(listen6_fd > maxfd) {
maxfd = listen6_fd;
}
}
if(listen6s_fd != -1) {
FD_SET(listen6s_fd, &lfdset);
if(listen6s_fd > maxfd) {
maxfd = listen6s_fd;
}
}
if(select(maxfd + 1, &lfdset, (fd_set*) 0, (fd_set*) 0, (struct timeval*) 0) < 0) {
if(errno == EINTR || errno == EAGAIN) {
continue;
}
syslog(LOG_CRIT, "select - %m");
perror("select");
exit(1);
}
is_ssl= 0;
conn_port = port;
if(do_ssl) {
if(sslPort == 0) {
is_ssl= 1;
} else if(listen4s_fd != -1 && FD_ISSET(listen4s_fd, &lfdset)) {
is_ssl = 1;
conn_port = sslPort;
} else if(listen6s_fd != -1 && FD_ISSET(listen6s_fd, &lfdset)) {
is_ssl = 1;
conn_port = sslPort;
}
}
sz = sizeof(usa);
if(listen4_fd != -1 && FD_ISSET(listen4_fd, &lfdset)) {
conn_fd = accept(listen4_fd, &usa.sa, &sz);
} else if(listen4s_fd != -1 && FD_ISSET(listen4s_fd, &lfdset)) {
conn_fd = accept(listen4s_fd, &usa.sa, &sz);
} else if(listen6_fd != -1 && FD_ISSET(listen6_fd, &lfdset)) {
conn_fd = accept(listen6_fd, &usa.sa, &sz);
} else if(listen6s_fd != -1 && FD_ISSET(listen6s_fd, &lfdset)) {
conn_fd = accept(listen6s_fd, &usa.sa, &sz);
} else {
syslog(LOG_CRIT, "select failure");
(void) fprintf(stderr, "%s: select failure\n", argv0);
exit(1);
}
if(conn_fd < 0) {
if(errno == EINTR || errno == EAGAIN) {
continue;
}
#ifdef EPROTO
if(errno == EPROTO) {
continue;
}
#endif
syslog(LOG_CRIT, "accept - %m");
perror("accept");
exit(1);
}
r = fork();
if(r < 0) {
syslog(LOG_CRIT, "fork - %m");
perror("fork");
exit(1);
}
if(r == 0) {
client_addr = usa;
if(listen4_fd != -1) {
(void) close(listen4_fd);
}
if(listen4s_fd != -1) {
(void) close(listen4s_fd);
}
if(listen6_fd != -1) {
(void) close(listen6_fd);
}
if(listen6s_fd != -1) {
(void) close(listen6s_fd);
}
handle_request(is_ssl, conn_port);
exit(0);
}
(void) close(conn_fd);
}
}
static char** make_argp(void) {
char** argp;
int argn;
char* cp1;
char* cp2;
argp = (char**) malloc((strlen(query) + 2) * sizeof(char*));
2013-08-12 19:25:06 +02:00
if(argp == NULL) {
return NULL;
2013-06-28 18:48:22 +02:00
}
argp[0] = strrchr(file, '/');
2013-08-12 19:25:06 +02:00
if(argp[0] != NULL) {
2013-06-28 18:48:22 +02:00
++argp[0];
} else {
argp[0] = file;
}
argn = 1;
2013-08-12 19:25:06 +02:00
if(strchr(query, '=') == NULL) {
2013-06-28 18:48:22 +02:00
for(cp1 = cp2 = query; *cp2 != '\0'; ++cp2) {
if(*cp2 == '+') {
*cp2 = '\0';
strdecode(cp1, cp1);
argp[argn++] = cp1;
cp1 = cp2 + 1;
}
}
if(cp2 != cp1) {
strdecode(cp1, cp1);
argp[argn++] = cp1;
}
}
argp[argn] = (char*) 0;
return argp;
}
static char** make_envp(int is_ssl, unsigned short conn_port) {
static char* envp[50];
int envn;
char* cp;
char buf[256];
envn = 0;
envp[envn++] = build_env("PATH=%s", CGI_PATH);
envp[envn++] = build_env("LD_LIBRARY_PATH=%s", CGI_LD_LIBRARY_PATH);
envp[envn++] = build_env("SERVER_SOFTWARE=%s", SERVER_SOFTWARE);
2013-07-05 23:11:35 +02:00
envp[envn++] = build_env("WWW_LANGUAGE=%s", language);
2013-06-28 18:48:22 +02:00
cp = hostname;
if(cp != (char*) 0) {
envp[envn++] = build_env("SERVER_NAME=%s", cp);
}
envp[envn++] = "GATEWAY_INTERFACE=CGI/1.1";
envp[envn++] = "SERVER_PROTOCOL=HTTP/1.0";
(void) snprintf(buf, sizeof(buf), "%d", (int) conn_port);
envp[envn++] = build_env("SERVER_PORT=%s", buf);
envp[envn++] = build_env("REQUEST_METHOD=%s", get_method_str(method));
envp[envn++] = build_env("SCRIPT_NAME=%s", path);
if(pathinfo != (char*) 0) {
envp[envn++] = build_env("PATH_INFO=/%s", pathinfo);
(void) snprintf(buf, sizeof(buf), "%s%s", cwd, pathinfo);
envp[envn++] = build_env("PATH_TRANSLATED=%s", buf);
}
if(query[0] != '\0') {
envp[envn++] = build_env("QUERY_STRING=%s", query);
}
envp[envn++] = build_env("REMOTE_ADDR=%s", ntoa(&client_addr));
if(referer[0] != '\0') {
envp[envn++] = build_env("HTTP_REFERER=%s", referer);
}
if(useragent[0] != '\0') {
envp[envn++] = build_env("HTTP_USER_AGENT=%s", useragent);
}
if(cookie != (char*) 0) {
envp[envn++] = build_env("HTTP_COOKIE=%s", cookie);
}
if(host != (char*) 0) {
envp[envn++] = build_env("HTTP_HOST=%s", host);
}
if(content_type != (char*) 0) {
envp[envn++] = build_env("CONTENT_TYPE=%s", content_type);
}
if(content_length != -1) {
(void) snprintf(buf, sizeof(buf), "%lu", (unsigned long) content_length);
envp[envn++] = build_env("CONTENT_LENGTH=%s", buf);
}
if(remoteuser != (char*) 0) {
envp[envn++] = build_env("REMOTE_USER=%s", remoteuser);
}
if(authorization != (char*) 0) {
envp[envn++] = build_env("AUTH_TYPE=%s", "Basic");
}
if(getenv("TZ") != (char*) 0) {
envp[envn++] = build_env("TZ=%s", getenv("TZ"));
}
envp[envn] = (char*) 0;
return envp;
}
static void make_log_entry(void) {
char* ru;
char url[500];
char bytes_str[40];
time_t now;
struct tm* t;
const char* cernfmt_nozone = "%d/%b/%Y:%H:%M:%S";
char date_nozone[100];
int zone;
char sign;
char date[100];
if(logfp == (FILE*) 0) {
return;
}
if(protocol == (char*) 0) {
protocol = "UNKNOWN";
}
if(path == (char*) 0) {
path = "";
}
if(remoteuser != (char*) 0) {
ru = remoteuser;
} else {
ru = "-";
}
now = time((time_t*) 0);
(void) snprintf(url, sizeof(url), "%s", path);
if(bytes >= 0) {
(void) snprintf(bytes_str, sizeof(bytes_str), "%lld", (long long int) bytes );
} else {
(void) strcpy(bytes_str, "-");
}
t = localtime(&now);
(void) strftime(date_nozone, sizeof(date_nozone), cernfmt_nozone, t);
zone = t->tm_gmtoff / 60L;
if(zone >= 0) {
sign = '+';
} else {
sign = '-';
zone = -zone;
}
zone = (zone / 60) * 100 + zone % 60;
(void) snprintf(date, sizeof(date), "%s %c%04d", date_nozone, sign, zone);
(void) fprintf(logfp, "%.80s - %.80s [%s] \"%.80s %.200s %.80s\" %d %s \"%.200s\" \"%.200s\"\n", ntoa(&client_addr), ru, date, get_method_str(method), url, protocol, status, bytes_str, referer, useragent);
(void) fflush(logfp);
}
static ssize_t my_read(char* buf, size_t size, int is_ssl) {
if(is_ssl) {
return SSL_read(ssl, buf, size);
} else {
return read(conn_fd, buf, size);
}
}
static ssize_t my_write(char* buf, size_t size, int is_ssl) {
if(is_ssl) {
return SSL_write(ssl, buf, size);
} else {
return write(conn_fd, buf, size);
}
}
static void no_value_required(char* name, char* value) {
if(value != (char*) 0) {
(void) fprintf(stderr, "%s: no value required for %s option\n", argv0, name);
exit(1);
}
}
static char* ntoa(usockaddr* usaP) {
static char str[200];
if(getnameinfo(&usaP->sa, sockaddr_len(usaP), str, sizeof(str), 0, 0, NI_NUMERICHOST) != 0) {
str[0] = '?';
str[1] = '\0';
} else if(IN6_IS_ADDR_V4MAPPED(&usaP->sa_in6.sin6_addr) && strncmp(str, "::ffff:", 7) == 0) {
(void) strcpy(str, &str[7]);
}
return str;
}
static void post_post_garbage_hack(int is_ssl) {
char buf[2];
if(is_ssl) {
return;
}
set_ndelay(conn_fd);
if(read(conn_fd, buf, sizeof(buf)) < 0) { ; };
clear_ndelay(conn_fd);
}
static void read_config(char* filename) {
FILE* fp;
char line[10000];
char* cp;
char* cp2;
char* name;
char* value;
fp = fopen(filename, "r");
if(fp == (FILE*) 0) {
syslog(LOG_CRIT, "%s - %m", filename);
perror(filename);
exit(1);
}
while(fgets(line, sizeof(line), fp) != (char*) 0) {
if((cp = strchr(line, '#')) != (char*) 0) {
*cp = '\0';
}
cp = line;
cp += strspn(cp, " \t\012\015");
while(*cp != '\0') {
cp2 = cp + strcspn(cp, " \t\012\015");
while(*cp2 == ' ' || *cp2 == '\t' || *cp2 == '\012' || *cp2 == '\015') {
*cp2++ = '\0';
}
name = cp;
value = strchr(name, '=');
if(value != (char*) 0) {
*value++ = '\0';
}
if(strcasecmp(name, "debug") == 0) {
no_value_required(name, value);
debug = 1;
} else if(strcasecmp(name, "port") == 0) {
value_required(name, value);
port = (unsigned short) atoi(value);
} else if(strcasecmp(name, "dir") == 0) {
value_required(name, value);
dir = e_strdup(value);
} else if(strcasecmp(name, "data_dir") == 0) {
value_required(name, value);
data_dir = e_strdup(value);
} else if(strcasecmp(name, "user") == 0) {
value_required(name, value);
user = e_strdup(value);
} else if(strcasecmp(name, "cgipat") == 0) {
value_required(name, value);
cgi_pattern = e_strdup(value);
} else if(strcasecmp(name, "urlpat") == 0) {
value_required(name, value);
url_pattern = e_strdup(value);
} else if(strcasecmp(name, "noemptyreferers") == 0) {
value_required(name, value);
no_empty_referers = 1;
} else if(strcasecmp(name, "localpat") == 0) {
value_required(name, value);
local_pattern = e_strdup(value);
} else if(strcasecmp(name, "host") == 0) {
value_required(name, value);
hostname = e_strdup(value);
} else if(strcasecmp(name, "logfile") == 0) {
value_required(name, value);
logfile = e_strdup(value);
} else if(strcasecmp(name, "pidfile") == 0) {
value_required(name, value);
pidfile = e_strdup(value);
} else if(strcasecmp(name, "charset") == 0) {
value_required(name, value);
charset = e_strdup(value);
} else if(strcasecmp(name, "p3p") == 0) {
value_required(name, value);
p3p = e_strdup(value);
} else if(strcasecmp(name, "max_age") == 0) {
value_required(name, value);
max_age = atoi(value);
} else if(strcasecmp(name, "default_realm_name") == 0) {
value_required(name, value);
defaultRealmName = e_strdup(value);
} else if(strcasecmp(name, "default_realm_password_file") == 0) {
value_required(name, value);
defaultRealmPasswordFile = e_strdup(value);
} else if(strcasecmp(name, "default_page_file") == 0) {
value_required(name, value);
defaultPageFile = e_strdup(value);
} else if(strcasecmp(name, "page_not_found_file") == 0) {
value_required(name, value);
pageNotFoundFile = e_strdup(value);
} else if(strcasecmp(name, "allow_directory_listing") == 0) {
value_required(name, value);
if(strcmp(value, "1") == 0 || strcmp(value, "true") == 0 || strcmp(value, "TRUE") == 0 || strcmp(value, "yes") == 0 || strcmp(value, "YES") == 0) {
allowDirectoryListing = 1;
} else {
allowDirectoryListing = 0;
}
} else if(strcasecmp(name, "ssl") == 0) {
no_value_required(name, value);
do_ssl = 1;
} else if(strcasecmp(name, "certfile") == 0) {
value_required(name, value);
certfile = e_strdup(value);
} else if(strcasecmp(name, "cipher") == 0) {
value_required(name, value);
cipher = e_strdup(value);
} else {
(void) fprintf(stderr, "%s: unknown config option '%s'\n", argv0, name);
exit(1);
}
cp = cp2;
cp += strspn(cp, " \t\012\015");
}
}
(void) fclose(fp);
}
static int really_check_referer(void) {
char* cp1;
char* cp2;
char* cp3;
char* refhost;
char *lp;
if(referer == (char*) 0 || referer[0] == '\0' || (cp1 = strstr(referer, "//")) == (char*) 0) {
if(no_empty_referers && match(url_pattern, path)) {
return 0;
} else {
return 1;
}
}
cp1 += 2;
for(cp2 = cp1; *cp2 != '/' && *cp2 != ':' && *cp2 != '\0'; ++cp2) {
continue;
}
refhost = (char*) e_malloc(cp2 - cp1 + 1);
for(cp3 = refhost; cp1 < cp2; ++cp1, ++cp3) {
if(isupper(*cp1)) {
*cp3 = tolower(*cp1);
} else {
*cp3 = *cp1;
}
}
*cp3 = '\0';
if(local_pattern != (char*) 0) {
lp = local_pattern;
} else {
lp = hostname;
if(lp == (char*) 0) {
return 1;
}
}
if(!match(lp, refhost) && match(url_pattern, path)) {
return 0;
} else {
return 1;
}
}
static void reopen_logfile(void) {
if(logfp != (FILE*) 0) {
(void) fclose(logfp);
logfp = (FILE*) 0;
}
if(logfile != (char*) 0) {
syslog(LOG_NOTICE, "re-opening logfile");
logfp = fopen(logfile, "a");
if(logfp == (FILE*) 0) {
syslog(LOG_CRIT, "%s - %m", logfile);
perror(logfile);
exit(1);
}
}
}
static void send_authenticate(char* realm, int is_ssl) {
char header[10000];
(void) snprintf(header, sizeof(header), "WWW-Authenticate: Basic realm=\"%s\"", realm);
send_error(401, "Unauthorized", header, "Authorization required.", is_ssl);
}
static void send_error(int s, char* title, char* extra_header, char* text, int is_ssl) {
add_headers(s, title, extra_header, "", "text/html; charset=%s", (off_t) -1, (time_t) -1);
send_error_body(s, title, text);
send_error_tail();
send_response(is_ssl);
SSL_free(ssl);
exit(1);
}
static void send_error_body(int s, char* title, char* text) {
char filename[1000];
char buf[10000];
int buflen;
(void) snprintf(filename, sizeof(filename), "%s/err%d.html", ERR_DIR, s);
if(send_error_file(filename)) {
return;
}
buflen = snprintf(buf, sizeof(buf), "<html>\n<head><title>Error %d - %s</title></head>\n<body bgcolor=\"#cc9999\" text=\"#000000\">\n<h2>Error %d</h2>\n", s, title, s);
add_to_response(buf, buflen);
buflen = snprintf(buf, sizeof(buf), "%s\n", text);
add_to_response(buf, buflen);
}
static int send_error_file(char* filename) {
FILE* fp;
char buf[1000];
size_t r;
fp = fopen(filename, "r");
if(fp == (FILE*) 0) {
return 0;
}
for(;;) {
r = fread(buf, 1, sizeof(buf), fp);
if(r == 0) {
break;
}
add_to_response(buf, r);
}
(void) fclose(fp);
return 1;
}
static void send_error_tail(void) {
char buf[500];
int buflen;
if(match("**MSIE**", useragent)) {
int n;
buflen = snprintf(buf, sizeof(buf), "<!--\n");
add_to_response(buf, buflen);
for(n = 0; n < 6; ++n) {
buflen = snprintf(buf, sizeof(buf), "Padding so that MSIE deigns to show this error instead of its own canned one.\n");
add_to_response(buf, buflen);
}
buflen = snprintf(buf, sizeof(buf), "-->\n");
add_to_response(buf, buflen);
}
buflen = snprintf(buf, sizeof(buf), "<hr>\n%s\n</body>\n</html>\n", SERVER_SOFTWARE);
add_to_response(buf, buflen);
}
static void send_redirect(char* extra_header, char* hostname, char* new_location, int is_ssl) {
char extra_header_buf[5000];
const char *sep = new_location[0] == '/' ? "" : "/";
const char *proto = is_ssl == 1 ? "https://" : "http://";
extra_header = extra_header == NULL ? "" : extra_header;
if(strcmp(extra_header, "") == 0) {
sprintf(extra_header_buf, "Location: %s%s%s%s", proto, hostname, sep, new_location);
} else {
sprintf(extra_header_buf, "%s\r\nLocation: %s%s%s%s", extra_header, proto, hostname, sep, new_location);
}
add_headers(301, "Moved Permanently", extra_header_buf, "", "text/html; charset=%s", (off_t) -1, (time_t) -1);
send_error_body(301, "Moved Permanently", "Moved Permanently");
send_error_tail();
send_response(is_ssl);
SSL_free(ssl);
exit(1);
}
static void send_response(is_ssl) {
(void) my_write(response, response_len, is_ssl);
}
static void send_via_write(int fd, off_t size, int is_ssl) {
if(size <= SIZE_T_MAX) {
size_t size_size = (size_t) size;
void* ptr = mmap(0, size_size, PROT_READ, MAP_PRIVATE, fd, 0);
if(ptr != (void*) -1) {
(void) my_write(ptr, size_size, is_ssl);
(void) munmap(ptr, size_size);
}
#ifdef MADV_SEQUENTIAL
(void) madvise(ptr, size_size, MADV_SEQUENTIAL);
#endif
} else {
char buf[30000];
ssize_t r, r2;
for(;;) {
r = read(fd, buf, sizeof(buf));
if(r < 0 && (errno == EINTR || errno == EAGAIN)) {
sleep(1);
continue;
}
if(r <= 0) {
return;
}
for(;;) {
r2 = my_write(buf, r, is_ssl);
if(r2 < 0 && (errno == EINTR || errno == EAGAIN)) {
sleep(1);
continue;
}
if(r2 != r) {
return;
}
break;
}
}
}
}
static void set_ndelay(int fd) {
int flags, newflags;
flags = fcntl(fd, F_GETFL, 0);
if(flags != -1) {
newflags = flags | (int) O_NDELAY;
if(newflags != flags) {
(void) fcntl(fd, F_SETFL, newflags);
}
}
}
static int sockaddr_check(usockaddr* usaP) {
switch (usaP->sa.sa_family) {
case AF_INET:
return 1;
case AF_INET6:
return 1;
default:
return 0;
}
}
static size_t sockaddr_len(usockaddr* usaP) {
switch(usaP->sa.sa_family) {
case AF_INET:
return sizeof(struct sockaddr_in);
case AF_INET6:
return sizeof(struct sockaddr_in6);
default:
return 0;
}
}
static void start_request(void) {
request_size = 0;
request_idx = 0;
}
static void start_response(void) {
response_size = 0;
}
static void strdecode(char* to, char* from) {
for(; *from != '\0'; ++to, ++from) {
if(from[0] == '%' && isxdigit(from[1]) && isxdigit(from[2])) {
*to = hexit(from[1]) * 16 + hexit(from[2]);
from += 2;
} else {
*to = *from;
}
}
*to = '\0';
}
static void strencode(char* to, size_t tosize, const char* from) {
int tolen;
for(tolen = 0; *from != '\0' && tolen + 4 < tosize; ++from) {
if(isalnum(*from) || strchr("/_.-~", *from) != (char*) 0) {
*to = *from;
++to;
++tolen;
} else {
(void) sprintf(to, "%%%02x", (int) *from & 0xff);
to += 3;
tolen += 3;
}
}
*to = '\0';
}
static void usage(void) {
(void) fprintf(stderr, "Usage: %s [-C configfile] [-D] [-S use ssl, if no ssl port is specified all connections will be SSL] [-E certfile] [-SP ssl port] [-Y cipher] [-p port] [-d dir] [-dd data_dir] [-c cgipat] [-u user] [-h hostname] [-l logfile] [-i pidfile] [-T charset] [-L language] [-P P3P] [-M maxage] [-DRN default realm name] [-DRP default realm password file] [-DPF default page file] [-PNF Page to load when 404 Not Found error occurs] \n", argv0);
exit(1);
}
static void value_required(char* name, char* value) {
if(value == (char*) 0) {
(void) fprintf(stderr, "%s: value required for %s option\n", argv0, name);
exit(1);
}
}