Mailing List Archive

cvs commit: apache/support suexec.c suexec.h
randy 96/12/27 14:21:25

Modified: htdocs/manual suexec.html
Log:
Update suEXEC docs to reflect recent changes.

Revision Changes Path
1.6 +3 -12 apache/htdocs/manual/suexec.html

Index: suexec.html
===================================================================
RCS file: /export/home/cvs/apache/htdocs/manual/suexec.html,v
retrieving revision 1.5
retrieving revision 1.6
diff -C3 -r1.5 -r1.6
*** suexec.html 1996/12/05 06:26:13 1.5
--- suexec.html 1996/12/27 22:15:46 1.6
***************
*** 54,71 ****
#define DOC_ROOT "/usr/local/etc/httpd/htdocs"

/*
! * NNAME -- Define this as the name for the nobody account
! * on your operating system. Most systems will just
! * need the default 'nobody'.
*/
! #define NNAME "nobody"
!
! /* NGID -- Define this as the *number* for the nogroup group
! * on your operating system. Most systems will have
! * a -1 or -2. Others might have something above
! * 65000.
! */
! #define NGID -1
</pre>
</code>

--- 54,63 ----
#define DOC_ROOT "/usr/local/etc/httpd/htdocs"

/*
! * SAFE_PATH -- Define a safe PATH environment to pass to CGI executables.
! *
*/
! #define SAFE_PATH "/usr/local/bin:/usr/bin:/bin"
</pre>
</code>

***************
*** 122,128 ****
<li>The target UID and GID <b>must be a valid user and group on this system</b>.
<li>The target UID and GID to execute as, <b>must match the UID and GID of the directory</b>.
<li>The target execution UID and GID <b>must not be the privledged ID 0</b>.
- <li>Group access list is set to NOGROUP and the command is executed.
</ol>
If any of these issues are too restrictive, or do not seem restrictive enough, you are
welcome to install your own version of the wrapper. We've given you the rope, now go
--- 114,119 ----




Modified: support suexec.c suexec.h
Log:
Properly fix DOCROOT matching code so that it can resolve symlinked homedirs.
Fix off by one month error when logging requests.
Nuke the PATH environment values and set to some sane value.
initgroups to the target user and group instead of trying unsuccessfully
to prune it.
Generally cleanup to make code match official Apache style.
Reviewed by: Jason Dour, Randy Terbush
Submitted by: Jason Dour, Randy Terbush

Revision Changes Path
1.8 +120 -134 apache/support/suexec.c

Index: suexec.c
===================================================================
RCS file: /export/home/cvs/apache/support/suexec.c,v
retrieving revision 1.7
retrieving revision 1.8
diff -C3 -r1.7 -r1.8
*** suexec.c 1996/12/24 18:28:52 1.7
--- suexec.c 1996/12/27 22:21:23 1.8
***************
*** 63,100 ****
*
***********************************************************************
*
- * Codebase originally from Majordomo(v1.93) release.
- * Heavy modifications by:
- * Jason A. Dour (jad@bcc.louisville.edu)
- * Randy Terbush (randy@zyzzyva.com)
*
- * Version 0.1.0 - Jason A. Dour
- * First beta. Removed HAVE_RLIMIT and related rlimit code
- * now that the server handles the funcitonality. Moved user-
- * defined code to suexec.h. Added "DON'T EDIT" warning in code.
- * No more "security by obscurity"...comments added at each step.
- *
- * Version 0.0.3 - Jason A. Dour
- * Third alpha. Added NNAME and NGID directives to fix
- * portability problem -- various systems have different
- * values for nobody and nogroup. Yuck. Submitted to the
- * Apache development group for consideration.
- *
- * Version 0.0.2 - Jason A. Dour
- * Second alpha. Cleaned up code and comments. Fixed some
- * test-case bugs. Cleaned up and locked down the groups access
- * list. Fixed setgid behaviour. Added more paranoia checks.
- * Added ~userid support. Cleaned up exit codes.
- *
- * Version 0.0.1 - Randy Terbush
- * First assigned version. Heavily modified to act as generic
- * SUID wrapper for Apache. Submitted to the Apache development
- * group for consideration.
- *
- * Version Primordial Ooze - Jason A. Dour
- * First version. Heavily modified from MDomo source. Acted
- * only for ~userdir requests out of the ~userdir/cgi-bin dir. Not
- * extremely useful...but it worked.
*/


