doc: Documented htpipe.
[ashd.git] / src / userplex.c
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
38 struct user {
39     struct user *next, *prev;
40     char *name;
41     int fd;
42 };
43
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;
51
52 static void login(struct passwd *pwd)
53 {
54     int fd;
55     
56     setsid();
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     }
78     if(usesyslog)
79         putenv("ASHD_USESYSLOG=1");
80     else
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)) {
91         dup2(fd, 1);
92         close(fd);
93     }
94     if(((fd = open(".ashd/error", O_WRONLY | O_APPEND)) >= 0) ||
95        ((fd = open("/dev/null", O_WRONLY)) >= 0)) {
96         dup2(fd, 2);
97         close(fd);
98     }
99 }
100
101 static 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
112 static void execchild(struct passwd *pwd, struct hthead *forreq, int reqfd)
113 {
114     if(!ignore)
115         execl(".ashd/handler", ".ashd/handler", NULL);
116     if(dirname != NULL) {
117         if(access(dirname, X_OK | R_OK)) {
118             discardreq(0);
119             simpleerror(reqfd, 404, "Not Found", "No such resource could be found.");
120             return;
121         }
122     }
123     execvp(childspec[0], childspec);
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.");
127 }
128
129 static int forkchild(char *usrnm, struct hthead *forreq, int reqfd)
130 {
131     struct passwd *pwd;
132     pid_t pid;
133     int fd[2];
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) {
147         dup2(fd[0], 0);
148         close(fd[0]);
149         close(fd[1]);
150         login(pwd);
151         execchild(pwd, forreq, reqfd);
152         exit(127);
153     }
154     close(fd[0]);
155     fcntl(fd[1], F_SETFD, FD_CLOEXEC);
156     return(fd[1]);
157 }
158
159 static void serve2(struct user *usr, struct hthead *req, int fd)
160 {
161     int serr;
162     
163     if(usr->fd < 0)
164         usr->fd = forkchild(usr->name, req, fd);
165     if(sendreq2(usr->fd, req, fd, MSG_NOSIGNAL | MSG_DONTWAIT)) {
166         serr = errno;
167         if((serr == EPIPE) || (serr == ECONNRESET)) {
168             /* Assume that the child has crashed and restart it. */
169             close(usr->fd);
170             usr->fd = forkchild(usr->name, req, fd);
171             if(!sendreq2(usr->fd, req, fd, MSG_NOSIGNAL | MSG_DONTWAIT))
172                 return;
173         }
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         }
179     }
180 }
181
182 static 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
231 static 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     
254 out:
255     free(usrnm);
256 }
257
258 static void sighandler(int sig)
259 {
260 }
261
262 static void usage(FILE *out)
263 {
264     fprintf(out, "usage: userplex [-hIs] [-g GROUP] [-m MIN-UID] [-d PUB-DIR] [PROGRAM ARGS...]\n");
265 }
266
267 int main(int argc, char **argv)
268 {
269     struct hthead *req;
270     int c;
271     int fd;
272     struct charvbuf csbuf;
273     
274     while((c = getopt(argc, argv, "+hIsg:m:d:")) >= 0) {
275         switch(c) {
276         case 'I':
277             ignore = 1;
278             break;
279         case 's':
280             usesyslog = 1;
281             break;
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;
304     } else {
305         if(dirname == NULL)
306             dirname = "htpub";
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);
314     signal(SIGPIPE, sighandler);
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 }