/* * Copyright (c) Cameron Rich * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the axTLS project nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include "axhttp.h" #define HTTP_VERSION "HTTP/1.1" static const char * index_file = "index.html"; static const char * rfc1123_format = "%a, %d %b %Y %H:%M:%S GMT"; static int special_read(struct connstruct *cn, void *buf, size_t count); static int special_write(struct connstruct *cn, const char *buf, size_t count); static void send_error(struct connstruct *cn, int err); static int hexit(char c); static void urldecode(char *buf); static void buildactualfile(struct connstruct *cn); static int sanitizefile(const char *buf); static int sanitizehost(char *buf); static int htaccess_check(struct connstruct *cn); static const char *getmimetype(const char *name); #if defined(CONFIG_HTTP_DIRECTORIES) static void urlencode(const uint8_t *s, char *t); static void procdirlisting(struct connstruct *cn); #endif #if defined(CONFIG_HTTP_HAS_CGI) static void proccgi(struct connstruct *cn); static void decode_path_info(struct connstruct *cn, char *path_info); static int init_read_post_data(char *buf, char *data, struct connstruct *cn, int old_rv); #endif #ifdef CONFIG_HTTP_HAS_AUTHORIZATION static int auth_check(struct connstruct *cn); #endif #if AXDEBUG #define AXDEBUGSTART \ { \ FILE *axdout; \ axdout = fopen("/var/log/axdebug", "a"); \ #define AXDEBUGEND \ fclose(axdout); \ } #else /* AXDEBUG */ #define AXDEBUGSTART #define AXDEBUGEND #endif /* AXDEBUG */ /* Returns 1 if elems should continue being read, 0 otherwise */ static int procheadelem(struct connstruct *cn, char *buf) { char *delim, *value; if ((delim = strchr(buf, ' ')) == NULL) return 0; *delim = 0; value = delim+1; if (strcmp(buf, "GET") == 0 || strcmp(buf, "HEAD") == 0 || strcmp(buf, "POST") == 0) { if (buf[0] == 'H') cn->reqtype = TYPE_HEAD; else if (buf[0] == 'P') cn->reqtype = TYPE_POST; if ((delim = strchr(value, ' ')) == NULL) /* expect HTTP type */ return 0; *delim++ = 0; urldecode(value); if (sanitizefile(value) == 0) { send_error(cn, 403); return 0; } #if defined(CONFIG_HTTP_HAS_CGI) decode_path_info(cn, value); #else my_strncpy(cn->filereq, value, MAXREQUESTLENGTH); #endif cn->if_modified_since = -1; if (strcmp(delim, "HTTP/1.0") == 0) /* v1.0 HTTP? */ cn->is_v1_0 = 1; } else if (strcasecmp(buf, "Host:") == 0) { if (sanitizehost(value) == 0) { removeconnection(cn); return 0; } my_strncpy(cn->server_name, value, MAXREQUESTLENGTH); } else if (strcasecmp(buf, "Connection:") == 0 && strcmp(value, "close") == 0) { cn->close_when_done = 1; } else if (strcasecmp(buf, "If-Modified-Since:") == 0) { cn->if_modified_since = tdate_parse(value); } else if (strcasecmp(buf, "Expect:") == 0) { /* supposed to be safe to ignore 100-continue */ if (strcasecmp(value, "100-continue") != 0) { send_error(cn, 417); /* expectation failed */ return 0; } } #ifdef CONFIG_HTTP_HAS_AUTHORIZATION else if (strcasecmp(buf, "Authorization:") == 0 && strncmp(value, "Basic ", 6) == 0) { int size = sizeof(cn->authorization); if (base64_decode(&value[6], strlen(&value[6]), (uint8_t *)cn->authorization, &size)) cn->authorization[0] = 0; /* error */ else cn->authorization[size] = 0; } #endif #if defined(CONFIG_HTTP_HAS_CGI) else if (strcasecmp(buf, "Content-Length:") == 0) { sscanf(value, "%d", &cn->content_length); } else if (strcasecmp(buf, "Content-Type:") == 0) { my_strncpy(cn->cgicontenttype, value, MAXREQUESTLENGTH); } else if (strcasecmp(buf, "Cookie:") == 0) { my_strncpy(cn->cookie, value, MAXREQUESTLENGTH); } #endif return 1; } #if defined(CONFIG_HTTP_DIRECTORIES) static void procdirlisting(struct connstruct *cn) { char buf[MAXREQUESTLENGTH]; char actualfile[1024]; if (cn->reqtype == TYPE_HEAD) { snprintf(buf, sizeof(buf), HTTP_VERSION " 200 OK\nContent-Type: text/html\n\n"); if (write(cn->networkdesc, buf, strlen(buf)) < 0) { printf("procdirlisting: could not write"); TTY_FLUSH(); } removeconnection(cn); return; } strcpy(actualfile, cn->actualfile); #ifdef WIN32 strcat(actualfile, "*"); cn->dirp = FindFirstFile(actualfile, &cn->file_data); if (cn->dirp == INVALID_HANDLE_VALUE) { send_error(cn, 404); return; } #else if ((cn->dirp = opendir(actualfile)) == NULL) { send_error(cn, 404); return; } #endif snprintf(buf, sizeof(buf), HTTP_VERSION " 200 OK\nContent-Type: text/html\n\n" "\nDirectory Listing\n" "

