The following reply was made to PR suexec/1769; it has been noted by GNATS.

From: Gary Shea <[EMAIL PROTECTED]>
To: [EMAIL PROTECTED]
Cc: [EMAIL PROTECTED], [EMAIL PROTECTED], [EMAIL PROTECTED]
Subject: Re: suexec/1769: suexec too limited -- need per-directory control, 
more permissive directory structures
Date: Fri, 6 Feb 1998 13:30:23 -0700 (MST)

 As promised, patches.  These patches were inspired by, and to some
 extent stolen from, Philippe Vanhaesendonck's ([EMAIL PROTECTED]) 
 mod_cgi_sugid patches.  Philippe's code/concept functioned entirely
 in mod_cgi and is no longer workable in 1.3b3 (I've not looked at
 1.2.* since 1.2b10), but is the source of the UserID and GroupID
 directive support code.
 
 I have hacked mod_cgi to directly request a uid/gid from call_exec()
 if there is an applicable UserID and/or GroupID.  call_exec() respects
 directly requested uid/gid's absolutely, but if none is specified, it'll
 still detect ~user uri's and pass the appropriate uid/gid to suexec.
 Thus it's identical from the point of view of mod_include, but can
 still give mod_cgi what it wants/expects..
 
 suexec is heavily hacked to suck up a configuration file more or less
 akin to standard apache config files, but I didn't attempt to use
 apache code -- it's my proof of concept I guess, easier to code it
 for testing than to work out integrating the apache code.  As a result
 the config files are quite picky about case, etc..  Also there
 are some holes still insofar as what happens if some of the
 directives are not specified.  I won't bother to fix those holes
 unless someone else wants to use this code.
 
 The configuration file takes three directives at present;
 everything else remains compiled in.  Here's an example. 
 
 UserDirSuffix: public_html/cgi-bin
 UserDirectories: on
 Directory: /users/src/a13b3/htd3/cgi-bin
 Directory: /users/src/a13b3/htd2/cgi-bin
 Directory: /users/src/a13b3/htd5/cgi-bin
 
 I have not yet converted suexec to a simple forking server sitting
 on a Unix socket, but if ther are adverse effects from startup time,
 I will.  Since most of what I run are large Perl codes that take close
 to a second to compile, that's unlikely!
 
 Here's a clip of a configuration file which uses the UserID and GroupID
 directives:
 
 ScriptAlias /htd2/cgi-bin /users/src/a13b3/htd2/cgi-bin
 <Location /htd2/cgi-bin>
 <Limit GET POST>
 UserId          shea
 GroupId         users
 </Limit>
 </Location>
 
 And.... HEEEEEEEEEEERE'S the PATCHES!
 
 
 diff -c --exclude=*.[oa] --recursive apache_1.3b3/src/Configuration 
a13b3/src/Configuration
 *** apache_1.3b3/src/Configuration     Wed Nov 19 17:49:57 1997
 --- a13b3/src/Configuration    Wed Feb  4 22:48:13 1998
 ***************
 *** 41,47 ****
   # Settings here have priority; If not set, Configure will attempt to guess
   # the C compiler, looking for gcc first, then cc.
   #
 ! EXTRA_CFLAGS=
   EXTRA_LDFLAGS=
   EXTRA_LIBS=
   EXTRA_INCLUDES=
 --- 41,47 ----
   # Settings here have priority; If not set, Configure will attempt to guess
   # the C compiler, looking for gcc first, then cc.
   #
 ! EXTRA_CFLAGS=-DHTTPD_ROOT=\"/users/src/a13b3\" -DDEBUG_CGI 
-DDEBUG_SUGID_CONFIG  -DSUEXEC_BIN=\"/users/src/a13b3/sbin/suexec\" 
   EXTRA_LDFLAGS=
   EXTRA_LIBS=
   EXTRA_INCLUDES=
 ***************
 *** 181,192 ****
   ## STATUS=yes (see the Rules section near the start of this file) to allow
   ## full status information.  Check conf/access.conf on how to enable this.
   
 ! # AddModule modules/standard/mod_status.o
   
   ## The Info module displays configuration information for the server and 
   ## all included modules. It's very useful for debugging.
   
 ! # AddModule         modules/standard/mod_info.o
   
   ## mod_include translates server-side include (SSI) statements in text files.
   ## mod_autoindex handles requests for directories which have no index file
 --- 181,192 ----
   ## STATUS=yes (see the Rules section near the start of this file) to allow
   ## full status information.  Check conf/access.conf on how to enable this.
   
 ! AddModule modules/standard/mod_status.o
   
   ## The Info module displays configuration information for the server and 
   ## all included modules. It's very useful for debugging.
   
 ! AddModule         modules/standard/mod_info.o
   
   ## mod_include translates server-side include (SSI) statements in text files.
   ## mod_autoindex handles requests for directories which have no index file
 diff -c --exclude=*.[oa] --recursive apache_1.3b3/src/main/util_script.c 
