Updated changelog.
[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
3227f13e 56 setsid();
3b5e2f2d
FT
57 if(getuid() == 0) {
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));
60 exit(1);
61 }
62 if(setgid(pwd->pw_gid)) {
63 flog(LOG_ERR, "could not switch group for %s: %s", pwd->pw_name, strerror(errno));
64 exit(1);
65 }
66 if(setuid(pwd->pw_uid)) {
67 flog(LOG_ERR, "could not switch user to %s: %s", pwd->pw_name, strerror(errno));
68 exit(1);
69 }
70 } else {
71 if(getuid() != pwd->pw_uid)
72 exit(1);
73 }
74 if(chdir(pwd->pw_dir)) {
75 flog(LOG_ERR, "could not change to home directory for %s: %s", pwd->pw_name, strerror(errno));
76 exit(1);
77 }
d341283f
FT
78 if(usesyslog)
79 putenv("ASHD_USESYSLOG=1");
80 else
81 unsetenv("ASHD_USESYSLOG");
3b5e2f2d
FT
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. */
1755d287 89 if(((fd = open(".ashd/output", O_WRONLY | O_APPEND)) >= 0) ||
9e70ef79 90 ((fd = open("/dev/null", O_WRONLY)) >= 0)) {
3b5e2f2d
FT
91 dup2(fd, 1);
92 close(fd);
93 }
1755d287 94 if(((fd = open(".ashd/error", O_WRONLY | O_APPEND)) >= 0) ||
9e70ef79 95 ((fd = open("/dev/null", O_WRONLY)) >= 0)) {
3b5e2f2d
FT
96 dup2(fd, 2);
97 close(fd);
98 }
99}
100
615f3ba3
FT
101static void discardreq(int fromfd)
102{
103 struct hthead *req;
104 int fd;
105
106 if((fd = recvreq(fromfd, &req)) >= 0) {
107 freehthead(req);
108 close(fd);
109 }
110}
111
112static void execchild(struct passwd *pwd, struct hthead *forreq, int reqfd)
3b5e2f2d
FT
113{
114 if(!ignore)
115 execl(".ashd/handler", ".ashd/handler", NULL);
116 if(dirname != NULL) {
615f3ba3
FT
117 if(access(dirname, X_OK | R_OK)) {
118 discardreq(0);
119 simpleerror(reqfd, 404, "Not Found", "No such resource could be found.");
3b5e2f2d 120 return;
615f3ba3 121 }
3b5e2f2d
FT
122 }
123 execvp(childspec[0], childspec);
615f3ba3
FT
124 discardreq(0);
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.");
3b5e2f2d
FT
127}
128
615f3ba3 129static int forkchild(char *usrnm, struct hthead *forreq, int reqfd)
3b5e2f2d
FT
130{
131 struct passwd *pwd;
132 pid_t pid;
470938bd 133 int fd[2];
3b5e2f2d
FT
134
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))
139 return(-1);
140 if((pwd = getpwnam(usrnm)) == NULL) {
141 flog(LOG_ERR, "already discovered user `%s' has disappeared", usrnm);
142 return(-1);
143 }
144 if((pid = fork()) < 0)
145 return(-1);
146 if(pid == 0) {
3b5e2f2d
FT
147 dup2(fd[0], 0);
148 close(fd[0]);
470938bd 149 close(fd[1]);
3b5e2f2d 150 login(pwd);
615f3ba3 151 execchild(pwd, forreq, reqfd);
3b5e2f2d
FT
152 exit(127);
153 }
154 close(fd[0]);
470938bd 155 fcntl(fd[1], F_SETFD, FD_CLOEXEC);
3b5e2f2d
FT
156 return(fd[1]);
157}
158
159static void serve2(struct user *usr, struct hthead *req, int fd)
160{
92db680b
FT
161 int serr;
162
3b5e2f2d 163 if(usr->fd < 0)
615f3ba3 164 usr->fd = forkchild(usr->name, req, fd);
92db680b
FT
165 if(sendreq2(usr->fd, req, fd, MSG_NOSIGNAL | MSG_DONTWAIT)) {
166 serr = errno;
167 if((serr == EPIPE) || (serr == ECONNRESET)) {
3b5e2f2d
FT
168 /* Assume that the child has crashed and restart it. */
169 close(usr->fd);
615f3ba3 170 usr->fd = forkchild(usr->name, req, fd);
92db680b 171 if(!sendreq2(usr->fd, req, fd, MSG_NOSIGNAL | MSG_DONTWAIT))
3b5e2f2d
FT
172 return;
173 }
92db680b
FT
174 flog(LOG_ERR, "could not pass on request to user `%s': %s", usr->name, strerror(serr));
175 if(serr != EAGAIN) {
176 close(usr->fd);
177 usr->fd = -1;
178 }
3b5e2f2d
FT
179 }
180}
181
182static void initnew(struct hthead *req, int fd, char *usrnm)
183{
184 struct user *usr;
185 struct passwd *pwd;
186 struct group *grp;
187 int i, valid;
188
189 pwd = getpwnam(usrnm);
190 if(pwd == NULL) {
191 simpleerror(fd, 404, "Not Found", "No such resource could be found.");
192 return;
193 }
194 if(pwd->pw_uid < minuid) {
195 simpleerror(fd, 404, "Not Found", "No such resource could be found.");
196 return;
197 }
198 if(mgroup) {
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.");
202 return;
203 }
204 valid = 0;
205 if(grp->gr_gid == pwd->pw_gid) {
206 valid = 1;
207 } else {
208 for(i = 0; grp->gr_mem[i] != NULL; i++) {
209 if(!strcmp(grp->gr_mem[i], usrnm)) {
210 valid = 1;
211 break;
212 }
213 }
214 }
215 if(!valid) {
216 simpleerror(fd, 404, "Not Found", "No such resource could be found.");
217 return;
218 }
219 }
220 omalloc(usr);
221 usr->name = sstrdup(usrnm);
222 usr->fd = -1;
223 usr->next = users;
224 usr->prev = NULL;
225 if(users != NULL)
226 users->prev = usr;
227 users = usr;
228 serve2(usr, req, fd);
229}
230
231static void serve(struct hthead *req, int fd)
232{
233 struct user *usr;
234 char *usrnm, *p;
235
236 if((p = strchr(req->rest, '/')) == NULL) {
237 if(*req->rest)
238 stdredir(req, fd, 301, sprintf3("%s/", req->url));
239 else
240 simpleerror(fd, 404, "Not Found", "No such resource could be found.");
241 return;
242 }
243 *(p++) = 0;
244 usrnm = sstrdup(req->rest);
245 replrest(req, p);
246 for(usr = users; usr != NULL; usr = usr->next) {
247 if(!strcmp(usr->name, usrnm)) {
248 serve2(usr, req, fd);
249 goto out;
250 }
251 }
252 initnew(req, fd, usrnm);
253
254out:
255 free(usrnm);
256}
257
fd735432
FT
258static void sighandler(int sig)
259{
260}
261
3b5e2f2d
FT
262static void usage(FILE *out)
263{
d341283f 264 fprintf(out, "usage: userplex [-hIs] [-g GROUP] [-m MIN-UID] [-d PUB-DIR] [PROGRAM ARGS...]\n");
3b5e2f2d
FT
265}
266
267int main(int argc, char **argv)
268{
269 struct hthead *req;
270 int c;
271 int fd;
272 struct charvbuf csbuf;
273
d341283f 274 while((c = getopt(argc, argv, "+hIsg:m:d:")) >= 0) {
3b5e2f2d
FT
275 switch(c) {
276 case 'I':
277 ignore = 1;
278 break;
d341283f
FT
279 case 's':
280 usesyslog = 1;
281 break;
3b5e2f2d
FT
282 case 'm':
283 if((minuid = atoi(optarg)) < 1) {
284 fprintf(stderr, "userplex: argument to -m must be greater than 0\n");
285 exit(1);
286 }
287 break;
288 case 'g':
289 mgroup = optarg;
290 break;
291 case 'd':
292 dirname = optarg;
293 break;
294 case 'h':
295 usage(stdout);
296 exit(0);
297 default:
298 usage(stderr);
299 exit(1);
300 }
301 }
302 if(optind < argc) {
303 childspec = argv + optind;
3b5e2f2d 304 } else {
23c627d2
FT
305 if(dirname == NULL)
306 dirname = "htpub";
3b5e2f2d
FT
307 bufinit(csbuf);
308 bufadd(csbuf, "dirplex");
309 bufadd(csbuf, dirname);
310 bufadd(csbuf, NULL);
311 childspec = csbuf.b;
312 }
313 signal(SIGCHLD, SIG_IGN);
fd735432 314 signal(SIGPIPE, sighandler);
3b5e2f2d
FT
315 while(1) {
316 if((fd = recvreq(0, &req)) < 0) {
317 if(errno != 0)
318 flog(LOG_ERR, "recvreq: %s", strerror(errno));
319 break;
320 }
321 serve(req, fd);
322 freehthead(req);
323 close(fd);
324 }
325 return(0);
326}