[svn-inject] Installing original source of suphp
[manu/suphp.git] / src / suphp.c
1 /*
2     suPHP - (c)2002-2004 Sebastian Marsching <sebastian@marsching.com>
3     
4     This file is part of suPHP.
5
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.
10
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.
15
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
19 */
20
21
22 #include <pwd.h>
23 #include <grp.h>
24 #include <sys/types.h>
25 #include <unistd.h>
26 #include <fcntl.h>
27 #include <sys/stat.h>
28 #include <stdlib.h>
29 #include <string.h>
30
31 #include "suphp.h"
32
33 extern char **environ;
34
35 void exec_script(char* scriptname)
36 {
37  char *php_config;
38  char *env;
39  char *const argv[] = { OPT_PATH_TO_PHP, NULL };
40  char *const *envp;
41  
42  // Set the enviroment (for compatibility reasons)
43
44  if (getenv("SCRIPT_FILENAME") == NULL)
45  {
46   error_msg_exit(ERRCODE_WRONG_ENVIRONMENT, "SCRIPT_FILENAME is not set", __FILE__, __LINE__);
47  } 
48  env = strdup(getenv("SCRIPT_FILENAME"));
49  suphp_setenv("PATH_TRANSLATED", env, 1);
50  free(env);
51  
52  env = NULL;
53  
54  suphp_setenv("REDIRECT_STATUS", "200", 0); // PHP may need this
55  
56  // Set secure PATH
57  
58  suphp_setenv("PATH", "/bin:/usr/bin", 1);
59  
60  // Check for PHP_CONFIG environment variable
61  
62  if (getenv("PHP_CONFIG"))
63  {
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");
68  }
69
70 #if (defined(OPT_USERGROUP_FORCE) || defined(OPT_USERGROUP_PARANOID))
71  suphp_unsetenv("PHP_SU_USER");
72  suphp_unsetenv("PHP_SU_GROUP");
73 #endif
74  
75  envp = suphp_copyenv((const char **) environ);
76
77  // Exec PHP
78  execve(OPT_PATH_TO_PHP, argv, envp);
79  
80  // Should never be reached
81  error_sysmsg_exit(ERRCODE_UNKNOWN, "execl() failed", __FILE__, __LINE__);
82 }
83
84 int suphp_passwdcpy(struct passwd *target, struct passwd *source)
85 {
86  if (!target || !source)
87   return 0;
88  
89  if (source->pw_name)
90  {
91   target->pw_name = strdup(source->pw_name);
92   if (!target->pw_name)
93    return 0;
94  }
95  else
96   target->pw_name = NULL;
97  
98  // target->pw_passwd is never needed
99  target->pw_passwd = NULL;
100  
101  target->pw_uid = source->pw_uid;
102  
103  target->pw_gid = source->pw_gid;
104  
105  // target->pw_gecos is never needed
106  target->pw_gecos = NULL;
107  
108  // target->pw_dir is never needed
109  target->pw_dir = NULL;
110  
111  // target->pw_shell is never needed
112  target->pw_shell = NULL;
113  
114  return 1;
115 }
116
117 int suphp_groupcpy(struct group *target, struct group *source)
118 {
119  if (!target || !source)
120   return 0;
121  
122  if (source->gr_name)
123  {
124   target->gr_name = strdup(source->gr_name);
125   if (!target->gr_name)
126    return 0;
127  }
128  else
129   target->gr_name = NULL;
130  
131  // target->gr_passwd is never needed
132  target->gr_passwd = NULL;
133  
134  target->gr_gid = source->gr_gid;
135  
136  // target->gr_mem is never needed
137  target->gr_mem = NULL;
138  
139  return 1;
140 }
141
142 int main(int argc, char* argv[])
143 {
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;
151
152 #if (defined(OPT_USERGROUP_FORCE) || defined(OPT_USERGROUP_PARANOID))
153  char *envusername = NULL;
154  char *envgroupname = NULL;
155
156 #if defined OPT_NO_PASSWD
157  int numeric_envuser = -1;
158 #endif
159
160 #if defined OPT_NO_GROUP
161  int numeric_envgroup = -1;
162 #endif
163
164 #endif
165
166 #ifdef OPT_USERGROUP_PARANOID
167  struct passwd envuser;
168  struct group envgroup;
169 #endif
170
171 #ifdef OPT_NO_PASSWD
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;
176 #endif
177
178 #ifdef OPT_NO_GROUP
179  // Declare emtpy structure for group
180  struct group emptygroup;
181 #endif
182
183  char *path_translated = NULL;
184  
185 #ifdef OPT_NO_PASSWD
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";
194 #endif
195
196 #ifdef OPT_NO_GROUP
197  // Initialize structure
198  emptygroup.gr_name = "";
199  emptygroup.gr_passwd = "";
200  emptygroup.gr_gid = 65534;
201  emptygroup.gr_mem = NULL;
202 #endif
203
204  // Open logfile
205  suphp_init_log();
206  
207  if(getenv("SCRIPT_FILENAME") != NULL)
208   path_translated = strdup(getenv("SCRIPT_FILENAME"));
209  else
210   error_msg_exit(ERRCODE_WRONG_ENVIRONMENT, "SCRIPT_FILENAME is not set", __FILE__, __LINE__);
211   
212  if ((ptruser = getpwnam(OPT_APACHE_USER))==NULL)
213  {
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__);
216  }
217  if (!suphp_passwdcpy(&apacheuser, ptruser))
218  {
219   error_sysmsg_exit(ERRCODE_UNKNOWN, "Could not copy struct passwd", __FILE__, __LINE__);
220  }
221  
222  if ((ptruser = getpwuid(getuid()))==NULL)
223  {
224   suphp_log_error("Could not get passwd information for calling UID %d", getuid());
225   error_sysmsg_exit(ERRCODE_UNKNOWN, "getpwuid() failed", __FILE__, __LINE__);
226  }
227  if (!suphp_passwdcpy(&calluser, ptruser))
228  {
229   error_sysmsg_exit(ERRCODE_UNKNOWN, "Could not copy struct passwd", __FILE__, __LINE__);
230  }
231
232  
233  if (calluser.pw_uid!=apacheuser.pw_uid)
234  {
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__);
237  }
238  
239 #ifdef OPT_CHECKPATH
240  // Is the script in the DOCUMENT_ROOT?
241  if(!check_path(path_translated))
242  {
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__);
245  }
246 #endif  
247
248  // Does the script exist?
249  if(!file_exists(path_translated))
250  {
251   suphp_log_error("File %s not found", path_translated);
252   error_msg_exit(ERRCODE_FILE_NOT_FOUND, "Script not found", __FILE__, __LINE__);
253  }
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__);
257  
258 #if (defined(OPT_USERGROUP_FORCE) || defined(OPT_USERGROUP_PARANOID))
259  if ((envusername = getenv("PHP_SU_USER")) != NULL)
260  {
261 #ifdef OPT_NO_PASSWD
262   if ((*envusername == '#') && (strspn(envusername+1, "0123456789") == strlen(envusername+1)))
263   {
264    envusername = strdup(envusername+1);
265    numeric_envuser = atoi(envusername);
266   }
267   else
268 #endif
269    envusername = strdup(envusername);
270  }
271  else
272   error_msg_exit(ERRCODE_WRONG_ENVIRONMENT, "PHP_SU_USER is not set", __FILE__, __LINE__);
273
274 #ifdef OPT_NO_PASSWD 
275  if ((numeric_envuser <= -1) && ((ptruser = getpwnam(envusername)) == NULL))
276 #else
277  if ((ptruser = getpwnam(envusername)) == NULL)
278 #endif
279  {
280   suphp_log_error("getpwnam() for user %s failed", envusername);
281   error_msg_exit(ERRCODE_UNKNOWN, "getpwnam() failed", __FILE__, __LINE__);
282  }
283  else
284  {
285 #ifdef OPT_USERGROUP_PARANOID
286 #ifdef OPT_NO_PASSWD
287   if ((numeric_envuser <= -1) && (!suphp_passwdcpy(&envuser, ptruser)))
288 #else
289   if (!suphp_passwdcpy(&envuser, ptruser))
290 #endif
291   {
292    error_sysmsg_exit(ERRCODE_UNKNOWN, "Could not copy struct passwd", __FILE__, __LINE__);
293   }
294
295 #ifdef OPT_NO_PASSWD
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)
299   {
300    envuser.pw_uid = numeric_envuser;
301    envuser.pw_name = "NOT AVAILABLE";
302   }
303 #endif
304
305 #endif
306 #ifdef OPT_USERGROUP_FORCE
307 #ifdef OPT_NO_PASSWD
308   if ((numeric_envuser <= -1) && !suphp_passwdcpy(&targetuser, ptruser))
309 #else
310   if (!suphp_passwdcpy(&targetuser, ptruser))
311 #endif
312   {
313    error_sysmsg_exit(ERRCODE_UNKNOWN, "Could not copy struct passwd", __FILE__, __LINE__);
314   }
315
316 #ifdef OPT_NO_PASSWD
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)
320   {
321    targetuser.pw_uid = numeric_envuser;  
322    targetuser.pw_name = "NOT AVAILABLE";
323   }
324 #endif
325
326 #endif
327   free(envusername);
328   envusername = NULL;
329  }
330 #endif  
331  
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)
335  {
336 #ifdef OPT_NO_PASSWD
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))
341   {
342    error_sysmsg_exit(ERRCODE_UNKNOWN, "Could not copy struct passwd", __FILE__, __LINE__);
343   }
344
345   use_supp_groups = 0;
346 #else
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__);
349 #endif
350  }
351  else
352  {
353   if (!suphp_passwdcpy(&targetuser, ptruser))
354   {
355    error_sysmsg_exit(ERRCODE_UNKNOWN, "Could not copy struct passwd", __FILE__, __LINE__);
356   }
357
358  }
359 #endif
360
361  if (targetuser.pw_uid < OPT_MIN_UID)
362  {
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__);
365  }
366
367 #ifdef OPT_USERGROUP_PARANOID
368  if (targetuser.pw_uid != envuser.pw_uid)
369  {
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__);
372  }
373 #endif
374  
375 #if (defined(OPT_USERGROUP_FORCE) || defined(OPT_USERGROUP_PARANOID))
376  if ((envgroupname = getenv("PHP_SU_GROUP")) != NULL)
377 #ifdef OPT_NO_GROUP
378   if ((*envgroupname == '#') && (strspn(envgroupname+1, "0123456789") == strlen(envgroupname+1)))
379   {
380    envgroupname = strdup(envgroupname+1);
381    numeric_envgroup = atoi(envgroupname);
382   }
383   else
384 #endif
385    envgroupname = strdup(getenv("PHP_SU_GROUP"));
386  else
387   error_msg_exit(ERRCODE_WRONG_ENVIRONMENT, "PHP_SU_GROUP is not set", __FILE__, __LINE__);
388  
389 #ifdef OPT_NO_GROUP
390  if ((numeric_envgroup <= -1) && (ptrgroup = getgrnam(envgroupname)) == NULL)
391 #else
392  if ((ptrgroup = getgrnam(envgroupname)) == NULL)
393 #endif
394  {
395   suphp_log_error("getgrnam() for group %s failed", envgroupname);
396   error_msg_exit(ERRCODE_UNKNOWN, "getgrnam() failed", __FILE__, __LINE__);
397  }
398  else
399  {
400 #ifdef OPT_USERGROUP_PARANOID
401 #ifdef OPT_NO_GROUP
402   if ((numeric_envgroup <= -1) && !suphp_groupcpy(&envgroup, ptrgroup))
403 #else
404   if (!suphp_groupcpy(&envgroup, ptrgroup))
405 #endif
406   {
407    error_sysmsg_exit(ERRCODE_UNKNOWN, "Could not copy struct group", __FILE__, __LINE__);
408   }
409
410 #ifdef OPT_NO_GROUP
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)
414   {
415    envgroup.gr_gid = numeric_envgroup;
416    envgroup.gr_name = "NOT AVAILABLE";
417   }
418 #endif
419
420 #endif
421
422 #ifdef OPT_USERGROUP_FORCE
423 #ifdef OPT_NO_GROUP
424   if ((numeric_envgroup <= -1) && !suphp_groupcpy(&targetgroup, ptrgroup))
425 #else
426   if (!suphp_groupcpy(&targetgroup, ptrgroup))
427 #endif
428   {
429    error_sysmsg_exit(ERRCODE_UNKNOWN, "Could not copy struct group", __FILE__, __LINE__);
430   }
431
432 #ifdef OPT_NO_GROUP
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)
436   {
437    targetgroup.gr_gid = numeric_envgroup;
438    targetgroup.gr_name = "NOT AVAILABLE";
439   }
440 #endif
441
442 #endif
443   free(envgroupname);
444  }
445 #endif  
446
447 #if (defined(OPT_NO_PASSWD) && defined(OPT_USERGROUP_PARANOID))
448  if (numeric_envuser > -1)
449   envuser.pw_gid = envgroup.gr_gid;
450 #endif
451
452 #if (defined(OPT_NO_PASSWD) && defined(OPT_USERGROUP_FORCE))
453  if (numeric_envuser > -1)
454   targetuser.pw_gid = targetgroup.gr_gid;
455 #endif
456
457 #if (defined(OPT_USERGROUP_OWNER) || defined(OPT_USERGROUP_PARANOID))
458  if ((ptrgroup = getgrgid(file_get_gid(path_translated)))==NULL)
459  {
460 #ifdef OPT_NO_GROUP
461   emptygroup.gr_gid = file_get_gid(path_translated);
462   emptygroup.gr_name = "NOT AVAILABLE";
463   if (!suphp_groupcpy(&targetgroup, &emptygroup))
464   {
465    error_sysmsg_exit(ERRCODE_UNKNOWN, "Could not copy struct group", __FILE__, __LINE__);
466   }
467
468 #else
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__);
471 #endif 
472  }
473  else
474  {
475   if (!suphp_groupcpy(&targetgroup, ptrgroup))
476   {
477    error_sysmsg_exit(ERRCODE_UNKNOWN, "Could not copy struct group", __FILE__, __LINE__);
478   }
479
480  }
481 #endif
482  
483  if (targetgroup.gr_gid < OPT_MIN_GID)
484  {
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__);
487  }
488
489 #ifdef OPT_USERGROUP_PARANOID
490  if (targetgroup.gr_gid != envgroup.gr_gid)
491  {
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__);
494  }
495 #endif
496
497 #if (defined(OPT_USERGROUP_OWNER) || defined(OPT_USERGROUP_PARANOID))
498  // Check if file is a symbollink
499  if (file_is_symbollink(path_translated))
500  {
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))
503   {
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__);
506   }
507   if (targetgroup.gr_gid != file_get_gid_l(path_translated))
508   {
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__);
511   }
512  }
513 #endif
514
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);
517  
518  // Set gid and uid
519  if (setgid(targetgroup.gr_gid))
520  {
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__); 
523  }
524  
525  // Initialize supplementary groups for user
526 #ifdef OPT_NO_PASSWD
527  if (use_supp_groups && initgroups(targetuser.pw_name, targetuser.pw_gid))
528  {
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__);
531  }
532  if (!use_supp_groups)
533  {
534   if (setgroups(0, NULL))
535   {
536    suphp_log_error("Could not set supplementary groups to null");
537    error_sysmsg_exit(ERRCODE_UNKNOWN, "setgroups() failed", __FILE__, __LINE__);
538   }
539  }
540 #else
541  if (initgroups(targetuser.pw_name, targetuser.pw_gid))
542  {
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__);
545  }
546 #endif 
547
548  
549  if (setuid(targetuser.pw_uid))
550  {
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__);
553  }
554  
555  // Execute the script with PHP
556  exec_script(path_translated);
557  
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;
562 }