diff options
author | Ian Jackson <ijackson@chiark.greenend.org.uk> | 2017-04-15 12:05:50 +0100 |
---|---|---|
committer | Ian Jackson <ijackson@chiark.greenend.org.uk> | 2017-04-15 12:05:50 +0100 |
commit | 5e217877d0d85c56c881da5c8a11fd253c11fe0a (patch) | |
tree | c1553b3d6847cc8c1489bc9c0dd27a3b9ecff733 /ucgi | |
parent | 186ea161e7d144dd8e3791f174a7173e2f399346 (diff) | |
download | userv-utils-5e217877d0d85c56c881da5c8a11fd253c11fe0a.tar.gz |
Rename www-cgi to ucgi (in nearly all places)
Leave userv service name the same, for compatibility.
Diffstat (limited to 'ucgi')
-rw-r--r-- | ucgi/.gitignore | 2 | ||||
-rw-r--r-- | ucgi/INSTALL | 48 | ||||
-rw-r--r-- | ucgi/Makefile | 55 | ||||
-rw-r--r-- | ucgi/README.custom-env-filter | 27 | ||||
-rwxr-xr-x | ucgi/check | 6 | ||||
-rw-r--r-- | ucgi/srm.conf.fragment | 2 | ||||
-rw-r--r-- | ucgi/ucgi.c | 164 | ||||
-rw-r--r-- | ucgi/ucgi.h | 61 | ||||
-rw-r--r-- | ucgi/ucgicommon.c | 221 | ||||
-rw-r--r-- | ucgi/ucgitarget.c | 216 | ||||
-rw-r--r-- | ucgi/user-cgi.text | 106 | ||||
-rw-r--r-- | ucgi/www-cgi | 23 |
12 files changed, 931 insertions, 0 deletions
diff --git a/ucgi/.gitignore b/ucgi/.gitignore new file mode 100644 index 0000000..79f731a --- /dev/null +++ b/ucgi/.gitignore @@ -0,0 +1,2 @@ +ucgi +ucgitarget diff --git a/ucgi/INSTALL b/ucgi/INSTALL new file mode 100644 index 0000000..33a2231 --- /dev/null +++ b/ucgi/INSTALL @@ -0,0 +1,48 @@ +To install the www-cgi service: + +1. Run make to build ucgi and ucgitarget. + +2. Create the directory /usr/local/lib/user-cgi/cgi + +3. Install the programs: +(a) ucgitarget as /usr/local/lib/user-cgi/target +(b) ucgi as /usr/local/lib/user-cgi/ucgi +(c) a symlink /usr/local/lib/user-cgi/ucgi-debug -> ucgi +(d) the script `check' as /usr/local/lib/user-cgi/check + +4. Put the extra ScriptAlias directives in srm.conf.fragment in the +appropriate part of your webserver configuration. + +5. Install the userv service: +(a) Put the file www-cgi in /etc/userv/services.d (you may need to + adjust it for your local configuration) +(b) If you don't already have it, add this line to /etc/userv/system.default: + include-lookup service /etc/userv/services.d + +6. As a test user, create a `public-cgi' directory, and a symlink in +it called `check' which points to /usr/local/lib/user-cgi/check. + +7. Test that all is working by visiting + http://www.example.com/ucgi-debug/~fred/check + http://www.example.com/ucgi/~fred/check + + +Copyright 1996-2013 Ian Jackson <ijackson@chiark.greenend.org.uk> +Copyright 1998 David Damerell <damerell@chiark.greenend.org.uk> +Copyright 1999,2003 + Chancellor Masters and Scholars of the University of Cambridge +Copyright 2010 Tony Finch <fanf@dotat.at> +Copyright 2013,2016 Mark Wooding <mdw@distorted.org.uk> + +All the utilities here are free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3 of the License, or (at your +option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with userv-utils; if not, see http://www.gnu.org/licenses/. diff --git a/ucgi/Makefile b/ucgi/Makefile new file mode 100644 index 0000000..1d8b34b --- /dev/null +++ b/ucgi/Makefile @@ -0,0 +1,55 @@ +# Copyright 1996-2013,2016 Ian Jackson <ijackson@chiark.greenend.org.uk> +# Copyright 1998 David Damerell <damerell@chiark.greenend.org.uk> +# Copyright 1999,2003 +# Chancellor Masters and Scholars of the University of Cambridge +# Copyright 2010 Tony Finch <fanf@dotat.at> +# Copyright 2013,2016 Mark Wooding <mdw@distorted.org.uk> +# +# This is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with userv-utils; if not, see http://www.gnu.org/licenses/. + + +include ../settings.make + +uslibdir= $(libdir)/userv/ucgi +uslibcgidir= $(uslibdir)/cgi +usdocdir= $(docdir)/userv-ucgi + +TARGETS= ucgi ucgitarget + +all: $(TARGETS) + +OBJS= ucgi.o ucgitarget.o ucgicommon.o + +ucgi: ucgi.o ucgicommon.o +ucgitarget: ucgitarget.o ucgicommon.o + +$(OBJS): ucgi.h + +install: all + mkdir -p $(uslibcgidir) + cp -b ucgitarget $(uslibdir)/target + cp -b ucgi $(uslibcgidir)/ + ln -sf ucgi $(uslibcgidir)/ucgi-debug + +install-examples: + +install-docs: + mkdir -p $(usdocdir)/examples + cp INSTALL README.custom-env-filter $(usdocdir)/. + cp srm.conf.fragment $(usdocdir)/examples/. + sed -e 's#/usr/local#$(prefix)#' user-cgi.text \ + >$(usdocdir)/userv-cgi.text + +clean distclean realclean: + rm -f $(TARGETS) *.o diff --git a/ucgi/README.custom-env-filter b/ucgi/README.custom-env-filter new file mode 100644 index 0000000..58ef39b --- /dev/null +++ b/ucgi/README.custom-env-filter @@ -0,0 +1,27 @@ +Allow customization of the environment filters. + +Sites can configure ucgi's environment filters, and end users can +configure ucgitarget's filters. + +By default, ucgi will look in /etc/userv/ucgi.env-filter, but if +UCGI_ENV_FILTER is set in its environment, it will look there +instead. The filter may contain wildcards and so on. + +By default, ucgitarget looks in .userv/ucgitarget.env-filter, or +/etc/userv/ucgitarget.env-filter, if the former doesn't exist; but if +passed a `-e FILTER' option on its command line, it will look in the +file FILTER instead. This filter may /not/ contain wildcards. + +In both cases, if an explicitly named filter file can't be found then +the program fails; if the default filter files can't be found then they +fall back to built-in lists. + +The reason for the asymmetry in interfaces is: it's hard to pass +command-line options to CGI scripts from webservers, but pretty easy to +set environment variables; whereas it's hard to pass environment +variables to a service program in a Userv configuration file, but easy +to pass command-line arguments. + + +The `?DEFAULTS' pattern can be specified to match the default set +(which is different in `ucgi' and `ucgitarget'). diff --git a/ucgi/check b/ucgi/check new file mode 100755 index 0000000..fca7431 --- /dev/null +++ b/ucgi/check @@ -0,0 +1,6 @@ +#!/bin/sh +echo "Content-type: text/plain" +echo + +echo "ucgi check - args:" "$@" +printenv | sort diff --git a/ucgi/srm.conf.fragment b/ucgi/srm.conf.fragment new file mode 100644 index 0000000..8acbe24 --- /dev/null +++ b/ucgi/srm.conf.fragment @@ -0,0 +1,2 @@ +ScriptAlias /ucgi/ /usr/lib/user-cgi/cgi/ucgi/ +ScriptAlias /ucgi-debug/ /usr/lib/user-cgi/cgi/ucgi-debug/ diff --git a/ucgi/ucgi.c b/ucgi/ucgi.c new file mode 100644 index 0000000..fc9888b --- /dev/null +++ b/ucgi/ucgi.c @@ -0,0 +1,164 @@ +/* + * Usage: as CGI script + */ +/* + * Copyright 1996-2013,2016 Ian Jackson <ijackson@chiark.greenend.org.uk> + * Copyright 1998 David Damerell <damerell@chiark.greenend.org.uk> + * Copyright 1999,2003 + * Chancellor Masters and Scholars of the University of Cambridge + * Copyright 2010 Tony Finch <fanf@dotat.at> + * Copyright 2013,2016 Mark Wooding <mdw@distorted.org.uk> + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with userv-utils; if not, see http://www.gnu.org/licenses/. + */ + +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include "ucgi.h" + +static const char *const default_envok[] = { + "AUTH_TYPE", + "CONTENT_TYPE", + "CONTENT_LENGTH", + "DOCUMENT_ROOT", + "GATEWAY_INTERFACE", + "HTTP_*", + "HTTPS", + "PATH_INFO", + "PATH_TRANSLATED", + "QUERY_STRING", + "REDIRECT_*", + "REMOTE_*", + "REQUEST_METHOD", + "REQUEST_URI", + "SCRIPT_*", + "SERVER_*", + "SSL_*", + 0 +}; + +struct buildargs { + const char **v; + int n, max; +}; + +static void addarg(struct buildargs *args, const char *a) { + if (args->n > args->max) error("too many arguments", 500); + args->v[args->n++]= a; +} + +static void add_userv_var(const char *fulln, + const char *en, const char *ev, void *p) { + struct buildargs *args= p; + size_t l; + char *a; + + l= strlen(ev); + if (l > MAX_ENVVAR_VALUE) error("environment variable too long", 500); + a= xmalloc(strlen(en)+l+6); + sprintf(a,"-DE_%s=%s",en,ev); + addarg(args, a); +} + +int main(int argc, const char **argv) { + char *username; + const char *slash2, *pathi, *ev, *av; + const char *const *envok = 0; + size_t usernamelen, l; + struct buildargs args; + pid_t child, rchild; + int status; + + l= strlen(argv[0]); + if (l>6 && !strcmp(argv[0]+l-6,"-debug")) debugmode= 1; + + if (debugmode) { + if (fputs("Content-Type: text/plain\n\n",stdout)==EOF || fflush(stdout)) + syserror("write stdout"); + if (dup2(1,2)<0) { perror("dup stdout to stderr"); exit(-1); } + D( printf(";;; UCGI\n"); ) + } + + if (argc > MAX_ARGS) error("too many arguments", 500); + + ev= getenv("UCGI_ENV_FILTER"); + if (ev) + envok= load_filters(LOADF_MUST, ev, LF_END); + else + envok= load_filters(0, "/etc/userv/ucgi.env-filter", LF_END); + + pathi= getenv("PATH_INFO"); + if (!pathi) error("PATH_INFO not found", 500); + D( if (debugmode) { + printf(";; find user name...\n" + ";; initial PATH_INFO = `%s'\n", + pathi); + } ) + if (pathi[0] != '/' || pathi[1] != '~') + error("PATH_INFO must start with /~", 400); + slash2= strchr(pathi+2,'/'); + if (!slash2) error("PATH_INFO must have more than one /", 400); + usernamelen= slash2-(pathi+2); + if (usernamelen > MAX_USERNAME_LEN) error("PATH_INFO username too long", 400); + username= xmalloc(usernamelen+1); + memcpy(username,pathi+2,usernamelen); username[usernamelen]= 0; + D( if (debugmode) + printf(";; user = `%s'; tail = `%s'\n", username, slash2); ) + if (!isalpha(username[0])) + error("username 1st character is not alphabetic", 400); + xsetenv("PATH_INFO",slash2,1); + + args.n= 0; args.max= argc + MAX_ENVVARS + 10; + args.v= xmalloc(args.max * sizeof(*args.v)); + + addarg(&args, "userv"); + if (debugmode) addarg(&args, "-DDEBUG=1"); + + filter_environment(FILTF_WILDCARD, "", envok, default_envok, + add_userv_var, &args); + + addarg(&args, username); + addarg(&args, "www-cgi"); + while ((av= (*++argv))) addarg(&args, av); + addarg(&args, 0); + + if (debugmode) { + D( fflush(stdout); ) + child= fork(); if (child==-1) syserror("fork"); + if (child) { + rchild= waitpid(child,&status,0); + if (rchild==-1) syserror("waitpid"); + printf("\nexit status %d %d\n",(status>>8)&0x0ff,status&0x0ff); + exit(0); + } + } + + D( if (debugmode) { + int i; + + printf(";; final command line...\n"); + for (i = 0; args.v[i]; i++) + printf(";; %s\n", args.v[i]); + fflush(stdout); + } ) + + execvp("userv",(char*const*)args.v); + syserror("exec userv"); + return -1; +} diff --git a/ucgi/ucgi.h b/ucgi/ucgi.h new file mode 100644 index 0000000..e20e764 --- /dev/null +++ b/ucgi/ucgi.h @@ -0,0 +1,61 @@ +/* + * Copyright 1996-2013,2016 Ian Jackson <ijackson@chiark.greenend.org.uk> + * Copyright 1998 David Damerell <damerell@chiark.greenend.org.uk> + * Copyright 1999,2003 + * Chancellor Masters and Scholars of the University of Cambridge + * Copyright 2010 Tony Finch <fanf@dotat.at> + * Copyright 2013,2016 Mark Wooding <mdw@distorted.org.uk> + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with userv-utils; if not, see http://www.gnu.org/licenses/. + */ + +#ifndef UCGI_H +#define UCGI_H + +#include <stdlib.h> + +#ifdef DEBUG +# define D(x) x +#else +# define D(x) +#endif + +#define MAX_ARGS 1024 +#define MAX_USERNAME_LEN 1024 +#define MAX_SCRIPTPATH_LEN 1024 +#define MAX_ENVVAR_NAME 128 +#define MAX_ENVVAR_VALUE (1024*1024) +#define MAX_ENVVARS 256 + +void syserror(const char *m); +void error(const char *m, int st); +void *xmalloc(size_t sz); +void xsetenv(const char *en, const char *ev, int overwrite); +void *xrealloc(void *ptr, size_t sz); + +const char **load_filters(unsigned flags, const char *first, ...); +#define LOADF_MUST 1u +#define LF_END ((const char *)0) + +void filter_environment(unsigned flags, const char *prefix_in, + const char *const *patv, + const char *const *defaults, + void (*foundone)(const char *fulln, const char *en, + const char *ev, void *p), + void *p); +#define FILTF_WILDCARD 1u + +extern int debugmode; + +#endif diff --git a/ucgi/ucgicommon.c b/ucgi/ucgicommon.c new file mode 100644 index 0000000..c3eb4b3 --- /dev/null +++ b/ucgi/ucgicommon.c @@ -0,0 +1,221 @@ +/* + * Copyright 1996-2013,2016 Ian Jackson <ijackson@chiark.greenend.org.uk> + * Copyright 1998 David Damerell <damerell@chiark.greenend.org.uk> + * Copyright 1999,2003 + * Chancellor Masters and Scholars of the University of Cambridge + * Copyright 2010 Tony Finch <fanf@dotat.at> + * Copyright 2013,2016 Mark Wooding <mdw@distorted.org.uk> + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with userv-utils; if not, see http://www.gnu.org/licenses/. + */ + +#include <assert.h> +#include <ctype.h> +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> + +#include <unistd.h> + +#include "ucgi.h" + +int debugmode= 0; + +static void outerror(void) { + perror("stdout"); + exit(debugmode ? 0 : -1); +} + +void syserror(const char *m) { + if (printf("Content-Type: text/plain\n" + "Status: 500\n\n" + "ucgi: system call error:\n" + "%s: %s\n", + m,strerror(errno))==EOF || fflush(stdout)) outerror(); + exit(0); +} + +void error(const char *m, int st) { + if (printf("Content-Type: text/plain\n" + "Status: %d\n\n" + "ucgi: error:\n" + "%s\n", + st, m)==EOF || fflush(stdout)) outerror(); + exit(0); +} + +void *xmalloc(size_t sz) { + void *r; + + r= malloc(sz); + if (!r) syserror("malloc failed"); + return r; +} + +void *xrealloc(void *ptr, size_t sz) { + void *r; + + r= realloc(ptr,sz); + if (!r) syserror("realloc failed"); + return r; +} + +void xsetenv(const char *en, const char *ev, int overwrite) { + if (setenv(en,ev,overwrite)) syserror("setenv"); +} + +const char **load_filters(unsigned flags, const char *first, ...) { + va_list ap; + const char *name, *p, *q, **v; + char *pp; + size_t l, n, sz; + FILE *fp; + char buf[MAX_ENVVAR_NAME]; + + D( if (debugmode) printf(";; load_filters...\n"); ) + va_start(ap, first); + for (name= first; name; name= va_arg(ap, const char *)) { + fp= fopen(name, "r"); if (fp) goto opened; + D( if (debugmode) + printf(";; file `%s': %s\n", name, strerror(errno)); ) + if (errno != ENOENT) syserror("failed to open environment filters"); + } + va_end(ap); + if (flags & LOADF_MUST) syserror("failed to open environment filters"); + D( if (debugmode) printf(";; using default filters\n"); ) + return 0; + +opened: + va_end(ap); + D( if (debugmode) printf(";; file `%s': OK\n", name); ) + + n= 0; sz= 128; v= xmalloc(sz * sizeof(*v)); + for (;;) { + if (!fgets(buf, sizeof(buf), fp)) break; + l= strlen(buf); + if (buf[l - 1] == '\n') buf[--l]= 0; + if (l + 1 == sizeof(buf)) + error("line too long in environment filter file", 500); + p= buf; q= p + l; + while (isspace((unsigned char)*p)) p++; + while (q > p && isspace((unsigned char)q[-1])) q--; + if (*p == '#' || p == q) continue; + l= q - p; + pp= xmalloc(l + 1); + memcpy(pp, p, l); + pp[l]= 0; + v[n++]= pp; + D( if (debugmode) printf(";; filter: `%s'\n", pp); ) + if (n >= sz) { + sz *= 2; + v= xrealloc(v, sz * sizeof(*v)); + } + } + if (ferror(fp)) syserror("failed to read environment filters"); + fclose(fp); + return v; +} + +static int envvar_match(unsigned flags, const char *en, + const char *const *patv, + const char *const *defaults, + const char **ev) { + const char *const *patp; + const char *q, *pat; + int acceptp; + int rc; + + if (!patv) { patv= defaults; defaults= 0; } + for (patp= patv; (pat= *patp); patp++) { + q= en; + acceptp= 1; + if (*pat == '!' && (flags & FILTF_WILDCARD)) { acceptp= 0; pat++; } + else if (*pat == '?') { + if (strcmp(pat + 1, "DEFAULTS") == 0) { + assert(defaults); + rc= envvar_match(flags, en, defaults, 0, ev); + if (rc) return rc; + } else + error("unknown pattern directive", 500); + continue; + } + + for (;;) { + if (!*pat) { + if (*q != '=') { + D( if (debugmode) + printf(";; mismatch `%s' (prefix)\n", *patp); ) + break; + } + D( if (debugmode) printf(";; matched pattern `%s'\n", *patp); ) + goto match; + } else if (*pat == '*' && (flags & FILTF_WILDCARD)) { + q = strchr(q, '='); + if (!q) { + D( if (debugmode) + printf(";; mismatch `%s' (discard: no `=')\n", *patp); ) + return -1; + } + D( if (debugmode) + printf(";; wildcard match for `%s'\n", *patp); ) + goto match; + } else { + if (*pat++ != *q++) { + D( if (debugmode) printf(";; mismatch `%s'\n", *patp); ) + break; + } + } + } + } + return 0; + +match: + if (!acceptp) return -1; + *ev= q + 1; + return +1; +} + +void filter_environment(unsigned flags, const char *prefix_in, + const char *const *patv, + const char *const *defaults, + void (*foundone)(const char *fulln, + const char *en, const char *ev, + void *p), + void *p) { + char *const *ep; + const char *en, *ev; + char enbuf[MAX_ENVVAR_NAME]; + size_t n, pn = strlen(prefix_in); + + D( if (debugmode) printf(";; filter_environment...\n"); ) + for (ep= environ; (en= *ep); ep++) { + D( if (debugmode) printf(";; consider env-var `%s'\n", en); ) + if (strncmp(en, prefix_in, pn) != 0 || !en[pn]) { + D( if (debugmode) printf(";; doesn't match prefix\n"); ) + continue; + } + if (envvar_match(flags, en + pn, patv, defaults, &ev) > 0) { + n= strcspn(en, "="); + if (n >= sizeof(enbuf)) + error("environment variable name too long", 500); + memcpy(enbuf, en, n); + enbuf[n]= 0; + D( if (debugmode) + printf(";; full = `%s'; tail = `%s'; value = `%s'\n", + enbuf, enbuf + pn, ev); ) + foundone(enbuf, enbuf + pn, ev, p); + } + } +} diff --git a/ucgi/ucgitarget.c b/ucgi/ucgitarget.c new file mode 100644 index 0000000..178fa4e --- /dev/null +++ b/ucgi/ucgitarget.c @@ -0,0 +1,216 @@ +/* + * Usage: as CGI script, but called by userv + * environment variables are USERV_U_E_... + */ +/* + * Copyright 1996-2013,2016 Ian Jackson <ijackson@chiark.greenend.org.uk> + * Copyright 1998 David Damerell <damerell@chiark.greenend.org.uk> + * Copyright 1999,2003 + * Chancellor Masters and Scholars of the University of Cambridge + * Copyright 2010 Tony Finch <fanf@dotat.at> + * Copyright 2013,2016 Mark Wooding <mdw@distorted.org.uk> + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with userv-utils; if not, see http://www.gnu.org/licenses/. + */ + +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <getopt.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> + +#include "ucgi.h" + +static const char *const default_envok[]= { + "AUTH_TYPE", + "CONTENT_LENGTH", + "CONTENT_TYPE", + "DOCUMENT_ROOT", + "GATEWAY_INTERFACE", + "HTTP_ACCEPT", + "HTTP_ACCEPT_CHARSET", + "HTTP_ACCEPT_ENCODING", + "HTTP_ACCEPT_LANGUAGE", + "HTTP_CACHE_CONTROL", + "HTTP_CONNECTION", + "HTTP_CONTENT_ENCODING", + "HTTP_COOKIE", + "HTTP_DNT", + "HTTP_HOST", + "HTTP_KEEP_ALIVE", + "HTTP_NEGOTIATE", + "HTTP_PRAGMA", + "HTTP_REFERER", + "HTTP_USER_AGENT", + "HTTP_VIA", + "HTTP_X_FORWARDED_FOR", + "HTTPS", + "PATH_INFO", + "PATH_TRANSLATED", + "QUERY_STRING", + "REDIRECT_HANDLER", + "REDIRECT_SCRIPT_URI", + "REDIRECT_SCRIPT_URL", + "REDIRECT_STATUS", + "REDIRECT_URL", + "REMOTE_ADDR", + "REMOTE_HOST", + "REMOTE_USER", + "REMOTE_IDENT", + "REQUEST_METHOD", + "REQUEST_URI", + "SCRIPT_FILENAME", + "SCRIPT_NAME", + "SCRIPT_URI", + "SCRIPT_URL", + "SERVER_ADDR", + "SERVER_ADMIN", + "SERVER_NAME", + "SERVER_PORT", + "SERVER_PROTOCOL", + "SERVER_SIGNATURE", + "SERVER_SOFTWARE", + "SSL_CIPHER", + "SSL_CLIENT_S_DN", + "SSL_CLIENT_VERIFY", + "SSL_PROTOCOL", + 0 +}; + +static void setenvar(const char *fulln, + const char *en, const char *ep, void *p) { + xsetenv(en, ep, 1); + unsetenv(fulln); +} + +int main(int argc, char **argv) { + char *scriptpath, *newvar; + const char *nextslash, *lastslash, *pathi, *ev, *ev2, *scriptdir, *av; + const char *const *envok; + const char **arguments; + size_t scriptdirlen, scriptpathlen, l; + struct stat stab; + int i, r, nargs; + const char *filters= 0; + + ev= getenv("USERV_U_DEBUG"); + if (ev && *ev) debugmode= 1; + + D( if (debugmode) printf(";;; UCGITARGET\n"); ) + if (argc > MAX_ARGS) error("too many arguments", 500); + + for (;;) { + i= getopt(argc, argv, "+e:"); if (i < 0) break; + switch (i) { + case 'e': filters= optarg; break; + default: error("bad command line", 500); break; + } + } + argc -= optind; argv += optind; + + if (!*argv) error("no script directory argument", 500); + ev= getenv("HOME"); if (!ev) error("no HOME env. var", 500); + l= strlen(*argv)+strlen(ev); + newvar= xmalloc(l+2); + sprintf(newvar,"%s/%s",ev,*argv); + scriptdir= newvar; + scriptdirlen= strlen(scriptdir); + + if (filters) + envok= load_filters(LOADF_MUST, filters, LF_END); + else { + envok= load_filters(0, + ".userv/ucgitarget.env-filter", + "/etc/userv/ucgitarget.env-filter", + LF_END); + } + + filter_environment(0, "USERV_U_E_", envok, default_envok, setenvar, 0); + + scriptpath= 0; + pathi= getenv("PATH_INFO"); + if (!pathi) error("PATH_INFO not found", 500); + lastslash= pathi; + D( if (debugmode) { + printf(";; find script name...\n" + ";; PATH_INFO = `%s'\n", + pathi); + } ) + for (;;) { + if (*lastslash != '/') error("PATH_INFO expected slash not found", 400); + if (lastslash[1]=='.' || lastslash[1]=='#' || !lastslash[1]) + error("bad char begin", 400); + nextslash= strchr(lastslash+1,'/'); + if (!nextslash) nextslash= lastslash+1+strlen(lastslash+1); + if (!nextslash) error("insufficient elements in PATH_INFO", 400); + if (nextslash==lastslash+1) error("empty component in PATH_INFO", 400); + if (nextslash-pathi > MAX_SCRIPTPATH_LEN) + error("PATH_INFO script path too long", 400); + scriptpathlen= scriptdirlen+(nextslash-pathi); + scriptpath= xrealloc(scriptpath,scriptpathlen+1); + strcpy(scriptpath,scriptdir); + memcpy(scriptpath+scriptdirlen,pathi,nextslash-pathi); + scriptpath[scriptpathlen]= 0; + if (scriptpath[scriptpathlen-1]=='~') error("bad char end", 400); + D( if (debugmode) printf(";; try `%s'\n", scriptpath); ) + r= stat(scriptpath,&stab); if (r) syserror("stat script"); + if (S_ISREG(stab.st_mode)) break; + if (!S_ISDIR(stab.st_mode)) error("script not directory or file", 500); + lastslash= nextslash; + } + D( if (debugmode) printf(";; found script: tail = `%s'\n", nextslash); ) + if (*nextslash) xsetenv("PATH_INFO",nextslash,1); + else unsetenv("PATH_INFO"); + + newvar= xmalloc(scriptpathlen+strlen(nextslash)+3); + sprintf(newvar,"%s%s",scriptpath,nextslash); + xsetenv("PATH_TRANSLATED",newvar,1); + + xsetenv("SCRIPT_FILENAME",scriptpath,1); + + ev= getenv("SCRIPT_NAME"); + if (ev) { + ev2= getenv("USER"); if (!ev2) error("no USER variable", 500); + newvar= xmalloc(strlen(ev)+2+strlen(ev2)+scriptpathlen-scriptdirlen+2); + sprintf(newvar,"%s/~%s%s",ev,ev2,scriptpath+scriptdirlen); + xsetenv("SCRIPT_NAME",newvar,1); + } + + arguments= xmalloc(sizeof(const char*)*(argc+5)); + nargs= 0; + + arguments[nargs++]= scriptpath; + while ((av= (*++argv))) arguments[nargs++]= av; + arguments[nargs++]= 0; + + D( if (debugmode) { + int i; + + printf(";; final environment...\n"); + for (i = 0; environ[i]; i++) + printf(";; %s\n", environ[i]); + + printf(";; final command line...\n"); + for (i = 0; arguments[i]; i++) + printf(";; %s\n", arguments[i]); + fflush(stdout); + } ) + + execvp(scriptpath,(char*const*)arguments); + syserror("exec script"); + return -1; +} diff --git a/ucgi/user-cgi.text b/ucgi/user-cgi.text new file mode 100644 index 0000000..464686b --- /dev/null +++ b/ucgi/user-cgi.text @@ -0,0 +1,106 @@ +Users can arrange to have CGI scripts run by the webserver. +This is achieved using userv (see +<URL:http://www.chiark.greenend.org.uk/~ian/userv/>). + +Before you write such scripts you should be aware of the security +issues involved. + +Paths in the http space of the form + /ucgi/~<username>/<path-to-script>/<extra-stuff>... +will be taken to refer to the CGI script + ~<username>/public-cgi/<path-to-script> +and /<extra-stuff> will be used as the PATH_INFO (as is +conventional). For example, + http://www.example.com/ucgi/~ijackson/spong/foo?bar=baz +will run ~ijackson/public-cgi/spong with PATH_INFO set to `/foo' and +QUERY_STRING set to `bar=baz'. + +You can debug your scripts by using + /ucgi-debug/~<username>/<path-to-script>... +which will return a text/plain document consisting of the standard +output and standard error of your script and a line at the bottom with +the high and low bytes of the script's exit status. + +Also, /usr/local/lib/user-cgi/cgi/check is a script which will dump +its arguments and environment as a text/plain output file. This can +be used to see what input your CGI program ought to expect. + +The default configuration does not enable userv's `set-environment' +feature, so the environment your scripts in will be rather minimal. +You can change this it if you want by saying something like + if glob service www-cgi + set-environment + fi +in your ~/.userv/rc file. This will cause your scripts to be run by a +shell which has sourced your ~/.environment file, if it exists. See +the userv documentation for details, and look in /etc/environment. + +CGI programs will be run in your account. They will be able to access +files exactly as if you had run them yourself directly. Their PATH +and other similar variables will be set correctly (see below) and can +and should be trusted. + +However, their arguments, input and webserver-provided environment +variables (the full list is in ucgicommon.c) will have come from the +client WWW browser and are highly untrustworthy. This means you must +be very careful when writing such programs. Beware particularly of + * buffer overruns in C + * trusting data not to have metacharacters. + You should generally not pass client-provided data to + - eval (Perl or shell) + - system (Perl or C) and exec (Perl) + - open (Perl) and popen (C) + - anything similar. + +Safely using untrusted client-provided data in shell scripts is very +difficult. I would recommend against programming CGI scripts in +shell. If you must, make sure you use appropriate quoting and +argument unparsing everywhere (and don't do it if you don't know what +I mean by argument unparsing). + +The invocation of user-provided CGI scripts is achieved by using userv +to invoke the `www-cgi' service. The webserver-provided environment +variables will be passed as userv parameters using +-DE_<variable>=<value>. The E_PATH_INFO parameter contains the +portion of the path beyond the username. + +The default configuration (/etc/userv/system.default) arranges for +www-cgi to run /usr/local/lib/user-cgi/target, which removes the +USERV_E_ from the start of the webserver-provided environment +variables and adjusts some of them for the script's actual location +and the calls the actual script. `target' takes one parameter, the +location of the user's public CGI directory relative to their home +directory (`public-cgi' in the default configuration). It must be a +relative path. + +You can run your own scripts from the command line by saying + userv -DE_PATH_INFO=/<script>[/<path-info>] \ + -DE_QUERY_STRING=<query> --spoof-user www - www-cgi \ + [<arg-for-isindex-query>] + +CGI programs' path components may not be empty, may not start with a +full stop `.', and may not end with a hash `#' or tilde `~'. + +It is important that the webserver removes /../ components from the +PATH_INFO - if it doesn't there is a security hole. + + +userv-utils are +Copyright 1996-2013 Ian Jackson <ijackson@chiark.greenend.org.uk> +Copyright 1998 David Damerell <damerell@chiark.greenend.org.uk> +Copyright 1999,2003 + Chancellor Masters and Scholars of the University of Cambridge +Copyright 2010 Tony Finch <fanf@dotat.at> + +All the utilities here are free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3 of the License, or (at your +option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with userv-utils; if not, see http://www.gnu.org/licenses/. diff --git a/ucgi/www-cgi b/ucgi/www-cgi new file mode 100644 index 0000000..6692349 --- /dev/null +++ b/ucgi/www-cgi @@ -0,0 +1,23 @@ +# This service which allows CGI programs to be provided which do not +# run as the webserver user, but instead are owned by a particular +# other account. +# +# Similar effects can be achieved with Apache's suexec; this facility +# is for administrators who do not trust suexec and wish to defend the +# webserver from the CGI script providers, and vice versa, as much as +# possible. This is achieved by using userv to do the cross-account +# call, rather than a custom setuid helper. +# +# This default configuration allows the webserver user to invoke +# users' CGI programs from each user's ~/public-cgi, but to allow +# external http clients to do this, the webserver will also need to be +# configured. + +if ( grep service-user-shell /etc/shells + & glob calling-user www-data + ) + reset + no-suppress-args + no-set-environment + execute /usr/local/lib/userv/ucgi/target public-cgi +fi |