build: Update configure.in to slightly more modern standards.
[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(WCOREDUMP(status))
289         flog(LOG_WARNING, "htextauth: authenticator process dumped core");
290     if(WIFEXITED(status) && (WEXITSTATUS(status) == 0))
291         rv = 1;
292     else
293         authinval(req, fd, msg);
294     buffree(ebuf);
295     return(rv);
296 }
297
298 static void usage(FILE *out)
299 {
300     fprintf(out, "usage: htextauth [-hCs] [-r REALM] AUTHCMD [ARGS...] -- CHILD [ARGS...]\n");
301 }
302
303 static void sighandler(int sig)
304 {
305 }
306
307 int main(int argc, char **argv)
308 {
309     int i, c, ret;
310     struct charvbuf cbuf;
311     struct pollfd pfd[2];
312     struct hthead *req;
313     int fd;
314     
315     while((c = getopt(argc, argv, "+hCsr:")) >= 0) {
316         switch(c) {
317         case 'h':
318             usage(stdout);
319             return(0);
320         case 'C':
321             docache = 0;
322             break;
323         case 's':
324             reqssl = 1;
325             break;
326         case 'r':
327             realm = optarg;
328             break;
329         default:
330             usage(stderr);
331             return(1);
332         }
333     }
334     bufinit(cbuf);
335     for(i = optind; i < argc; i++) {
336         if(!strcmp(argv[i], "--"))
337             break;
338         bufadd(cbuf, argv[i]);
339     }
340     if((cbuf.d == 0) || (i == argc)) {
341         usage(stderr);
342         return(1);
343     }
344     bufadd(cbuf, NULL);
345     authcmd = cbuf.b;
346     i++;
347     if(i == argc) {
348         usage(stderr);
349         return(1);
350     }
351     if((ch = stdmkchild(argv + i, NULL, NULL)) < 0) {
352         flog(LOG_ERR, "htextauth: could not fork child: %s", strerror(errno));
353         return(1);
354     }
355     signal(SIGCHLD, sighandler);
356     signal(SIGPIPE, sighandler);
357     while(1) {
358         memset(pfd, 0, sizeof(pfd));
359         pfd[0].fd = 0;
360         pfd[0].events = POLLIN;
361         pfd[1].fd = ch;
362         pfd[1].events = POLLHUP;
363         if((ret = poll(pfd, 2, -1)) < 0) {
364             if(errno != EINTR) {
365                 flog(LOG_ERR, "htextauth: error in poll: %s", strerror(errno));
366                 exit(1);
367             }
368         }
369         if(pfd[0].revents) {
370             if((fd = recvreq(0, &req)) < 0) {
371                 if(errno == 0)
372                     break;
373                 flog(LOG_ERR, "htextauth: error in recvreq: %s", strerror(errno));
374                 exit(1);
375             }
376             serve(req, fd);
377             freehthead(req);
378             close(fd);
379         }
380         if(pfd[1].revents & POLLHUP)
381             break;
382     }
383     cleancache(1);
384     return(0);
385 }