--- 63,69 ----
***************
*** 115,151 ****

static FILE *log;

! static void err_output (const char *fmt, va_list ap)
{
time_t timevar;
struct tm *lt;

if (!log)
! if ((log = fopen (LOG_EXEC, "a")) == NULL)
! {
! fprintf (stderr, "failed to open log file\n");
! perror ("fopen");
}

! time (&timevar);
! lt = localtime (&timevar);

! fprintf (log, "[%.2d:%.2d:%.2d %.2d-%.2d-%.2d]: ", lt->tm_hour, lt->tm_min,
! lt->tm_sec, lt->tm_mday, lt->tm_mon, lt->tm_year);

! vfprintf (log, fmt, ap);

! fflush (log);
return;
}

! void log_err (const char *fmt, ...)
{
#ifdef LOG_EXEC
va_list ap;

! va_start (ap, fmt);
! err_output (fmt, ap);
va_end(ap);
#endif /* LOG_EXEC */
return;
--- 84,120 ----

static FILE *log;

! static void err_output(const char *fmt, va_list ap)
{
time_t timevar;
struct tm *lt;

if (!log)
! if ((log = fopen(LOG_EXEC, "a")) == NULL) {
! fprintf(stderr, "failed to open log file\n");
! perror("fopen");
! exit(1);
}

! time(&timevar);
! lt = localtime(&timevar);

! fprintf(log, "[%.2d:%.2d:%.2d %.2d-%.2d-%.2d]: ", lt->tm_hour, lt->tm_min,
! lt->tm_sec, lt->tm_mday, (lt->tm_mon + 1), lt->tm_year);

! vfprintf(log, fmt, ap);

! fflush(log);
return;
}

! void log_err(const char *fmt, ...)
{
#ifdef LOG_EXEC
va_list ap;

! va_start(ap, fmt);
! err_output(fmt, ap);
va_end(ap);
#endif /* LOG_EXEC */
return;
***************
*** 153,167 ****

int main(int argc, char *argv[], char **env)
{
! int homelen; /* length of homedir path */
! int cgilen; /* length of cgidir path */
int userdir = 0; /* ~userdir flag */
uid_t uid; /* user information */
char *target_uname; /* target user name */
char *target_gname; /* target group name */
char *prog; /* name of this program */
char *cmd; /* command to be executed */
! char *cwd; /* current working directory */
struct passwd *pw; /* password entry holder */
struct group *gr; /* group entry holder */
struct stat dir_info; /* directory info holder */
--- 122,137 ----

int main(int argc, char *argv[], char **env)
{
! int doclen; /* length of the docroot */
int userdir = 0; /* ~userdir flag */
uid_t uid; /* user information */
+ gid_t gid; /* target group placeholder */
char *target_uname; /* target user name */
char *target_gname; /* target group name */
char *prog; /* name of this program */
char *cmd; /* command to be executed */
! char cwd[MAXPATHLEN]; /* current working directory */
! char dwd[MAXPATHLEN]; /* docroot working directory */
struct passwd *pw; /* password entry holder */
struct group *gr; /* group entry holder */
struct stat dir_info; /* directory info holder */
***************
*** 174,182 ****
* all of them to variables. Otherwise, error out.
*/
prog = argv[0];
! if (argc < 4)
! {
! log_err ("too few arguments\n");
exit(101);
}
target_uname = argv[1];
--- 144,151 ----
* all of them to variables. Otherwise, error out.
*/
prog = argv[0];
! if (argc < 4) {
! log_err("too few arguments\n");
exit(101);
}
target_uname = argv[1];
***************
*** 188,197 ****
* running this program. Error out if invalid.
*/
uid = getuid();
! if ((pw = getpwuid (uid)) == NULL)
! {
! log_err ("invalid uid: (%ld)\n", uid);
! exit (102);
}

/*
--- 157,165 ----
* running this program. Error out if invalid.
*/
uid = getuid();
! if ((pw = getpwuid(uid)) == NULL) {
! log_err("invalid uid: (%ld)\n", uid);
! exit(102);
}

/*
***************
*** 199,208 ****
* is the user allowed to do so as defined in
* suexec.h. If not the allowed user, error out.
*/
! if (strcmp (HTTPD_USER, pw->pw_name))
! {
! log_err ("user mismatch (%s)\n", pw->pw_name);
! exit (103);
}

/*
--- 167,175 ----
* is the user allowed to do so as defined in
* suexec.h. If not the allowed user, error out.
*/
! if (strcmp(HTTPD_USER, pw->pw_name)) {
! log_err("user mismatch (%s)\n", pw->pw_name);
! exit(103);
}

/*
***************
*** 210,219 ****
* to protect against attacks. If a '/' is
* found, error out. Naughty naughty crackers.
*/
! if ((strchr (cmd, '/')) != NULL )
! {
! log_err ("invalid command (%s)\n", cmd);
! exit (104);
}

/*
--- 177,185 ----
* to protect against attacks. If a '/' is
* found, error out. Naughty naughty crackers.
*/
! if ((strchr(cmd, '/')) != NULL ) {
! log_err("invalid command (%s)\n", cmd);
! exit(104);
}

/*
***************
*** 221,228 ****
* so, set the flag, and remove the '~' from the
* target username.
*/
! if (!strncmp( "~", target_uname, 1))
! {
target_uname++;
userdir = 1;
}
--- 187,193 ----
* so, set the flag, and remove the '~' from the
* target username.
*/
! if (!strncmp("~", target_uname, 1)) {
target_uname++;
userdir = 1;
}
***************
*** 230,318 ****
/*
* Error out if the target username is invalid.
*/
! if ((pw = getpwnam (target_uname)) == NULL )
! {
! log_err ("invalid target user name: (%s)\n", target_uname);
! exit (105);
}

/*
* Error out if the target group name is invalid.
*/
! if ((gr = getgrnam (target_gname)) == NULL )
! {
! log_err ("invalid target group name: (%s)\n", target_gname);
! exit (106);
}

/*
! * 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 command is
! * not in the docroot.
! */
! if (userdir)
! {
! homelen = strlen(pw->pw_dir) + 1;
! cgilen = strlen(USER_CGI_BIN) + 1;
! strncpy(cwd, pw->pw_dir, (homelen < MAXPATHLEN ? homelen : MAXPATHLEN));
! homelen = MAXPATHLEN - homelen - 1; /* get space left */
! strncat(cwd, USER_CGI_BIN, (cgilen < homelen ? cgilen : homelen));
}
else
! strncpy(cwd, DOC_ROOT, MAXPATHLEN);
!

- if (!(chdir(cwd))) {
- log_err("cannot chdir directory: (%s)\n", cwd);
- exit(107);
- }

/*
* Stat the cwd and verify it is a directory, or error out.
*/
! if ( (lstat (cwd, &dir_info)) ||
! !(S_ISDIR(dir_info.st_mode)) )
! {
! log_err ("cannot stat directory: (%s)\n", cwd);
! exit (108);
}

/*
* Error out if cwd 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", cwd);
! exit (109);
}

/*
* Error out if we cannot stat the program.
*/
! if ((lstat (cmd, &prg_info)) || (S_ISLNK(prg_info.st_mode)))
! {
! log_err ("cannot stat program: (%s)\n", cmd);
! exit (110);
}

/*
* Error out if the program is writable by others.
*/
! if ((prg_info.st_mode & S_IWOTH) || (prg_info.st_mode & S_IWGRP))
! {
! log_err ("file is writable by others: (%s/%s)\n", cwd, cmd);
! exit (111);
}

/*
* Error out if the file is setuid or setgid.
*/
! if ((prg_info.st_mode & S_ISUID) || (prg_info.st_mode & S_ISGID))
! {
! log_err ("file is either setuid or setgid: (%s/%s)\n",cwd,cmd);
! exit (112);
}

/*
--- 195,298 ----
/*
* Error out if the target username is invalid.
*/
! if ((pw = getpwnam(target_uname)) == NULL) {
! log_err("invalid target user name: (%s)\n", target_uname);
! exit(105);
}

/*
* Error out if the target group name is invalid.
*/
! if ((gr = getgrnam(target_gname)) == NULL) {
! log_err("invalid target group name: (%s)\n", target_gname);
! exit(106);
}

/*
! * 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. NOTE: We also free() dwd since it is
! * the last usage...
! */
! if (getcwd(cwd, MAXPATHLEN) == NULL) {
! log_err("cannot get current working directory\n");
! exit(107);
! }
!
! if (userdir) {
! if (((chdir(pw->pw_dir)) != 0) ||
! ((getcwd(dwd, MAXPATHLEN)) == NULL) ||
! ((chdir(cwd)) != 0))
! {
! log_err("cannot get docroot information (%s)\n", pw->pw_dir);
! exit(108);
! }
! }
! else {
! if (((chdir(DOC_ROOT)) != 0) ||
! ((getcwd(dwd, MAXPATHLEN)) == NULL) ||
! ((chdir(cwd)) != 0))
! {
! log_err("cannot get docroot information (%s)\n", DOC_ROOT);
! exit(108);
! }
! }
!
! doclen = strlen(dwd);
! if (strncmp(cwd, dwd, doclen) != 0) {
! free(dwd);
! log_err("command not in docroot (%s/%s)\n", cwd, cmd);
! exit(109);
}
else
! free(dwd);


/*
* Stat the cwd and verify it is a directory, or error out.
+ * NOTE: This is the last use of cwd, so we must free() it.
*/
! if (((lstat(cwd, &dir_info)) != 0) || !(S_ISDIR(dir_info.st_mode))) {
! free(cwd);
! log_err("cannot stat directory: (%s)\n", cwd);
! exit(110);
}
+ else
+ free(cwd);

/*
* Error out if cwd 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", cwd);
! exit(111);
}

/*
* Error out if we cannot stat the program.
*/
! if (((lstat(cmd, &prg_info)) != 0) || (S_ISLNK(prg_info.st_mode))) {
! log_err("cannot stat program: (%s)\n", cmd);
! exit(112);
}

/*
* Error out if the program is writable by others.
*/
! if ((prg_info.st_mode & S_IWOTH) || (prg_info.st_mode & S_IWGRP)) {
! log_err("file is writable by others: (%s/%s)\n", cwd, cmd);
! exit(113);
}

/*
* Error out if the file is setuid or setgid.
*/
! if ((prg_info.st_mode & S_ISUID) || (prg_info.st_mode & S_ISGID)) {
! log_err("file is either setuid or setgid: (%s/%s)\n",cwd,cmd);
! exit(114);
}

/*
***************
*** 324,383 ****
(pw->pw_uid != prg_info.st_uid) ||
(gr->gr_gid != prg_info.st_gid))
{
! log_err ("target uid/gid (%ld/%ld) mismatch with directory (%ld/%ld) or program (%ld/%ld)\n",
pw->pw_uid, gr->gr_gid,
dir_info.st_uid, dir_info.st_gid,
prg_info.st_uid, prg_info.st_gid);
! exit (113);
}

/*
* Error out if attempt is made to execute as root. Tsk tsk.
*/
! if (pw->pw_uid == 0)
! {
! log_err ("cannot run as uid 0 (%s)\n", cmd);
! exit (114);
}

/*
* Error out if attempt is made to execute as root group. Tsk tsk.
*/
! if (gr->gr_gid == 0)
! {
! log_err ("cannot run as gid 0 (%s)\n", cmd);
! exit (115);
}

/*
* Log the transaction here to be sure we have an open log
* before we setuid().
*/
! log_err ("uid: (%s) gid: (%s) %s\n", target_uname, target_gname, cmd);

/*
* Initialize the group access list for the target user,
* and setgid() to the target group. If unsuccessful, error out.
*/
! if ((setgid (gr->gr_gid)) != 0)
! {
! log_err ("failed to setgid (%ld: %s/%s)\n", gr->gr_gid, cwd, cmd);
! exit (116);
}

/*
* setuid() to the target user. Error out on fail.
*/
! if ((setuid (pw->pw_uid)) != 0)
! {
! log_err ("failed to setuid (%ld: %s/%s)\n", pw->pw_uid, cwd, cmd);
! exit (117);
}

/*
* Execute the command, replacing our image with its own.
*/
! execve (cmd, &argv[3], env);

/*
* (I can't help myself...sorry.)
--- 304,369 ----
(pw->pw_uid != prg_info.st_uid) ||
(gr->gr_gid != prg_info.st_gid))
{
! log_err("target uid/gid (%ld/%ld) mismatch with directory (%ld/%ld) or program (%ld/%ld)\n",
pw->pw_uid, gr->gr_gid,
dir_info.st_uid, dir_info.st_gid,
prg_info.st_uid, prg_info.st_gid);
! exit(115);
}

/*
* Error out if attempt is made to execute as root. Tsk tsk.
*/
! if (pw->pw_uid == 0) {
! log_err("cannot run as uid 0 (%s)\n", cmd);
! exit(116);
}

/*
* Error out if attempt is made to execute as root group. Tsk tsk.
*/
! if (gr->gr_gid == 0) {
! log_err("cannot run as gid 0 (%s)\n", cmd);
! exit(117);
}

/*
* Log the transaction here to be sure we have an open log
* before we setuid().
*/
! log_err("uid: (%s/%s) gid: (%s/%s) %s\n",
! target_uname, pw->pw_name,
! target_gname, gr->gr_name,
! cmd);

/*
* Initialize the group access list for the target user,
* and setgid() to the target group. If unsuccessful, error out.
*/
! uid = pw->pw_uid;
! gid = gr->gr_gid;
! if ((initgroups(target_uname,gid) != 0) || ((setgid(gid)) != 0)) {
! log_err("failed to setgid (%ld: %s/%s)\n", gid, cwd, cmd);
! exit(118);
}

/*
* 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(119);
}

+ if ((setenv("PATH", SAFE_PATH, 1)) != 0) {
+ log_err("cannot reset environment PATH\n");
+ exit(120);
+ }
+
/*
* Execute the command, replacing our image with its own.
*/
! execve(cmd, &argv[3], env);

/*
* (I can't help myself...sorry.)
***************
*** 387,392 ****
*
* Oh well, log the failure and error out.
*/
! log_err ("exec failed (%s)\n", cmd);
exit(255);
}
--- 373,378 ----
*
* Oh well, log the failure and error out.
*/
! log_err("exec failed (%s)\n", cmd);
exit(255);
}



1.5 +4 -23 apache/support/suexec.h

Index: suexec.h
===================================================================
RCS file: /export/home/cvs/apache/support/suexec.h,v
retrieving revision 1.4
retrieving revision 1.5
diff -C3 -r1.4 -r1.5
*** suexec.h 1996/12/24 18:28:52 1.4
--- suexec.h 1996/12/27 22:21:23 1.5
***************
*** 86,115 ****
#endif

/*
! * USER_CGI_BIN -- Define this as the correct cgi-bin directory
! * for regular users (~user). NOTE: it needs the
! * initial '/' character
*/
! #ifndef USER_CGI_BIN
! #define USER_CGI_BIN "/public_html/cgi-bin"
! #endif
!
! /*
! * NNAME -- Define this as the name for the nobody account
! * on your operating system. Most systems will just
! * need the default 'nobody'.
! */
! #ifndef NNAME
! #define NNAME "nobody"
! #endif
!
! /* NGID -- Define this as the *number* for the nogroup group
! * on your operating system. Most systems will have
! * a -1 or -2. Others might have something above
! * 65000.
! */
! #ifndef NGID
! #define NGID -1
#endif

#endif /* _SUEXEC_H */
--- 86,96 ----
#endif

/*
! * SAFE_PATH -- Define a safe PATH environment to pass to CGI executables.
! *
*/
! #ifndef SAFE_PATH
! #define SAFE_PATH "/usr/local/bin:/usr/bin:/bin"
#endif

#endif /* _SUEXEC_H */