/**************************************************************************** * apps/netutils/thttpd/cgi-src/ssi.c * Server-side-includes CGI program * * Copyright (C) 2009, 2015 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * Derived from the file of the same name in the original THTTPD package: * * Copyright 1995 by Jef Poskanzer . * 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 #include #include #include #include #include #include #include #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"; printf("%s\n" "

%s

\n" "Something unusual went wrong during a server-side-includes request:\n" "
\n" "%s\n" "
\n" "\n", title, title, reason); } static void not_found(char *filename) { char *title = "404 Not Found"; printf("%s\n" "

%s

\n" "The requested server-side-includes filename, %s,\n" "does not seem to exist.\n" "\n", title, title, filename); } static void not_found2(char *directive, char *tag, char *filename) { char *title = "Not Found"; printf("

%s

\n" "The filename requested in a %s %s directive, %s,\n" "does not seem to exist.\n" "
\n", title, directive, tag, filename); } static void not_permitted(char *directive, char *tag, char *val) { char *title = "Not Permitted"; printf("

%s

\n" "The filename requested in the %s %s=%s directive\n" "may not be fetched.\n" "
\n", title, directive, tag, val); } static void unknown_directive(char *filename, char *directive) { char *title = "Unknown Directive"; printf("

%s

\n" "The requested server-side-includes filename, %s,\n" "tried to use an unknown directive, %s.\n" "
\n", title, filename, directive); } static void unknown_tag(char *filename, char *directive, char *tag) { char *title = "Unknown Tag"; printf("

%s

\n" "The requested server-side-includes filename, %s,\n" "tried to use the directive %s with an unknown tag, %s.\n" "
\n", title, filename, directive, tag); } static void unknown_value(char *filename, char *directive, char *tag, char *val) { char *title = "Unknown Value"; printf("

%s

\n" "The requested server-side-includes filename, %s,\n" "tried to use the directive %s %s with an unknown value, %s.\n" "
\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 size; 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); size = fl - vl; 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[size]) != 0) { return -1; } if (size + strlen(val) >= fnsize) { return -1; } strncpy(fn, filename, size); strlcpy(&fn[size], val, fnsize - size); } 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; } strlcpy(fn, filename, fnsize); cp = strrchr(fn, '/'); if (cp == (char *)0) { cp = &fn[strlen(fn)]; *cp = '/'; } cp++; strlcpy(cp, val, fnsize - (cp - fn)); } 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) { strlcpy(dirname, ".", fnl + 1); } else { *cp = '\0'; } fnl = strlen(dirname) + 1 + sizeof(CONFIG_AUTH_FILE); authname = malloc(fnl); if (!authname) { /* out of memory */ free(dirname); return 0; } snprintf(authname, fnl, "%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 && !fnmatch(cgi_pattern, filename, 0)) { 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) { puts(g_iobuffer2); } } static void show_size(off_t size) { switch (g_sizefmt) { case SF_BYTES: printf("%ld", (long)size); /* spec says should have commas */ break; case SF_ABBREV: if (size < 1024) { printf("%ld", (long)size); } else if (size < 1024 * 1024) { printf("%ldK", (long)size / 1024L); } else if (size < 1024 * 1024 * 1024) { printf("%ldM", (long)size / (1024L * 1024L)); } else { 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) { 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 == NULL) { not_found2(directive, tag, g_iobuffer1); return; } if (strcmp(tag, "virtual") == 0) { if (strlen(val) < BUFFER_SIZE) { strlcpy(g_iobuffer2, val, sizeof(g_iobuffer2)); } else { strlcpy(g_iobuffer2, g_iobuffer1, sizeof(g_iobuffer2)); /* same size, has to fit */ } } else { if (strlen(vfilename) + 1 + strlen(val) < BUFFER_SIZE) { char *cp; strlcpy(g_iobuffer2, vfilename, sizeof(g_iobuffer2)); cp = strrchr(g_iobuffer2, '/'); if (cp == (char *)0) { cp = &g_iobuffer2[strlen(g_iobuffer2)]; *cp = '/'; } cp++; strlcpy(cp, val, sizeof(g_iobuffer2) - (cp - g_iobuffer2)); } else { strlcpy(g_iobuffer2, g_iobuffer1, sizeof(g_iobuffer2)); /* same size, has to fit */ } } read_file(instream2, g_iobuffer2, g_iobuffer1); 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. */ puts(filename); } else if (strcmp(val, "DOCUMENT_URI") == 0) { /* The virtual path to this file (such as /~robm/foo.shtml). */ 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) { 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); } #if 0 /* fstat is not yet supported */ 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); } } #endif else { /* Try an environment variable. */ cp = getenv(val); if (cp == (char *)0) { unknown_value(filename, directive, tag, val); } else { 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; puts("