callscgi: Exit properly on SIGTERM and SIGINT.
[ashd.git] / src / userplex.c
... / ...
CommitLineData
1/*
2 ashd - A Sane HTTP Daemon
3 Copyright (C) 2008 Fredrik Tolf <fredrik@dolda2000.com>
4
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.
9
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.
14
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/>.
17*/
18
19#include <stdlib.h>
20#include <stdio.h>
21#include <unistd.h>
22#include <fcntl.h>
23#include <string.h>
24#include <signal.h>
25#include <sys/socket.h>
26#include <errno.h>
27#include <pwd.h>
28#include <grp.h>
29
30#ifdef HAVE_CONFIG_H
31#include <config.h>
32#endif
33#include <utils.h>
34#include <log.h>
35#include <req.h>
36#include <resp.h>
37
38struct user {
39 struct user *next, *prev;
40 char *name;
41 int fd;
42};
43
44static int ignore = 0;
45static char *mgroup = NULL;
46static char *dirname = "htpub";
47static char **childspec;
48static uid_t minuid = 0;
49static struct user *users = NULL;
50
51static void login(struct passwd *pwd)
52{
53 int fd;
54
55 if(getuid() == 0) {
56 if(initgroups(pwd->pw_name, pwd->pw_gid)) {
57 flog(LOG_ERR, "could not init group list for %s: %s", pwd->pw_name, strerror(errno));
58 exit(1);
59 }
60 if(setgid(pwd->pw_gid)) {
61 flog(LOG_ERR, "could not switch group for %s: %s", pwd->pw_name, strerror(errno));
62 exit(1);
63 }
64 if(setuid(pwd->pw_uid)) {
65 flog(LOG_ERR, "could not switch user to %s: %s", pwd->pw_name, strerror(errno));
66 exit(1);
67 }
68 } else {
69 if(getuid() != pwd->pw_uid)
70 exit(1);
71 }
72 if(chdir(pwd->pw_dir)) {
73 flog(LOG_ERR, "could not change to home directory for %s: %s", pwd->pw_name, strerror(errno));
74 exit(1);
75 }
76 putenv(sprintf2("HOME=%s", pwd->pw_dir));
77 putenv(sprintf2("SHELL=%s", pwd->pw_shell));
78 putenv(sprintf2("USER=%s", pwd->pw_name));
79 putenv(sprintf2("LOGNAME", pwd->pw_name));
80 /* There's whole load of other stuff one could want to do here --
81 * getting Kerberos credentials, running PAM session modules, and
82 * who knows what. I'll add them along as I find them useful. */
83 if(((fd = open(".ashd/output", O_WRONLY | O_APPEND)) >= 0) ||
84 ((fd = open("/dev/null", 0)) >= 0)) {
85 dup2(fd, 1);
86 close(fd);
87 }
88 if(((fd = open(".ashd/error", O_WRONLY | O_APPEND)) >= 0) ||
89 ((fd = open("/dev/null", 0)) >= 0)) {
90 dup2(fd, 2);
91 close(fd);
92 }
93}
94
95static void execchild(struct passwd *pwd)
96{
97 if(!ignore)
98 execl(".ashd/handler", ".ashd/handler", NULL);
99 if(dirname != NULL) {
100 if(access(dirname, X_OK | R_OK))
101 return;
102 }
103 execvp(childspec[0], childspec);
104}
105
106static int forkchild(char *usrnm)
107{
108 struct passwd *pwd;
109 pid_t pid;
110 int i, fd[2];
111
112 /* XXX: There should be a way for the child to report errors (like
113 * 404 when htpub doesn't exist), but for now I don't bother with
114 * that. I might return to it at some later time. */
115 if(socketpair(PF_UNIX, SOCK_SEQPACKET, 0, fd))
116 return(-1);
117 if((pwd = getpwnam(usrnm)) == NULL) {
118 flog(LOG_ERR, "already discovered user `%s' has disappeared", usrnm);
119 return(-1);
120 }
121 if((pid = fork()) < 0)
122 return(-1);
123 if(pid == 0) {
124 for(i = 3; i < FD_SETSIZE; i++) {
125 if(i != fd[0])
126 close(i);
127 }
128 dup2(fd[0], 0);
129 close(fd[0]);
130 login(pwd);
131 execchild(pwd);
132 exit(127);
133 }
134 close(fd[0]);
135 return(fd[1]);
136}
137
138static void serve2(struct user *usr, struct hthead *req, int fd)
139{
140 if(usr->fd < 0)
141 usr->fd = forkchild(usr->name);
142 if(sendreq(usr->fd, req, fd)) {
143 if(errno == EPIPE) {
144 /* Assume that the child has crashed and restart it. */
145 close(usr->fd);
146 usr->fd = forkchild(usr->name);
147 if(!sendreq(usr->fd, req, fd))
148 return;
149 }
150 flog(LOG_ERR, "could not pass on request to user `%s': %s", usr->name, strerror(errno));
151 close(usr->fd);
152 usr->fd = -1;
153 simpleerror(fd, 500, "User Error", "The request handler for that user keeps crashing.");
154 }
155}
156
157static void initnew(struct hthead *req, int fd, char *usrnm)
158{
159 struct user *usr;
160 struct passwd *pwd;
161 struct group *grp;
162 int i, valid;
163
164 pwd = getpwnam(usrnm);
165 if(pwd == NULL) {
166 simpleerror(fd, 404, "Not Found", "No such resource could be found.");
167 return;
168 }
169 if(pwd->pw_uid < minuid) {
170 simpleerror(fd, 404, "Not Found", "No such resource could be found.");
171 return;
172 }
173 if(mgroup) {
174 if((grp = getgrnam(mgroup)) == NULL) {
175 flog(LOG_ERR, "unknown group %s specified to userplex", mgroup);
176 simpleerror(fd, 500, "Configuration Error", "The server has been erroneously configured.");
177 return;
178 }
179 valid = 0;
180 if(grp->gr_gid == pwd->pw_gid) {
181 valid = 1;
182 } else {
183 for(i = 0; grp->gr_mem[i] != NULL; i++) {
184 if(!strcmp(grp->gr_mem[i], usrnm)) {
185 valid = 1;
186 break;
187 }
188 }
189 }
190 if(!valid) {
191 simpleerror(fd, 404, "Not Found", "No such resource could be found.");
192 return;
193 }
194 }
195 omalloc(usr);
196 usr->name = sstrdup(usrnm);
197 usr->fd = -1;
198 usr->next = users;
199 usr->prev = NULL;
200 if(users != NULL)
201 users->prev = usr;
202 users = usr;
203 serve2(usr, req, fd);
204}
205
206static void serve(struct hthead *req, int fd)
207{
208 struct user *usr;
209 char *usrnm, *p;
210
211 if((p = strchr(req->rest, '/')) == NULL) {
212 if(*req->rest)
213 stdredir(req, fd, 301, sprintf3("%s/", req->url));
214 else
215 simpleerror(fd, 404, "Not Found", "No such resource could be found.");
216 return;
217 }
218 *(p++) = 0;
219 usrnm = sstrdup(req->rest);
220 replrest(req, p);
221 for(usr = users; usr != NULL; usr = usr->next) {
222 if(!strcmp(usr->name, usrnm)) {
223 serve2(usr, req, fd);
224 goto out;
225 }
226 }
227 initnew(req, fd, usrnm);
228
229out:
230 free(usrnm);
231}
232
233static void usage(FILE *out)
234{
235 fprintf(out, "usage: userplex [-hI] [-g GROUP] [-m MIN-UID] [-d PUB-DIR] [PROGRAM ARGS...]\n");
236}
237
238int main(int argc, char **argv)
239{
240 struct hthead *req;
241 int c;
242 int fd;
243 struct charvbuf csbuf;
244
245 while((c = getopt(argc, argv, "+hIg:m:d:")) >= 0) {
246 switch(c) {
247 case 'I':
248 ignore = 1;
249 break;
250 case 'm':
251 if((minuid = atoi(optarg)) < 1) {
252 fprintf(stderr, "userplex: argument to -m must be greater than 0\n");
253 exit(1);
254 }
255 break;
256 case 'g':
257 mgroup = optarg;
258 break;
259 case 'd':
260 dirname = optarg;
261 break;
262 case 'h':
263 usage(stdout);
264 exit(0);
265 default:
266 usage(stderr);
267 exit(1);
268 }
269 }
270 if(optind < argc) {
271 childspec = argv + optind;
272 dirname = NULL;
273 } else {
274 bufinit(csbuf);
275 bufadd(csbuf, "dirplex");
276 bufadd(csbuf, dirname);
277 bufadd(csbuf, NULL);
278 childspec = csbuf.b;
279 }
280 signal(SIGCHLD, SIG_IGN);
281 while(1) {
282 if((fd = recvreq(0, &req)) < 0) {
283 if(errno != 0)
284 flog(LOG_ERR, "recvreq: %s", strerror(errno));
285 break;
286 }
287 serve(req, fd);
288 freehthead(req);
289 close(fd);
290 }
291 return(0);
292}