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