source: server/fedora/ref-prepatch/suexec.c @ 105

Last change on this file since 105 was 33, checked in by jbarnold, 18 years ago
added reference source files that are known to work with our patches
File size: 18.0 KB
Line 
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)
69int 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)
77extern 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
91extern char **environ;
92static FILE *log = NULL;
93
94static const char *const safe_env_lst[] =
95{
96    /* variable name starts with */
97    "HTTP_",
98    "HTTPS_",
99    "SSL_",
100
101    /* variable name is */
102    "AUTH_TYPE=",
103    "CONTENT_LENGTH=",
104    "CONTENT_TYPE=",
105    "DATE_GMT=",
106    "DATE_LOCAL=",
107    "DOCUMENT_NAME=",
108    "DOCUMENT_PATH_INFO=",
109    "DOCUMENT_ROOT=",
110    "DOCUMENT_URI=",
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_HANDLER=",
124    "REDIRECT_QUERY_STRING=",
125    "REDIRECT_REMOTE_USER=",
126    "REDIRECT_STATUS=",
127    "REDIRECT_URL=",
128    "REQUEST_METHOD=",
129    "REQUEST_URI=",
130    "SCRIPT_FILENAME=",
131    "SCRIPT_NAME=",
132    "SCRIPT_URI=",
133    "SCRIPT_URL=",
134    "SERVER_ADMIN=",
135    "SERVER_NAME=",
136    "SERVER_ADDR=",
137    "SERVER_PORT=",
138    "SERVER_PROTOCOL=",
139    "SERVER_SIGNATURE=",
140    "SERVER_SOFTWARE=",
141    "UNIQUE_ID=",
142    "USER_NAME=",
143    "TZ=",
144    "PHPRC=",
145    NULL
146};
147
148
149static void err_output(int is_error, const char *fmt, va_list ap)
150{
151#ifdef AP_LOG_EXEC
152    time_t timevar;
153    struct tm *lt;
154
155    if (!log) {
156        if ((log = fopen(AP_LOG_EXEC, "a")) == NULL) {
157            fprintf(stderr, "suexec failure: could not open log file\n");
158            perror("fopen");
159            exit(1);
160        }
161    }
162
163    if (is_error) {
164        fprintf(stderr, "suexec policy violation: see suexec log for more "
165                        "details\n");
166    }
167
168    time(&timevar);
169    lt = localtime(&timevar);
170
171    fprintf(log, "[%d-%.2d-%.2d %.2d:%.2d:%.2d]: ",
172            lt->tm_year + 1900, lt->tm_mon + 1, lt->tm_mday,
173            lt->tm_hour, lt->tm_min, lt->tm_sec);
174
175    vfprintf(log, fmt, ap);
176
177    fflush(log);
178#endif /* AP_LOG_EXEC */
179    return;
180}
181
182static void log_err(const char *fmt,...)
183{
184#ifdef AP_LOG_EXEC
185    va_list ap;
186
187    va_start(ap, fmt);
188    err_output(1, fmt, ap); /* 1 == is_error */
189    va_end(ap);
190#endif /* AP_LOG_EXEC */
191    return;
192}
193
194static void log_no_err(const char *fmt,...)
195{
196#ifdef AP_LOG_EXEC
197    va_list ap;
198
199    va_start(ap, fmt);
200    err_output(0, fmt, ap); /* 0 == !is_error */
201    va_end(ap);
202#endif /* AP_LOG_EXEC */
203    return;
204}
205
206static void clean_env(void)
207{
208    char pathbuf[512];
209    char **cleanenv;
210    char **ep;
211    int cidx = 0;
212    int idx;
213
214    /* While cleaning the environment, the environment should be clean.
215     * (e.g. malloc() may get the name of a file for writing debugging info.
216     * Bad news if MALLOC_DEBUG_FILE is set to /etc/passwd.  Sprintf() may be
217     * susceptible to bad locale settings....)
218     * (from PR 2790)
219     */
220    char **envp = environ;
221    char *empty_ptr = NULL;
222
223    environ = &empty_ptr; /* VERY safe environment */
224
225    if ((cleanenv = (char **) calloc(AP_ENVBUF, sizeof(char *))) == NULL) {
226        log_err("failed to malloc memory for environment\n");
227        exit(120);
228    }
229
230    sprintf(pathbuf, "PATH=%s", AP_SAFE_PATH);
231    cleanenv[cidx] = strdup(pathbuf);
232    cidx++;
233
234    for (ep = envp; *ep && cidx < AP_ENVBUF-1; ep++) {
235        for (idx = 0; safe_env_lst[idx]; idx++) {
236            if (!strncmp(*ep, safe_env_lst[idx],
237                         strlen(safe_env_lst[idx]))) {
238                cleanenv[cidx] = *ep;
239                cidx++;
240                break;
241            }
242        }
243    }
244
245    cleanenv[cidx] = NULL;
246
247    environ = cleanenv;
248}
249
250int main(int argc, char *argv[])
251{
252    int userdir = 0;        /* ~userdir flag             */
253    uid_t uid;              /* user information          */
254    gid_t gid;              /* target group placeholder  */
255    char *target_uname;     /* target user name          */
256    char *target_gname;     /* target group name         */
257    char *target_homedir;   /* target home directory     */
258    char *actual_uname;     /* actual user name          */
259    char *actual_gname;     /* actual group name         */
260    char *prog;             /* name of this program      */
261    char *cmd;              /* command to be executed    */
262    char cwd[AP_MAXPATH];   /* current working directory */
263    char dwd[AP_MAXPATH];   /* docroot working directory */
264    struct passwd *pw;      /* password entry holder     */
265    struct group *gr;       /* group entry holder        */
266    struct stat dir_info;   /* directory info holder     */
267    struct stat prg_info;   /* program info holder       */
268
269    /*
270     * Start with a "clean" environment
271     */
272    clean_env();
273
274    prog = argv[0];
275    /*
276     * Check existence/validity of the UID of the user
277     * running this program.  Error out if invalid.
278     */
279    uid = getuid();
280    if ((pw = getpwuid(uid)) == NULL) {
281        log_err("crit: invalid uid: (%ld)\n", uid);
282        exit(102);
283    }
284    /*
285     * See if this is a 'how were you compiled' request, and
286     * comply if so.
287     */
288    if ((argc > 1)
289        && (! strcmp(argv[1], "-V"))
290        && ((uid == 0)
291#ifdef _OSD_POSIX
292        /* User name comparisons are case insensitive on BS2000/OSD */
293            || (! strcasecmp(AP_HTTPD_USER, pw->pw_name)))
294#else  /* _OSD_POSIX */
295            || (! strcmp(AP_HTTPD_USER, pw->pw_name)))
296#endif /* _OSD_POSIX */
297        ) {
298#ifdef AP_DOC_ROOT
299        fprintf(stderr, " -D AP_DOC_ROOT=\"%s\"\n", AP_DOC_ROOT);
300#endif
301#ifdef AP_GID_MIN
302        fprintf(stderr, " -D AP_GID_MIN=%d\n", AP_GID_MIN);
303#endif
304#ifdef AP_HTTPD_USER
305        fprintf(stderr, " -D AP_HTTPD_USER=\"%s\"\n", AP_HTTPD_USER);
306#endif
307#ifdef AP_LOG_EXEC
308        fprintf(stderr, " -D AP_LOG_EXEC=\"%s\"\n", AP_LOG_EXEC);
309#endif
310#ifdef AP_SAFE_PATH
311        fprintf(stderr, " -D AP_SAFE_PATH=\"%s\"\n", AP_SAFE_PATH);
312#endif
313#ifdef AP_SUEXEC_UMASK
314        fprintf(stderr, " -D AP_SUEXEC_UMASK=%03o\n", AP_SUEXEC_UMASK);
315#endif
316#ifdef AP_UID_MIN
317        fprintf(stderr, " -D AP_UID_MIN=%d\n", AP_UID_MIN);
318#endif
319#ifdef AP_USERDIR_SUFFIX
320        fprintf(stderr, " -D AP_USERDIR_SUFFIX=\"%s\"\n", AP_USERDIR_SUFFIX);
321#endif
322        exit(0);
323    }
324    /*
325     * If there are a proper number of arguments, set
326     * all of them to variables.  Otherwise, error out.
327     */
328    if (argc < 4) {
329        log_err("too few arguments\n");
330        exit(101);
331    }
332    target_uname = argv[1];
333    target_gname = argv[2];
334    cmd = argv[3];
335
336    /*
337     * Check to see if the user running this program
338     * is the user allowed to do so as defined in
339     * suexec.h.  If not the allowed user, error out.
340     */
341#ifdef _OSD_POSIX
342    /* User name comparisons are case insensitive on BS2000/OSD */
343    if (strcasecmp(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#else  /*_OSD_POSIX*/
348    if (strcmp(AP_HTTPD_USER, pw->pw_name)) {
349        log_err("user mismatch (%s instead of %s)\n", pw->pw_name, AP_HTTPD_USER);
350        exit(103);
351    }
352#endif /*_OSD_POSIX*/
353
354    /*
355     * Check for a leading '/' (absolute path) in the command to be executed,
356     * or attempts to back up out of the current directory,
357     * to protect against attacks.  If any are
358     * found, error out.  Naughty naughty crackers.
359     */
360    if ((cmd[0] == '/') || (!strncmp(cmd, "../", 3))
361        || (strstr(cmd, "/../") != NULL)) {
362        log_err("invalid command (%s)\n", cmd);
363        exit(104);
364    }
365
366    /*
367     * Check to see if this is a ~userdir request.  If
368     * so, set the flag, and remove the '~' from the
369     * target username.
370     */
371    if (!strncmp("~", target_uname, 1)) {
372        target_uname++;
373        userdir = 1;
374    }
375
376    /*
377     * Error out if the target username is invalid.
378     */
379    if (strspn(target_uname, "1234567890") != strlen(target_uname)) {
380        if ((pw = getpwnam(target_uname)) == NULL) {
381            log_err("invalid target user name: (%s)\n", target_uname);
382            exit(105);
383        }
384    }
385    else {
386        if ((pw = getpwuid(atoi(target_uname))) == NULL) {
387            log_err("invalid target user id: (%s)\n", target_uname);
388            exit(121);
389        }
390    }
391
392    /*
393     * Error out if the target group name is invalid.
394     */
395    if (strspn(target_gname, "1234567890") != strlen(target_gname)) {
396        if ((gr = getgrnam(target_gname)) == NULL) {
397            log_err("invalid target group name: (%s)\n", target_gname);
398            exit(106);
399        }
400        gid = gr->gr_gid;
401        actual_gname = strdup(gr->gr_name);
402    }
403    else {
404        gid = atoi(target_gname);
405        actual_gname = strdup(target_gname);
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
442    /*
443     * Log the transaction here to be sure we have an open log
444     * before we setuid().
445     */
446    log_no_err("uid: (%s/%s) gid: (%s/%s) cmd: %s\n",
447               target_uname, actual_uname,
448               target_gname, actual_gname,
449               cmd);
450
451    /*
452     * Error out if attempt is made to execute as root or as
453     * a UID less than AP_UID_MIN.  Tsk tsk.
454     */
455    if ((uid == 0) || (uid < AP_UID_MIN)) {
456        log_err("cannot run as forbidden uid (%d/%s)\n", uid, cmd);
457        exit(107);
458    }
459
460    /*
461     * Error out if attempt is made to execute as root group
462     * or as a GID less than AP_GID_MIN.  Tsk tsk.
463     */
464    if ((gid == 0) || (gid < AP_GID_MIN)) {
465        log_err("cannot run as forbidden gid (%d/%s)\n", gid, cmd);
466        exit(108);
467    }
468
469    /*
470     * Change UID/GID here so that the following tests work over NFS.
471     *
472     * Initialize the group access list for the target user,
473     * and setgid() to the target group. If unsuccessful, error out.
474     */
475    if (((setgid(gid)) != 0) || (initgroups(actual_uname, gid) != 0)) {
476        log_err("failed to setgid (%ld: %s)\n", gid, cmd);
477        exit(109);
478    }
479
480    /*
481     * setuid() to the target user.  Error out on fail.
482     */
483    if ((setuid(uid)) != 0) {
484        log_err("failed to setuid (%ld: %s)\n", uid, cmd);
485        exit(110);
486    }
487
488    /*
489     * Get the current working directory, as well as the proper
490     * document root (dependant upon whether or not it is a
491     * ~userdir request).  Error out if we cannot get either one,
492     * or if the current working directory is not in the docroot.
493     * Use chdir()s and getcwd()s to avoid problems with symlinked
494     * directories.  Yuck.
495     */
496    if (getcwd(cwd, AP_MAXPATH) == NULL) {
497        log_err("cannot get current working directory\n");
498        exit(111);
499    }
500
501    if (userdir) {
502        if (((chdir(target_homedir)) != 0) ||
503            ((chdir(AP_USERDIR_SUFFIX)) != 0) ||
504            ((getcwd(dwd, AP_MAXPATH)) == NULL) ||
505            ((chdir(cwd)) != 0)) {
506            log_err("cannot get docroot information (%s)\n", target_homedir);
507            exit(112);
508        }
509    }
510    else {
511        if (((chdir(AP_DOC_ROOT)) != 0) ||
512            ((getcwd(dwd, AP_MAXPATH)) == NULL) ||
513            ((chdir(cwd)) != 0)) {
514            log_err("cannot get docroot information (%s)\n", AP_DOC_ROOT);
515            exit(113);
516        }
517    }
518    char *expected = malloc(strlen(target_homedir)+strlen(AP_USERDIR_SUFFIX)+1);
519    sprintf(expected, "%s/%s", target_homedir, AP_USERDIR_SUFFIX);
520    if ((strncmp(cwd, expected, strlen(expected))) != 0) {
521        log_err("error: file's directory not a subdirectory of user's home directory (%s, %s)\n", cwd, expected);
522        exit(114);
523    }
524
525    if ((strncmp(cwd, dwd, strlen(dwd))) != 0) {
526        log_err("command not in docroot (%s/%s)\n", cwd, cmd);
527        exit(114);
528    }
529
530    /*
531     * Stat the cwd and verify it is a directory, or error out.
532     */
533    if (((lstat(cwd, &dir_info)) != 0) || !(S_ISDIR(dir_info.st_mode))) {
534        log_err("cannot stat directory: (%s)\n", cwd);
535        exit(115);
536    }
537
538    /*
539     * Error out if cwd is writable by others.
540     */
541#if 0
542    if ((dir_info.st_mode & S_IWOTH) || (dir_info.st_mode & S_IWGRP)) {
543        log_err("directory is writable by others: (%s)\n", cwd);
544        exit(116);
545    }
546#endif
547
548    /*
549     * Error out if we cannot stat the program.
550     */
551    if (((lstat(cmd, &prg_info)) != 0) /*|| (S_ISLNK(prg_info.st_mode))*/) {
552        log_err("cannot stat program: (%s)\n", cmd);
553        exit(117);
554    }
555
556    /*
557     * Error out if the program is writable by others.
558     */
559#if 0
560    if ((prg_info.st_mode & S_IWOTH) || (prg_info.st_mode & S_IWGRP)) {
561        log_err("file is writable by others: (%s/%s)\n", cwd, cmd);
562        exit(118);
563    }
564#endif
565
566    /*
567     * Error out if the file is setuid or setgid.
568     */
569    if ((prg_info.st_mode & S_ISUID) || (prg_info.st_mode & S_ISGID)) {
570        log_err("file is either setuid or setgid: (%s/%s)\n", cwd, cmd);
571        exit(119);
572    }
573
574    /*
575     * Error out if the target name/group is different from
576     * the name/group of the cwd or the program.
577     */
578#if 0
579    if ((uid != dir_info.st_uid) ||
580        (gid != dir_info.st_gid) ||
581        (uid != prg_info.st_uid) ||
582        (gid != prg_info.st_gid)) {
583        log_err("target uid/gid (%ld/%ld) mismatch "
584                "with directory (%ld/%ld) or program (%ld/%ld)\n",
585                uid, gid,
586                dir_info.st_uid, dir_info.st_gid,
587                prg_info.st_uid, prg_info.st_gid);
588        exit(120);
589    }
590#endif
591    /*
592     * Error out if the program is not executable for the user.
593     * Otherwise, she won't find any error in the logs except for
594     * "[error] Premature end of script headers: ..."
595     */
596    if (!(prg_info.st_mode & S_IXUSR)) {
597        log_err("file has no execute permission: (%s/%s)\n", cwd, cmd);
598        exit(121);
599    }
600
601#ifdef AP_SUEXEC_UMASK
602    /*
603     * umask() uses inverse logic; bits are CLEAR for allowed access.
604     */
605    if ((~AP_SUEXEC_UMASK) & 0022) {
606        log_err("notice: AP_SUEXEC_UMASK of %03o allows "
607                "write permission to group and/or other\n", AP_SUEXEC_UMASK);
608    }
609    umask(AP_SUEXEC_UMASK);
610#endif /* AP_SUEXEC_UMASK */
611
612    /*
613     * Be sure to close the log file so the CGI can't
614     * mess with it.  If the exec fails, it will be reopened
615     * automatically when log_err is called.  Note that the log
616     * might not actually be open if AP_LOG_EXEC isn't defined.
617     * However, the "log" cell isn't ifdef'd so let's be defensive
618     * and assume someone might have done something with it
619     * outside an ifdef'd AP_LOG_EXEC block.
620     */
621    if (log != NULL) {
622        fclose(log);
623        log = NULL;
624    }
625
626    /*
627     * Execute the command, replacing our image with its own.
628     */
629#ifdef NEED_HASHBANG_EMUL
630    /* We need the #! emulation when we want to execute scripts */
631    {
632        extern char **environ;
633
634        ap_execve(cmd, &argv[3], environ);
635    }
636#else /*NEED_HASHBANG_EMUL*/
637    execv(cmd, &argv[3]);
638#endif /*NEED_HASHBANG_EMUL*/
639
640    /*
641     * (I can't help myself...sorry.)
642     *
643     * Uh oh.  Still here.  Where's the kaboom?  There was supposed to be an
644     * EARTH-shattering kaboom!
645     *
646     * Oh well, log the failure and error out.
647     */
648    log_err("(%d)%s: exec failed (%s)\n", errno, strerror(errno), cmd);
649    exit(255);
650}
Note: See TracBrowser for help on using the repository browser.