2 suPHP - (c)2002-2004 Sebastian Marsching <sebastian@marsching.com>
4 This file is part of suPHP.
6 suPHP is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 suPHP is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with suPHP; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 #include <sys/types.h>
33 extern char **environ;
35 void exec_script(char* scriptname)
39 char *const argv[] = { OPT_PATH_TO_PHP, NULL };
42 // Set the enviroment (for compatibility reasons)
44 if (getenv("SCRIPT_FILENAME") == NULL)
46 error_msg_exit(ERRCODE_WRONG_ENVIRONMENT, "SCRIPT_FILENAME is not set", __FILE__, __LINE__);
48 env = strdup(getenv("SCRIPT_FILENAME"));
49 suphp_setenv("PATH_TRANSLATED", env, 1);
54 suphp_setenv("REDIRECT_STATUS", "200", 0); // PHP may need this
58 suphp_setenv("PATH", "/bin:/usr/bin", 1);
60 // Check for PHP_CONFIG environment variable
62 if (getenv("PHP_CONFIG"))
64 if ((php_config = strdup(getenv("PHP_CONFIG")))==NULL)
65 error_sysmsg_exit(ERRCODE_UNKNOWN, "strdup() failed", __FILE__, __LINE__);
66 suphp_setenv("PHPRC", php_config, 1);
67 suphp_unsetenv("PHP_CONFIG");
70 #if (defined(OPT_USERGROUP_FORCE) || defined(OPT_USERGROUP_PARANOID))
71 suphp_unsetenv("PHP_SU_USER");
72 suphp_unsetenv("PHP_SU_GROUP");
75 envp = suphp_copyenv((const char **) environ);
78 execve(OPT_PATH_TO_PHP, argv, envp);
80 // Should never be reached
81 error_sysmsg_exit(ERRCODE_UNKNOWN, "execl() failed", __FILE__, __LINE__);
84 int suphp_passwdcpy(struct passwd *target, struct passwd *source)
86 if (!target || !source)
91 target->pw_name = strdup(source->pw_name);
96 target->pw_name = NULL;
98 // target->pw_passwd is never needed
99 target->pw_passwd = NULL;
101 target->pw_uid = source->pw_uid;
103 target->pw_gid = source->pw_gid;
105 // target->pw_gecos is never needed
106 target->pw_gecos = NULL;
108 // target->pw_dir is never needed
109 target->pw_dir = NULL;
111 // target->pw_shell is never needed
112 target->pw_shell = NULL;
117 int suphp_groupcpy(struct group *target, struct group *source)
119 if (!target || !source)
124 target->gr_name = strdup(source->gr_name);
125 if (!target->gr_name)
129 target->gr_name = NULL;
131 // target->gr_passwd is never needed
132 target->gr_passwd = NULL;
134 target->gr_gid = source->gr_gid;
136 // target->gr_mem is never needed
137 target->gr_mem = NULL;
142 int main(int argc, char* argv[])
144 // Check, if program has been started by Apache
145 struct passwd apacheuser;
146 struct passwd calluser;
147 struct passwd targetuser;
148 struct group targetgroup;
149 struct passwd *ptruser = NULL;
150 struct group *ptrgroup = NULL;
152 #if (defined(OPT_USERGROUP_FORCE) || defined(OPT_USERGROUP_PARANOID))
153 char *envusername = NULL;
154 char *envgroupname = NULL;
156 #if defined OPT_NO_PASSWD
157 int numeric_envuser = -1;
160 #if defined OPT_NO_GROUP
161 int numeric_envgroup = -1;
166 #ifdef OPT_USERGROUP_PARANOID
167 struct passwd envuser;
168 struct group envgroup;
172 // Declare empty structure for user
173 struct passwd emptyuser;
174 // Declare variable for information whether to use supplementary groups
175 int use_supp_groups = 1;
179 // Declare emtpy structure for group
180 struct group emptygroup;
183 char *path_translated = NULL;
186 // Initialize structure
187 emptyuser.pw_name = "";
188 emptyuser.pw_passwd = "";
189 emptyuser.pw_uid = 65534;
190 emptyuser.pw_gid = 65534;
191 emptyuser.pw_gecos = "";
192 emptyuser.pw_dir = "/dev/null";
193 emptyuser.pw_shell = "/bin/false";
197 // Initialize structure
198 emptygroup.gr_name = "";
199 emptygroup.gr_passwd = "";
200 emptygroup.gr_gid = 65534;
201 emptygroup.gr_mem = NULL;
207 if(getenv("SCRIPT_FILENAME") != NULL)
208 path_translated = strdup(getenv("SCRIPT_FILENAME"));
210 error_msg_exit(ERRCODE_WRONG_ENVIRONMENT, "SCRIPT_FILENAME is not set", __FILE__, __LINE__);
212 if ((ptruser = getpwnam(OPT_APACHE_USER))==NULL)
214 suphp_log_error("Could not get passwd information for Apache user (%s)", OPT_APACHE_USER);
215 error_sysmsg_exit(ERRCODE_UNKNOWN, "getpwnam() failed", __FILE__, __LINE__);
217 if (!suphp_passwdcpy(&apacheuser, ptruser))
219 error_sysmsg_exit(ERRCODE_UNKNOWN, "Could not copy struct passwd", __FILE__, __LINE__);
222 if ((ptruser = getpwuid(getuid()))==NULL)
224 suphp_log_error("Could not get passwd information for calling UID %d", getuid());
225 error_sysmsg_exit(ERRCODE_UNKNOWN, "getpwuid() failed", __FILE__, __LINE__);
227 if (!suphp_passwdcpy(&calluser, ptruser))
229 error_sysmsg_exit(ERRCODE_UNKNOWN, "Could not copy struct passwd", __FILE__, __LINE__);
233 if (calluser.pw_uid!=apacheuser.pw_uid)
235 suphp_log_error("suPHP was called by %s (%d), not by %s (%d)", calluser.pw_name, calluser.pw_uid, apacheuser.pw_name, apacheuser.pw_uid);
236 error_msg_exit(ERRCODE_WRONG_PARENT, "UID of calling process mismatches", __FILE__, __LINE__);
240 // Is the script in the DOCUMENT_ROOT?
241 if(!check_path(path_translated))
243 suphp_log_error("Script %s is not in the DOCUMENT_ROOT (%s)", path_translated, getenv("DOCUMENT_ROOT"));
244 error_msg_exit(ERRCODE_WRONG_PATH, "Script is not in document root", __FILE__, __LINE__);
248 // Does the script exist?
249 if(!file_exists(path_translated))
251 suphp_log_error("File %s not found", path_translated);
252 error_msg_exit(ERRCODE_FILE_NOT_FOUND, "Script not found", __FILE__, __LINE__);
254 // Check permissions for the script
255 if(!check_permissions(path_translated))
256 error_msg_exit(ERRCODE_WRONG_PERMISSIONS, "Inappropriate permissions set on script", __FILE__, __LINE__);
258 #if (defined(OPT_USERGROUP_FORCE) || defined(OPT_USERGROUP_PARANOID))
259 if ((envusername = getenv("PHP_SU_USER")) != NULL)
262 if ((*envusername == '#') && (strspn(envusername+1, "0123456789") == strlen(envusername+1)))
264 envusername = strdup(envusername+1);
265 numeric_envuser = atoi(envusername);
269 envusername = strdup(envusername);
272 error_msg_exit(ERRCODE_WRONG_ENVIRONMENT, "PHP_SU_USER is not set", __FILE__, __LINE__);
275 if ((numeric_envuser <= -1) && ((ptruser = getpwnam(envusername)) == NULL))
277 if ((ptruser = getpwnam(envusername)) == NULL)
280 suphp_log_error("getpwnam() for user %s failed", envusername);
281 error_msg_exit(ERRCODE_UNKNOWN, "getpwnam() failed", __FILE__, __LINE__);
285 #ifdef OPT_USERGROUP_PARANOID
287 if ((numeric_envuser <= -1) && (!suphp_passwdcpy(&envuser, ptruser)))
289 if (!suphp_passwdcpy(&envuser, ptruser))
292 error_sysmsg_exit(ERRCODE_UNKNOWN, "Could not copy struct passwd", __FILE__, __LINE__);
296 if ((numeric_envuser > -1) && !suphp_passwdcpy(&envuser, &emptyuser))
297 error_sysmsg_exit(ERRCODE_UNKNOWN, "Could not copy struct passwd", __FILE__, __LINE__);
298 else if (numeric_envuser > -1)
300 envuser.pw_uid = numeric_envuser;
301 envuser.pw_name = "NOT AVAILABLE";
306 #ifdef OPT_USERGROUP_FORCE
308 if ((numeric_envuser <= -1) && !suphp_passwdcpy(&targetuser, ptruser))
310 if (!suphp_passwdcpy(&targetuser, ptruser))
313 error_sysmsg_exit(ERRCODE_UNKNOWN, "Could not copy struct passwd", __FILE__, __LINE__);
317 if ((numeric_envuser > -1) && !suphp_passwdcpy(&targetuser, &emptyuser))
318 error_sysmsg_exit(ERRCODE_UNKNOWN, "Could not copy struct passwd", __FILE__, __LINE__);
319 else if (numeric_envuser > -1)
321 targetuser.pw_uid = numeric_envuser;
322 targetuser.pw_name = "NOT AVAILABLE";
332 #if (defined(OPT_USERGROUP_OWNER) || defined(OPT_USERGROUP_PARANOID))
333 // Get gid and uid of the file and check it
334 if ((ptruser = getpwuid(file_get_uid(path_translated)))==NULL)
337 emptyuser.pw_uid = file_get_uid(path_translated);
338 emptyuser.pw_gid = file_get_gid(path_translated);
339 emptyuser.pw_name = "NOT AVAILABLE";
340 if (!suphp_passwdcpy(&targetuser, &emptyuser))
342 error_sysmsg_exit(ERRCODE_UNKNOWN, "Could not copy struct passwd", __FILE__, __LINE__);
347 suphp_log_error ("Could not get passwd information for UID %d", file_get_uid(path_translated));
348 error_sysmsg_exit(ERRCODE_UNKNOWN, "getpwuid() failed", __FILE__, __LINE__);
353 if (!suphp_passwdcpy(&targetuser, ptruser))
355 error_sysmsg_exit(ERRCODE_UNKNOWN, "Could not copy struct passwd", __FILE__, __LINE__);
361 if (targetuser.pw_uid < OPT_MIN_UID)
363 suphp_log_error("UID of %s or its target (%d / %s) < %d", path_translated, targetuser.pw_uid, targetuser.pw_name, OPT_MIN_UID);
364 error_msg_exit(ERRCODE_LOW_UID, "User is not allowed to run scripts", __FILE__, __LINE__);
367 #ifdef OPT_USERGROUP_PARANOID
368 if (targetuser.pw_uid != envuser.pw_uid)
370 suphp_log_error("UID of owner of %s or its target (%d / %s) mismatches target UID specified in configuration (%d / %s)", path_translated, targetuser.pw_uid, targetuser.pw_name, envuser.pw_uid, envuser.pw_name);
371 error_msg_exit(ERRCODE_WRONG_UID, "Script has wrong UID", __FILE__, __LINE__);
375 #if (defined(OPT_USERGROUP_FORCE) || defined(OPT_USERGROUP_PARANOID))
376 if ((envgroupname = getenv("PHP_SU_GROUP")) != NULL)
378 if ((*envgroupname == '#') && (strspn(envgroupname+1, "0123456789") == strlen(envgroupname+1)))
380 envgroupname = strdup(envgroupname+1);
381 numeric_envgroup = atoi(envgroupname);
385 envgroupname = strdup(getenv("PHP_SU_GROUP"));
387 error_msg_exit(ERRCODE_WRONG_ENVIRONMENT, "PHP_SU_GROUP is not set", __FILE__, __LINE__);
390 if ((numeric_envgroup <= -1) && (ptrgroup = getgrnam(envgroupname)) == NULL)
392 if ((ptrgroup = getgrnam(envgroupname)) == NULL)
395 suphp_log_error("getgrnam() for group %s failed", envgroupname);
396 error_msg_exit(ERRCODE_UNKNOWN, "getgrnam() failed", __FILE__, __LINE__);
400 #ifdef OPT_USERGROUP_PARANOID
402 if ((numeric_envgroup <= -1) && !suphp_groupcpy(&envgroup, ptrgroup))
404 if (!suphp_groupcpy(&envgroup, ptrgroup))
407 error_sysmsg_exit(ERRCODE_UNKNOWN, "Could not copy struct group", __FILE__, __LINE__);
411 if ((numeric_envgroup > -1) && !(suphp_groupcpy(&envgroup, &emptygroup)))
412 error_sysmsg_exit(ERRCODE_UNKNOWN, "Could not copy struct group", __FILE__, __LINE__);
413 else if (numeric_envgroup > -1)
415 envgroup.gr_gid = numeric_envgroup;
416 envgroup.gr_name = "NOT AVAILABLE";
422 #ifdef OPT_USERGROUP_FORCE
424 if ((numeric_envgroup <= -1) && !suphp_groupcpy(&targetgroup, ptrgroup))
426 if (!suphp_groupcpy(&targetgroup, ptrgroup))
429 error_sysmsg_exit(ERRCODE_UNKNOWN, "Could not copy struct group", __FILE__, __LINE__);
433 if ((numeric_envgroup > -1) && !suphp_groupcpy(&targetgroup, &emptygroup))
434 error_sysmsg_exit(ERRCODE_UNKNOWN, "Could not copy struct group", __FILE__, __LINE__);
435 else if (numeric_envgroup > -1)
437 targetgroup.gr_gid = numeric_envgroup;
438 targetgroup.gr_name = "NOT AVAILABLE";
447 #if (defined(OPT_NO_PASSWD) && defined(OPT_USERGROUP_PARANOID))
448 if (numeric_envuser > -1)
449 envuser.pw_gid = envgroup.gr_gid;
452 #if (defined(OPT_NO_PASSWD) && defined(OPT_USERGROUP_FORCE))
453 if (numeric_envuser > -1)
454 targetuser.pw_gid = targetgroup.gr_gid;
457 #if (defined(OPT_USERGROUP_OWNER) || defined(OPT_USERGROUP_PARANOID))
458 if ((ptrgroup = getgrgid(file_get_gid(path_translated)))==NULL)
461 emptygroup.gr_gid = file_get_gid(path_translated);
462 emptygroup.gr_name = "NOT AVAILABLE";
463 if (!suphp_groupcpy(&targetgroup, &emptygroup))
465 error_sysmsg_exit(ERRCODE_UNKNOWN, "Could not copy struct group", __FILE__, __LINE__);
469 suphp_log_error ("Could not get group information for GID %d", file_get_gid(path_translated));
470 error_msg_exit(ERRCODE_UNKNOWN, "getgrgid() failed", __FILE__, __LINE__);
475 if (!suphp_groupcpy(&targetgroup, ptrgroup))
477 error_sysmsg_exit(ERRCODE_UNKNOWN, "Could not copy struct group", __FILE__, __LINE__);
483 if (targetgroup.gr_gid < OPT_MIN_GID)
485 suphp_log_error ("GID of %s or its target (%d / %s) < %d", path_translated, targetgroup.gr_gid, targetgroup.gr_name, OPT_MIN_GID);
486 error_msg_exit(ERRCODE_LOW_GID, "Group is not allowed to run scripts", __FILE__, __LINE__);
489 #ifdef OPT_USERGROUP_PARANOID
490 if (targetgroup.gr_gid != envgroup.gr_gid)
492 suphp_log_error("GID of group-owner of %s or its target (%d / %s) mismatches target GID specified in configuration (%d / %s)", path_translated, targetgroup.gr_gid, targetgroup.gr_name, envgroup.gr_gid, envgroup.gr_name);
493 error_msg_exit(ERRCODE_WRONG_GID, "Script has wrong GID", __FILE__, __LINE__);
497 #if (defined(OPT_USERGROUP_OWNER) || defined(OPT_USERGROUP_PARANOID))
498 // Check if file is a symbollink
499 if (file_is_symbollink(path_translated))
501 // Get gid and uid of the symbollink and check if it matches to the target
502 if (targetuser.pw_uid != file_get_uid_l(path_translated))
504 suphp_log_error ("UID of symbollink %s does not match its target", path_translated);
505 error_msg_exit(ERRCODE_SYMBOLLINK_NO_MATCH, "Symbol link UID mismatches target's UID", __FILE__, __LINE__);
507 if (targetgroup.gr_gid != file_get_gid_l(path_translated))
509 suphp_log_error ("GID of symbollink %s does not match its target", path_translated);
510 error_msg_exit(ERRCODE_SYMBOLLINK_NO_MATCH, "Symbol link GID mismatches target'S GID", __FILE__, __LINE__);
515 // We have to create the log entry before losing root privileges
516 suphp_log_info("Executing %s as user %s (%d), group %s (%d)", path_translated, targetuser.pw_name, targetuser.pw_uid, targetgroup.gr_name, targetgroup.gr_gid);
519 if (setgid(targetgroup.gr_gid))
521 suphp_log_error("Could not change GID to %d (%s)", targetgroup.gr_gid, targetgroup.gr_name);
522 error_sysmsg_exit(ERRCODE_UNKNOWN, "setgid() failed", __FILE__, __LINE__);
525 // Initialize supplementary groups for user
527 if (use_supp_groups && initgroups(targetuser.pw_name, targetuser.pw_gid))
529 suphp_log_error("Could not initialize supplementary groups for user %s (%d)", targetuser.pw_name, targetuser.pw_uid);
530 error_sysmsg_exit(ERRCODE_UNKNOWN, "initgroups() failed", __FILE__, __LINE__);
532 if (!use_supp_groups)
534 if (setgroups(0, NULL))
536 suphp_log_error("Could not set supplementary groups to null");
537 error_sysmsg_exit(ERRCODE_UNKNOWN, "setgroups() failed", __FILE__, __LINE__);
541 if (initgroups(targetuser.pw_name, targetuser.pw_gid))
543 suphp_log_error("Could not initialize supplementary groups for user %s (%d)", targetuser.pw_name, targetuser.pw_uid);
544 error_sysmsg_exit(ERRCODE_UNKNOWN, "initgroups() failed", __FILE__, __LINE__);
549 if (setuid(targetuser.pw_uid))
551 suphp_log_error("Could not change UID to %d (%s)", targetuser.pw_uid, targetuser.pw_name);
552 error_sysmsg_exit(ERRCODE_UNKNOWN, "setuid() failed", __FILE__, __LINE__);
555 // Execute the script with PHP
556 exec_script(path_translated);
558 // Still here? This cannot be right...
559 suphp_log_error("Error on exec() PHP for %s", path_translated);
560 error_exit(ERRCODE_UNKNOWN);
561 return ERRCODE_UNKNOWN;