]> granicus.if.org Git - apache/blob - modules/mappers/mod_userdir.c
Manoj has been pushing for this for a while, but I've been too dense
[apache] / modules / mappers / mod_userdir.c
1 /* ====================================================================
2  * Copyright (c) 1995-1999 The Apache Group.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  *
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in
13  *    the documentation and/or other materials provided with the
14  *    distribution.
15  *
16  * 3. All advertising materials mentioning features or use of this
17  *    software must display the following acknowledgment:
18  *    "This product includes software developed by the Apache Group
19  *    for use in the Apache HTTP server project (http://www.apache.org/)."
20  *
21  * 4. The names "Apache Server" and "Apache Group" must not be used to
22  *    endorse or promote products derived from this software without
23  *    prior written permission. For written permission, please contact
24  *    apache@apache.org.
25  *
26  * 5. Products derived from this software may not be called "Apache"
27  *    nor may "Apache" appear in their names without prior written
28  *    permission of the Apache Group.
29  *
30  * 6. Redistributions of any form whatsoever must retain the following
31  *    acknowledgment:
32  *    "This product includes software developed by the Apache Group
33  *    for use in the Apache HTTP server project (http://www.apache.org/)."
34  *
35  * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
36  * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
37  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
38  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE APACHE GROUP OR
39  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
41  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
42  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
43  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
44  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
45  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
46  * OF THE POSSIBILITY OF SUCH DAMAGE.
47  * ====================================================================
48  *
49  * This software consists of voluntary contributions made by many
50  * individuals on behalf of the Apache Group and was originally based
51  * on public domain software written at the National Center for
52  * Supercomputing Applications, University of Illinois, Urbana-Champaign.
53  * For more information on the Apache Group and the Apache HTTP server
54  * project, please see <http://www.apache.org/>.
55  *
56  */
57
58 /*
59  * mod_userdir... implement the UserDir command.  Broken away from the
60  * Alias stuff for a couple of good and not-so-good reasons:
61  *
62  * 1) It shows a real minimal working example of how to do something like
63  *    this.
64  * 2) I know people who are actually interested in changing this *particular*
65  *    aspect of server functionality without changing the rest of it.  That's
66  *    what this whole modular arrangement is supposed to be good at...
67  *
68  * Modified by Alexei Kosut to support the following constructs
69  * (server running at www.foo.com, request for /~bar/one/two.html)
70  *
71  * UserDir public_html      -> ~bar/public_html/one/two.html
72  * UserDir /usr/web         -> /usr/web/bar/one/two.html
73  * UserDir /home/ * /www     -> /home/bar/www/one/two.html
74  *  NOTE: theses ^ ^ space only added allow it to work in a comment, ignore
75  * UserDir http://x/users   -> (302) http://x/users/bar/one/two.html
76  * UserDir http://x/ * /y     -> (302) http://x/bar/y/one/two.html
77  *  NOTE: here also ^ ^
78  *
79  * In addition, you can use multiple entries, to specify alternate
80  * user directories (a la Directory Index). For example:
81  *
82  * UserDir public_html /usr/web http://www.xyz.com/users
83  *
84  * Modified by Ken Coar to provide for the following:
85  *
86  * UserDir disable[d] username ...
87  * UserDir enable[d] username ...
88  *
89  * If "disabled" has no other arguments, *all* ~<username> references are
90  * disabled, except those explicitly turned on with the "enabled" keyword.
91  */
92
93 #include "ap_config.h"
94 #include "httpd.h"
95 #include "http_config.h"
96 #include "http_request.h"
97 #ifdef HAVE_PWD_H
98 #include <pwd.h>
99 #endif
100
101 /* The default directory in user's home dir */
102 #ifndef DEFAULT_USER_DIR
103 #define DEFAULT_USER_DIR "public_html"
104 #endif
105
106 module userdir_module;
107
108 typedef struct userdir_config {
109     int globally_disabled;
110     char *userdir;
111     ap_table_t *enabled_users;
112     ap_table_t *disabled_users;
113 }              userdir_config;
114
115 /*
116  * Server config for this module: global disablement flag, a list of usernames
117  * ineligible for UserDir access, a list of those immune to global (but not
118  * explicit) disablement, and the replacement string for all others.
119  */
120
121 static void *create_userdir_config(ap_context_t *p, server_rec *s)
122 {
123     userdir_config
124     * newcfg = (userdir_config *) ap_pcalloc(p, sizeof(userdir_config));
125
126     newcfg->globally_disabled = 0;
127     newcfg->userdir = DEFAULT_USER_DIR;
128     newcfg->enabled_users = ap_make_table(p, 4);
129     newcfg->disabled_users = ap_make_table(p, 4);
130     return (void *) newcfg;
131 }
132
133 #define O_DEFAULT 0
134 #define O_ENABLE 1
135 #define O_DISABLE 2
136
137 static const char *set_user_dir(cmd_parms *cmd, void *dummy, char *arg)
138 {
139     userdir_config
140     * s_cfg = (userdir_config *) ap_get_module_config
141     (
142      cmd->server->module_config,
143      &userdir_module
144     );
145     char *username;
146     const char
147         *usernames = arg;
148     char *kw = ap_getword_conf(cmd->pool, &usernames);
149     ap_table_t *usertable;
150
151     /*
152      * Let's do the comparisons once.
153      */
154     if ((!strcasecmp(kw, "disable")) || (!strcasecmp(kw, "disabled"))) {
155         /*
156          * If there are no usernames specified, this is a global disable - we
157          * need do no more at this point than record the fact.
158          */
159         if (strlen(usernames) == 0) {
160             s_cfg->globally_disabled = 1;
161             return NULL;
162         }
163         usertable = s_cfg->disabled_users;
164     }
165     else if ((!strcasecmp(kw, "enable")) || (!strcasecmp(kw, "enabled"))) {
166         /*
167          * The "disable" keyword can stand alone or take a list of names, but
168          * the "enable" keyword requires the list.  Whinge if it doesn't have
169          * it.
170          */
171         if (strlen(usernames) == 0) {
172             return "UserDir \"enable\" keyword requires a list of usernames";
173         }
174         usertable = s_cfg->enabled_users;
175     }
176     else {
177         /*
178          * If the first (only?) value isn't one of our keywords, just copy
179          * the string to the userdir string.
180          */
181         s_cfg->userdir = ap_pstrdup(cmd->pool, arg);
182         return NULL;
183     }
184     /*
185      * Now we just take each word in turn from the command line and add it to
186      * the appropriate table.
187      */
188     while (*usernames) {
189         username = ap_getword_conf(cmd->pool, &usernames);
190         ap_table_setn(usertable, username, kw);
191     }
192     return NULL;
193 }
194
195 static const command_rec userdir_cmds[] = {
196     {"UserDir", set_user_dir, NULL, RSRC_CONF, RAW_ARGS,
197     "the public subdirectory in users' home directories, or 'disabled', or 'disabled username username...', or 'enabled username username...'"},
198     {NULL}
199 };
200
201 static int translate_userdir(request_rec *r)
202 {
203     void *server_conf = r->server->module_config;
204     const userdir_config *s_cfg =
205     (userdir_config *) ap_get_module_config(server_conf, &userdir_module);
206     char *name = r->uri;
207     const char *userdirs = s_cfg->userdir;
208     const char *w, *dname;
209     char *redirect;
210     char *x = NULL;
211     ap_finfo_t statbuf;
212
213     /*
214      * If the URI doesn't match our basic pattern, we've nothing to do with
215      * it.
216      */
217     if (
218         (s_cfg->userdir == NULL) ||
219         (name[0] != '/') ||
220         (name[1] != '~')
221         ) {
222         return DECLINED;
223     }
224
225     dname = name + 2;
226     w = ap_getword(r->pool, &dname, '/');
227
228     /*
229      * The 'dname' funny business involves backing it up to capture the '/'
230      * delimiting the "/~user" part from the rest of the URL, in case there
231      * was one (the case where there wasn't being just "GET /~user HTTP/1.0",
232      * for which we don't want to tack on a '/' onto the filename).
233      */
234
235     if (dname[-1] == '/') {
236         --dname;
237     }
238
239     /*
240      * If there's no username, it's not for us.  Ignore . and .. as well.
241      */
242     if (w[0] == '\0' || (w[1] == '.' && (w[2] == '\0' || (w[2] == '.' && w[3] == '\0')))) {
243         return DECLINED;
244     }
245     /*
246      * Nor if there's an username but it's in the disabled list.
247      */
248     if (ap_table_get(s_cfg->disabled_users, w) != NULL) {
249         return DECLINED;
250     }
251     /*
252      * If there's a global interdiction on UserDirs, check to see if this
253      * name is one of the Blessed.
254      */
255     if (
256         s_cfg->globally_disabled &&
257         (ap_table_get(s_cfg->enabled_users, w) == NULL)
258         ) {
259         return DECLINED;
260     }
261
262     /*
263      * Special cases all checked, onward to normal substitution processing.
264      */
265
266     while (*userdirs) {
267         const char *userdir = ap_getword_conf(r->pool, &userdirs);
268         char *filename = NULL;
269
270         if (strchr(userdir, '*'))
271             x = ap_getword(r->pool, &userdir, '*');
272
273         if (userdir[0] == '\0' || ap_os_is_path_absolute(userdir)) {
274             if (x) {
275 #ifdef HAVE_DRIVE_LETTERS
276                 /*
277                  * Crummy hack. Need to figure out whether we have been
278                  * redirected to a URL or to a file on some drive. Since I
279                  * know of no protocols that are a single letter, if the : is
280                  * the second character, I will assume a file was specified
281                  */
282                 if (strchr(x + 2, ':'))
283 #else
284                 if (strchr(x, ':'))
285 #endif                          /* WIN32 */
286                 {
287                     redirect = ap_pstrcat(r->pool, x, w, userdir, dname, NULL);
288                     ap_table_setn(r->headers_out, "Location", redirect);
289                     return REDIRECT;
290                 }
291                 else
292                     filename = ap_pstrcat(r->pool, x, w, userdir, NULL);
293             }
294             else
295                 filename = ap_pstrcat(r->pool, userdir, "/", w, NULL);
296         }
297         else if (strchr(userdir, ':')) {
298             redirect = ap_pstrcat(r->pool, userdir, "/", w, dname, NULL);
299             ap_table_setn(r->headers_out, "Location", redirect);
300             return REDIRECT;
301         }
302         else {
303 #ifdef WIN32
304             /* Need to figure out home dirs on NT */
305             return DECLINED;
306 #else                           /* WIN32 */
307             struct passwd *pw;
308             if ((pw = getpwnam(w))) {
309 #ifdef OS2
310                 /* Need to manually add user name for OS/2 */
311                 filename = ap_pstrcat(r->pool, pw->pw_dir, w, "/", userdir, NULL);
312 #else
313                 filename = ap_pstrcat(r->pool, pw->pw_dir, "/", userdir, NULL);
314 #endif
315             }
316 #endif                          /* WIN32 */
317         }
318
319         /*
320          * Now see if it exists, or we're at the last entry. If we are at the
321          * last entry, then use the filename generated (if there is one)
322          * anyway, in the hope that some handler might handle it. This can be
323          * used, for example, to run a CGI script for the user.
324          */
325         if (filename && (!*userdirs || 
326             ap_stat(&statbuf, filename, r->pool) == APR_SUCCESS)) {
327             r->filename = ap_pstrcat(r->pool, filename, dname, NULL);
328             /* when statbuf contains info on r->filename we can save a syscall
329              * by copying it to r->finfo
330              */
331             if (*userdirs && dname[0] == 0)
332                 r->finfo = statbuf;
333             return OK;
334         }
335     }
336
337     return DECLINED;
338 }
339
340 static void register_hooks(void)
341 {
342     static const char * const aszSucc[]={ "mod_alias.c",NULL };
343
344     ap_hook_translate_name(translate_userdir,NULL,aszSucc,HOOK_MIDDLE);
345 }
346
347 module userdir_module = {
348     STANDARD20_MODULE_STUFF,
349     NULL,                       /* dir config creater */
350     NULL,                       /* dir merger --- default is to override */
351     create_userdir_config,      /* server config */
352     NULL,                       /* merge server config */
353     userdir_cmds,               /* command ap_table_t */
354     NULL,                       /* handlers */
355     register_hooks              /* register hooks */
356 };