2 ashd - A Sane HTTP Daemon
3 Copyright (C) 2008 Fredrik Tolf <fredrik@dolda2000.com>
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
25 #include <sys/socket.h>
39 struct user *next, *prev;
44 static int ignore = 0;
45 static char *mgroup = NULL;
46 static char *dirname = NULL;
47 static char **childspec;
48 static uid_t minuid = 0;
49 static int usesyslog = 0;
50 static struct user *users = NULL;
52 static void login(struct passwd *pwd)
58 if(initgroups(pwd->pw_name, pwd->pw_gid)) {
59 flog(LOG_ERR, "could not init group list for %s: %s", pwd->pw_name, strerror(errno));
62 if(setgid(pwd->pw_gid)) {
63 flog(LOG_ERR, "could not switch group for %s: %s", pwd->pw_name, strerror(errno));
66 if(setuid(pwd->pw_uid)) {
67 flog(LOG_ERR, "could not switch user to %s: %s", pwd->pw_name, strerror(errno));
71 if(getuid() != pwd->pw_uid)
74 if(chdir(pwd->pw_dir)) {
75 flog(LOG_ERR, "could not change to home directory for %s: %s", pwd->pw_name, strerror(errno));
79 putenv("ASHD_USESYSLOG=1");
81 unsetenv("ASHD_USESYSLOG");
82 putenv(sprintf2("HOME=%s", pwd->pw_dir));
83 putenv(sprintf2("SHELL=%s", pwd->pw_shell));
84 putenv(sprintf2("USER=%s", pwd->pw_name));
85 putenv(sprintf2("LOGNAME", pwd->pw_name));
86 /* There's whole load of other stuff one could want to do here --
87 * getting Kerberos credentials, running PAM session modules, and
88 * who knows what. I'll add them along as I find them useful. */
89 if(((fd = open(".ashd/output", O_WRONLY | O_APPEND)) >= 0) ||
90 ((fd = open("/dev/null", O_WRONLY)) >= 0)) {
94 if(((fd = open(".ashd/error", O_WRONLY | O_APPEND)) >= 0) ||
95 ((fd = open("/dev/null", O_WRONLY)) >= 0)) {
101 static void discardreq(int fromfd)
106 if((fd = recvreq(fromfd, &req)) >= 0) {
112 static void execchild(struct passwd *pwd, struct hthead *forreq, int reqfd)
115 execl(".ashd/handler", ".ashd/handler", NULL);
116 if(dirname != NULL) {
117 if(access(dirname, X_OK | R_OK)) {
119 simpleerror(reqfd, 404, "Not Found", "No such resource could be found.");
123 execvp(childspec[0], childspec);
125 flog(LOG_ERR, "could not start request handler for user `%s': %s", pwd->pw_name, strerror(errno));
126 simpleerror(reqfd, 500, "User Error", "Could not start any request handler for that user.");
129 static int forkchild(char *usrnm, struct hthead *forreq, int reqfd)
135 /* XXX: There should be a way for the child to report errors (like
136 * 404 when htpub doesn't exist), but for now I don't bother with
137 * that. I might return to it at some later time. */
138 if(socketpair(PF_UNIX, SOCK_SEQPACKET, 0, fd))
140 if((pwd = getpwnam(usrnm)) == NULL) {
141 flog(LOG_ERR, "already discovered user `%s' has disappeared", usrnm);
144 if((pid = fork()) < 0)
151 execchild(pwd, forreq, reqfd);
155 fcntl(fd[1], F_SETFD, FD_CLOEXEC);
159 static void serve2(struct user *usr, struct hthead *req, int fd)
164 usr->fd = forkchild(usr->name, req, fd);
165 if(sendreq2(usr->fd, req, fd, MSG_NOSIGNAL | MSG_DONTWAIT)) {
167 if((serr == EPIPE) || (serr == ECONNRESET)) {
168 /* Assume that the child has crashed and restart it. */
170 usr->fd = forkchild(usr->name, req, fd);
171 if(!sendreq2(usr->fd, req, fd, MSG_NOSIGNAL | MSG_DONTWAIT))
174 flog(LOG_ERR, "could not pass on request to user `%s': %s", usr->name, strerror(serr));
182 static void initnew(struct hthead *req, int fd, char *usrnm)
189 pwd = getpwnam(usrnm);
191 simpleerror(fd, 404, "Not Found", "No such resource could be found.");
194 if(pwd->pw_uid < minuid) {
195 simpleerror(fd, 404, "Not Found", "No such resource could be found.");
199 if((grp = getgrnam(mgroup)) == NULL) {
200 flog(LOG_ERR, "unknown group %s specified to userplex", mgroup);
201 simpleerror(fd, 500, "Configuration Error", "The server has been erroneously configured.");
205 if(grp->gr_gid == pwd->pw_gid) {
208 for(i = 0; grp->gr_mem[i] != NULL; i++) {
209 if(!strcmp(grp->gr_mem[i], usrnm)) {
216 simpleerror(fd, 404, "Not Found", "No such resource could be found.");
221 usr->name = sstrdup(usrnm);
228 serve2(usr, req, fd);
231 static void serve(struct hthead *req, int fd)
236 if((p = strchr(req->rest, '/')) == NULL) {
238 stdredir(req, fd, 301, sprintf3("%s/", req->url));
240 simpleerror(fd, 404, "Not Found", "No such resource could be found.");
244 usrnm = sstrdup(req->rest);
246 for(usr = users; usr != NULL; usr = usr->next) {
247 if(!strcmp(usr->name, usrnm)) {
248 serve2(usr, req, fd);
252 initnew(req, fd, usrnm);
258 static void sighandler(int sig)
262 static void usage(FILE *out)
264 fprintf(out, "usage: userplex [-hIs] [-g GROUP] [-m MIN-UID] [-d PUB-DIR] [PROGRAM ARGS...]\n");
267 int main(int argc, char **argv)
272 struct charvbuf csbuf;
274 while((c = getopt(argc, argv, "+hIsg:m:d:")) >= 0) {
283 if((minuid = atoi(optarg)) < 1) {
284 fprintf(stderr, "userplex: argument to -m must be greater than 0\n");
303 childspec = argv + optind;
308 bufadd(csbuf, "dirplex");
309 bufadd(csbuf, dirname);
313 signal(SIGCHLD, SIG_IGN);
314 signal(SIGPIPE, sighandler);
316 if((fd = recvreq(0, &req)) < 0) {
318 flog(LOG_ERR, "recvreq: %s", strerror(errno));