]> granicus.if.org Git - apache/blob - support/suexec.c
Add back suexec support.
[apache] / support / suexec.c
1 /* ====================================================================
2  * The Apache Software License, Version 1.1
3  *
4  * Copyright (c) 2000 The Apache Software Foundation.  All rights
5  * reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  *
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in
16  *    the documentation and/or other materials provided with the
17  *    distribution.
18  *
19  * 3. The end-user documentation included with the redistribution,
20  *    if any, must include the following acknowledgment:
21  *       "This product includes software developed by the
22  *        Apache Software Foundation (http://www.apache.org/)."
23  *    Alternately, this acknowledgment may appear in the software itself,
24  *    if and wherever such third-party acknowledgments normally appear.
25  *
26  * 4. The names "Apache" and "Apache Software Foundation" must
27  *    not be used to endorse or promote products derived from this
28  *    software without prior written permission. For written
29  *    permission, please contact apache@apache.org.
30  *
31  * 5. Products derived from this software may not be called "Apache",
32  *    nor may "Apache" appear in their name, without prior written
33  *    permission of the Apache Software Foundation.
34  *
35  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38  * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
46  * SUCH DAMAGE.
47  * ====================================================================
48  *
49  * This software consists of voluntary contributions made by many
50  * individuals on behalf of the Apache Software Foundation.  For more
51  * information on the Apache Software Foundation, please see
52  * <http://www.apache.org/>.
53  */
54
55 /*
56  * suexec.c -- "Wrapper" support program for suEXEC behaviour for Apache
57  *
58  ***********************************************************************
59  *
60  * NOTE! : DO NOT edit this code!!!  Unless you know what you are doing,
61  *         editing this code might open up your system in unexpected 
62  *         ways to would-be crackers.  Every precaution has been taken 
63  *         to make this code as safe as possible; alter it at your own
64  *         risk.
65  *
66  ***********************************************************************
67  *
68  *
69  */
70
71 #include "ap_config.h"
72 #include <sys/param.h>
73 #include <sys/stat.h>
74 #include <sys/types.h>
75
76 #include <stdarg.h>
77
78 #include "suexec.h"
79
80 #if HAVE_PWD_H
81 #include <pwd.h>
82 #endif
83
84 #if HAVE_GRP_H
85 #include <grp.h>
86 #endif
87
88 /*
89  ***********************************************************************
90  * There is no initgroups() in QNX, so I believe this is safe :-)
91  * Use cc -osuexec -3 -O -mf -DQNX suexec.c to compile.
92  *
93  * May 17, 1997.
94  * Igor N. Kovalenko -- infoh@mail.wplus.net
95  ***********************************************************************
96  */
97
98 #if defined(NEED_INITGROUPS)
99 int initgroups(const char *name, gid_t basegid)
100 {
101 /* QNX and MPE do not appear to support supplementary groups. */
102     return 0;
103 }
104 #endif
105
106 #if defined(PATH_MAX)
107 #define AP_MAXPATH PATH_MAX
108 #elif defined(MAXPATHLEN)
109 #define AP_MAXPATH MAXPATHLEN
110 #else
111 #define AP_MAXPATH 8192
112 #endif
113
114 #define AP_ENVBUF 256
115
116 extern char **environ;
117 static FILE *log = NULL;
118
119 char *safe_env_lst[] =
120 {
121     "AUTH_TYPE",
122     "CONTENT_LENGTH",
123     "CONTENT_TYPE",
124     "DATE_GMT",
125     "DATE_LOCAL",
126     "DOCUMENT_NAME",
127     "DOCUMENT_PATH_INFO",
128     "DOCUMENT_ROOT",
129     "DOCUMENT_URI",
130     "FILEPATH_INFO",
131     "GATEWAY_INTERFACE",
132     "LAST_MODIFIED",
133     "PATH_INFO",
134     "PATH_TRANSLATED",
135     "QUERY_STRING",
136     "QUERY_STRING_UNESCAPED",
137     "REMOTE_ADDR",
138     "REMOTE_HOST",
139     "REMOTE_IDENT",
140     "REMOTE_PORT",
141     "REMOTE_USER",
142     "REDIRECT_QUERY_STRING",
143     "REDIRECT_STATUS",
144     "REDIRECT_URL",
145     "REQUEST_METHOD",
146     "REQUEST_URI",
147     "SCRIPT_FILENAME",
148     "SCRIPT_NAME",
149     "SCRIPT_URI",
150     "SCRIPT_URL",
151     "SERVER_ADMIN",
152     "SERVER_NAME",
153     "SERVER_ADDR",
154     "SERVER_PORT",
155     "SERVER_PROTOCOL",
156     "SERVER_SOFTWARE",
157     "UNIQUE_ID",
158     "USER_NAME",
159     "TZ",
160     NULL
161 };
162
163
164 static void err_output(const char *fmt, va_list ap)
165 {
166 #ifdef LOG_EXEC
167     time_t timevar;
168     struct tm *lt;
169
170     if (!log) {
171         if ((log = fopen(LOG_EXEC, "a")) == NULL) {
172             fprintf(stderr, "failed to open log file\n");
173             perror("fopen");
174             exit(1);
175         }
176     }
177
178     time(&timevar);
179     lt = localtime(&timevar);
180
181     fprintf(log, "[%d-%.2d-%.2d %.2d:%.2d:%.2d]: ",
182             lt->tm_year + 1900, lt->tm_mon + 1, lt->tm_mday,
183             lt->tm_hour, lt->tm_min, lt->tm_sec);
184
185     vfprintf(log, fmt, ap);
186
187     fflush(log);
188 #endif /* LOG_EXEC */
189     return;
190 }
191
192 static void log_err(const char *fmt,...)
193 {
194 #ifdef LOG_EXEC
195     va_list ap;
196
197     va_start(ap, fmt);
198     err_output(fmt, ap);
199     va_end(ap);
200 #endif /* LOG_EXEC */
201     return;
202 }
203
204 static void clean_env(void)
205 {
206     char pathbuf[512];
207     char **cleanenv;
208     char **ep;
209     int cidx = 0;
210     int idx;
211
212
213     if ((cleanenv = (char **) calloc(AP_ENVBUF, sizeof(char *))) == NULL) {
214         log_err("failed to malloc memory for environment\n");
215         exit(120);
216     }
217
218     sprintf(pathbuf, "PATH=%s", SAFE_PATH);
219     cleanenv[cidx] = strdup(pathbuf);
220     cidx++;
221
222     for (ep = environ; *ep && cidx < AP_ENVBUF-1; ep++) {
223         if (!strncmp(*ep, "HTTP_", 5)) {
224             cleanenv[cidx] = *ep;
225             cidx++;
226         }
227         else {
228             for (idx = 0; safe_env_lst[idx]; idx++) {
229                 if (!strncmp(*ep, safe_env_lst[idx],
230                              strlen(safe_env_lst[idx]))) {
231                     cleanenv[cidx] = *ep;
232                     cidx++;
233                     break;
234                 }
235             }
236         }
237     }
238
239     cleanenv[cidx] = NULL;
240
241     environ = cleanenv;
242 }
243
244 int main(int argc, char *argv[])
245 {
246     int userdir = 0;            /* ~userdir flag             */
247     uid_t uid;                  /* user information          */
248     gid_t gid;                  /* target group placeholder  */
249     char *target_uname;         /* target user name          */
250     char *target_gname;         /* target group name         */
251     char *target_homedir;       /* target home directory     */
252     char *actual_uname;         /* actual user name          */
253     char *actual_gname;         /* actual group name         */
254     char *prog;                 /* name of this program      */
255     char *cmd;                  /* command to be executed    */
256     char cwd[AP_MAXPATH];       /* current working directory */
257     char dwd[AP_MAXPATH];       /* docroot working directory */
258     struct passwd *pw;          /* password entry holder     */
259     struct group *gr;           /* group entry holder        */
260     struct stat dir_info;       /* directory info holder     */
261     struct stat prg_info;       /* program info holder       */
262     char buf[120];
263
264     /*
265      * If there are a proper number of arguments, set
266      * all of them to variables.  Otherwise, error out.
267      */
268     prog = argv[0];
269     if (argc < 4) {
270         log_err("too few arguments\n");
271         exit(101);
272     }
273     target_uname = argv[1];
274     target_gname = argv[2];
275     cmd = argv[3];
276
277     /*
278      * Check existence/validity of the UID of the user
279      * running this program.  Error out if invalid.
280      */
281     uid = getuid();
282     if ((pw = getpwuid(uid)) == NULL) {
283         log_err("invalid uid: (%ld)\n", uid);
284         exit(102);
285     }
286
287     /*
288      * Check to see if the user running this program
289      * is the user allowed to do so as defined in
290      * suexec.h.  If not the allowed user, error out.
291      */
292 #ifdef _OSD_POSIX
293     /* User name comparisons are case insensitive on BS2000/OSD */
294     if (strcasecmp(HTTPD_USER, pw->pw_name)) {
295         log_err("user mismatch (%s instead of %s)\n", pw->pw_name, HTTPD_USER);
296         exit(103);
297     }
298 #else  /*_OSD_POSIX*/
299     if (strcmp(HTTPD_USER, pw->pw_name)) {
300         log_err("user mismatch (%s instead of %s)\n", pw->pw_name, HTTPD_USER);
301         exit(103);
302     }
303 #endif /*_OSD_POSIX*/
304
305     /*
306      * Check for a leading '/' (absolute path) in the command to be executed,
307      * or attempts to back up out of the current directory,
308      * to protect against attacks.  If any are
309      * found, error out.  Naughty naughty crackers.
310      */
311     if ((cmd[0] == '/') || (!strncmp(cmd, "../", 3))
312         || (strstr(cmd, "/../") != NULL)) {
313         log_err("invalid command (%s)\n", cmd);
314         exit(104);
315     }
316
317     /*
318      * Check to see if this is a ~userdir request.  If
319      * so, set the flag, and remove the '~' from the
320      * target username.
321      */
322     if (!strncmp("~", target_uname, 1)) {
323         target_uname++;
324         userdir = 1;
325     }
326
327     /*
328      * Error out if the target username is invalid.
329      */
330     if (strspn(target_uname, "1234567890") != strlen(target_uname)) {
331         if ((pw = getpwnam(target_uname)) == NULL) {
332             log_err("invalid target user name: (%s)\n", target_uname);
333             exit(105);
334         }
335     }
336     else {
337         if ((pw = getpwuid(atoi(target_uname))) == NULL) {
338             log_err("invalud target user id: (%s)\n", target_uname);
339             exit(121);
340         }
341     }
342
343     /*
344      * Error out if the target group name is invalid.
345      */
346     if (strspn(target_gname, "1234567890") != strlen(target_gname)) {
347         if ((gr = getgrnam(target_gname)) == NULL) {
348             log_err("invalid target group name: (%s)\n", target_gname);
349             exit(106);
350         }
351         gid = gr->gr_gid;
352         actual_gname = strdup(gr->gr_name);
353     }
354     else {
355         gid = atoi(target_gname);
356         actual_gname = strdup(target_gname);
357     }
358
359 #ifdef _OSD_POSIX
360     /*
361      * Initialize BS2000 user environment
362      */
363     {
364         pid_t pid;
365         int status;
366
367         switch (pid = ufork(target_uname))
368         {
369         case -1:        /* Error */
370             log_err("failed to setup bs2000 environment for user %s: %s\n",
371                     target_uname, apr_strerror(errno, buf, sizeof(buf)));
372             exit(150);
373         case 0: /* Child */
374             break;
375         default:        /* Father */
376             while (pid != waitpid(pid, &status, 0))
377                 ;
378             /* @@@ FIXME: should we deal with STOP signals as well? */
379             if (WIFSIGNALED(status))
380                 kill (getpid(), WTERMSIG(status));
381             exit(WEXITSTATUS(status));
382         }
383     }
384 #endif /*_OSD_POSIX*/
385
386     /*
387      * Save these for later since initgroups will hose the struct
388      */
389     uid = pw->pw_uid;
390     actual_uname = strdup(pw->pw_name);
391     target_homedir = strdup(pw->pw_dir);
392
393     /*
394      * Log the transaction here to be sure we have an open log 
395      * before we setuid().
396      */
397     log_err("uid: (%s/%s) gid: (%s/%s) cmd: %s\n",
398             target_uname, actual_uname,
399             target_gname, actual_gname,
400             cmd);
401
402     /*
403      * Error out if attempt is made to execute as root or as
404      * a UID less than UID_MIN.  Tsk tsk.
405      */
406     if ((uid == 0) || (uid < UID_MIN)) {
407         log_err("cannot run as forbidden uid (%d/%s)\n", uid, cmd);
408         exit(107);
409     }
410
411     /*
412      * Error out if attempt is made to execute as root group
413      * or as a GID less than GID_MIN.  Tsk tsk.
414      */
415     if ((gid == 0) || (gid < GID_MIN)) {
416         log_err("cannot run as forbidden gid (%d/%s)\n", gid, cmd);
417         exit(108);
418     }
419
420     /*
421      * Change UID/GID here so that the following tests work over NFS.
422      *
423      * Initialize the group access list for the target user,
424      * and setgid() to the target group. If unsuccessful, error out.
425      */
426     if (((setgid(gid)) != 0) || (initgroups(actual_uname, gid) != 0)) {
427         log_err("failed to setgid (%ld: %s)\n", gid, cmd);
428         exit(109);
429     }
430
431     /*
432      * setuid() to the target user.  Error out on fail.
433      */
434     if ((setuid(uid)) != 0) {
435         log_err("failed to setuid (%ld: %s)\n", uid, cmd);
436         exit(110);
437     }
438
439     /*
440      * Get the current working directory, as well as the proper
441      * document root (dependant upon whether or not it is a
442      * ~userdir request).  Error out if we cannot get either one,
443      * or if the current working directory is not in the docroot.
444      * Use chdir()s and getcwd()s to avoid problems with symlinked
445      * directories.  Yuck.
446      */
447     if (getcwd(cwd, AP_MAXPATH) == NULL) {
448         log_err("cannot get current working directory\n");
449         exit(111);
450     }
451
452     if (userdir) {
453         if (((chdir(target_homedir)) != 0) ||
454             ((chdir(USERDIR_SUFFIX)) != 0) ||
455             ((getcwd(dwd, AP_MAXPATH)) == NULL) ||
456             ((chdir(cwd)) != 0)) {
457             log_err("cannot get docroot information (%s)\n", target_homedir);
458             exit(112);
459         }
460     }
461     else {
462         if (((chdir(DOC_ROOT)) != 0) ||
463             ((getcwd(dwd, AP_MAXPATH)) == NULL) ||
464             ((chdir(cwd)) != 0)) {
465             log_err("cannot get docroot information (%s)\n", DOC_ROOT);
466             exit(113);
467         }
468     }
469
470     if ((strncmp(cwd, dwd, strlen(dwd))) != 0) {
471         log_err("command not in docroot (%s/%s)\n", cwd, cmd);
472         exit(114);
473     }
474
475     /*
476      * Stat the cwd and verify it is a directory, or error out.
477      */
478     if (((lstat(cwd, &dir_info)) != 0) || !(S_ISDIR(dir_info.st_mode))) {
479         log_err("cannot stat directory: (%s)\n", cwd);
480         exit(115);
481     }
482
483     /*
484      * Error out if cwd is writable by others.
485      */
486     if ((dir_info.st_mode & S_IWOTH) || (dir_info.st_mode & S_IWGRP)) {
487         log_err("directory is writable by others: (%s)\n", cwd);
488         exit(116);
489     }
490
491     /*
492      * Error out if we cannot stat the program.
493      */
494     if (((lstat(cmd, &prg_info)) != 0) || (S_ISLNK(prg_info.st_mode))) {
495         log_err("cannot stat program: (%s)\n", cmd);
496         exit(117);
497     }
498
499     /*
500      * Error out if the program is writable by others.
501      */
502     if ((prg_info.st_mode & S_IWOTH) || (prg_info.st_mode & S_IWGRP)) {
503         log_err("file is writable by others: (%s/%s)\n", cwd, cmd);
504         exit(118);
505     }
506
507     /*
508      * Error out if the file is setuid or setgid.
509      */
510     if ((prg_info.st_mode & S_ISUID) || (prg_info.st_mode & S_ISGID)) {
511         log_err("file is either setuid or setgid: (%s/%s)\n", cwd, cmd);
512         exit(119);
513     }
514
515     /*
516      * Error out if the target name/group is different from
517      * the name/group of the cwd or the program.
518      */
519     if ((uid != dir_info.st_uid) ||
520         (gid != dir_info.st_gid) ||
521         (uid != prg_info.st_uid) ||
522         (gid != prg_info.st_gid)) {
523         log_err("target uid/gid (%ld/%ld) mismatch "
524                 "with directory (%ld/%ld) or program (%ld/%ld)\n",
525                 uid, gid,
526                 dir_info.st_uid, dir_info.st_gid,
527                 prg_info.st_uid, prg_info.st_gid);
528         exit(120);
529     }
530     /*
531      * Error out if the program is not executable for the user.
532      * Otherwise, she won't find any error in the logs except for
533      * "[error] Premature end of script headers: ..."
534      */
535     if (!(prg_info.st_mode & S_IXUSR)) {
536         log_err("file has no execute permission: (%s/%s)\n", cwd, cmd);
537         exit(121);
538     }
539
540     clean_env();
541
542     /* 
543      * Be sure to close the log file so the CGI can't
544      * mess with it.  If the exec fails, it will be reopened 
545      * automatically when log_err is called.  Note that the log
546      * might not actually be open if LOG_EXEC isn't defined.
547      * However, the "log" cell isn't ifdef'd so let's be defensive
548      * and assume someone might have done something with it
549      * outside an ifdef'd LOG_EXEC block.
550      */
551     if (log != NULL) {
552         fclose(log);
553         log = NULL;
554     }
555
556     /*
557      * Execute the command, replacing our image with its own.
558      */
559 #ifdef NEED_HASHBANG_EMUL
560     /* We need the #! emulation when we want to execute scripts */
561     {
562         extern char **environ;
563
564         ap_execve(cmd, &argv[3], environ);
565     }
566 #else /*NEED_HASHBANG_EMUL*/
567     execv(cmd, &argv[3]);
568 #endif /*NEED_HASHBANG_EMUL*/
569
570     /*
571      * (I can't help myself...sorry.)
572      *
573      * Uh oh.  Still here.  Where's the kaboom?  There was supposed to be an
574      * EARTH-shattering kaboom!
575      *
576      * Oh well, log the failure and error out.
577      */
578     log_err("(%d)%s: exec failed (%s)\n", errno, 
579             apr_strerror(errno, buf, sizeof(buf)), cmd);
580     exit(255);
581 }