a13b3/src/main/util_script.c
 *** apache_1.3b3/src/main/util_script.c        Sun Nov 16 08:45:22 1997
 --- a13b3/src/main/util_script.c       Fri Feb  6 08:47:03 1998
 ***************
 *** 559,567 ****
   #endif
   
   
 ! API_EXPORT(int) call_exec(request_rec *r, char *argv0, char **env, int 
shellcmd)
   {
       int pid = 0;
   #if defined(RLIMIT_CPU)  || defined(RLIMIT_NPROC) || \
       defined(RLIMIT_DATA) || defined(RLIMIT_VMEM)
   
 --- 559,569 ----
   #endif
   
   
 ! API_EXPORT(int) call_exec(request_rec *r, char *argv0, char **env, int 
shellcmd, uid_t req_uid, gid_t req_gid)
   {
       int pid = 0;
 +     int change_ids = 0; /* Fork suexec or (by default) the target command? */
 +     char *execuser, *grpname; /* Only used if change_ids gets set.  Ick. */
   #if defined(RLIMIT_CPU)  || defined(RLIMIT_NPROC) || \
       defined(RLIMIT_DATA) || defined(RLIMIT_VMEM)
   
 ***************
 *** 778,840 ****
        return (pid);
       }
   #else
 !     if (suexec_enabled &&
 !      ((r->server->server_uid != user_id) ||
 !       (r->server->server_gid != group_id) ||
 !       (!strncmp("/~", r->uri, 2)))) {
 ! 
 !      char *execuser, *grpname;
 !      struct passwd *pw;
 !      struct group *gr;
 ! 
 !      if (!strncmp("/~", r->uri, 2)) {
 !          gid_t user_gid;
 !          char *username = pstrdup(r->pool, r->uri + 2);
 !          int pos = ind(username, '/');
   
 !          if (pos >= 0)
 !              username[pos] = '\0';
   
 !          if ((pw = getpwnam(username)) == NULL) {
                aplog_error(APLOG_MARK, APLOG_ERR, r->server,
 !                          "getpwnam: invalid username %s", username);
 !              return (pid);
 !          }
 !          execuser = pstrcat(r->pool, "~", pw->pw_name, NULL);
 !          user_gid = pw->pw_gid;
 ! 
 !          if ((gr = getgrgid(user_gid)) == NULL) {
 !              if ((grpname = palloc(r->pool, 16)) == NULL)
 !                  return (pid);
 !              else
 !                  ap_snprintf(grpname, 16, "%ld", (long) user_gid);
 !          }
 !          else
 !              grpname = gr->gr_name;
 !      }
 !      else {
 !          if ((pw = getpwuid(r->server->server_uid)) == NULL) {
 !              aplog_error(APLOG_MARK, APLOG_ERR, r->server,
 !                          "getpwuid: invalid userid %ld",
 !                          (long) r->server->server_uid);
                return (pid);
            }
            execuser = pstrdup(r->pool, pw->pw_name);
   
 !          if ((gr = getgrgid(r->server->server_gid)) == NULL) {
                aplog_error(APLOG_MARK, APLOG_ERR, r->server,
 !                          "getgrgid: invalid groupid %ld",
 !                          (long) r->server->server_gid);
                return (pid);
            }
 !          grpname = gr->gr_name;
        }
   
        if (shellcmd)
 !          execle(SUEXEC_BIN, SUEXEC_BIN, execuser, grpname, argv0, NULL, env);
   
        else if ((!r->args) || (!r->args[0]) || (ind(r->args, '=') >= 0))
 !          execle(SUEXEC_BIN, SUEXEC_BIN, execuser, grpname, argv0, NULL, env);
   
        else {
            execve(SUEXEC_BIN,
 --- 780,849 ----
        return (pid);
       }
   #else
 !     if (suexec_enabled) {
 !      uid_t target_uid;
 !      gid_t target_gid;
 ! 
 !      if (req_uid > 0 || req_gid > 0) {
 !          /*
 !          *  In the cgi module, the user id and group id are requested
 !          *  independently, so provide sensible defaults in case one
 !          *  is not specified.
 !          */
 !          target_uid = req_uid > 0 ? req_uid : r->server->server_uid;
 !          target_gid = req_gid > 0 ? req_gid : r->server->server_gid;
 !      } else if (strncmp("/~", r->uri, 2) == 0) {
 !             struct passwd *pw;
 !             char *username = pstrdup(r->pool, r->uri + 2);
 !             int pos = ind(username, '/');
 ! 
 !             if (pos >= 0)
 !                 username[pos] = '\0';
 ! 
 !             if ((pw = getpwnam(username)) == NULL) {
 !                 aplog_error(APLOG_MARK, APLOG_ERR, r->server,
 !                             "getpwnam: invalid username %s", username);
 !                 exit (0);
 !             }
 !             target_uid = pw->pw_uid;
 !             target_gid = pw->pw_gid;
 !      } else {
 !          target_uid = r->server->server_uid;
 !          target_gid = r->server->server_gid;
 !      }
 ! 
 !      if (target_uid != user_id || target_gid != group_id) {
 !          struct passwd *pw;
 !          struct group *gr;
   
 !          change_ids = 1;
   
 !          if ((pw = getpwuid(target_uid)) == NULL) {
                aplog_error(APLOG_MARK, APLOG_ERR, r->server,
 !                          "getpwuid: invalid userid %ld",
 !                          (long) target_uid);
                return (pid);
            }
            execuser = pstrdup(r->pool, pw->pw_name);
   
 !          if ((gr = getgrgid(target_gid)) == NULL) {
                aplog_error(APLOG_MARK, APLOG_ERR, r->server,
 !                          "getgrgid: invalid groupid %ld",
 !                          (long) target_gid);
                return (pid);
            }
 !          grpname = pstrdup(r->pool, gr->gr_name);
        }
 +     }
   
 +     if (change_ids) {
        if (shellcmd)
 !          execle(SUEXEC_BIN,
 !                 SUEXEC_BIN, execuser, grpname, argv0, NULL, env);
   
        else if ((!r->args) || (!r->args[0]) || (ind(r->args, '=') >= 0))
 !          execle(SUEXEC_BIN,
 !                 SUEXEC_BIN, execuser, grpname, argv0, NULL, env);
   
        else {
            execve(SUEXEC_BIN,
 ***************
 *** 842,849 ****
                               argv0, r->args),
                   env);
        }
 !     }
 !     else {
        if (shellcmd)
            execle(SHELL_PATH, SHELL_PATH, "-c", argv0, NULL, env);
   
 --- 851,857 ----
                               argv0, r->args),
                   env);
        }
 !     } else {
        if (shellcmd)
            execle(SHELL_PATH, SHELL_PATH, "-c", argv0, NULL, env);
   
 diff -c --exclude=*.[oa] --recursive apache_1.3b3/src/main/util_script.h 
a13b3/src/main/util_script.h
 *** apache_1.3b3/src/main/util_script.h        Wed Oct 22 14:29:53 1997
 --- a13b3/src/main/util_script.h       Fri Feb  6 08:48:53 1998
 ***************
 *** 67,70 ****
   API_EXPORT(int) scan_script_header_err(request_rec *r, FILE *f, char 
*buffer);
   API_EXPORT(int) scan_script_header_err_buff(request_rec *r, BUFF *f, char 
*buffer);
   API_EXPORT(void) send_size(size_t size, request_rec *r);
 ! API_EXPORT(int) call_exec(request_rec *r, char *argv0, char **env, int 
shellcmd);
 --- 67,70 ----
   API_EXPORT(int) scan_script_header_err(request_rec *r, FILE *f, char 
*buffer);
   API_EXPORT(int) scan_script_header_err_buff(request_rec *r, BUFF *f, char 
*buffer);
   API_EXPORT(void) send_size(size_t size, request_rec *r);
 ! API_EXPORT(int) call_exec(request_rec *r, char *argv0, char **env, int 
shellcmd, uid_t request_uid, gid_t request_gid);
 Only in a13b3/src/modules: CVS
 Only in a13b3/src/modules: Makefile
 Only in a13b3/src/modules/standard: CVS
 Only in a13b3/src/modules/standard: Makefile
 diff -c --exclude=*.[oa] --recursive 
apache_1.3b3/src/modules/standard/mod_cgi.c a13b3/src/modules/standard/mod_cgi.c
 *** apache_1.3b3/src/modules/standard/mod_cgi.c        Fri Nov  7 18:20:02 1997
 --- a13b3/src/modules/standard/mod_cgi.c       Fri Feb  6 08:47:43 1998
 ***************
 *** 99,104 ****
 --- 99,111 ----
       int bufbytes;
   } cgi_server_conf;
   
 + typedef struct
 + {
 +     uid_t userid;
 +     gid_t groupid;
 + } cgi_dir_conf;
 + 
 + 
   static void *create_cgi_config(pool *p, server_rec *s)
   {
       cgi_server_conf *c =
 ***************
 *** 118,123 ****
 --- 125,144 ----
       return overrides->logname ? overrides : base;
   }
   
 + void *create_cgi_dir_config (pool *p, char *dummy)
 + {
 +     cgi_dir_conf *c = (cgi_dir_conf *) palloc (p,sizeof(cgi_dir_conf));
 + 
 + #   ifdef DEBUG_SUGID_CONFIG
 +       fprintf(stderr,"Create dir config: %s\n",dummy ? dummy : "<NULL>");
 + #   endif
 + 
 +     c->userid = 0;
 +     c->groupid = 0;
 + 
 +     return (void *) c;
 + }
 + 
   static const char *set_scriptlog(cmd_parms *cmd, void *dummy, char *arg)
   {
       server_rec *s = cmd->server;
 ***************
 *** 148,153 ****
 --- 169,219 ----
       return NULL;
   }
   
 + const char *set_userid(cmd_parms *cmd,cgi_dir_conf *conf, char *user) {
 +     struct passwd *ent;
 + 
 + #   ifdef DEBUG_SUGID_CONFIG
 +       fprintf(stderr,"Add UserId: %s\n",user);
 +       fprintf(stderr,"\tPrevious value: %d\n",conf->userid);
 +       fprintf(stderr,"\tConfig File   : %s\n",cmd->config_file->name);
 +       fprintf(stderr,"\tPath    : %s\n",
 +         cmd->path ? cmd->path : "<NULL>");
 + #   endif
 + 
 +     if (user[0] == '#') {
 +       conf->userid = atoi(&user[1]);
 +       return NULL;
 +     } else if (!(ent = getpwnam(user))) {
 +       return "Invalid User Name";
 +     } else {
 +       conf->userid = ent->pw_uid;
 +       return NULL;
 +     }
 + }
 + 
 + const char *set_groupid(cmd_parms *cmd,cgi_dir_conf *conf, char *grp)
 + {
 +     struct group *ent;
 + 
 + #   ifdef DEBUG_SUGID_CONFIG
 +       fprintf(stderr,"Add GroupId: %s\n",grp);
 +       fprintf(stderr,"\tPrevious value: %d\n",conf->groupid);
 +       fprintf(stderr,"\tConfig File   : %s\n",cmd->config_file->name);
 +       fprintf(stderr,"\tPath    : %s\n",
 +         cmd->path ? cmd->path : "<NULL>");
 + #   endif
 + 
 +     if (grp[0] == '#') {
 +       conf->groupid = atoi(&grp[1]);
 +       return NULL;
 +     } else if (!(ent = getgrnam(grp))) {
 +       return "Invalid Group Name";
 +     } else {
 +       conf->groupid = ent->gr_gid;
 +       return NULL;
 +     }
 + }
 + 
   static command_rec cgi_cmds[] =
   {
       {"ScriptLog", set_scriptlog, NULL, RSRC_CONF, TAKE1,
 ***************
 *** 156,161 ****
 --- 222,231 ----
        "the maximum length (in bytes) of the script debug log"},
       {"ScriptLogBuffer", set_scriptlog_buffer, NULL, RSRC_CONF, TAKE1,
        "the maximum size (in bytes) to record of a POST request"},
 +     { "UserId", set_userid, NULL, RSRC_CONF | ACCESS_CONF, TAKE1,
 +       "a UserName or #UserId"},
 +     { "GroupId", set_groupid, NULL, RSRC_CONF | ACCESS_CONF, TAKE1,
 +       "a GroupName or #GroupId"},
       {NULL}
   };
   
 ***************
 *** 281,286 ****
 --- 351,361 ----
       request_rec *r = cld->r;
       char *argv0 = cld->argv0;
       int child_pid;
 +     cgi_dir_conf *conf = (cgi_dir_conf *)
 +       get_module_config (r->per_dir_config,&cgi_module);
 +     uid_t request_uid = 0;
 +     gid_t request_gid = 0;
 + 
   
   #ifdef DEBUG_CGI
   #ifdef __EMX__
 ***************
 *** 296,303 ****
   
       RAISE_SIGSTOP(CGI_CHILD);
   #ifdef DEBUG_CGI
 !     fprintf(dbg, "Attempting to exec %s as %sCGI child (argv0 = %s)\n",
 !          r->filename, nph ? "NPH " : "", argv0);
   #endif
   
       add_cgi_vars(r);
 --- 371,378 ----
   
       RAISE_SIGSTOP(CGI_CHILD);
   #ifdef DEBUG_CGI
 !     fprintf(dbg, "%s (uri=<%s>) as CGI child (argv0 = %s)\n",
 !          r->filename, r->uri, argv0);
   #endif
   
       add_cgi_vars(r);
 ***************
 *** 309,314 ****
 --- 384,417 ----
        fprintf(dbg, "'%s'\n", env[i]);
   #endif
   
 + 
 + #if ! defined(__EMX__) && ! defined(WIN32)
 +     /*
 +     * See under which uid we will run the request.
 +     * If there's a UserId or GroupId available, use those.
 +     * The request_[ug]id args to call_exec are set non-zero
 +     * to override the per-virtual or overall-server defaults.
 +     * If they're left 0, 0, they will be set (in main/util_script.c)
 +     * to a user's uid/gid if this is a ~user uri and suexec
 +     * is enabled.
 +     */
 +     if (suexec_enabled) {
 +      if (conf) {
 +          if (conf->userid > 0) {
 +              request_uid = conf->userid;
 +          }
 +          if (conf->groupid > 0) {
 +              request_gid = conf->groupid;
 +          }
 +      }
 + #ifdef DEBUG_SUGID_CONFIG
 +      fprintf(dbg, "sugid parameters: request_uid=%d request_gid=%d\n",
 +              request_uid, request_gid);
 + #endif
 + 
 +     }
 + #endif
 + 
       chdir_file(r->filename);
       if (!cld->debug)
        error_log2stderr(r->server);
 ***************
 *** 319,325 ****
   
       cleanup_for_exec();
   
 !     child_pid = call_exec(r, argv0, env, 0);
   #ifdef WIN32
       return (child_pid);
   #else
 --- 422,428 ----
   
       cleanup_for_exec();
   
 !     child_pid = call_exec(r, argv0, env, 0, request_uid, request_gid);
   #ifdef WIN32
       return (child_pid);
   #else
 ***************
 *** 551,557 ****
   {
       STANDARD_MODULE_STUFF,
       NULL,                    /* initializer */
 !     NULL,                    /* dir config creater */
       NULL,                    /* dir merger --- default is to override */
       create_cgi_config,               /* server config */
       merge_cgi_config,                /* merge server config */
 --- 654,660 ----
   {
       STANDARD_MODULE_STUFF,
       NULL,                    /* initializer */
 !     create_cgi_dir_config,   /* dir config creater */
       NULL,                    /* dir merger --- default is to override */
       create_cgi_config,               /* server config */
       merge_cgi_config,                /* merge server config */
 diff -c --exclude=*.[oa] --recursive 
apache_1.3b3/src/modules/standard/mod_include.c 
a13b3/src/modules/standard/mod_include.c
 *** apache_1.3b3/src/modules/standard/mod_include.c    Sun Nov  9 13:40:34 1997
 --- a13b3/src/modules/standard/mod_include.c   Fri Feb  6 08:45:57 1998
 ***************
 *** 733,739 ****
   #endif
       cleanup_for_exec();
       /* set shellcmd flag to pass arg to SHELL_PATH */
 !     child_pid = call_exec(r, s, create_environment(r->pool, env), 1);
   #ifdef WIN32
       return (child_pid);
   #else
 --- 733,739 ----
   #endif
       cleanup_for_exec();
       /* set shellcmd flag to pass arg to SHELL_PATH */
 !     child_pid = call_exec(r, s, create_environment(r->pool, env), 1, 0, 0);
   #ifdef WIN32
       return (child_pid);
   #else
 diff -c --exclude=*.[oa] --recursive apache_1.3b3/src/support/suexec.c 
a13b3/src/support/suexec.c
 *** apache_1.3b3/src/support/suexec.c  Wed Oct 22 14:30:46 1997
 --- a13b3/src/support/suexec.c Fri Feb  6 07:43:58 1998
 ***************
 *** 112,117 ****
 --- 112,118 ----
   
   extern char **environ;
   static FILE *log;
 + static int debug_flag = 1;
   
   char *safe_env_lst[] =
   {
 ***************
 *** 155,160 ****
 --- 156,174 ----
       NULL
   };
   
 + typedef struct dir_list {
 +     char *dir;
 +     struct dir_list *next;
 + } dir_list;
 + 
 + typedef struct {
 +     int user_dir_on;
 +     char *user_dir_suff;
 +     dir_list *dirs[2];
 + } suexec_attribs;
 + 
 + suexec_attribs attr;
 + 
   
   static void err_output(const char *fmt, va_list ap)
   {
 ***************
 *** 194,199 ****
 --- 208,226 ----
       return;
   }
   
 + void debug (const char *fmt,...) {
 + #ifdef LOG_EXEC
 +     if (debug_flag) {
 +      va_list ap;
 + 
 +      va_start(ap, fmt);
 +      err_output(fmt, ap);
 +      va_end(ap);
 +     }
 + #endif /* LOG_EXEC */
 +     return;
 + }
 + 
   void clean_env()
   {
       char pathbuf[512];
 ***************
 *** 231,236 ****
 --- 258,443 ----
       environ = cleanenv;
   }
   
 + void load_setup_file () {
 +     char pathbuf[512];
 +     char linebuf[512];
 +     int linecount = 0;
 +     FILE *fp;
 +     struct stat dir_info;    /* directives directory info holder */
 +     struct stat file_info;   /* directives file info holder */
 + 
 +     attr.user_dir_on = 0;
 +     attr.user_dir_suff = 0;
 +     attr.dirs[0] = attr.dirs[1] = 0;
 + 
 +     /*
 +      * Stat the cwd and verify it is a directory, or error out.
 +      */
 +     if (((lstat(DIRECTIVE_DIR, &dir_info)) != 0)
 +          || !(S_ISDIR(dir_info.st_mode))) {
 +      log_err("cannot stat directory: (%s)\n", DIRECTIVE_DIR);
 +      exit(130);
 +     }
 + 
 +     /*
 +      * Error out if DIRECTIVE_DIR is writable by others.
 +      */
 +     if ((dir_info.st_mode & S_IWOTH) || (dir_info.st_mode & S_IWGRP)) {
 +      log_err("directory is writable by others: (%s)\n", DIRECTIVE_DIR);
 +      exit(131);
 +     }
 + 
 +     strcpy (pathbuf, DIRECTIVE_DIR);
 +     strcat (pathbuf, "/");
 +     strcat (pathbuf, DIRECTIVE_FILENAME);
 + 
 +     /*
 +      * Error out if we cannot stat the directives file.
 +      */
 +     if (((lstat(pathbuf, &file_info)) != 0) || (S_ISLNK(file_info.st_mode))) 
{
 +      log_err("cannot stat directives file: (%s)\n", pathbuf);
 +      exit(132);
 +     }
 + 
 +     /*
 +      * Error out if the directives file is writable by others.
 +      */
 +     if ((file_info.st_mode & S_IWOTH) || (file_info.st_mode & S_IWGRP)) {
 +      log_err("file is writable by others: (%s)\n", pathbuf);
 +      exit(133);
 +     }
 + 
 +     if ((fp = fopen (pathbuf, "r")) == 0) {
 +      log_err("failed to open directives file: (%s)\n", pathbuf);
 +      exit(134);
 +     }
 + 
 +     while (fgets (linebuf, 512, fp)) {
 +         char *key = linebuf;
 +      char *val;
 +      char *cp;
 +      int len = strlen (linebuf);
 + 
 +      ++linecount;
 + 
 +      /*
 +      *  Get rid of that trailing newline!
 +      *  If there isn't one, then the line is too long.
 +      */
 +      if (linebuf [len - 1] == '\n') {
 +          linebuf [len - 1] = '\0';
 +      } else {
 +          log_err("line %d: line too long: (%s)\n", linecount, linebuf);
 +          exit(135);
 +      }
 + 
 +         /*
 +      *  Directives may not extend across newline boundaries.
 +      *  Honor '#' signs as comment delimiters.
 +      *
 +      *  First strip off all leading white space,
 +      *  determine the directive, isolate the value + trailing stuff,
 +      *  then step back from the tail getting rid of trailing
 +      *  comments and white space.
 +      */
 +      while (*key && isspace (*key)) {
 +          ++key;
 +      }
 +      if (! *key || *key == '#') {
 +          continue;
 +      }
 +      debug ("beginning of key: <%s>\n", key);
 + 
 +      /*
 +      * That should be the beginning of the directive.
 +      * Find the first whitespace or ':' and set it null
 +      * to isolate the directive.
 +      */
 +      cp = key;
 +      while (*cp && *cp != ':' && ! isspace (*cp)) {
 +          ++cp;
 +      }
 +      if (! *cp) {
 +          log_err("line %d: end of directive not found: (%s)\n",
 +              linecount, linebuf);
 +          exit(136);
 +      }
 +      *cp = '\0';
 +      debug ("finished key: <%s>\n", key);
 + 
 +      /*
 +      *  Now find the beginning of the value, which is the
 +      * next non-null, non-colon character.
 +      */
 +      ++cp;
 +      while (*cp && (*cp == ':' || isspace (*cp))) {
 +          ++cp;
 +      }
 +      if (! *cp) {
 +          log_err("line %d: beginning of value not found: (%s)\n",
 +              linecount, linebuf);
 +          exit(137);
 +      }
 +      val = cp;
 +      debug ("beginning of value: <%s>\n", val);
 + 
 +      /*
 +      *  If there's a comment, get rid of it.
 +      *  Then strip off trailing whitespace.
 +      */
 +      if (cp = strchr (val, '#')) {
 +          *cp = '\0';
 +      }
 +      cp = val + strlen (val) - 1;
 +      while (cp > val && isspace (*cp)) {
 +          *cp = '\0';
 +          --cp;
 +      }
 +      debug ("val w/o trailing comments: <%s>\n", val);
 + 
 +      /*
 +      *  Ok, it's a take.
 +      *  Directory directives get tacked onto the
 +      *  dirs list; the others go to their specific
 +      *  suexec_attribs field.
 +      */
 + 
 +      if (strcmp (key, "Directory") == 0) {
 +          dir_list *dl;
 +          if (! (dl = malloc (sizeof (dir_list)))) {
 +              log_err("line %d: out of memory\n", linecount);
 +              exit(138);
 +          }
 +          dl->next = 0;
 +          if (! (dl->dir = strdup (val))) {
 +              log_err("line %d: out of memory\n", linecount);
 +              exit(139);
 +          }
 +          if (! attr.dirs[0]) {
 +              attr.dirs[0] = attr.dirs[1] = dl;
 +          } else {
 +              attr.dirs[1]->next = dl;
 +              attr.dirs[1] = dl;
 +          }
 +      } else if (strcmp (key, "UserDirectories") == 0) {
 +          if (strcmp (val, "on") == 0) {
 +              attr.user_dir_on = 1;
 +          } else {
 +              attr.user_dir_on = 0;
 +          }
 +      } else if (strcmp (key, "UserDirSuffix") == 0) {
 +          if (! (attr.user_dir_suff = strdup (val))) {
 +              log_err("line %d: out of memory\n", linecount);
 +              exit(140);
 +          }
 +      } else {
 +          log_err("line %d: unknown directive: (%s)\n",
 +              linecount, key);
 +          exit(141);
 +      }
 +     }
 + }
 + 
   int main(int argc, char *argv[])
   {
       int userdir = 0;         /* ~userdir flag             */
 ***************
 *** 249,254 ****
 --- 456,462 ----
       struct group *gr;                /* group entry holder        */
       struct stat dir_info;    /* directory info holder     */
       struct stat prg_info;    /* program info holder       */
 +     int dirfound = 0;
   
   
   
 ***************
 *** 264,269 ****
 --- 472,479 ----
       target_uname = argv[1];
       target_gname = argv[2];
       cmd = argv[3];
 +     debug ("uname=<%s> gname=<%s> cmd=<%s>\n",
 +         target_uname, target_gname, cmd);
   
       /*
        * Check existence/validity of the UID of the user
 ***************
 *** 286,313 ****
       }
   
       /*
 !      * Check for a leading '/' (absolute path) in the command to be executed,
 !      * or attempts to back up out of the current directory,
        * to protect against attacks.  If any are
        * found, error out.  Naughty naughty crackers.
        */
 !     if ((cmd[0] == '/') || (!strncmp(cmd, "../", 3))
        || (strstr(cmd, "/../") != NULL)) {
           log_err("invalid command (%s)\n", cmd);
        exit(104);
       }
   
       /*
 -      * Check to see if this is a ~userdir request.  If
 -      * so, set the flag, and remove the '~' from the
 -      * target username.
 -      */
 -     if (!strncmp("~", target_uname, 1)) {
 -      target_uname++;
 -      userdir = 1;
 -     }
 - 
 -     /*
        * Error out if the target username is invalid.
        */
       if ((pw = getpwnam(target_uname)) == NULL) {
 --- 496,512 ----
       }
   
       /*
 !      * Check for attempts to back up out of the current directory,
        * to protect against attacks.  If any are
        * found, error out.  Naughty naughty crackers.
        */
 !     if ((!strncmp(cmd, "../", 3))
        || (strstr(cmd, "/../") != NULL)) {
           log_err("invalid command (%s)\n", cmd);
        exit(104);
       }
   
       /*
        * Error out if the target username is invalid.
        */
       if ((pw = getpwnam(target_uname)) == NULL) {
 ***************
 *** 372,378 ****
        * and setgid() to the target group. If unsuccessful, error out.
        */
       if (((setgid(gid)) != 0) || (initgroups(actual_uname, gid) != 0)) {
 !      log_err("failed to setgid (%ld: %s/%s)\n", gid, cwd, cmd);
        exit(109);
       }
   
 --- 571,577 ----
        * and setgid() to the target group. If unsuccessful, error out.
        */
       if (((setgid(gid)) != 0) || (initgroups(actual_uname, gid) != 0)) {
 !      log_err("failed to setgid (%ld: %s)\n", gid, cmd);
        exit(109);
       }
   
 ***************
 *** 380,394 ****
        * setuid() to the target user.  Error out on fail.
        */
       if ((setuid(uid)) != 0) {
 !      log_err("failed to setuid (%ld: %s/%s)\n", uid, cwd, cmd);
        exit(110);
       }
   
       /*
 !      * Get the current working directory, as well as the proper
 !      * document root (dependant upon whether or not it is a
 !      * ~userdir request).  Error out if we cannot get either one,
 !      * or if the current working directory is not in the docroot.
        * Use chdir()s and getcwd()s to avoid problems with symlinked
        * directories.  Yuck.
        */
 --- 579,590 ----
        * setuid() to the target user.  Error out on fail.
        */
       if ((setuid(uid)) != 0) {
 !      log_err("failed to setuid (%ld: %s)\n", uid, cmd);
        exit(110);
       }
   
       /*
 !      * Get the current working directory.
        * Use chdir()s and getcwd()s to avoid problems with symlinked
        * directories.  Yuck.
        */
 ***************
 *** 396,424 ****
        log_err("cannot get current working directory\n");
        exit(111);
       }
 ! 
 !     if (userdir) {
 !      if (((chdir(target_homedir)) != 0) ||
 !          ((chdir(USERDIR_SUFFIX)) != 0) ||
 !          ((getcwd(dwd, AP_MAXPATH)) == NULL) ||
 !          ((chdir(cwd)) != 0)) {
 !          log_err("cannot get docroot information (%s)\n", target_homedir);
 !          exit(112);
 !      }
 !     }
 !     else {
 !      if (((chdir(DOC_ROOT)) != 0) ||
 !          ((getcwd(dwd, AP_MAXPATH)) == NULL) ||
 !          ((chdir(cwd)) != 0)) {
 !          log_err("cannot get docroot information (%s)\n", DOC_ROOT);
 !          exit(113);
 !      }
 !     }
 ! 
 !     if ((strncmp(cwd, dwd, strlen(dwd))) != 0) {
 !      log_err("command not in docroot (%s/%s)\n", cwd, cmd);
 !      exit(114);
 !     }
   
       /*
        * Stat the cwd and verify it is a directory, or error out.
 --- 592,598 ----
        log_err("cannot get current working directory\n");
        exit(111);
       }
 !     debug ("cwd=<%s>\n", cwd);
   
       /*
        * Stat the cwd and verify it is a directory, or error out.
 ***************
 *** 434,439 ****
 --- 608,664 ----
       if ((dir_info.st_mode & S_IWOTH) || (dir_info.st_mode & S_IWGRP)) {
        log_err("directory is writable by others: (%s)\n", cwd);
        exit(116);
 +     }
 + 
 +     /*
 +      * Load the setup file as late as possible.
 +      */
 +     load_setup_file ();
 + 
 +     /*
 +     *  Search the directory lists to determine if the command
 +     *  is in a legal directory.  The current directory is the
 +     *  directory where the command is supposed to be.  If the
 +     *  current directory is the same as, or a subdirectory of,
 +     *  one of the list directories, then it's ok to exec it.
 +     */
 + 
 +     if (attr.user_dir_on) {
 +      char td[AP_MAXPATH];    /* test directory */
 +         /*
 +      *  Construct the home directory for the specified
 +      *  user.
 +      */
 +      strcpy (td, target_homedir);
 +      strcat (td, "/");
 +      strcat (td, attr.user_dir_suff);
 +      debug ("check cwd=<%s> against user dir <%s>\n", cwd, td);
 +      if (strncmp (cwd, td, strlen (td)) == 0) {
 +          ++dirfound;
 +      }
 +     }
 +     if (! dirfound) {
 +         dir_list *dl = attr.dirs[0];
 +         /*
 +      *  Step through the directories which suexec is
 +      *  allowed to work in, looking for a prefix match
 +      *  with the current directory.
 +      */
 +      while (dl) {
 +          debug ("check cwd=<%s> against <%s>\n", cwd, dl->dir);
 +          if (strncmp (cwd, dl->dir, strlen (dl->dir)) == 0) {
 +              ++dirfound;
 +              break;
 +          } else {
 +              dl = dl->next;
 +          }
 +      }
 +     }
 + 
 +     if (! dirfound) {
 +      log_err("unable to validate directory (%s) for user (%s), "
 +           "group (%s), cmd (%s)\n", cwd, target_uname, target_gname, cmd);
 +      exit(121);
       }
   
       /*
 diff -c --exclude=*.[oa] --recursive apache_1.3b3/src/support/suexec.h 
a13b3/src/support/suexec.h
 *** apache_1.3b3/src/support/suexec.h  Sat Oct 25 16:35:20 1997
 --- a13b3/src/support/suexec.h Wed Feb  4 23:45:28 1998
 ***************
 *** 65,71 ****
    *               this program.
    */
   #ifndef HTTPD_USER
 ! #define HTTPD_USER "www"
   #endif
   
   /*
 --- 65,71 ----
    *               this program.
    */
   #ifndef HTTPD_USER
 ! #define HTTPD_USER "nobody"
   #endif
   
   /*
 ***************
 *** 115,121 ****
    *             debugging purposes.
    */
   #ifndef LOG_EXEC
 ! #define LOG_EXEC "/usr/local/apache/logs/cgi.log"    /* Need me? */
   #endif
   
   /*
 --- 115,121 ----
    *             debugging purposes.
    */
   #ifndef LOG_EXEC
 ! #define LOG_EXEC "/users/src/a13b3/logs/cgi.log"     /* Need me? */
   #endif
   
   /*
 ***************
 *** 133,138 ****
 --- 133,154 ----
    */
   #ifndef SAFE_PATH
   #define SAFE_PATH "/usr/local/bin:/usr/bin:/bin"
 + #endif
 + 
 + /*
 +  * DIRECTIVE_DIR -- Path to the directives file.
 +  *
 +  */
 + #ifndef DIRECTIVE_DIR
 + #define DIRECTIVE_DIR "/users/src/a13b3/conf"
 + #endif
 + 
 + /*
 +  * DIRECTIVE_FILENAME -- Name of the directives file.
 +  *
 +  */
 + #ifndef DIRECTIVE_FILENAME
 + #define DIRECTIVE_FILENAME "suexec.conf"
   #endif
   
   #endif /* _SUEXEC_H */
 
 
 
 

Reply via email to