Directory listing of %s://%s%s


\n", cn->is_ssl ? "https" : "http", cn->server_name, cn->filereq); special_write(cn, buf, strlen(buf)); cn->state = STATE_DOING_DIR; } void procdodir(struct connstruct *cn) { #ifndef WIN32 struct dirent *dp; #endif char buf[MAXREQUESTLENGTH]; char encbuf[1024]; char *file; do { buf[0] = 0; #ifdef WIN32 if (!FindNextFile(cn->dirp, &cn->file_data)) #else if ((dp = readdir(cn->dirp)) == NULL) #endif { snprintf(buf, sizeof(buf), "\n"); special_write(cn, buf, strlen(buf)); removeconnection(cn); #ifndef WIN32 closedir(cn->dirp); #endif return; } #ifdef WIN32 file = cn->file_data.cFileName; #else file = dp->d_name; #endif /* if no index file, don't display the ".." directory */ if (cn->filereq[0] == '/' && cn->filereq[1] == '\0' && strcmp(file, "..") == 0) continue; /* don't display files beginning with "." */ if (file[0] == '.' && file[1] != '.') continue; /* make sure a '/' is at the end of a directory */ if (cn->filereq[strlen(cn->filereq)-1] != '/') strcat(cn->filereq, "/"); /* see if the dir + file is another directory */ snprintf(buf, sizeof(buf), "%s%s", cn->actualfile, file); if (isdir(buf)) strcat(file, "/"); urlencode((uint8_t *)file, encbuf); snprintf(buf, sizeof(buf), "%s
\n", cn->filereq, encbuf, file); } while (special_write(cn, buf, strlen(buf))); } /* Encode funny chars -> %xx in newly allocated storage */ /* (preserves '/' !) */ static void urlencode(const uint8_t *s, char *t) { const uint8_t *p = s; char *tp = t; for (; *p; p++) { if ((*p > 0x00 && *p < ',') || (*p > '9' && *p < 'A') || (*p > 'Z' && *p < '_') || (*p > '_' && *p < 'a') || (*p > 'z' && *p < 0xA1)) { sprintf((char *)tp, "%%%02X", *p); tp += 3; } else { *tp = *p; tp++; } } *tp='\0'; } #endif void procreadhead(struct connstruct *cn) { char buf[MAXREADLENGTH], *tp, *next; int rv; memset(buf, 0, sizeof(buf)); rv = special_read(cn, buf, sizeof(buf)-1); if (rv <= 0) { if (rv < 0 || !cn->is_ssl) /* really dead? */ removeconnection(cn); return; } buf[rv] = '\0'; next = tp = buf; #ifdef CONFIG_HTTP_HAS_AUTHORIZATION cn->authorization[0] = 0; #endif /* Split up lines and send to procheadelem() */ while (*next != '\0') { /* If we have a blank line, advance to next stage */ if (*next == '\r' || *next == '\n') { #if defined(CONFIG_HTTP_HAS_CGI) if (cn->reqtype == TYPE_POST && cn->content_length > 0) { if (init_read_post_data(buf, next, cn, rv) == 0) return; } #endif buildactualfile(cn); cn->state = STATE_WANT_TO_SEND_HEAD; return; } while (*next != '\r' && *next != '\n' && *next != '\0') next++; if (*next == '\r') { *next = '\0'; next += 2; } else if (*next == '\n') *next++ = '\0'; if (procheadelem(cn, tp) == 0) return; tp = next; } } /* In this function we assume that the file has been checked for * maliciousness (".."s, etc) and has been decoded */ void procsendhead(struct connstruct *cn) { char buf[MAXREQUESTLENGTH]; struct stat stbuf; time_t t_time; struct tm *ptm; char date[32]; char last_modified[32]; char expires[32]; int file_exists; /* are we trying to access a file over the HTTP connection instead of a * HTTPS connection? Or is this directory disabled? */ if (htaccess_check(cn)) { send_error(cn, 403); return; } #ifdef CONFIG_HTTP_HAS_AUTHORIZATION if (auth_check(cn)) /* see if there is a '.htpasswd' file */ { #ifdef CONFIG_HTTP_VERBOSE printf("axhttpd: access to %s denied\n", cn->filereq); TTY_FLUSH(); #endif removeconnection(cn); return; } #endif file_exists = stat(cn->actualfile, &stbuf); #if defined(CONFIG_HTTP_HAS_CGI) if (file_exists != -1 && cn->is_cgi) { proccgi(cn); return; } #endif /* look for "index.html"? */ if (isdir(cn->actualfile)) { char tbuf[MAXREQUESTLENGTH]; snprintf(tbuf, MAXREQUESTLENGTH, "%s%s", cn->actualfile, index_file); if ((file_exists = stat(tbuf, &stbuf)) != -1) my_strncpy(cn->actualfile, tbuf, MAXREQUESTLENGTH); else { #if defined(CONFIG_HTTP_DIRECTORIES) /* If not, we do a directory listing of it */ procdirlisting(cn); #else send_error(cn, 404); #endif return; } } if (file_exists == -1) { send_error(cn, 404); return; } time(&t_time); ptm = gmtime(&t_time); strftime(date, sizeof(date), rfc1123_format, ptm); /* has the file been read before? */ if (cn->if_modified_since != -1) { ptm = gmtime(&stbuf.st_mtime); t_time = mktime(ptm); if (cn->if_modified_since >= t_time) { snprintf(buf, sizeof(buf), HTTP_VERSION" 304 Not Modified\nServer: " "%s\nDate: %s\n\n", server_version, date); special_write(cn, buf, strlen(buf)); cn->state = STATE_WANT_TO_READ_HEAD; return; } } if (cn->reqtype == TYPE_HEAD) { removeconnection(cn); return; } else { int flags = O_RDONLY; #if defined(WIN32) || defined(CONFIG_PLATFORM_CYGWIN) flags |= O_BINARY; #endif cn->filedesc = open(cn->actualfile, flags); if (cn->filedesc < 0) { send_error(cn, 404); return; } ptm = gmtime(&stbuf.st_mtime); strftime(last_modified, sizeof(last_modified), rfc1123_format, ptm); t_time += CONFIG_HTTP_TIMEOUT; ptm = gmtime(&t_time); strftime(expires, sizeof(expires), rfc1123_format, ptm); snprintf(buf, sizeof(buf), HTTP_VERSION" 200 OK\nServer: %s\n" "Content-Type: %s\nContent-Length: %ld\n" "Date: %s\nLast-Modified: %s\nExpires: %s\n\n", server_version, getmimetype(cn->actualfile), (long) stbuf.st_size, date, last_modified, expires); special_write(cn, buf, strlen(buf)); #ifdef CONFIG_HTTP_VERBOSE printf("axhttpd: %s:/%s\n", cn->is_ssl ? "https" : "http", cn->filereq); TTY_FLUSH(); #endif #ifdef WIN32 for (;;) { procreadfile(cn); if (cn->filedesc == -1) break; do { procsendfile(cn); } while (cn->state != STATE_WANT_TO_READ_FILE); } #else cn->state = STATE_WANT_TO_READ_FILE; #endif } } void procreadfile(struct connstruct *cn) { int rv = read(cn->filedesc, cn->databuf, BLOCKSIZE); if (rv <= 0) { close(cn->filedesc); cn->filedesc = -1; if (cn->close_when_done) /* close immediately */ removeconnection(cn); else { if (cn->is_v1_0) /* die now */ removeconnection(cn); else /* keep socket open - HTTP 1.1 */ { cn->state = STATE_WANT_TO_READ_HEAD; cn->numbytes = 0; } } return; } cn->numbytes = rv; cn->state = STATE_WANT_TO_SEND_FILE; } void procsendfile(struct connstruct *cn) { int rv = special_write(cn, cn->databuf, cn->numbytes); if (rv < 0) removeconnection(cn); else if (rv == cn->numbytes) { cn->state = STATE_WANT_TO_READ_FILE; } else if (rv == 0) { /* Do nothing */ } else { memmove(cn->databuf, cn->databuf + rv, cn->numbytes - rv); cn->numbytes -= rv; } } #if defined(CONFIG_HTTP_HAS_CGI) /* Should this be a bit more dynamic? It would mean more calls to malloc etc */ #define CGI_ARG_SIZE 17 static void proccgi(struct connstruct *cn) { int tpipe[2], spipe[2]; char *myargs[3]; char cgienv[CGI_ARG_SIZE][MAXREQUESTLENGTH]; char * cgiptr[CGI_ARG_SIZE+4]; const char *type = "HEAD"; int cgi_index = 0, i; pid_t pid; #ifdef WIN32 int tmp_stdout; #endif snprintf(cgienv[0], MAXREQUESTLENGTH, HTTP_VERSION" 200 OK\nServer: %s\n%s", server_version, (cn->reqtype == TYPE_HEAD) ? "\n" : ""); special_write(cn, cgienv[0], strlen(cgienv[0])); if (cn->reqtype == TYPE_HEAD) { removeconnection(cn); return; } #ifdef CONFIG_HTTP_VERBOSE printf("[CGI]: %s:/%s\n", cn->is_ssl ? "https" : "http", cn->filereq); TTY_FLUSH(); #endif /* win32 cgi is a bit too painful */ #ifndef WIN32 /* set up pipe that is used for sending POST query data to CGI script*/ if (cn->reqtype == TYPE_POST) { if (pipe(spipe) == -1) { printf("[CGI]: could not create pipe"); TTY_FLUSH(); return; } } if (pipe(tpipe) == -1) { printf("[CGI]: could not create pipe"); TTY_FLUSH(); return; } /* * use vfork() instead of fork() for performance */ if ((pid = vfork()) > 0) /* parent */ { /* Send POST query data to CGI script */ if ((cn->reqtype == TYPE_POST) && (cn->content_length > 0)) { if (write(spipe[1], cn->post_data, cn->content_length) == -1) { printf("[CGI]: could write to pipe"); TTY_FLUSH(); return; } close(spipe[0]); close(spipe[1]); /* free the memory that is allocated in read_post_data() */ free(cn->post_data); cn->post_data = NULL; } /* Close the write descriptor */ close(tpipe[1]); cn->filedesc = tpipe[0]; cn->state = STATE_WANT_TO_READ_FILE; cn->close_when_done = 1; return; } if (pid < 0) /* vfork failed */ exit(1); /* The problem child... */ /* Our stdout/stderr goes to the socket */ dup2(tpipe[1], 1); dup2(tpipe[1], 2); close(tpipe[0]); close(tpipe[1]); /* If it was a POST request, send the socket data to our stdin */ if (cn->reqtype == TYPE_POST) { dup2(spipe[0], 0); close(spipe[0]); close(spipe[1]); } else /* Otherwise we can shutdown the read side of the sock */ shutdown(cn->networkdesc, 0); myargs[0] = CONFIG_HTTP_CGI_LAUNCHER; myargs[1] = cn->actualfile; myargs[2] = NULL; /* * set the cgi args. A url is defined by: * http://$SERVER_NAME:$SERVER_PORT$SCRIPT_NAME$PATH_INFO?$QUERY_STRING * TODO: other CGI parameters? */ sprintf(cgienv[cgi_index++], "SERVER_SOFTWARE=%s", server_version); strcpy(cgienv[cgi_index++], "DOCUMENT_ROOT=" CONFIG_HTTP_WEBROOT); snprintf(cgienv[cgi_index++], MAXREQUESTLENGTH, "SERVER_NAME=%s", cn->server_name); sprintf(cgienv[cgi_index++], "SERVER_PORT=%d", cn->is_ssl ? CONFIG_HTTP_HTTPS_PORT : CONFIG_HTTP_PORT); snprintf(cgienv[cgi_index++], MAXREQUESTLENGTH, "REQUEST_URI=%s", cn->uri_request); snprintf(cgienv[cgi_index++], MAXREQUESTLENGTH, "SCRIPT_NAME=%s", cn->filereq); snprintf(cgienv[cgi_index++], MAXREQUESTLENGTH, "PATH_INFO=%s", cn->uri_path_info); snprintf(cgienv[cgi_index++], MAXREQUESTLENGTH, "QUERY_STRING=%s", cn->uri_query); snprintf(cgienv[cgi_index++], MAXREQUESTLENGTH, "REMOTE_ADDR=%s", cn->remote_addr); snprintf(cgienv[cgi_index++], MAXREQUESTLENGTH, "HTTP_COOKIE=%s", cn->cookie); /* note: small size */ #if defined(CONFIG_HTTP_HAS_AUTHORIZATION) snprintf(cgienv[cgi_index++], MAXREQUESTLENGTH, "REMOTE_USER=%s", cn->authorization); #endif switch (cn->reqtype) { case TYPE_GET: type = "GET"; break; #if defined(CONFIG_HTTP_HAS_CGI) case TYPE_POST: type = "POST"; sprintf(cgienv[cgi_index++], "CONTENT_LENGTH=%d", cn->content_length); snprintf(cgienv[cgi_index++], MAXREQUESTLENGTH, "CONTENT_TYPE=%s", cn->cgicontenttype); break; #endif } sprintf(cgienv[cgi_index++], "REQUEST_METHOD=%s", type); if (cn->is_ssl) strcpy(cgienv[cgi_index++], "HTTPS=on"); if (cgi_index >= CGI_ARG_SIZE) { printf("Content-type: text/plain\n\nToo many CGI args (%d, %d)\n", cgi_index, CGI_ARG_SIZE); _exit(1); } /* copy across the pointer indexes */ for (i = 0; i < cgi_index; i++) cgiptr[i] = cgienv[i]; cgiptr[i++] = "AUTH_TYPE=Basic"; cgiptr[i++] = "GATEWAY_INTERFACE=CGI/1.1"; cgiptr[i++] = "SERVER_PROTOCOL="HTTP_VERSION; cgiptr[i] = NULL; execve(myargs[0], myargs, cgiptr); printf("Content-type: text/plain\n\nshouldn't get here\n"); _exit(1); #endif } static char * cgi_filetype_match(struct connstruct *cn, const char *fn) { struct cgiextstruct *tp = cgiexts; while (tp != NULL) { char *t; if ((t = strstr(fn, tp->ext)) != NULL) { t += strlen(tp->ext); if (*t == '/' || *t == '\0') return t; else return NULL; } tp = tp->next; } return NULL; } static void decode_path_info(struct connstruct *cn, char *path_info) { char *cgi_delim; #if defined(CONFIG_HTTP_HAS_CGI) cn->is_cgi = 0; #endif *cn->uri_request = '\0'; *cn->uri_path_info = '\0'; *cn->uri_query = '\0'; my_strncpy(cn->uri_request, path_info, MAXREQUESTLENGTH); /* query info? */ if ((cgi_delim = strchr(path_info, '?'))) { *cgi_delim = '\0'; my_strncpy(cn->uri_query, cgi_delim+1, MAXREQUESTLENGTH); } #if defined(CONFIG_HTTP_HAS_CGI) if ((cgi_delim = cgi_filetype_match(cn, path_info)) != NULL) { cn->is_cgi = 1; /* definitely a CGI script */ /* path info? */ if (*cgi_delim != '\0') { my_strncpy(cn->uri_path_info, cgi_delim, MAXREQUESTLENGTH); *cgi_delim = '\0'; } } #endif /* the bit at the start must be the script name */ my_strncpy(cn->filereq, path_info, MAXREQUESTLENGTH); } static int init_read_post_data(char *buf, char *data, struct connstruct *cn, int old_rv) { char *next = data; int rv = old_rv; char *post_data; /* Too much Post data to send. MAXPOSTDATASIZE should be configured (now it can be changed in the header file) */ if (cn->content_length > MAXPOSTDATASIZE) { send_error(cn, 418); return 0; } /* remove CRLF */ while ((*next == '\r' || *next == '\n') && (next < &buf[rv])) next++; if (cn->post_data == NULL) { /* Allocate buffer for the POST data that will be used by proccgi to send POST data to the CGI script */ cn->post_data = (char *)calloc(1, (cn->content_length + 1)); } cn->post_state = 0; cn->post_read = 0; post_data = cn->post_data; while (next < &buf[rv]) { /* copy POST data to buffer */ *post_data++ = *next++; cn->post_read++; if (cn->post_read == cn->content_length) { /* No more POST data to be copied */ *post_data = '\0'; return 1; } } /* More POST data has to be read. read_post_data will continue with that */ cn->post_state = 1; return 0; } void read_post_data(struct connstruct *cn) { char buf[MAXREADLENGTH], *next; char *post_data; int rv; memset(buf, 0, sizeof(buf)); rv = special_read(cn, buf, sizeof(buf)-1); if (rv <= 0) { if (rv < 0 || !cn->is_ssl) /* really dead? */ removeconnection(cn); return; } buf[rv] = '\0'; next = buf; post_data = &cn->post_data[cn->post_read]; while (next < &buf[rv]) { *post_data++ = *next++; cn->post_read++; if (cn->post_read == cn->content_length) { /* No more POST data to be copied */ *post_data='\0'; cn->post_state = 0; buildactualfile(cn); cn->state = STATE_WANT_TO_SEND_HEAD; return; } } /* More POST data to read */ } #endif /* CONFIG_HTTP_HAS_CGI */ /* Decode string %xx -> char (in place) */ static void urldecode(char *buf) { int v; char *p, *s, *w; w = p = buf; while (*p) { v = 0; if (*p == '%') { s = p; s++; if (isxdigit((int) s[0]) && isxdigit((int) s[1])) { v = hexit(s[0])*16 + hexit(s[1]); if (v) { /* do not decode %00 to null char */ *w = (char)v; p = &s[1]; } } } if (!v) *w=*p; p++; w++; } *w='\0'; } static int hexit(char c) { if (c >= '0' && c <= '9') return c - '0'; else if (c >= 'a' && c <= 'f') return c - 'a' + 10; else if (c >= 'A' && c <= 'F') return c - 'A' + 10; else return 0; } static void buildactualfile(struct connstruct *cn) { char *cp; snprintf(cn->actualfile, MAXREQUESTLENGTH, ".%s", cn->filereq); #ifndef WIN32 /* Add directory slash if not there */ if (isdir(cn->actualfile) && cn->actualfile[strlen(cn->actualfile)-1] != '/') strcat(cn->actualfile, "/"); /* work out the directory name */ strncpy(cn->dirname, cn->actualfile, MAXREQUESTLENGTH); if ((cp = strrchr(cn->dirname, '/')) == NULL) cn->dirname[0] = 0; else *cp = 0; #else { char curr_dir[MAXREQUESTLENGTH]; char path[MAXREQUESTLENGTH]; char *t = cn->actualfile; GetCurrentDirectory(MAXREQUESTLENGTH, curr_dir); /* convert all the forward slashes to back slashes */ while ((t = strchr(t, '/'))) *t++ = '\\'; snprintf(path, MAXREQUESTLENGTH, "%s%s", curr_dir, cn->actualfile); memcpy(cn->actualfile, path, MAXREQUESTLENGTH); /* Add directory slash if not there */ if (isdir(cn->actualfile) && cn->actualfile[strlen(cn->actualfile)-1] != '\\') strcat(cn->actualfile, "\\"); /* work out the directory name */ strncpy(cn->dirname, cn->actualfile, MAXREQUESTLENGTH); if ((cp = strrchr(cn->dirname, '\\')) == NULL) cn->dirname[0] = 0; else *cp = 0; } #endif } static int sanitizefile(const char *buf) { int len, i; /* Don't accept anything not starting with a / */ if (*buf != '/') return 0; len = strlen(buf); for (i = 0; i < len; i++) { /* Check for "/." i.e. don't send files starting with a . */ if (buf[i] == '/' && buf[i+1] == '.') return 0; } return 1; } static int sanitizehost(char *buf) { while (*buf != '\0') { /* Handle the port */ if (*buf == ':') { *buf = '\0'; return 1; } /* Enforce some basic URL rules... */ if ((isalnum((int)(*buf)) == 0 && *buf != '-' && *buf != '.') || (*buf == '.' && *(buf+1) == '.') || (*buf == '.' && *(buf+1) == '-') || (*buf == '-' && *(buf+1) == '.')) return 0; buf++; } return 1; } static FILE * exist_check(struct connstruct *cn, const char *check_file) { char pathname[MAXREQUESTLENGTH]; snprintf(pathname, MAXREQUESTLENGTH, "%s/%s", cn->dirname, check_file); return fopen(pathname, "r"); } #ifdef CONFIG_HTTP_HAS_AUTHORIZATION static void send_authenticate(struct connstruct *cn, const char *realm) { char buf[1024]; snprintf(buf, sizeof(buf), HTTP_VERSION" 401 Unauthorized\n" "WWW-Authenticate: Basic\n" "realm=\"%s\"\n", realm); special_write(cn, buf, strlen(buf)); } static int check_digest(char *salt, const char *msg_passwd) { uint8_t b256_salt[MAXREQUESTLENGTH]; uint8_t real_passwd[MD5_SIZE]; int salt_size = sizeof(b256_salt); int password_size = sizeof(real_passwd); char *b64_passwd; uint8_t md5_result[MD5_SIZE]; MD5_CTX ctx; /* retrieve the salt */ if ((b64_passwd = strchr(salt, '$')) == NULL) return -1; *b64_passwd++ = 0; if (base64_decode(salt, strlen(salt), b256_salt, &salt_size)) return -1; if (base64_decode(b64_passwd, strlen(b64_passwd), real_passwd, &password_size)) return -1; /* very simple MD5 crypt algorithm, but then the salt we use is large */ MD5_Init(&ctx); MD5_Update(&ctx, b256_salt, salt_size); /* process the salt */ MD5_Update(&ctx, (uint8_t *)msg_passwd, strlen(msg_passwd)); MD5_Final(md5_result, &ctx); return memcmp(md5_result, real_passwd, MD5_SIZE);/* 0 = ok */ } static int auth_check(struct connstruct *cn) { char line[MAXREQUESTLENGTH]; FILE *fp; char *cp; if ((fp = exist_check(cn, ".htpasswd")) == NULL) return 0; /* no .htpasswd file, so let though */ if (cn->authorization[0] == 0) goto error; /* cn->authorization is in form "username:password" */ if ((cp = strchr(cn->authorization, ':')) == NULL) goto error; else *cp++ = 0; /* cp becomes the password */ while (fgets(line, sizeof(line), fp) != NULL) { char *b64_file_passwd; int l = strlen(line); /* nuke newline */ if (line[l-1] == '\n') line[l-1] = 0; /* line is form "username:salt(b64)$password(b64)" */ if ((b64_file_passwd = strchr(line, ':')) == NULL) continue; *b64_file_passwd++ = 0; if (strcmp(line, cn->authorization)) /* our user? */ continue; if (check_digest(b64_file_passwd, cp) == 0) { fclose(fp); return 0; } } error: fclose(fp); send_authenticate(cn, cn->server_name); return -1; } #endif static int htaccess_check(struct connstruct *cn) { char line[MAXREQUESTLENGTH]; FILE *fp; int ret = 0; if ((fp = exist_check(cn, ".htaccess")) == NULL) return 0; /* no .htaccess file, so let though */ while (fgets(line, sizeof(line), fp) != NULL) { if (strstr(line, "Deny all") || /* access to this dir denied */ /* Access will be denied unless SSL is active */ (!cn->is_ssl && strstr(line, "SSLRequireSSL")) || /* Access will be denied if SSL is active */ (cn->is_ssl && strstr(line, "SSLDenySSL"))) { ret = -1; break; } } fclose(fp); return ret; } static void send_error(struct connstruct *cn, int err) { char buf[MAXREQUESTLENGTH]; char *title; char *text; switch (err) { case 403: title = "Forbidden"; text = "File is protected"; #ifdef CONFIG_HTTP_VERBOSE printf("axhttpd: access to %s denied\n", cn->filereq); TTY_FLUSH(); #endif break; case 404: title = "Not Found"; text = title; break; case 418: title = "POST data size is too large"; text = title; break; default: title = "Unknown"; text = "Unknown"; break; } snprintf(buf, sizeof(buf), HTTP_VERSION" 200 OK\n" "Content-Type: text/html\n\n" "\n%s\n" "

Error %d - %s

\n\n", title, err, text); special_write(cn, buf, strlen(buf)); #ifdef CONFIG_HTTP_VERBOSE printf("axhttpd: http error: %s [%d]\n", title, err); TTY_FLUSH(); #endif removeconnection(cn); } static const char *getmimetype(const char *name) { /* only bother with a few mime types - let the browser figure the rest out */ if (strstr(name, ".htm")) return "text/html"; else if (strstr(name, ".css")) return "text/css"; else if (strstr(name, ".php")) return "application/x-http-php"; else return "application/octet-stream"; } static int special_write(struct connstruct *cn, const char *buf, size_t count) { if (cn->is_ssl) { SSL *ssl = cn->ssl; return ssl ? ssl_write(ssl, (uint8_t *)buf, count) : -1; } else return SOCKET_WRITE(cn->networkdesc, buf, count); } static int special_read(struct connstruct *cn, void *buf, size_t count) { int res; if (cn->is_ssl) { uint8_t *read_buf; if ((res = ssl_read(cn->ssl, &read_buf)) > SSL_OK) { memcpy(buf, read_buf, res > (int)count ? count : res); } } else res = SOCKET_READ(cn->networkdesc, buf, count); return res; }