Merge branch 'master' of git.dolda2000.com:/srv/git/r/ashd
[ashd.git] / src / htextauth.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 <string.h>
23 #include <errno.h>
24 #include <sys/poll.h>
25 #include <sys/wait.h>
26 #include <time.h>
27 #include <signal.h>
28
29 #ifdef HAVE_CONFIG_H
30 #include <config.h>
31 #endif
32 #include <utils.h>
33 #include <log.h>
34 #include <req.h>
35 #include <proc.h>
36 #include <resp.h>
37
38 struct cache {
39     struct cache *next, *prev;
40     char *user, *pass;
41     time_t lastuse;
42 };
43
44 static int ch;
45 static char **authcmd;
46 static char *realm;
47 static int docache = 1, reqssl;
48 static struct cache *cache;
49 static time_t now, lastclean;
50
51 static int auth(struct hthead *req, int fd, char *user, char *pass);
52
53 static void reqauth(struct hthead *req, int fd)
54 {
55     struct charbuf buf;
56     FILE *out;
57     char *rn;
58     
59     rn = realm;
60     if(rn == NULL)
61         rn = "auth";
62     bufinit(buf);
63     bufcatstr(buf, "<?xml version=\"1.0\" encoding=\"US-ASCII\"?>\r\n");
64     bufcatstr(buf, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\r\n");
65     bufcatstr(buf, "<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en-US\" xml:lang=\"en-US\">\r\n");
66     bufcatstr(buf, "<head>\r\n");
67     bprintf(&buf, "<title>Authentication Required</title>\r\n");
68     bufcatstr(buf, "</head>\r\n");
69     bufcatstr(buf, "<body>\r\n");
70     bprintf(&buf, "<h1>Authentication Required</h1>\r\n");
71     bprintf(&buf, "<p>You need to authenticate to access the requested resource.</p>\r\n");
72     bufcatstr(buf, "</body>\r\n");
73     bufcatstr(buf, "</html>\r\n");
74     out = fdopen(dup(fd), "w");
75     fprintf(out, "HTTP/1.1 401 Authentication Required\n");
76     fprintf(out, "WWW-Authenticate: Basic realm=\"%s\"\n", rn);
77     fprintf(out, "Content-Type: text/html\n");
78     fprintf(out, "Content-Length: %zi\n", buf.d);
79     fprintf(out, "\n");
80     fwrite(buf.b, 1, buf.d, out);
81     fclose(out);
82     buffree(buf);
83 }
84
85 static void authinval(struct hthead *req, int fd, char *msg)
86 {
87     struct charbuf buf;
88     FILE *out;
89     char *rn;
90     
91     rn = realm;
92     if(rn == NULL)
93         rn = "auth";
94     bufinit(buf);
95     bufcatstr(buf, "<?xml version=\"1.0\" encoding=\"US-ASCII\"?>\r\n");
96     bufcatstr(buf, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\r\n");
97     bufcatstr(buf, "<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en-US\" xml:lang=\"en-US\">\r\n");
98     bufcatstr(buf, "<head>\r\n");
99     bprintf(&buf, "<title>Invalid authentication</title>\r\n");
100     bufcatstr(buf, "</head>\r\n");
101     bufcatstr(buf, "<body>\r\n");
102     bprintf(&buf, "<h1>Invalid authentication</h1>\r\n");
103     bprintf(&buf, "<p>%s</p>\r\n", htmlquote(msg));
104     bufcatstr(buf, "</body>\r\n");
105     bufcatstr(buf, "</html>\r\n");
106     out = fdopen(dup(fd), "w");
107     fprintf(out, "HTTP/1.1 401 Invalid authentication\n");
108     fprintf(out, "WWW-Authenticate: Basic realm=\"%s\"\n", rn);
109     fprintf(out, "Content-Type: text/html\n");
110     fprintf(out, "Content-Length: %zi\n", buf.d);
111     fprintf(out, "\n");
112     fwrite(buf.b, 1, buf.d, out);
113     fclose(out);
114     buffree(buf);
115 }
116
117 static void cleancache(int complete)
118 {
119     struct cache *c, *n;
120     
121     for(c = cache; c != NULL; c = n) {
122         n = c->next;
123         if(complete || (now - c->lastuse > 3600)) {
124             if(c->next)
125                 c->next->prev = c->prev;
126             if(c->prev)
127                 c->prev->next = c->next;
128             if(c == cache)
129                 cache = c->next;
130             memset(c->pass, 0, strlen(c->pass));
131             free(c->user);
132             free(c->pass);
133             free(c);
134         }
135     }
136     lastclean = now;
137 }
138
139 static int ckcache(char *user, char *pass)
140 {
141     struct cache *c;
142     
143     for(c = cache; c != NULL; c = c->next) {
144         if(!strcmp(user, c->user) && !strcmp(pass, c->pass)) {
145             c->lastuse = now;
146             return(1);
147         }
148     }
149     return(0);
150 }
151
152 static struct cache *addcache(char *user, char *pass)
153 {
154     struct cache *c;
155     
156     omalloc(c);
157     c->user = sstrdup(user);
158     c->pass = sstrdup(pass);
159     c->lastuse = now;
160     c->next = cache;
161     if(cache != NULL)
162         cache->prev = c;
163     cache = c;
164     return(c);
165 }
166
167 static void serve2(struct hthead *req, int fd, char *user)
168 {
169     headappheader(req, "X-Ash-Remote-User", user);
170     if(sendreq(ch, req, fd)) {
171         flog(LOG_ERR, "htextauth: could not pass request to child: %s", strerror(errno));
172         exit(1);
173     }
174 }
175
176 static void serve(struct hthead *req, int fd)
177 {
178     char *raw, *dec, *p;
179     size_t declen;
180     
181     now = time(NULL);
182     dec = NULL;
183     if(reqssl && (((raw = getheader(req, "X-Ash-Protocol")) == NULL) || strcmp(raw, "https"))) {
184         simpleerror(fd, 403, "Forbidden", "The requested resource must be requested over HTTPS.");
185         goto out;
186     }
187     if(((raw = getheader(req, "Authorization")) == NULL) || strncasecmp(raw, "basic ", 6)) {
188         reqauth(req, fd);
189         goto out;
190     }
191     if((dec = base64decode(raw + 6, &declen)) == NULL) {
192         simpleerror(fd, 400, "Invalid request", "The authentication data is not proper base64.");
193         goto out;
194     }
195     memset(raw, 0, strlen(raw));
196     headrmheader(req, "Authorization");
197     for(p = dec; *p; p++) {
198         if(*p < 32) {
199             simpleerror(fd, 400, "Invalid request", "The authentication data is invalid.");
200             goto out;
201         }
202     }
203     if((p = strchr(dec, ':')) == NULL) {
204         simpleerror(fd, 400, "Invalid request", "The authentication data is invalid.");
205         goto out;
206     }
207     *(p++) = 0;
208     if(docache && ckcache(dec, p)) {
209         serve2(req, fd, dec);
210         goto out;
211     }
212     if(auth(req, fd, dec, p)) {
213         if(docache)
214             addcache(dec, p);
215         serve2(req, fd, dec);
216         goto out;
217     }
218     
219 out:
220     if(dec != NULL) {
221         memset(dec, 0, declen);
222         free(dec);
223     }
224     if(docache && (now - lastclean > 60))
225         cleancache(0);
226 }
227
228 static int auth(struct hthead *req, int fd, char *user, char *pass)
229 {
230     int i, rv, status;
231     ssize_t len;
232     char *msg;
233     struct charbuf ebuf;
234     pid_t pid;
235     int pfd[2], efd[2];
236     FILE *out;
237     
238     rv = 0;
239     msg = "The supplied credentials are invalid.";
240     pipe(pfd);
241     pipe(efd);
242     if((pid = fork()) < 0) {
243         flog(LOG_ERR, "htextauth: could not fork: %s", strerror(errno));
244         simpleerror(fd, 500, "Server Error", "An internal error occurred.");
245         close(pfd[0]); close(pfd[1]);
246         close(efd[0]); close(efd[1]);
247         return(0);
248     }
249     if(pid == 0) {
250         dup2(pfd[0], 0);
251         dup2(efd[1], 1);
252         for(i = 3; i < FD_SETSIZE; i++)
253             close(i);
254         execvp(authcmd[0], authcmd);
255         flog(LOG_ERR, "htextauth: could not exec %s: %s", authcmd[0], strerror(errno));
256         exit(127);
257     }
258     close(pfd[0]);
259     close(efd[1]);
260     out = fdopen(pfd[1], "w");
261     fprintf(out, "%s\n", user);
262     fprintf(out, "%s\n", pass);
263     fclose(out);
264     bufinit(ebuf);
265     while(1) {
266         sizebuf(ebuf, ebuf.d + 128);
267         len = read(efd[0], ebuf.b + ebuf.d, ebuf.s - ebuf.d);
268         if(len < 0) {
269             if(errno == EINTR)
270                 continue;
271             break;
272         } else if(len == 0) {
273             break;
274         }
275         ebuf.d += len;
276     }
277     if(ebuf.d > 0) {
278         bufadd(ebuf, 0);
279         msg = ebuf.b;
280     }
281     close(efd[0]);
282     if(waitpid(pid, &status, 0) < 0) {
283         flog(LOG_ERR, "htextauth: could not wait: %s", strerror(errno));
284         simpleerror(fd, 500, "Server Error", "An internal error occurred.");
285         buffree(ebuf);
286         return(0);
287     }
288     if(WIFEXITED(status) && (WEXITSTATUS(status) == 0))
289         rv = 1;
290     else
291         authinval(req, fd, msg);
292     buffree(ebuf);
293     return(rv);
294 }
295
296 static void usage(FILE *out)
297 {
298     fprintf(out, "usage: htextauth [-hCs] [-r REALM] AUTHCMD [ARGS...] -- CHILD [ARGS...]\n");
299 }
300
301 static void sighandler(int sig)
302 {
303 }
304
305 int main(int argc, char **argv)
306 {
307     int i, c, ret;
308     struct charvbuf cbuf;
309     struct pollfd pfd[2];
310     struct hthead *req;
311     int fd;
312     
313     while((c = getopt(argc, argv, "+hCsr:")) >= 0) {
314         switch(c) {
315         case 'h':
316             usage(stdout);
317             return(0);
318         case 'C':
319             docache = 0;
320             break;
321         case 's':
322             reqssl = 1;
323             break;
324         case 'r':
325             realm = optarg;
326             break;
327         default:
328             usage(stderr);
329             return(1);
330         }
331     }
332     bufinit(cbuf);
333     for(i = optind; i < argc; i++) {
334         if(!strcmp(argv[i], "--"))
335             break;
336         bufadd(cbuf, argv[i]);
337     }
338     if((cbuf.d == 0) || (i == argc)) {
339         usage(stderr);
340         return(1);
341     }
342     bufadd(cbuf, NULL);
343     authcmd = cbuf.b;
344     i++;
345     if(i == argc) {
346         usage(stderr);
347         return(1);
348     }
349     if((ch = stdmkchild(argv + i, NULL, NULL)) < 0) {
350         flog(LOG_ERR, "htextauth: could not fork child: %s", strerror(errno));
351         return(1);
352     }
353     signal(SIGCHLD, sighandler);
354     signal(SIGPIPE, sighandler);
355     while(1) {
356         memset(pfd, 0, sizeof(pfd));
357         pfd[0].fd = 0;
358         pfd[0].events = POLLIN;
359         pfd[1].fd = ch;
360         pfd[1].events = POLLHUP;
361         if((ret = poll(pfd, 2, -1)) < 0) {
362             if(errno != EINTR) {
363                 flog(LOG_ERR, "htextauth: error in poll: %s", strerror(errno));
364                 exit(1);
365             }
366         }
367         if(pfd[0].revents) {
368             if((fd = recvreq(0, &req)) < 0) {
369                 if(errno == 0)
370                     break;
371                 flog(LOG_ERR, "htextauth: error in recvreq: %s", strerror(errno));
372                 exit(1);
373             }
374             serve(req, fd);
375             freehthead(req);
376             close(fd);
377         }
378         if(pfd[1].revents & POLLHUP)
379             break;
380     }
381     cleancache(1);
382     return(0);
383 }