0.6.0-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_USERGRPUP
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_USERGRPUP
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, RSRC_CONF|ACCESS_CONF,
253      ITERATE, "Tells mod_suphp to handle these MIME-types"},
254     {"suphp_RemoveHandler", suphp_handle_cmd_remove_handler, NULL, RSRC_CONF|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     struct stat finfo;
352     
353     int rv;
354     
355     char *auth_user = NULL;
356     char *auth_pass = NULL;
357
358     pool *p;
359
360     BUFF *script_in, *script_out, *script_err;
361
362     sconf = ap_get_module_config(r->server->module_config, &suphp_module);
363     dconf = ap_get_module_config(r->per_dir_config, &suphp_module);
364
365     p = r->main ? r->main->pool : r->pool;
366
367     /* only handle request if mod_suphp is active for this handler */
368     /* check only first byte of value (second has to be \0) */
369     if ((ap_table_get(dconf->handlers, r->handler) == NULL)
370         || (*(ap_table_get(dconf->handlers, r->handler)) == '0'))
371         return DECLINED;
372
373     /* check if suPHP is enabled for this request */
374
375     if (((sconf->engine != SUPHP_ENGINE_ON)
376          && (dconf->engine != SUPHP_ENGINE_ON))
377         || ((sconf->engine == SUPHP_ENGINE_ON)
378             && (dconf->engine == SUPHP_ENGINE_OFF)))
379         return DECLINED;
380
381     /* check if file is existing and accessible */
382     
383     rv = stat(ap_pstrdup(p, r->filename), &finfo);
384     if (rv == 0) {
385         ; /* do nothing */
386     } else if (errno == EACCES) {
387         ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "access to %s denied",
388                       r->filename);
389         return HTTP_FORBIDDEN;
390     } else if (errno == ENOENT || errno == ENOTDIR) {
391         ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "File does not exist: %s",
392                       r->filename);
393         return HTTP_NOT_FOUND;
394     } else {
395         ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "could not get fileinfo: %s",
396                       r->filename);
397         return HTTP_NOT_FOUND;
398     }
399
400 #ifdef SUPHP_USE_USERGROUP
401     if ((sconf->target_user == NULL || sconf->target_group == NULL)
402         && (dconf->target_user == NULL || dconf->target_group == NULL)) {
403         ap_log_rerror(APLOG_MARK, APLOG_ERR, r, 
404                       "No user or group set - set suPHP_UserGroup");
405         return HTTP_INTERNAL_SERVER_ERROR;
406     }
407 #endif /* SUPHP_USE_USERGROUP */
408
409
410     /* prepare environment for new process */
411     
412     ap_add_common_vars(r);
413     ap_add_cgi_vars(r);
414
415     ap_table_unset(r->subprocess_env, "SUPHP_PHP_CONFIG");
416     ap_table_unset(r->subprocess_env, "SUHP_AUTH_USER");
417     ap_table_unset(r->subprocess_env, "SUPHP_AUTH_PW");
418     
419 #ifdef SUPHP_USE_USERGROUP
420     ap_table_unset(r->subprocess_env, "SUPHP_USER");
421     ap_table_unset(r->subprocess_env, "SUPHP_GROUP");
422 #endif /* SUPHP_USE_USERGROUP */
423
424     if (dconf->php_config) {
425         ap_table_set(r->subprocess_env, "SUPHP_PHP_CONFIG", dconf->php_config);
426     }
427
428     ap_table_set(r->subprocess_env, "SUPHP_HANDLER", r->handler);
429
430     if (r->headers_in) {
431         const char *auth;
432         auth = ap_table_get(r->headers_in, "Authorization");
433         if (auth && auth[0] != 0 && strncmp(auth, "Basic ", 6) == 0) {
434             char *user;
435             char *pass;
436             user = ap_pbase64decode(p, auth + 6);
437             if (user) {
438                 pass = strchr(user, ':');
439                 if (pass) {
440                     *pass++ = '\0';
441                     auth_user = ap_pstrdup(p, user);
442                     auth_pass = ap_pstrdup(p, pass);
443                 }
444             }
445         }
446     }
447     
448     if (auth_user && auth_pass) {
449         ap_table_setn(r->subprocess_env, "SUPHP_AUTH_USER", auth_user);
450         ap_table_setn(r->subprocess_env, "SUPHP_AUTH_PW", auth_pass);
451     }
452
453 #ifdef SUPHP_USE_USERGROUP
454     if (dconf->target_user) {
455         ap_table_set(r->subprocess_env, "SUPHP_USER", dconf->target_user);
456     } else {
457         ap_table_set(r->subprocess_env, "SUPHP_USER", sconf->target_user);
458     }
459
460     if (dconf->target_group) {
461         ap_table_set(r->subprocess_env, "SUPHP_GROUP", dconf->target_group);
462     } else {
463         ap_table_set(r->subprocess_env, "SUPHP_GROUP", sconf->target_group);
464     }
465 #endif /* SUPHP_USE_USERGROUP */
466     
467     /* Fork child process */
468     
469     if (!ap_bspawn_child(p, suphp_child, (void *) r, kill_after_timeout,
470                          &script_in, &script_out, &script_err)) {
471         ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
472                       "couldn't spawn child process for: %s", r->filename);
473         return HTTP_INTERNAL_SERVER_ERROR;
474     }
475     
476     /* Transfer request body to script */
477     
478     if ((rv = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR)) != OK) {
479         /* Call failed, return status */
480         return rv;
481     }
482     
483     if (ap_should_client_block(r)) {
484         char buffer[HUGE_STRING_LEN];
485         int len_read;
486
487         ap_hard_timeout("reading request body", r);
488
489         while ((len_read = ap_get_client_block(r, buffer, HUGE_STRING_LEN)) 
490                > 0) {
491             ap_reset_timeout(r);
492             if (ap_bwrite(script_in, buffer, len_read) < len_read) {
493                 /* silly script stopped reading, soak up remaining message */
494                 while (ap_get_client_block(r, buffer, HUGE_STRING_LEN) > 0) {
495                     /* dump it */
496                 }
497                 break;
498             }
499         }
500         
501         ap_bflush(script_in);
502         ap_kill_timeout(r);
503     }
504
505     ap_bclose(script_in);
506     
507     /* Transfer output from script to client */
508
509     if (script_out) {
510         const char *location;
511         char hbuffer[MAX_STRING_LEN];
512         char buffer[HUGE_STRING_LEN];
513         
514         if (rv = ap_scan_script_header_err_buff(r, script_out, hbuffer)) {
515             return HTTP_INTERNAL_SERVER_ERROR;
516         }
517         
518         location = ap_table_get(r->headers_out, "Location");
519         if (location && r->status == 200) {
520             /* Soak up all the script output */
521             ap_hard_timeout("reading from script", r);
522             while (ap_bgets(buffer, HUGE_STRING_LEN, script_out) > 0) {
523                 continue;
524             }
525             ap_kill_timeout(r);
526             ap_bclose(script_out);
527             ap_bclose(script_err);
528         
529             if (location[0] == '/') { 
530                 /* Redirect has always GET method */
531                 r->method = ap_pstrdup(p, "GET");
532                 r->method_number = M_GET;
533                 
534                 /* Remove Content-Length - redirect should not read  *
535                  * request body                                      */
536                 ap_table_unset(r->headers_in, "Content-Length");
537                 
538                 /* Do the redirect */
539                 ap_internal_redirect_handler(location, r);
540                 return OK;
541             } else {
542                 /* Script did not set status 302 - so it does not want *
543                  * to send its own body. Simply set redirect status    */
544                 return REDIRECT;
545             }
546         }
547             
548         /* Output headers and body */
549
550         ap_send_http_header(r);
551         if (!r->header_only) {
552             ap_send_fb(script_out, r);
553         }
554         ap_bclose(script_out);
555         /* Errors have already been logged by child */
556         ap_bclose(script_err);
557     }
558     
559     return OK;
560 }
561
562
563 /* Handlers table */
564
565 static handler_rec suphp_handlers[] = {
566     {"*", suphp_handler},
567     {NULL}
568 };
569
570 /* Module definition */
571
572 module MODULE_VAR_EXPORT suphp_module = {
573     STANDARD_MODULE_STUFF,
574     NULL, /* initializer */
575     suphp_create_dir_config,     /* create directory config */
576     suphp_merge_dir_config,      /* merge directory config */
577     suphp_create_server_config,  /* create server config */
578     suphp_merge_server_config,   /* merge server config */
579     suphp_cmds,                  /* command table */
580     suphp_handlers,              /* content handlers */
581     NULL,                        /* URI-to-filename translation */
582     NULL,                        /* check/validate user_id */
583     NULL,                        /* check user_id is valid *here* */
584     NULL,                        /* check access by host address */
585     NULL,                        /* MIME type checker/setter */
586     NULL,                        /* fixups */
587     NULL,                        /* logger */
588     NULL,                        /* header parser */
589     NULL,                        /* process initialaization */
590     NULL,                        /* process exit/cleanup */
591     NULL                         /* post read_request handling */
592 };