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
71extern char **environ;
72static FILE *log = NULL;
73
74static 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
131static void log_err(const char *fmt,...)
132    __attribute__((format(printf,1,2)));
133static void log_no_err(const char *fmt,...)
134    __attribute__((format(printf,1,2)));
135static void err_output(int is_error, const char *fmt, va_list ap)
136    __attribute__((format(printf,2,0)));
137
138static 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
175static 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
187static 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
199static 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
247int 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}
652