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