diff options
Diffstat (limited to 'apps/netutils/thttpd/cgi-src/ssi.c')
-rw-r--r-- | apps/netutils/thttpd/cgi-src/ssi.c | 957 |
1 files changed, 957 insertions, 0 deletions
diff --git a/apps/netutils/thttpd/cgi-src/ssi.c b/apps/netutils/thttpd/cgi-src/ssi.c new file mode 100644 index 000000000..4afb141f9 --- /dev/null +++ b/apps/netutils/thttpd/cgi-src/ssi.c @@ -0,0 +1,957 @@ +/**************************************************************************** + * netutils/thttpd/cgi-src/ssi.c + * Server-side-includes CGI program + * + * Copyright (C) 2009 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Derived from the file of the same name in the original THTTPD package: + * + * Copyright © 1995 by Jef Poskanzer <jef@mail.acme.com>. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include <nuttx/regex.h> + +#include "config.h" + +/**************************************************************************** + * Pre-Processor Definitions + ****************************************************************************/ + +#define ST_GROUND 0 +#define ST_LESSTHAN 1 +#define ST_BANG 2 +#define ST_MINUS1 3 +#define ST_MINUS2 4 + +#define SF_BYTES 0 +#define SF_ABBREV 1 + +#define DI_CONFIG 0 +#define DI_INCLUDE 1 +#define DI_ECHO 2 +#define DI_FSIZE 3 +#define DI_FLASTMOD 4 + +#define BUFFER_SIZE 512 +#define TIMEFMT_SIZE 80 +#define MAX_TAGS 32 + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static void read_file(FILE *instream, char *vfilename, char *filename); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static char *g_url; +static char g_timeformat[TIMEFMT_SIZE]; +static char g_iobuffer1[BUFFER_SIZE]; +static char g_iobuffer2[BUFFER_SIZE]; +static char *g_tags[MAX_TAGS]; +static int g_sizefmt; +static struct stat g_sb; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void internal_error(char *reason) +{ + char *title = "500 Internal Error"; + + (void)printf("\ +<HTML><HEAD><TITLE>%s</TITLE></HEAD>\n\ +<BODY><H2>%s</H2>\n\ +Something unusual went wrong during a server-side-includes request:\n\ +<BLOCKQUOTE>\n\ +%s\n\ +</BLOCKQUOTE>\n\ +</BODY></HTML>\n", title, title, reason); +} + +static void not_found(char *filename) +{ + char *title = "404 Not Found"; + + (void)printf("\ +<HTML><HEAD><TITLE>%s</TITLE></HEAD>\n\ +<BODY><H2>%s</H2>\n\ +The requested server-side-includes filename, %s,\n\ +does not seem to exist.\n\ +</BODY></HTML>\n", title, title, filename); +} + +static void not_found2(char *directive, char *tag, char *filename) +{ + char *title = "Not Found"; + + (void)printf("\ +<HR><H2>%s</H2>\n\ +The filename requested in a %s %s directive, %s,\n\ +does not seem to exist.\n\ +<HR>\n", title, directive, tag, filename); +} + +static void not_permitted(char *directive, char *tag, char *val) +{ + char *title = "Not Permitted"; + + (void)printf("\ +<HR><H2>%s</H2>\n\ +The filename requested in the %s %s=%s directive\n\ +may not be fetched.\n\ +<HR>\n", title, directive, tag, val); +} + +static void unknown_directive(char *filename, char *directive) +{ + char *title = "Unknown Directive"; + + (void)printf("\ +<HR><H2>%s</H2>\n\ +The requested server-side-includes filename, %s,\n\ +tried to use an unknown directive, %s.\n\ +<HR>\n", title, filename, directive); +} + +static void unknown_tag(char *filename, char *directive, char *tag) +{ + char *title = "Unknown Tag"; + + (void)printf("\ +<HR><H2>%s</H2>\n\ +The requested server-side-includes filename, %s,\n\ +tried to use the directive %s with an unknown tag, %s.\n\ +<HR>\n", title, filename, directive, tag); +} + +static void unknown_value(char *filename, char *directive, char *tag, char *val) +{ + char *title = "Unknown Value"; + + (void)printf("\ +<HR><H2>%s</H2>\n\ +The requested server-side-includes filename, %s,\n\ +tried to use the directive %s %s with an unknown value, %s.\n\ +<HR>\n", title, filename, directive, tag, val); +} + +static int get_filename(char *vfilename, char *filename, + char *directive, char *tag, char *val, char *fn, + int fnsize) +{ + char *cp; + int vl; + int fl; + + /* Used for the various commands that accept a file name. These commands + * accept two tags: virtual Gives a virtual path to a document on the + * server. file Gives a pathname relative to the current directory. ../ + * cannot be used in this pathname, nor can absolute paths be used. + */ + + vl = strlen(vfilename); + fl = strlen(filename); + + if (strcmp(tag, "virtual") == 0) + { + if (strstr(val, "../") != (char *)0) + { + not_permitted(directive, tag, val); + return -1; + } + + /* Figure out root using difference between vfilename and filename. */ + + if (vl > fl || strcmp(vfilename, &filename[fl - vl]) != 0) + { + return -1; + } + + if (fl - vl + strlen(val) >= fnsize) + { + return -1; + } + + (void)strncpy(fn, filename, fl - vl); + (void)strcpy(&fn[fl - vl], val); + } + else if (strcmp(tag, "file") == 0) + { + if (val[0] == '/' || strstr(val, "../") != (char *)0) + { + not_permitted(directive, tag, val); + return -1; + } + if (fl + 1 + strlen(val) >= fnsize) + { + return -1; + } + + (void)strcpy(fn, filename); + cp = strrchr(fn, '/'); + if (cp == (char *)0) + { + cp = &fn[strlen(fn)]; + *cp = '/'; + } + (void)strcpy(++cp, val); + } + else + { + unknown_tag(filename, directive, tag); + return -1; + } + return 0; +} + +static int check_filename(char *filename) +{ + static int inited = 0; + static char *cgi_pattern; +#ifdef CONFIG_AUTH_FILE + struct stat sb; + char *dirname; + char *authname; + char *cp; + int fnl; + int r; +#endif + + if (!inited) + { + /* Get the cgi pattern. */ + + cgi_pattern = getenv("CGI_PATTERN"); +#ifdef CGI_PATTERN + if (cgi_pattern == (char *)0) + { + cgi_pattern = CGI_PATTERN; + } +#endif /* CGI_PATTERN */ + inited = 1; + } + + /* ../ is not permitted. */ + + if (strstr(filename, "../") !=NULL) + { + return 0; + } + + /* Ensure that we are not reading a basic auth password file. */ + +#ifdef CONFIG_AUTH_FILE + fnl = strlen(filename); + if (strcmp(filename, CONFIG_AUTH_FILE) == 0 || + (fnl >= sizeof(CONFIG_AUTH_FILE) && + strcmp(&filename[fnl - sizeof(CONFIG_AUTH_FILE) + 1], CONFIG_AUTH_FILE) == 0 && + filename[fnl - sizeof(CONFIG_AUTH_FILE)] == '/')) + { + return 0; + } + + /* Check for an auth file in the same directory. We can't do an actual ** + * auth password check here because CGI programs are not given the ** + * authorization header, for security reasons. So instead we just ** + * prohibit access to all auth-protected files. + */ + + dirname = strdup(filename); + if (dirname == (char *)0) + { + /* out of memory */ + + return 0; + } + + cp = strrchr(dirname, '/'); + if (cp == (char *)0) + { + (void)strcpy(dirname, "."); + } + else + { + *cp = '\0'; + } + + authname = malloc(strlen(dirname) + 1 + sizeof(CONFIG_AUTH_FILE)); + if (!authname) + { + /* out of memory */ + + free(dirname); + return 0; + } + + (void)sprintf(authname, "%s/%s", dirname, CONFIG_AUTH_FILE); + r = stat(authname, &sb); + + free(dirname); + free(authname); + + if (r == 0) + { + return 0; + } +#endif /* CONFIG_AUTH_FILE */ + + /* Ensure that we are not reading a CGI file. */ + + if (cgi_pattern != (char *)0 && match(cgi_pattern, filename)) + { + return 0; + } + return 1; +} + +static void show_time(time_t t, int gmt) +{ + struct tm *tmP; + + if (gmt) + { + tmP = gmtime(&t); + } + else + { + tmP = localtime(&t); + } + + if (strftime(g_iobuffer2, BUFFER_SIZE, g_timeformat, tmP) > 0) + { + (void)puts(g_iobuffer2); + } +} + +static void show_size(off_t size) +{ + switch (g_sizefmt) + { + case SF_BYTES: + (void)printf("%ld", (long)size); /* spec says should have commas */ + break; + + case SF_ABBREV: + if (size < 1024) + { + (void)printf("%ld", (long)size); + } + else if (size < 1024) + { + (void)printf("%ldK", (long)size / 1024L); + } + else if (size < 1024 * 1024) + { + (void)printf("%ldM", (long)size / (1024L * 1024L)); + } + else + { + (void)printf("%ldG", (long)size / (1024L * 1024L * 1024L)); + } + break; + } +} + +static void do_config(FILE *instream, char *vfilename, char *filename, + char *directive, char *tag, char *val) +{ + /* The config directive controls various aspects of the file parsing. ** + * There are two valid tags: g_timeformat Gives the server a new format to + * use when providing dates. This is a string compatible with the + * strftime library call. g_sizefmt Determines the formatting to be used + * when displaying the size of a file. Valid choices are bytes, for a + * formatted byte count (formatted as 1,234,567), or abbrev for an + * abbreviated version displaying the number of kilobytes or megabytes the + * file occupies. + */ + + if (strcmp(tag, "g_timeformat") == 0) + { + (void)strncpy(g_timeformat, val, TIMEFMT_SIZE - 1); + g_timeformat[TIMEFMT_SIZE - 1] = '\0'; + } + else if (strcmp(tag, "g_sizefmt") == 0) + { + if (strcmp(val, "bytes") == 0) + { + g_sizefmt = SF_BYTES; + } + else if (strcmp(val, "abbrev") == 0) + { + g_sizefmt = SF_ABBREV; + } + else + { + unknown_value(filename, directive, tag, val); + } + } + else + { + unknown_tag(filename, directive, tag); + } +} + +static void do_include(FILE *instream, char *vfilename, char *filename, + char *directive, char *tag, char *val) +{ + FILE *instream2; + int ret; + + /* Inserts the text of another document into the parsed document. */ + + ret = get_filename(vfilename, filename, directive, tag, val, g_iobuffer1, BUFFER_SIZE); + if (ret < 0) + { + return; + } + + if (!check_filename(g_iobuffer1)) + { + not_permitted(directive, tag, g_iobuffer1); + return; + } + + instream2 = fopen(g_iobuffer1, "r"); + if (instream2 == (FILE *) 0) + { + not_found2(directive, tag, g_iobuffer1); + return; + } + + if (strcmp(tag, "virtual") == 0) + { + if (strlen(val) <BUFFER_SIZE) + { + (void)strcpy(g_iobuffer2, val); + } + else + { + (void)strcpy(g_iobuffer2, g_iobuffer1); /* same size, has to fit */ + } + } + else + { + if (strlen(vfilename) + 1 + strlen(val) < BUFFER_SIZE) + { + char *cp; + (void)strcpy(g_iobuffer2, vfilename); + cp = strrchr(g_iobuffer2, '/'); + if (cp == (char *)0) + { + cp = &g_iobuffer2[strlen(g_iobuffer2)]; + *cp = '/'; + } + (void)strcpy(++cp, val); + } + else + { + (void)strcpy(g_iobuffer2, g_iobuffer1); /* same size, has to fit */ + } + } + + read_file(instream2, g_iobuffer2, g_iobuffer1); + (void)fclose(instream2); +} + +static void do_echo(FILE *instream, char *vfilename, char *filename, + char *directive, char *tag, char *val) +{ + char *cp; + + /* Prints the value of one of the include variables. Any dates are + * printed subject to the currently configured g_timeformat. The only valid + * tag is var, whose value is the name of the variable you wish to echo. + */ + + if (strcmp(tag, "var") != 0) + { + unknown_tag(filename, directive, tag); + } + else + { + if (strcmp(val, "DOCUMENT_NAME") == 0) + { + /* The current filename. */ + + (void)puts(filename); + } + else if (strcmp(val, "DOCUMENT_URI") == 0) + { + /* The virtual path to this file (such as /~robm/foo.shtml). */ + + (void)puts(vfilename); + } + else if (strcmp(val, "QUERY_STRING_UNESCAPED") == 0) + { + /* The unescaped version of any search query the client sent. */ + + cp = getenv("QUERY_STRING"); + if (cp != (char *)0) + { + (void)puts(cp); + } + } + else if (strcmp(val, "DATE_LOCAL") == 0) + { + struct timeval tm; + + /* The current date, local time zone. */ + + gettimeofday(&tm, NULL); + show_time(tm.tv_sec, 0); + } + else if (strcmp(val, "DATE_GMT") == 0) + { + struct timeval tm; + + /* Same as DATE_LOCAL but in Greenwich mean time. */ + + gettimeofday(&tm, NULL); + show_time(tm.tv_sec, 1); + } + else if (strcmp(val, "LAST_MODIFIED") == 0) + { + /* The last modification date of the current document. */ + + if (fstat(fileno(instream), &g_sb) >= 0) + { + show_time(g_sb.st_mtime, 0); + } + } + else + { + /* Try an environment variable. */ + + cp = getenv(val); + if (cp == (char *)0) + { + unknown_value(filename, directive, tag, val); + } + else + { + (void)puts(cp); + } + } + } +} + +static void do_fsize(FILE *instream, char *vfilename, char *filename, + char *directive, char *tag, char *val) +{ + int ret; + + /* Prints the size of the specified file. */ + + ret = get_filename(vfilename, filename, directive, tag, val, g_iobuffer1, BUFFER_SIZE); + if (ret < 0) + { + return; + } + + if (stat(g_iobuffer1, &g_sb) < 0) + { + not_found2(directive, tag, g_iobuffer1); + return; + } + + show_size(g_sb.st_size); +} + +static void do_flastmod(FILE *instream, char *vfilename, char *filename, + char *directive, char *tag, char *val) +{ + int ret; + + /* Prints the last modification date of the specified file. */ + + ret = get_filename(vfilename, filename, directive, tag, val, g_iobuffer1, BUFFER_SIZE); + if (ret < 0) + { + return; + } + + if (stat(g_iobuffer1, &g_sb) < 0) + { + not_found2(directive, tag, g_iobuffer1); + return; + } + show_time(g_sb.st_mtime, 0); +} + +static void parse(FILE *instream, char *vfilename, char *filename, char *str) +{ + char *directive; + char *cp; + int ntags; + int dirn; + int i; + char *val; + + directive = str; + directive += strspn(directive, " \t\n\r"); + + ntags = 0; + cp = directive; + for (;;) + { + cp = strpbrk(cp, " \t\n\r\""); + if (cp == (char *)0) + { + break; + } + + if (*cp == '"') + { + cp = strpbrk(cp + 1, "\""); + cp++; + if (*cp == '\0') + { + break; + } + } + + *cp++ = '\0'; + cp += strspn(cp, " \t\n\r"); + if (*cp == '\0') + { + break; + } + + if (ntags < MAX_TAGS) + { + g_tags[ntags++] = cp; + } + } + + if (strcmp(directive, "config") == 0) + { + dirn = DI_CONFIG; + } + else if (strcmp(directive, "include") == 0) + { + dirn = DI_INCLUDE; + } + else if (strcmp(directive, "echo") == 0) + { + dirn = DI_ECHO; + } + else if (strcmp(directive, "fsize") == 0) + { + dirn = DI_FSIZE; + } + else if (strcmp(directive, "flastmod") == 0) + { + dirn = DI_FLASTMOD; + } + else + { + unknown_directive(filename, directive); + return; + } + + for (i = 0; i < ntags; ++i) + { + if (i > 0) + { + putchar(' '); + } + + val = strchr(g_tags[i], '='); + if (val == (char *)0) + { + val = ""; + } + else + { + *val++ = '\0'; + } + + if (*val == '"' && val[strlen(val) - 1] == '"') + { + val[strlen(val) - 1] = '\0'; + ++val; + } + + switch (dirn) + { + case DI_CONFIG: + do_config(instream, vfilename, filename, directive, g_tags[i], val); + break; + + case DI_INCLUDE: + do_include(instream, vfilename, filename, directive, g_tags[i], val); + break; + + case DI_ECHO: + do_echo(instream, vfilename, filename, directive, g_tags[i], val); + break; + + case DI_FSIZE: + do_fsize(instream, vfilename, filename, directive, g_tags[i], val); + break; + + case DI_FLASTMOD: + do_flastmod(instream, vfilename, filename, directive, g_tags[i], val); + break; + } + } +} + +static void slurp(FILE *instream, char *vfilename, char *filename) +{ + int state; + int ich; + int i; + + /* Now slurp in the rest of the comment from the input file. */ + + i = 0; + state = ST_GROUND; + while ((ich = getc(instream)) != EOF) + { + switch (state) + { + case ST_GROUND: + if (ich == '-') + { + state = ST_MINUS1; + } + break; + + case ST_MINUS1: + if (ich == '-') + { + state = ST_MINUS2; + } + else + { + state = ST_GROUND; + } + break; + + case ST_MINUS2: + if (ich == '>') + { + g_iobuffer1[i - 2] = '\0'; + parse(instream, vfilename, filename, g_iobuffer1); + return; + } + else if (ich != '-') + { + state = ST_GROUND; + } + break; + } + + if (i < BUFFER_SIZE - 1) + { + g_iobuffer1[i++] = (char)ich; + } + } +} + +static void read_file(FILE *instream, char *vfilename, char *filename) +{ + int ich; + int state; + + /* Copy it to output, while running a state-machine to look for SSI + * directives. + */ + + state = ST_GROUND; + while ((ich = getc(instream)) != EOF) + { + switch (state) + { + case ST_GROUND: + if (ich == '<') + { + state = ST_LESSTHAN; + continue; + } + break; + + case ST_LESSTHAN: + if (ich == '!') + { + state = ST_BANG; + continue; + } + else + { + state = ST_GROUND; + putchar('<'); + } + break; + + case ST_BANG: + if (ich == '-') + { + state = ST_MINUS1; + continue; + } + else + { + state = ST_GROUND; + (void)puts("<!"); + } + break; + + case ST_MINUS1: + if (ich == '-') + { + state = ST_MINUS2; + continue; + } + else + { + state = ST_GROUND; + (void)puts("<!-"); + } + break; + + case ST_MINUS2: + if (ich == '#') + { + slurp(instream, vfilename, filename); + state = ST_GROUND; + continue; + } + else + { + state = ST_GROUND; + (void)puts("<!--"); + } + break; + } + + putchar((char)ich); + } +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int main(int argc, char **argv) +{ + FILE *instream; + char *script_name; + char *path_info; + char *path_translated; + int err = 0; + + /* Default formats. */ + + (void)strcpy(g_timeformat, "%a %b %e %T %Z %Y"); + g_sizefmt = SF_BYTES; + + /* The MIME type has to be text/html. */ + + (void)puts("Content-type: text/html\n\n"); + + /* Get the name that we were run as. */ + + script_name = getenv("SCRIPT_NAME"); + if (!script_name) + { + internal_error("Couldn't get SCRIPT_NAME environment variable."); + return 1; + } + + /* Append the PATH_INFO, if any, to get the full URL. */ + + path_info = getenv("PATH_INFO"); + if (!path_info) + { + path_info = ""; + } + + g_url = (char*)malloc(strlen(script_name) + strlen(path_info) + 1); + if (!g_url) + { + internal_error("Out of memory."); + return 2; + } + (void)sprintf(g_url, "%s%s", script_name, path_info); + + /* Get the name of the file to parse. */ + + path_translated = getenv("PATH_TRANSLATED"); + if (!path_translated) + { + internal_error("Couldn't get PATH_TRANSLATED environment variable."); + err = 3; + goto errout_with_g_url; + } + + if (!check_filename(path_translated)) + { + not_permitted("initial", "PATH_TRANSLATED", path_translated); + err = 4; + goto errout_with_g_url; + } + + /* Open it. */ + + instream = fopen(path_translated, "r"); + if (!instream) + { + not_found(path_translated); + err = 5; + goto errout_with_g_url; + } + + /* Read and handle the file. */ + + read_file(instream, path_info, path_translated); + + (void)fclose(instream); + +errout_with_g_url: + free(g_url); + return err; +} |