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