/* ==================================================================== * * Copyright (c) 1996 NeoSoft, Inc. All rights reserved. * * You may freely redistribute most NeoSoft extensions to the Apache webserver * for any purpose except commercial resale and/or use in secure servers, * which requires, in either case, written permission from NeoSoft, Inc. Any * redistribution of this software must retain this copyright, unmodified * from the original. * * Certain NeoSoft extensions, such as those in support of electronic * commerce, require a license for use and may not be redistributed * without explicit written permission, obtained in advance of any * such distribution from NeoSoft, Inc. These files are clearly marked * with a different copyright. * * Other packages included with this distribution may contain their own * copyrights. It is your responsibility to insure that you are operating * in compliance with all relevant copyrights. The NeoSoft copyright is * not intenteded to infringe on the rights of the authors or owners of * said copyrights. * * Some of the software in this file may be derived from code * Copyright (c) 1995 The Apache Group. All rights reserved. * * Redistribution and use of Apache code in source and binary forms is * permitted under most conditions. Please consult the source code to * a standard Apache module, such as mod_include.c, for the exact * terms of this copyright. * * THIS SOFTWARE IS PROVIDED BY NEOSOFT ``AS IS'' AND ANY * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NEOSOFT, THE APACHE GROUP, OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * ==================================================================== */ /* * mod_neo_userdir... implement the UserDir command. Broken away from the * Alias stuff for a couple of good and not-so-good reasons: * * 1) It shows a real minimal working example of how to do something like * this. * 2) I know people who are actually interested in changing this *particular* * aspect of server functionality without changing the rest of it. That's * what this whole modular arrangement is supposed to be good at... * * This is the same as mod_userdir, except that if the user's shell * field in their password file entry contains the string "lock" then * it redirects to a user-is-locked URL. * */ #include "httpd.h" #include "http_config.h" #include "ap_compat.h" #include "tcl.h" #define LOCKED_URL "http://www.neosoft.com/neosoft/locked.html" module neo_userdir_module; /* * Sever config for this module is a little unconventional... * It's just one string anyway, so why pretend? */ typedef struct id_userdir { int from_id; int to_id; char *dir; struct id_userdir *next; } id_userdir; typedef struct user_subdir { char *dir; int len; struct user_subdir *next; } user_subdir; typedef struct { char *userdir; id_userdir *uiddir; id_userdir *giddir; user_subdir *forbidden_subdir; } user_dir_config; typedef struct { char *locked_url; } userdir_per_dir; void *create_userdir_config (pool *p, server_rec *s) { user_dir_config *new = (user_dir_config *) pcalloc(p, sizeof(user_dir_config)); new->userdir = DEFAULT_USER_DIR; return new; } void *create_userdir_per_dir (pool *p, char *dummy) { userdir_per_dir *new = (userdir_per_dir *) palloc (p, sizeof(userdir_per_dir)); new->locked_url = NULL; return new; } void *merge_userdir_per_dirs (pool *p, void *parentv, void *childv) { userdir_per_dir *child = (userdir_per_dir *)childv; userdir_per_dir *new = (userdir_per_dir *) palloc(p, sizeof(userdir_per_dir)); new->locked_url = pstrdup(p, child->locked_url); return new; } static int user_to_uid(char *user) { struct passwd *pw = getpwnam(user); if (pw) return pw->pw_uid; return -1; } static int group_to_gid(char *group) { struct group *grp = getgrnam(group); if (grp) return grp->gr_gid; return -1; } static int parse_id_range(char *id, int *from_id, int *to_id, int convert()) { char *p; if ((p = strchr(id, '-'))) { *p++ = '\0'; *from_id = atoi(id); *to_id = atoi(p); } else { *from_id = *to_id = convert(id); } return 1; } const char *set_user_dir (cmd_parms *cmd, void *dummy, char *arg) { server_rec *s = cmd->server; user_dir_config *uc; uc = (user_dir_config *)get_module_config(s->module_config, &neo_userdir_module); uc->userdir = pstrdup(cmd->pool, arg); return NULL; } const char *set_locked_url (cmd_parms *cmd, userdir_per_dir *pd, char *arg) { pd->locked_url = pstrdup(cmd->pool, arg); return NULL; } const char *set_forbidden_user_subdir (cmd_parms *cmd, void *dummy, char *arg) { server_rec *s = cmd->server; user_subdir *forbidden; user_dir_config *uc; uc = (user_dir_config *)get_module_config(s->module_config, &neo_userdir_module); forbidden = palloc(cmd->pool, sizeof(user_subdir)); forbidden->dir = pstrdup(cmd->pool, arg); forbidden->len = strlen(forbidden->dir); forbidden->next = uc->forbidden_subdir; uc->forbidden_subdir = forbidden; return NULL; } const char *set_uid_user_dir (cmd_parms *cmd, void *dummy, char *uid, char *dir) { server_rec *s = cmd->server; user_dir_config *uc; id_userdir *uiddir; uc = (user_dir_config *)get_module_config(s->module_config, &neo_userdir_module); uiddir = (id_userdir *)palloc(cmd->pool, sizeof(id_userdir)); parse_id_range(uid, &uiddir->from_id, &uiddir->to_id, user_to_uid); uiddir->dir = pstrdup(cmd->pool, dir); uiddir->next = uc->uiddir; uc->uiddir = uiddir; return NULL; } const char *set_gid_user_dir (cmd_parms *cmd, void *dummy, char *gid, char *dir) { server_rec *s = cmd->server; user_dir_config *uc; id_userdir *giddir; uc = (user_dir_config *)get_module_config(s->module_config, &neo_userdir_module); giddir = (id_userdir *)palloc(cmd->pool, sizeof(id_userdir)); parse_id_range(gid, &giddir->from_id, &giddir->to_id, group_to_gid); giddir->dir = pstrdup(cmd->pool, dir); giddir->next = uc->giddir; uc->giddir = giddir; return NULL; } int translate_userdir (request_rec *r) { void *server_conf = r->server->module_config; user_dir_config *uc = (user_dir_config *)get_module_config(server_conf, &neo_userdir_module); char *name = r->uri; userdir_per_dir *up = (userdir_per_dir *)get_module_config(r->per_dir_config, &neo_userdir_module); /* * If the URI doesn't match our basic pattern, we've nothing to do with * it. */ if ( (uc->userdir == NULL) || (name[0] != '/') || (name[1] != '~') || strcasecmp(uc->userdir, "disabled") ) { return DECLINED; } else { struct passwd *pw; const char *w, *dname, *userdir; id_userdir *d; user_subdir *forbid; dname = name + 2; w = getword(r->pool, &dname, '/'); /* The 'dname' funny business involves backing it up to capture * the '/' delimiting the "/~user" part from the rest of the URL, * in case there was one (the case where there wasn't being just * "GET /~user HTTP/1.0", for which we don't want to tack on a * '/' onto the filename). */ if (dname[-1] == '/') { --dname; } /* * If there's no username, it's not for us. Ignore . and .. as well. */ if (w[0] == '\0' || (w[1] == '.' && (w[2] == '\0' || (w[2] == '.' && w[3] == '\0')))) { return DECLINED; } if (!(pw = getpwnam(w))) { return NOT_FOUND; } if (strstr (pw->pw_shell, "lock") != NULL) { if (up->locked_url) table_set (r->headers_out, "Location", up->locked_url); else table_set (r->headers_out, "Location", LOCKED_URL); return REDIRECT; } userdir = uc->userdir; for (d = uc->giddir; d; d = d->next) { if (pw->pw_gid >= d->from_id && pw->pw_gid <= d->to_id) { userdir = d->dir; if (strcasecmp(userdir, "disabled") == 0) { return FORBIDDEN; } break; } } for (d = uc->uiddir; d; d = d->next) { if (pw->pw_uid >= d->from_id && pw->pw_uid <= d->to_id) { userdir = d->dir; if (strcasecmp(userdir, "disabled") == 0) { return FORBIDDEN; } break; } } for (forbid = uc->forbidden_subdir; forbid; forbid = forbid->next) { if (strncmp(dname+1, forbid->dir, forbid->len) == 0 && (dname[forbid->len+1] == '\0' || dname[forbid->len+1] == '/')) { return DECLINED; } } if (userdir[0] == '!') { extern Tcl_Interp *interp; int code; char *where; code = Tcl_VarEval(interp, userdir+1, " ", pw->pw_name, " ", pw->pw_dir, " {", pw->pw_gecos, "} {", dname+1, "}", (char*)NULL); if (code == TCL_OK && (where = Tcl_GetVar(interp, "userdir", TCL_GLOBAL_ONLY))) { if (strcasecmp(interp->result, "REDIRECT") == 0) { table_set (r->headers_out, "Location", where); return REDIRECT; } else { r->filename = pstrdup (r->pool, where); return OK; } } else { log_printf(r->server, "%s failed to resolve ~%s: %s", userdir+1, pw->pw_name, interp->result); return DECLINED; } } else { r->filename = pstrcat(r->pool, pw->pw_dir, "/", userdir, dname, NULL); } return OK; } return DECLINED; } command_rec userdir_cmds[] = { { "NeoUserDir", set_user_dir, NULL, RSRC_CONF, TAKE1, "the public subdirectory in users' home directories, or 'disabled'" }, { "LockedUrl", set_locked_url, NULL, RSRC_CONF|OR_AUTHCFG, TAKE1, NULL }, { "UidUserDir", set_uid_user_dir, NULL, RSRC_CONF, TAKE2, NULL }, { "GidUserDir", set_gid_user_dir, NULL, RSRC_CONF, TAKE2, NULL }, { "ForbidUserSubdir", set_forbidden_user_subdir, NULL, RSRC_CONF, TAKE1, NULL }, { NULL } }; module neo_userdir_module = { STANDARD_MODULE_STUFF, NULL, /* initializer */ create_userdir_per_dir, /* dir config creater */ merge_userdir_per_dirs, /* dir merger --- default is to override */ create_userdir_config, /* server config */ NULL, /* merge server config */ userdir_cmds, /* command table */ NULL, /* handlers */ translate_userdir, /*filename translation */ NULL, /* check_user_id */ NULL, /* check auth */ NULL, /* check access */ NULL, /* type_checker */ NULL, /* fixups */ NULL /* logger */ };