]> granicus.if.org Git - apache/blob - support/suexec.c
filepath_info wasn't set by the httpd for over 7 years
[apache] / support / suexec.c
1 /* Copyright 1999-2004 The Apache Software Foundation
2  *
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15
16 /*
17  * suexec.c -- "Wrapper" support program for suEXEC behaviour for Apache
18  *
19  ***********************************************************************
20  *
21  * NOTE! : DO NOT edit this code!!!  Unless you know what you are doing,
22  *         editing this code might open up your system in unexpected 
23  *         ways to would-be crackers.  Every precaution has been taken 
24  *         to make this code as safe as possible; alter it at your own
25  *         risk.
26  *
27  ***********************************************************************
28  *
29  *
30  */
31
32 #include "apr.h"
33 #include "ap_config.h"
34 #include "suexec.h"
35
36 #include <sys/param.h>
37 #include <sys/stat.h>
38 #include <sys/types.h>
39 #include <string.h>
40 #include <time.h>
41 #if APR_HAVE_UNISTD_H
42 #include <unistd.h>
43 #endif
44
45 #include <stdio.h>
46 #include <stdarg.h>
47 #include <stdlib.h>
48
49 #ifdef HAVE_PWD_H
50 #include <pwd.h>
51 #endif
52
53 #ifdef HAVE_GRP_H
54 #include <grp.h>
55 #endif
56
57 /*
58  ***********************************************************************
59  * There is no initgroups() in QNX, so I believe this is safe :-)
60  * Use cc -osuexec -3 -O -mf -DQNX suexec.c to compile.
61  *
62  * May 17, 1997.
63  * Igor N. Kovalenko -- infoh mail.wplus.net
64  ***********************************************************************
65  */
66
67 #if defined(NEED_INITGROUPS)
68 int initgroups(const char *name, gid_t basegid)
69 {
70     /* QNX and MPE do not appear to support supplementary groups. */
71     return 0;
72 }
73 #endif
74
75 #if defined(SUNOS4)
76 extern char *sys_errlist[];
77 #define strerror(x) sys_errlist[(x)]
78 #endif
79
80 #if defined(PATH_MAX)
81 #define AP_MAXPATH PATH_MAX
82 #elif defined(MAXPATHLEN)
83 #define AP_MAXPATH MAXPATHLEN
84 #else
85 #define AP_MAXPATH 8192
86 #endif
87
88 #define AP_ENVBUF 256
89
90 extern char **environ;
91 static FILE *log = NULL;
92
93 char *safe_env_lst[] =
94 {
95     /* variable name starts with */
96     "HTTP_",
97     "SSL_",
98
99     /* variable name is */
100     "AUTH_TYPE=",
101     "CONTENT_LENGTH=",
102     "CONTENT_TYPE=",
103     "DATE_GMT=",
104     "DATE_LOCAL=",
105     "DOCUMENT_NAME=",
106     "DOCUMENT_PATH_INFO=",
107     "DOCUMENT_ROOT=",
108     "DOCUMENT_URI=",
109     "GATEWAY_INTERFACE=",
110     "HTTPS=",
111     "LAST_MODIFIED=",
112     "PATH_INFO=",
113     "PATH_TRANSLATED=",
114     "QUERY_STRING=",
115     "QUERY_STRING_UNESCAPED=",
116     "REMOTE_ADDR=",
117     "REMOTE_HOST=",
118     "REMOTE_IDENT=",
119     "REMOTE_PORT=",
120     "REMOTE_USER=",
121     "REDIRECT_HANDLER=",
122     "REDIRECT_QUERY_STRING=",
123     "REDIRECT_REMOTE_USER=",
124     "REDIRECT_STATUS=",
125     "REDIRECT_URL=",
126     "REQUEST_METHOD=",
127     "REQUEST_URI=",
128     "SCRIPT_FILENAME=",
129     "SCRIPT_NAME=",
130     "SCRIPT_URI=",
131     "SCRIPT_URL=",
132     "SERVER_ADMIN=",
133     "SERVER_NAME=",
134     "SERVER_ADDR=",
135     "SERVER_PORT=",
136     "SERVER_PROTOCOL=",
137     "SERVER_SOFTWARE=",
138     "UNIQUE_ID=",
139     "USER_NAME=",
140     "TZ=",
141     NULL
142 };
143
144
145 static void err_output(int is_error, const char *fmt, va_list ap)
146 {
147 #ifdef AP_LOG_EXEC
148     time_t timevar;
149     struct tm *lt;
150
151     if (!log) {
152         if ((log = fopen(AP_LOG_EXEC, "a")) == NULL) {
153             fprintf(stderr, "suexec failure: could not open log file\n");
154             perror("fopen");
155             exit(1);
156         }
157     }
158
159     if (is_error) {
160         fprintf(stderr, "suexec policy violation: see suexec log for more "
161                         "details\n");
162     }
163
164     time(&timevar);
165     lt = localtime(&timevar);
166
167     fprintf(log, "[%d-%.2d-%.2d %.2d:%.2d:%.2d]: ",
168             lt->tm_year + 1900, lt->tm_mon + 1, lt->tm_mday,
169             lt->tm_hour, lt->tm_min, lt->tm_sec);
170
171     vfprintf(log, fmt, ap);
172
173     fflush(log);
174 #endif /* AP_LOG_EXEC */
175     return;
176 }
177
178 static void log_err(const char *fmt,...)
179 {
180 #ifdef AP_LOG_EXEC
181     va_list ap;
182
183     va_start(ap, fmt);
184     err_output(1, fmt, ap); /* 1 == is_error */
185     va_end(ap);
186 #endif /* AP_LOG_EXEC */
187     return;
188 }
189
190 static void log_no_err(const char *fmt,...)
191 {
192 #ifdef AP_LOG_EXEC
193     va_list ap;
194
195     va_start(ap, fmt);
196     err_output(0, fmt, ap); /* 0 == !is_error */
197     va_end(ap);
198 #endif /* AP_LOG_EXEC */
199     return;
200 }
201
202 static void clean_env(void)
203 {
204     char pathbuf[512];
205     char **cleanenv;
206     char **ep;
207     int cidx = 0;
208     int idx;
209
210     /* While cleaning the environment, the environment should be clean.
211      * (e.g. malloc() may get the name of a file for writing debugging info.
212      * Bad news if MALLOC_DEBUG_FILE is set to /etc/passwd.  Sprintf() may be
213      * susceptible to bad locale settings....)
214      * (from PR 2790)
215      */
216     char **envp = environ;
217     char *empty_ptr = NULL;
218  
219     environ = &empty_ptr; /* VERY safe environment */
220     
221     if ((cleanenv = (char **) calloc(AP_ENVBUF, sizeof(char *))) == NULL) {
222         log_err("failed to malloc memory for environment\n");
223         exit(120);
224     }
225
226     sprintf(pathbuf, "PATH=%s", AP_SAFE_PATH);
227     cleanenv[cidx] = strdup(pathbuf);
228     cidx++;
229
230     for (ep = envp; *ep && cidx < AP_ENVBUF-1; ep++) {
231         for (idx = 0; safe_env_lst[idx]; idx++) {
232             if (!strncmp(*ep, safe_env_lst[idx],
233                          strlen(safe_env_lst[idx]))) {
234                 cleanenv[cidx] = *ep;
235                 cidx++;
236                 break;
237             }
238         }
239     }
240
241     cleanenv[cidx] = NULL;
242
243     environ = cleanenv;
244 }
245
246 int main(int argc, char *argv[])
247 {
248     int userdir = 0;        /* ~userdir flag             */
249     uid_t uid;              /* user information          */
250     gid_t gid;              /* target group placeholder  */
251     char *target_uname;     /* target user name          */
252     char *target_gname;     /* target group name         */
253     char *target_homedir;   /* target home directory     */
254     char *actual_uname;     /* actual user name          */
255     char *actual_gname;     /* actual group name         */
256     char *prog;             /* name of this program      */
257     char *cmd;              /* command to be executed    */
258     char cwd[AP_MAXPATH];   /* current working directory */
259     char dwd[AP_MAXPATH];   /* docroot working directory */
260     struct passwd *pw;      /* password entry holder     */
261     struct group *gr;       /* group entry holder        */
262     struct stat dir_info;   /* directory info holder     */
263     struct stat prg_info;   /* program info holder       */
264
265     /*
266      * Start with a "clean" environment
267      */
268     clean_env();
269
270     prog = argv[0];
271     /*
272      * Check existence/validity of the UID of the user
273      * running this program.  Error out if invalid.
274      */
275     uid = getuid();
276     if ((pw = getpwuid(uid)) == NULL) {
277         log_err("crit: invalid uid: (%ld)\n", uid);
278         exit(102);
279     }
280     /*
281      * See if this is a 'how were you compiled' request, and
282      * comply if so.
283      */
284     if ((argc > 1)
285         && (! strcmp(argv[1], "-V"))
286         && ((uid == 0)
287 #ifdef _OSD_POSIX
288         /* User name comparisons are case insensitive on BS2000/OSD */
289             || (! strcasecmp(AP_HTTPD_USER, pw->pw_name)))
290 #else  /* _OSD_POSIX */
291             || (! strcmp(AP_HTTPD_USER, pw->pw_name)))
292 #endif /* _OSD_POSIX */
293         ) {
294 #ifdef AP_DOC_ROOT
295         fprintf(stderr, " -D AP_DOC_ROOT=\"%s\"\n", AP_DOC_ROOT);
296 #endif
297 #ifdef AP_GID_MIN
298         fprintf(stderr, " -D AP_GID_MIN=%d\n", AP_GID_MIN);
299 #endif
300 #ifdef AP_HTTPD_USER
301         fprintf(stderr, " -D AP_HTTPD_USER=\"%s\"\n", AP_HTTPD_USER);
302 #endif
303 #ifdef AP_LOG_EXEC
304         fprintf(stderr, " -D AP_LOG_EXEC=\"%s\"\n", AP_LOG_EXEC);
305 #endif
306 #ifdef AP_SAFE_PATH
307         fprintf(stderr, " -D AP_SAFE_PATH=\"%s\"\n", AP_SAFE_PATH);
308 #endif
309 #ifdef AP_SUEXEC_UMASK
310         fprintf(stderr, " -D AP_SUEXEC_UMASK=%03o\n", AP_SUEXEC_UMASK);
311 #endif
312 #ifdef AP_UID_MIN
313         fprintf(stderr, " -D AP_UID_MIN=%d\n", AP_UID_MIN);
314 #endif
315 #ifdef AP_USERDIR_SUFFIX
316         fprintf(stderr, " -D AP_USERDIR_SUFFIX=\"%s\"\n", AP_USERDIR_SUFFIX);
317 #endif
318         exit(0);
319     }
320     /*
321      * If there are a proper number of arguments, set
322      * all of them to variables.  Otherwise, error out.
323      */
324     if (argc < 4) {
325         log_err("too few arguments\n");
326         exit(101);
327     }
328     target_uname = argv[1];
329     target_gname = argv[2];
330     cmd = argv[3];
331
332     /*
333      * Check to see if the user running this program
334      * is the user allowed to do so as defined in
335      * suexec.h.  If not the allowed user, error out.
336      */
337 #ifdef _OSD_POSIX
338     /* User name comparisons are case insensitive on BS2000/OSD */
339     if (strcasecmp(AP_HTTPD_USER, pw->pw_name)) {
340         log_err("user mismatch (%s instead of %s)\n", pw->pw_name, AP_HTTPD_USER);
341         exit(103);
342     }
343 #else  /*_OSD_POSIX*/
344     if (strcmp(AP_HTTPD_USER, pw->pw_name)) {
345         log_err("user mismatch (%s instead of %s)\n", pw->pw_name, AP_HTTPD_USER);
346         exit(103);
347     }
348 #endif /*_OSD_POSIX*/
349
350     /*
351      * Check for a leading '/' (absolute path) in the command to be executed,
352      * or attempts to back up out of the current directory,
353      * to protect against attacks.  If any are
354      * found, error out.  Naughty naughty crackers.
355      */
356     if ((cmd[0] == '/') || (!strncmp(cmd, "../", 3))
357         || (strstr(cmd, "/../") != NULL)) {
358         log_err("invalid command (%s)\n", cmd);
359         exit(104);
360     }
361
362     /*
363      * Check to see if this is a ~userdir request.  If
364      * so, set the flag, and remove the '~' from the
365      * target username.
366      */
367     if (!strncmp("~", target_uname, 1)) {
368         target_uname++;
369         userdir = 1;
370     }
371
372     /*
373      * Error out if the target username is invalid.
374      */
375     if (strspn(target_uname, "1234567890") != strlen(target_uname)) {
376         if ((pw = getpwnam(target_uname)) == NULL) {
377             log_err("invalid target user name: (%s)\n", target_uname);
378             exit(105);
379         }
380     }
381     else {
382         if ((pw = getpwuid(atoi(target_uname))) == NULL) {
383             log_err("invalid target user id: (%s)\n", target_uname);
384             exit(121);
385         }
386     }
387
388     /*
389      * Error out if the target group name is invalid.
390      */
391     if (strspn(target_gname, "1234567890") != strlen(target_gname)) {
392         if ((gr = getgrnam(target_gname)) == NULL) {
393             log_err("invalid target group name: (%s)\n", target_gname);
394             exit(106);
395         }
396         gid = gr->gr_gid;
397         actual_gname = strdup(gr->gr_name);
398     }
399     else {
400         gid = atoi(target_gname);
401         actual_gname = strdup(target_gname);
402     }
403
404 #ifdef _OSD_POSIX
405     /*
406      * Initialize BS2000 user environment
407      */
408     {
409         pid_t pid;
410         int status;
411
412         switch (pid = ufork(target_uname)) {
413         case -1:    /* Error */
414             log_err("failed to setup bs2000 environment for user %s: %s\n",
415                     target_uname, strerror(errno));
416             exit(150);
417         case 0:     /* Child */
418             break;
419         default:    /* Father */
420             while (pid != waitpid(pid, &status, 0))
421                 ;
422             /* @@@ FIXME: should we deal with STOP signals as well? */
423             if (WIFSIGNALED(status)) {
424                 kill (getpid(), WTERMSIG(status));
425             }
426             exit(WEXITSTATUS(status));
427         }
428     }
429 #endif /*_OSD_POSIX*/
430     
431     /*
432      * Save these for later since initgroups will hose the struct
433      */
434     uid = pw->pw_uid;
435     actual_uname = strdup(pw->pw_name);
436     target_homedir = strdup(pw->pw_dir);
437
438     /*
439      * Log the transaction here to be sure we have an open log 
440      * before we setuid().
441      */
442     log_no_err("uid: (%s/%s) gid: (%s/%s) cmd: %s\n",
443                target_uname, actual_uname,
444                target_gname, actual_gname,
445                cmd);
446
447     /*
448      * Error out if attempt is made to execute as root or as
449      * a UID less than AP_UID_MIN.  Tsk tsk.
450      */
451     if ((uid == 0) || (uid < AP_UID_MIN)) {
452         log_err("cannot run as forbidden uid (%d/%s)\n", uid, cmd);
453         exit(107);
454     }
455
456     /*
457      * Error out if attempt is made to execute as root group
458      * or as a GID less than AP_GID_MIN.  Tsk tsk.
459      */
460     if ((gid == 0) || (gid < AP_GID_MIN)) {
461         log_err("cannot run as forbidden gid (%d/%s)\n", gid, cmd);
462         exit(108);
463     }
464
465     /*
466      * Change UID/GID here so that the following tests work over NFS.
467      *
468      * Initialize the group access list for the target user,
469      * and setgid() to the target group. If unsuccessful, error out.
470      */
471     if (((setgid(gid)) != 0) || (initgroups(actual_uname, gid) != 0)) {
472         log_err("failed to setgid (%ld: %s)\n", gid, cmd);
473         exit(109);
474     }
475
476     /*
477      * setuid() to the target user.  Error out on fail.
478      */
479     if ((setuid(uid)) != 0) {
480         log_err("failed to setuid (%ld: %s)\n", uid, cmd);
481         exit(110);
482     }
483
484     /*
485      * Get the current working directory, as well as the proper
486      * document root (dependant upon whether or not it is a
487      * ~userdir request).  Error out if we cannot get either one,
488      * or if the current working directory is not in the docroot.
489      * Use chdir()s and getcwd()s to avoid problems with symlinked
490      * directories.  Yuck.
491      */
492     if (getcwd(cwd, AP_MAXPATH) == NULL) {
493         log_err("cannot get current working directory\n");
494         exit(111);
495     }
496
497     if (userdir) {
498         if (((chdir(target_homedir)) != 0) ||
499             ((chdir(AP_USERDIR_SUFFIX)) != 0) ||
500             ((getcwd(dwd, AP_MAXPATH)) == NULL) ||
501             ((chdir(cwd)) != 0)) {
502             log_err("cannot get docroot information (%s)\n", target_homedir);
503             exit(112);
504         }
505     }
506     else {
507         if (((chdir(AP_DOC_ROOT)) != 0) ||
508             ((getcwd(dwd, AP_MAXPATH)) == NULL) ||
509             ((chdir(cwd)) != 0)) {
510             log_err("cannot get docroot information (%s)\n", AP_DOC_ROOT);
511             exit(113);
512         }
513     }
514
515     if ((strncmp(cwd, dwd, strlen(dwd))) != 0) {
516         log_err("command not in docroot (%s/%s)\n", cwd, cmd);
517         exit(114);
518     }
519
520     /*
521      * Stat the cwd and verify it is a directory, or error out.
522      */
523     if (((lstat(cwd, &dir_info)) != 0) || !(S_ISDIR(dir_info.st_mode))) {
524         log_err("cannot stat directory: (%s)\n", cwd);
525         exit(115);
526     }
527
528     /*
529      * Error out if cwd is writable by others.
530      */
531     if ((dir_info.st_mode & S_IWOTH) || (dir_info.st_mode & S_IWGRP)) {
532         log_err("directory is writable by others: (%s)\n", cwd);
533         exit(116);
534     }
535
536     /*
537      * Error out if we cannot stat the program.
538      */
539     if (((lstat(cmd, &prg_info)) != 0) || (S_ISLNK(prg_info.st_mode))) {
540         log_err("cannot stat program: (%s)\n", cmd);
541         exit(117);
542     }
543
544     /*
545      * Error out if the program is writable by others.
546      */
547     if ((prg_info.st_mode & S_IWOTH) || (prg_info.st_mode & S_IWGRP)) {
548         log_err("file is writable by others: (%s/%s)\n", cwd, cmd);
549         exit(118);
550     }
551
552     /*
553      * Error out if the file is setuid or setgid.
554      */
555     if ((prg_info.st_mode & S_ISUID) || (prg_info.st_mode & S_ISGID)) {
556         log_err("file is either setuid or setgid: (%s/%s)\n", cwd, cmd);
557         exit(119);
558     }
559
560     /*
561      * Error out if the target name/group is different from
562      * the name/group of the cwd or the program.
563      */
564     if ((uid != dir_info.st_uid) ||
565         (gid != dir_info.st_gid) ||
566         (uid != prg_info.st_uid) ||
567         (gid != prg_info.st_gid)) {
568         log_err("target uid/gid (%ld/%ld) mismatch "
569                 "with directory (%ld/%ld) or program (%ld/%ld)\n",
570                 uid, gid,
571                 dir_info.st_uid, dir_info.st_gid,
572                 prg_info.st_uid, prg_info.st_gid);
573         exit(120);
574     }
575     /*
576      * Error out if the program is not executable for the user.
577      * Otherwise, she won't find any error in the logs except for
578      * "[error] Premature end of script headers: ..."
579      */
580     if (!(prg_info.st_mode & S_IXUSR)) {
581         log_err("file has no execute permission: (%s/%s)\n", cwd, cmd);
582         exit(121);
583     }
584
585 #ifdef AP_SUEXEC_UMASK
586     /*
587      * umask() uses inverse logic; bits are CLEAR for allowed access.
588      */
589     if ((~AP_SUEXEC_UMASK) & 0022) {
590         log_err("notice: AP_SUEXEC_UMASK of %03o allows "
591                 "write permission to group and/or other\n", AP_SUEXEC_UMASK);
592     }
593     umask(AP_SUEXEC_UMASK);
594 #endif /* AP_SUEXEC_UMASK */
595
596     /* 
597      * Be sure to close the log file so the CGI can't
598      * mess with it.  If the exec fails, it will be reopened 
599      * automatically when log_err is called.  Note that the log
600      * might not actually be open if AP_LOG_EXEC isn't defined.
601      * However, the "log" cell isn't ifdef'd so let's be defensive
602      * and assume someone might have done something with it
603      * outside an ifdef'd AP_LOG_EXEC block.
604      */
605     if (log != NULL) {
606         fclose(log);
607         log = NULL;
608     }
609
610     /*
611      * Execute the command, replacing our image with its own.
612      */
613 #ifdef NEED_HASHBANG_EMUL
614     /* We need the #! emulation when we want to execute scripts */
615     {
616         extern char **environ;
617
618         ap_execve(cmd, &argv[3], environ);
619     }
620 #else /*NEED_HASHBANG_EMUL*/
621     execv(cmd, &argv[3]);
622 #endif /*NEED_HASHBANG_EMUL*/
623
624     /*
625      * (I can't help myself...sorry.)
626      *
627      * Uh oh.  Still here.  Where's the kaboom?  There was supposed to be an
628      * EARTH-shattering kaboom!
629      *
630      * Oh well, log the failure and error out.
631      */
632     log_err("(%d)%s: exec failed (%s)\n", errno, strerror(errno), cmd);
633     exit(255);
634 }