examples: Added some commentary.
[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     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     }
77     if(usesyslog)
78         putenv("ASHD_USESYSLOG=1");
79     else
80         unsetenv("ASHD_USESYSLOG");
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. */
88     if(((fd = open(".ashd/output", O_WRONLY | O_APPEND)) >= 0) ||
89        ((fd = open("/dev/null", 0)) >= 0)) {
90         dup2(fd, 1);
91         close(fd);
92     }
93     if(((fd = open(".ashd/error", O_WRONLY | O_APPEND)) >= 0) ||
94        ((fd = open("/dev/null", 0)) >= 0)) {
95         dup2(fd, 2);
96         close(fd);
97     }
98 }
99
100 static 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
111 static int forkchild(char *usrnm)
112 {
113     struct passwd *pwd;
114     pid_t pid;
115     int i, fd[2];
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) {
129         for(i = 3; i < FD_SETSIZE; i++) {
130             if(i != fd[0])
131                 close(i);
132         }
133         dup2(fd[0], 0);
134         close(fd[0]);
135         login(pwd);
136         execchild(pwd);
137         exit(127);
138     }
139     close(fd[0]);
140     return(fd[1]);
141 }
142
143 static void serve2(struct user *usr, struct hthead *req, int fd)
144 {
145     if(usr->fd < 0)
146         usr->fd = forkchild(usr->name);
147     if(sendreq(usr->fd, req, fd)) {
148         if((errno == EPIPE) || (errno == ECONNRESET)) {
149             /* Assume that the child has crashed and restart it. */
150             close(usr->fd);
151             usr->fd = forkchild(usr->name);
152             if(!sendreq(usr->fd, req, fd))
153                 return;
154         }
155         flog(LOG_ERR, "could not pass on request to user `%s': %s", usr->name, strerror(errno));
156         close(usr->fd);
157         usr->fd = -1;
158         simpleerror(fd, 500, "User Error", "The request handler for that user keeps crashing.");
159     }
160 }
161
162 static void initnew(struct hthead *req, int fd, char *usrnm)
163 {
164     struct user *usr;
165     struct passwd *pwd;
166     struct group *grp;
167     int i, valid;
168
169     pwd = getpwnam(usrnm);
170     if(pwd == NULL) {
171         simpleerror(fd, 404, "Not Found", "No such resource could be found.");
172         return;
173     }
174     if(pwd->pw_uid < minuid) {
175         simpleerror(fd, 404, "Not Found", "No such resource could be found.");
176         return;
177     }
178     if(mgroup) {
179         if((grp = getgrnam(mgroup)) == NULL) {
180             flog(LOG_ERR, "unknown group %s specified to userplex", mgroup);
181             simpleerror(fd, 500, "Configuration Error", "The server has been erroneously configured.");
182             return;
183         }
184         valid = 0;
185         if(grp->gr_gid == pwd->pw_gid) {
186             valid = 1;
187         } else {
188             for(i = 0; grp->gr_mem[i] != NULL; i++) {
189                 if(!strcmp(grp->gr_mem[i], usrnm)) {
190                     valid = 1;
191                     break;
192                 }
193             }
194         }
195         if(!valid) {
196             simpleerror(fd, 404, "Not Found", "No such resource could be found.");
197             return;
198         }
199     }
200     omalloc(usr);
201     usr->name = sstrdup(usrnm);
202     usr->fd = -1;
203     usr->next = users;
204     usr->prev = NULL;
205     if(users != NULL)
206         users->prev = usr;
207     users = usr;
208     serve2(usr, req, fd);
209 }
210
211 static void serve(struct hthead *req, int fd)
212 {
213     struct user *usr;
214     char *usrnm, *p;
215     
216     if((p = strchr(req->rest, '/')) == NULL) {
217         if(*req->rest)
218             stdredir(req, fd, 301, sprintf3("%s/", req->url));
219         else
220             simpleerror(fd, 404, "Not Found", "No such resource could be found.");
221         return;
222     }
223     *(p++) = 0;
224     usrnm = sstrdup(req->rest);
225     replrest(req, p);
226     for(usr = users; usr != NULL; usr = usr->next) {
227         if(!strcmp(usr->name, usrnm)) {
228             serve2(usr, req, fd);
229             goto out;
230         }
231     }
232     initnew(req, fd, usrnm);
233     
234 out:
235     free(usrnm);
236 }
237
238 static void sighandler(int sig)
239 {
240 }
241
242 static void usage(FILE *out)
243 {
244     fprintf(out, "usage: userplex [-hIs] [-g GROUP] [-m MIN-UID] [-d PUB-DIR] [PROGRAM ARGS...]\n");
245 }
246
247 int main(int argc, char **argv)
248 {
249     struct hthead *req;
250     int c;
251     int fd;
252     struct charvbuf csbuf;
253     
254     while((c = getopt(argc, argv, "+hIsg:m:d:")) >= 0) {
255         switch(c) {
256         case 'I':
257             ignore = 1;
258             break;
259         case 's':
260             usesyslog = 1;
261             break;
262         case 'm':
263             if((minuid = atoi(optarg)) < 1) {
264                 fprintf(stderr, "userplex: argument to -m must be greater than 0\n");
265                 exit(1);
266             }
267             break;
268         case 'g':
269             mgroup = optarg;
270             break;
271         case 'd':
272             dirname = optarg;
273             break;
274         case 'h':
275             usage(stdout);
276             exit(0);
277         default:
278             usage(stderr);
279             exit(1);
280         }
281     }
282     if(optind < argc) {
283         childspec = argv + optind;
284     } else {
285         if(dirname == NULL)
286             dirname = "htpub";
287         bufinit(csbuf);
288         bufadd(csbuf, "dirplex");
289         bufadd(csbuf, dirname);
290         bufadd(csbuf, NULL);
291         childspec = csbuf.b;
292     }
293     signal(SIGCHLD, SIG_IGN);
294     signal(SIGPIPE, sighandler);
295     while(1) {
296         if((fd = recvreq(0, &req)) < 0) {
297             if(errno != 0)
298                 flog(LOG_ERR, "recvreq: %s", strerror(errno));
299             break;
300         }
301         serve(req, fd);
302         freehthead(req);
303         close(fd);
304     }
305     return(0);
306 }