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