0.6.1.20060928-1 release
[manu/suphp.git] / src / apache / mod_suphp.c
1 /*
2     suPHP - (c)2002-2005 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 #define CORE_PRIVATE
23
24 #include "httpd.h"
25 #include "http_config.h"
26 #include "http_request.h"
27 #include "http_core.h"
28 #include "http_protocol.h"
29 #include "http_main.h"
30 #include "http_log.h"
31 #include "util_script.h"
32
33
34 #define SUPHP_CONFIG_MODE_SERVER 1
35 #define SUPHP_CONFIG_MODE_DIRECTORY 2
36
37 #define SUPHP_ENGINE_OFF 0
38 #define SUPHP_ENGINE_ON 1
39 #define SUPHP_ENGINE_UNDEFINED 2
40
41 #ifndef SUPHP_PATH_TO_SUPHP
42 #define SUPHP_PATH_TO_SUPHP "/usr/sbin/suphp"
43 #endif
44
45
46 /* Module declaration */
47 module MODULE_VAR_EXPORT suphp_module;
48
49
50 /* Configuration structure */
51 typedef struct {
52     int engine; // Status of suPHP_Engine
53     char *php_config;
54     int cmode;  // Server of directory configuration?
55 #ifdef SUPHP_USE_USERGROUP
56     char *target_user;
57     char *target_group;
58 #endif
59     table *handlers;
60 } suphp_conf;
61
62
63 /* Configuration mergers/creators */
64
65 static void *suphp_create_dir_config(pool *p, char *dir) {
66     suphp_conf *cfg = (suphp_conf *) ap_pcalloc(p, sizeof(suphp_conf));
67     cfg->php_config = NULL;
68     cfg->engine = SUPHP_ENGINE_UNDEFINED;
69     cfg->cmode = SUPHP_CONFIG_MODE_DIRECTORY;
70     
71 #ifdef SUPHP_USE_USERGROUP
72     cfg->target_user = NULL;
73     cfg->target_group = NULL;
74 #endif
75
76     /* Create table with 0 initial elements */
77     /* This size may be increased for performance reasons */
78     cfg->handlers = ap_make_table(p, 0);
79     
80     return (void *) cfg;
81 }
82
83 static void *suphp_merge_dir_config(pool *p, void *base, void *overrides) {
84     suphp_conf *parent = (suphp_conf *) base;
85     suphp_conf *child = (suphp_conf *) overrides;
86     suphp_conf *merged = (suphp_conf *) ap_pcalloc(p, sizeof(suphp_conf));
87
88     merged->cmode = SUPHP_CONFIG_MODE_DIRECTORY;
89     
90     if (child->php_config)
91         merged->php_config = ap_pstrdup(p, child->php_config);
92     else if (parent->php_config)
93         merged->php_config = ap_pstrdup(p, parent->php_config);
94     else 
95         merged->php_config = NULL;
96     
97     if (child->engine != SUPHP_ENGINE_UNDEFINED)
98         merged->engine = child->engine;
99     else
100         merged->engine = parent->engine;
101
102 #ifdef SUPHP_USE_USERGROUP
103     if (child->target_user)
104         merged->target_user = ap_pstrdup(p, child->target_user);
105     else if (parent->target_user)
106         merged->target_user = ap_pstrdup(p, parent->target_user);
107     else
108         merged->target_user = NULL;
109
110     if (child->target_group)
111         merged->target_group = ap_pstrdup(p, child->target_group);
112     else if (parent->target_group)
113         merged->target_group = ap_pstrdup(p, parent->target_group);
114     else
115         merged->target_group = NULL;
116 #endif
117     
118     merged->handlers = ap_overlay_tables(p, child->handlers, parent->handlers);
119
120     return (void *) merged;
121 }
122
123
124 static void *suphp_create_server_config(pool *p, server_rec *s) {
125     suphp_conf *cfg = (suphp_conf *) ap_pcalloc(p, sizeof(suphp_conf));
126     
127     cfg->engine = SUPHP_ENGINE_UNDEFINED;
128     cfg->cmode = SUPHP_CONFIG_MODE_SERVER;
129     
130 #ifdef SUPHP_USE_USERGROUP
131     cfg->target_user = NULL;
132     cfg->target_group = NULL;
133 #endif
134
135     return (void *) cfg;
136 }
137
138
139 static void *suphp_merge_server_config(pool *p, void *base, void *overrides) {
140     suphp_conf *parent = (suphp_conf *) base;
141     suphp_conf *child = (suphp_conf *) overrides;
142     suphp_conf *merged = (suphp_conf *) ap_pcalloc(p, sizeof(suphp_conf));
143     
144     if (child->engine != SUPHP_ENGINE_UNDEFINED)
145         merged->engine = child->engine;
146     else
147         merged->engine = parent->engine;
148
149 #ifdef SUPHP_USE_USERGROUP
150     if (child->target_user)
151         merged->target_user = ap_pstrdup(p, child->target_user);
152     else if (parent->target_user)
153         merged->target_user = ap_pstrdup(p, parent->target_user);
154     else
155         merged->target_user = NULL;
156
157     if (child->target_group)
158         merged->target_group = ap_pstrdup(p, child->target_group);
159     else if (parent->target_group)
160         merged->target_group = ap_pstrdup(p, parent->target_group);
161     else
162         merged->target_group = NULL;
163 #endif
164
165     return (void *) merged;
166 }
167
168
169 /* Command handlers */
170
171 static const char *suphp_handle_cmd_engine(cmd_parms *cmd, void *mconfig,
172                                            int flag) {
173     suphp_conf *cfg;
174
175     if (mconfig)
176         cfg = (suphp_conf *) mconfig;
177     else
178         cfg = (suphp_conf *) ap_get_module_config(cmd->server->module_config,
179                                                   &suphp_module);
180     
181     if (flag)
182         cfg->engine = SUPHP_ENGINE_ON;
183     else
184         cfg->engine = SUPHP_ENGINE_OFF;
185
186     return NULL;
187 }
188
189
190 static const char *suphp_handle_cmd_config(cmd_parms *cmd, void *mconfig,
191                                            const char *arg) {
192     suphp_conf *cfg = (suphp_conf *) mconfig;
193     
194     cfg->php_config = ap_pstrdup(cmd->pool, arg);
195     
196     return NULL;
197 }
198
199
200 #ifdef SUPHP_USE_USERGROUP
201 static const char *suphp_handle_cmd_user_group(cmd_parms *cmd, void *mconfig,
202                                                const char *arg1, 
203                                                const char *arg2) {
204     suphp_conf *cfg;
205
206     if (mconfig)
207         cfg = (suphp_conf *) mconfig;
208     else
209         cfg = ap_get_module_config(cmd->server->module_config, &suphp_module);
210     
211     cfg->target_user = ap_pstrdup(cmd->pool, arg1);
212     cfg->target_group = ap_pstrdup(cmd->pool, arg2);
213
214     return NULL;
215 }
216 #endif
217
218
219 static const char *suphp_handle_cmd_add_handler(cmd_parms *cmd, void *mconfig,
220                                                 const char *arg) {
221     suphp_conf *cfg = (suphp_conf *) mconfig;
222
223     // Mark active handlers with '1'
224     ap_table_set(cfg->handlers, arg, "1");
225
226     return NULL;
227 }
228
229 static const char *suphp_handle_cmd_remove_handler(cmd_parms *cmd, 
230                                                    void *mconfig, 
231                                                    const char *arg) {
232     suphp_conf *cfg = (suphp_conf *) mconfig;
233     
234     // Mark deactivated handlers with '0'
235     ap_table_set(cfg->handlers, arg, "0");
236
237     return NULL;
238 }
239
240
241 /* Command table */
242
243 static const command_rec suphp_cmds[] = {
244     {"suPHP_Engine", suphp_handle_cmd_engine, NULL, RSRC_CONF|ACCESS_CONF,
245      FLAG, "Whether suPHP is on or off, default is off"},
246     {"suPHP_ConfigPath", suphp_handle_cmd_config, NULL, OR_OPTIONS, TAKE1, 
247      "Where the php.ini resides, default is the PHP default"},
248 #ifdef SUPHP_USE_USERGROUP
249     {"suPHP_UserGroup", suphp_handle_cmd_user_group, NULL, 
250      RSRC_CONF|ACCESS_CONF, TAKE2, "User and group scripts shall be run as"},
251 #endif 
252     {"suPHP_AddHandler", suphp_handle_cmd_add_handler, NULL, ACCESS_CONF,
253      ITERATE, "Tells mod_suphp to handle these MIME-types"},
254     {"suphp_RemoveHandler", suphp_handle_cmd_remove_handler, NULL, ACCESS_CONF,
255      ITERATE, "Tells mod_suphp not to handle these MIME-types"},
256     {NULL}
257 };
258
259
260 /* Helper function which is called when spawning child process */
261
262 int suphp_child(void *rp, child_info *cinfo) {
263     request_rec *r = (request_rec *) rp;
264     core_dir_config *core_conf;
265     pool *p = r->main ? r->main->pool : r->pool;
266      
267     char **argv, **env;
268
269     core_conf = (core_dir_config *) ap_get_module_config(
270         r->per_dir_config, &core_module);
271     
272     /* We want to log output written to stderr */
273     ap_error_log2stderr(r->server);
274
275     /* prepare argv for new process */
276     
277     argv = ap_palloc(p, 2 * sizeof(char *));
278     argv[0] = SUPHP_PATH_TO_SUPHP;
279     argv[1] = NULL;
280
281     /* prepare environment */
282
283     env = ap_create_environment(p, r->subprocess_env);
284
285     /* We cannot use ap_call_exec because of the interference with suExec */
286     /* So we do everything ourselves */
287  
288     /* Set resource limits from core config */
289    
290 #ifdef RLIMIT_CPU
291     if (core_conf->limit_cpu != NULL) {
292         if ((setrlimit(RLIMIT_CPU, core_conf->limit_cpu)) != 0) {
293             ap_log_error(APLOG_MARK, APLOG_ERR, r->server, 
294                          "setrlimit: failed to set CPU usage limit");
295         }
296     }
297 #endif /* RLIMIT_CPU */
298 #ifdef RLIMIT_NPROC
299     if (core_conf->limit_nproc != NULL) {
300         if ((setrlimit(RLIMIT_NPROC, core_conf->limit_nproc)) != 0) {
301             ap_log_error(APLOG_MARK, APLOG_ERR, r->server, 
302                          "setrlimit: failed to set process limit");
303         }
304     }
305 #endif /* RLIMIT_NPROC */
306 #ifdef RLIMIT_AS
307     if (core_conf->limit_mem != NULL) {
308         if ((setrlimit(RLIMIT_AS, core_conf->limit_mem)) != 0) {
309             ap_log_error(APLOG_MARK, APLOG_ERR, r->server, 
310                          "setrlimit: failed to set memory limit");
311         }
312     }
313 #endif /* RLIMIT_VMEM */
314 #ifdef RLIMIT_DATA
315     if (core_conf->limit_mem != NULL) {
316         if ((setrlimit(RLIMIT_DATA, core_conf->limit_mem)) != 0) {
317             ap_log_error(APLOG_MARK, APLOG_ERR, r->server, 
318                          "setrlimit: failed to set memory limit");
319         }
320     }
321 #endif /* RLIMIT_VMEM */
322 #ifdef RLIMIT_VMEM
323     if (core_conf->limit_mem != NULL) {
324         if ((setrlimit(RLIMIT_VMEM, core_conf->limit_mem)) != 0) {
325             ap_log_error(APLOG_MARK, APLOG_ERR, r->server, 
326                          "setrlimit: failed to set memory limit");
327         }
328     }
329 #endif /* RLIMIT_VMEM */
330
331     /* mandatory cleanup before execution */
332     ap_cleanup_for_exec();
333     
334     execve(SUPHP_PATH_TO_SUPHP, argv, env);
335
336     /* We are still here? Okay - exec failed */
337     ap_log_error(APLOG_MARK, APLOG_ERR, NULL, "exec of %s failed",
338                  SUPHP_PATH_TO_SUPHP);
339     exit(0);
340     /* NOT REACHED */
341     return (0);
342 }
343
344
345 /* Handlers */
346
347 static int suphp_handler(request_rec *r) {
348     suphp_conf *sconf;
349     suphp_conf *dconf;
350
351 #ifdef SUPHP_USE_USERGROUP
352     char *ud_user = NULL;
353     char *ud_group = NULL;
354 #endif
355     
356     struct stat finfo;
357     
358     int rv;
359     
360     char *auth_user = NULL;
361     char *auth_pass = NULL;
362
363     pool *p;
364
365     BUFF *script_in, *script_out, *script_err;
366
367     sconf = ap_get_module_config(r->server->module_config, &suphp_module);
368     dconf = ap_get_module_config(r->per_dir_config, &suphp_module);
369
370     p = r->main ? r->main->pool : r->pool;
371
372     /* only handle request if mod_suphp is active for this handler */
373     /* check only first byte of value (second has to be \0) */
374     if ((ap_table_get(dconf->handlers, r->handler) == NULL)
375         || (*(ap_table_get(dconf->handlers, r->handler)) == '0'))
376         return DECLINED;
377
378     /* check if suPHP is enabled for this request */
379
380     if (((sconf->engine != SUPHP_ENGINE_ON)
381          && (dconf->engine != SUPHP_ENGINE_ON))
382         || ((sconf->engine == SUPHP_ENGINE_ON)
383             && (dconf->engine == SUPHP_ENGINE_OFF)))
384         return DECLINED;
385
386     /* check if file is existing and accessible */
387     
388     rv = stat(ap_pstrdup(p, r->filename), &finfo);
389     if (rv == 0) {
390         ; /* do nothing */
391     } else if (errno == EACCES) {
392         ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "access to %s denied",
393                       r->filename);
394         return HTTP_FORBIDDEN;
395     } else if (errno == ENOENT || errno == ENOTDIR) {
396         ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "File does not exist: %s",
397                       r->filename);
398         return HTTP_NOT_FOUND;
399     } else {
400         ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "could not get fileinfo: %s",
401                       r->filename);
402         return HTTP_NOT_FOUND;
403     }
404
405 #ifdef SUPHP_USE_USERGROUP
406     if ((sconf->target_user == NULL || sconf->target_group == NULL)
407         && (dconf->target_user == NULL || dconf->target_group == NULL)) {
408         
409         /* Identify mod_userdir request
410            As Apache 1.3 does not yet provide a clean way to see
411            whether a request was handled by mod_userdir, we assume
412            this is true for any request beginning with ~ */
413
414         int ud_success = 0; /* set to 1 on success */
415
416         if (!strncmp("/~", r->uri, 2)) {
417             char *username = ap_pstrdup(r->pool, r->uri + 2);
418             char *pos = strchr(username, '/');
419             if (pos) {
420                 *pos = 0;
421                 if (strlen(username)) {
422                     struct passwd *pw;
423                     struct group *gr;
424                     gid_t gid;
425                     char *grpname;
426                     if ((pw = getpwnam(username)) != NULL) {
427                         gid = pw->pw_gid;
428                         
429                         if ((gr = getgrgid(gid)) != NULL) {
430                             grpname = gr->gr_name;
431                         } else {
432                             if ((grpname = ap_palloc(r->pool, 16)) == NULL) {
433                                 return HTTP_INTERNAL_SERVER_ERROR;
434                             }
435                             ap_snprintf(grpname, 16, "#%ld", (long) gid);
436                         }
437                         
438                         ud_user = username;
439                         ud_group = grpname;
440                         ud_success = 1;
441                     }
442                 }
443             }       
444         }
445         
446         if (!ud_success) {
447             /* This is not a userdir request and user/group are not
448                set, so log the error and return */
449             ap_log_rerror(APLOG_MARK, APLOG_ERR, r, 
450                           "No user or group set - set suPHP_UserGroup");
451             return HTTP_INTERNAL_SERVER_ERROR;
452         }
453     }
454 #endif /* SUPHP_USE_USERGROUP */
455
456
457     /* prepare environment for new process */
458     
459     ap_add_common_vars(r);
460     ap_add_cgi_vars(r);
461
462     ap_table_unset(r->subprocess_env, "SUPHP_PHP_CONFIG");
463     ap_table_unset(r->subprocess_env, "SUPHP_AUTH_USER");
464     ap_table_unset(r->subprocess_env, "SUPHP_AUTH_PW");
465     
466 #ifdef SUPHP_USE_USERGROUP
467     ap_table_unset(r->subprocess_env, "SUPHP_USER");
468     ap_table_unset(r->subprocess_env, "SUPHP_GROUP");
469 #endif /* SUPHP_USE_USERGROUP */
470
471     if (dconf->php_config) {
472         ap_table_set(r->subprocess_env, "SUPHP_PHP_CONFIG", dconf->php_config);
473     }
474
475     ap_table_set(r->subprocess_env, "SUPHP_HANDLER", r->handler);
476
477     if (r->headers_in) {
478         const char *auth;
479         auth = ap_table_get(r->headers_in, "Authorization");
480         if (auth && auth[0] != 0 && strncmp(auth, "Basic ", 6) == 0) {
481             char *user;
482             char *pass;
483             user = ap_pbase64decode(p, auth + 6);
484             if (user) {
485                 pass = strchr(user, ':');
486                 if (pass) {
487                     *pass++ = '\0';
488                     auth_user = ap_pstrdup(p, user);
489                     auth_pass = ap_pstrdup(p, pass);
490                 }
491             }
492         }
493     }
494     
495     if (auth_user && auth_pass) {
496         ap_table_setn(r->subprocess_env, "SUPHP_AUTH_USER", auth_user);
497         ap_table_setn(r->subprocess_env, "SUPHP_AUTH_PW", auth_pass);
498     }
499
500 #ifdef SUPHP_USE_USERGROUP
501     if (dconf->target_user) {
502         ap_table_set(r->subprocess_env, "SUPHP_USER", dconf->target_user);
503     } else if (sconf->target_user) {
504         ap_table_set(r->subprocess_env, "SUPHP_USER", sconf->target_user);
505     } else {
506         ap_table_set(r->subprocess_env, "SUPHP_USER", ud_user);
507     }
508
509     if (dconf->target_group) {
510         ap_table_set(r->subprocess_env, "SUPHP_GROUP", dconf->target_group);
511     } else if (sconf->target_group) {
512         ap_table_set(r->subprocess_env, "SUPHP_GROUP", sconf->target_group);
513     } else {
514         ap_table_set(r->subprocess_env, "SUPHP_GROUP", ud_group);
515     }
516 #endif /* SUPHP_USE_USERGROUP */
517     
518     /* Fork child process */
519     
520     if (!ap_bspawn_child(p, suphp_child, (void *) r, kill_after_timeout,
521                          &script_in, &script_out, &script_err)) {
522         ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
523                       "couldn't spawn child process for: %s", r->filename);
524         return HTTP_INTERNAL_SERVER_ERROR;
525     }
526     
527     /* Transfer request body to script */
528     
529     if ((rv = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR)) != OK) {
530         /* Call failed, return status */
531         return rv;
532     }
533     
534     if (ap_should_client_block(r)) {
535         char buffer[HUGE_STRING_LEN];
536         int len_read;
537
538         ap_hard_timeout("reading request body", r);
539
540         while ((len_read = ap_get_client_block(r, buffer, HUGE_STRING_LEN)) 
541                > 0) {
542             ap_reset_timeout(r);
543             if (ap_bwrite(script_in, buffer, len_read) < len_read) {
544                 /* silly script stopped reading, soak up remaining message */
545                 while (ap_get_client_block(r, buffer, HUGE_STRING_LEN) > 0) {
546                     /* dump it */
547                 }
548                 break;
549             }
550         }
551         
552         ap_bflush(script_in);
553         ap_kill_timeout(r);
554     }
555
556     ap_bclose(script_in);
557     
558     /* Transfer output from script to client */
559
560     if (script_out) {
561         const char *location;
562         char hbuffer[MAX_STRING_LEN];
563         char buffer[HUGE_STRING_LEN];
564         
565         rv = ap_scan_script_header_err_buff(r, script_out, hbuffer);
566         if (rv == HTTP_NOT_MODIFIED) {
567             return rv;
568         } else if (rv) {
569             return HTTP_INTERNAL_SERVER_ERROR;
570         }
571         
572         location = ap_table_get(r->headers_out, "Location");
573         if (location && r->status == 200) {
574             /* Soak up all the script output */
575             ap_hard_timeout("reading from script", r);
576             while (ap_bgets(buffer, HUGE_STRING_LEN, script_out) > 0) {
577                 continue;
578             }
579             ap_kill_timeout(r);
580             ap_bclose(script_out);
581             ap_bclose(script_err);
582         
583             if (location[0] == '/') { 
584                 /* Redirect has always GET method */
585                 r->method = ap_pstrdup(p, "GET");
586                 r->method_number = M_GET;
587                 
588                 /* Remove Content-Length - redirect should not read  *
589                  * request body                                      */
590                 ap_table_unset(r->headers_in, "Content-Length");
591                 
592                 /* Do the redirect */
593                 ap_internal_redirect_handler(location, r);
594                 return OK;
595             } else {
596                 /* Script did not set status 302 - so it does not want *
597                  * to send its own body. Simply set redirect status    */
598                 return REDIRECT;
599             }
600         }
601             
602         /* Output headers and body */
603
604         ap_send_http_header(r);
605         if (!r->header_only) {
606             ap_send_fb(script_out, r);
607         }
608         ap_bclose(script_out);
609         /* Errors have already been logged by child */
610         ap_bclose(script_err);
611     }
612     
613     return OK;
614 }
615
616
617 /* Handlers table */
618
619 static handler_rec suphp_handlers[] = {
620     {"*", suphp_handler},
621     {NULL}
622 };
623
624 /* Module definition */
625
626 module MODULE_VAR_EXPORT suphp_module = {
627     STANDARD_MODULE_STUFF,
628     NULL, /* initializer */
629     suphp_create_dir_config,     /* create directory config */
630     suphp_merge_dir_config,      /* merge directory config */
631     suphp_create_server_config,  /* create server config */
632     suphp_merge_server_config,   /* merge server config */
633     suphp_cmds,                  /* command table */
634     suphp_handlers,              /* content handlers */
635     NULL,                        /* URI-to-filename translation */
636     NULL,                        /* check/validate user_id */
637     NULL,                        /* check user_id is valid *here* */
638     NULL,                        /* check access by host address */
639     NULL,                        /* MIME type checker/setter */
640     NULL,                        /* fixups */
641     NULL,                        /* logger */
642     NULL,                        /* header parser */
643     NULL,                        /* process initialaization */
644     NULL,                        /* process exit/cleanup */
645     NULL                         /* post read_request handling */
646 };