Added a Basic authentication filter.
[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 cleancache(int complete)
86 {
87     struct cache *c, *n;
88     
89     for(c = cache; c != NULL; c = n) {
90         n = c->next;
91         if(complete || (now - c->lastuse > 3600)) {
92             if(c->next)
93                 c->next->prev = c->prev;
94             if(c->prev)
95                 c->prev->next = c->next;
96             if(c == cache)
97                 cache = c->next;
98             memset(c->pass, 0, strlen(c->pass));
99             free(c->user);
100             free(c->pass);
101             free(c);
102         }
103     }
104     lastclean = now;
105 }
106
107 static int ckcache(char *user, char *pass)
108 {
109     struct cache *c;
110     
111     for(c = cache; c != NULL; c = c->next) {
112         if(!strcmp(user, c->user) && !strcmp(pass, c->pass)) {
113             c->lastuse = now;
114             return(1);
115         }
116     }
117     return(0);
118 }
119
120 static struct cache *addcache(char *user, char *pass)
121 {
122     struct cache *c;
123     
124     omalloc(c);
125     c->user = sstrdup(user);
126     c->pass = sstrdup(pass);
127     c->lastuse = now;
128     c->next = cache;
129     if(cache != NULL)
130         cache->prev = c;
131     cache = c;
132     return(c);
133 }
134
135 static void serve2(struct hthead *req, int fd, char *user)
136 {
137     headappheader(req, "X-Ash-Remote-User", user);
138     if(sendreq(ch, req, fd)) {
139         flog(LOG_ERR, "htextauth: could not pass request to child: %s", strerror(errno));
140         exit(1);
141     }
142 }
143
144 static void serve(struct hthead *req, int fd)
145 {
146     char *raw, *dec, *p;
147     size_t declen;
148     
149     now = time(NULL);
150     if(reqssl && (((raw = getheader(req, "X-Ash-Protocol")) == NULL) || strcmp(raw, "https"))) {
151         simpleerror(fd, 403, "Forbidden", "The requested resource must be requested over HTTPS.");
152         goto out;
153     }
154     dec = NULL;
155     if(((raw = getheader(req, "Authorization")) == NULL) || strncasecmp(raw, "basic ", 6)) {
156         reqauth(req, fd);
157         goto out;
158     }
159     if((dec = base64decode(raw + 6, &declen)) == NULL) {
160         simpleerror(fd, 400, "Invalid request", "The authentication data is not proper base64.");
161         goto out;
162     }
163     memset(raw, 0, strlen(raw));
164     headrmheader(req, "Authorization");
165     if((p = strchr(dec, ':')) == NULL) {
166         simpleerror(fd, 400, "Invalid request", "The authentication data is invalid.");
167         goto out;
168     }
169     *(p++) = 0;
170     if(docache && ckcache(dec, p)) {
171         serve2(req, fd, dec);
172         goto out;
173     }
174     if(auth(req, fd, dec, p)) {
175         if(docache)
176             addcache(dec, p);
177         serve2(req, fd, dec);
178         goto out;
179     }
180     
181 out:
182     if(dec != NULL) {
183         memset(dec, 0, declen);
184         free(dec);
185     }
186     if(docache && (now - lastclean > 60))
187         cleancache(0);
188 }
189
190 static int auth(struct hthead *req, int fd, char *user, char *pass)
191 {
192     int i, rv, status;
193     ssize_t len;
194     char *msg;
195     struct charbuf ebuf;
196     pid_t pid;
197     int pfd[2], efd[2];
198     FILE *out;
199     
200     rv = 0;
201     if(strchr(user, '\n') || strchr(pass, '\n')) {
202         simpleerror(fd, 401, "Invalid authentication", "The supplied credentials are invalid.");
203         return(0);
204     }
205     msg = "The supplied credentials are invalid.";
206     pipe(pfd);
207     pipe(efd);
208     if((pid = fork()) < 0) {
209         flog(LOG_ERR, "htextauth: could not fork: %s", strerror(errno));
210         simpleerror(fd, 500, "Server Error", "An internal error occurred.");
211         close(pfd[0]); close(pfd[1]);
212         close(efd[0]); close(efd[1]);
213         return(0);
214     }
215     if(pid == 0) {
216         dup2(pfd[0], 0);
217         dup2(efd[1], 1);
218         for(i = 3; i < FD_SETSIZE; i++)
219             close(i);
220         execvp(authcmd[0], authcmd);
221         flog(LOG_ERR, "htextauth: could not exec %s: %s", authcmd[0], strerror(errno));
222         exit(127);
223     }
224     close(pfd[0]);
225     close(efd[1]);
226     out = fdopen(pfd[1], "w");
227     fprintf(out, "%s\n", user);
228     fprintf(out, "%s\n", pass);
229     fclose(out);
230     bufinit(ebuf);
231     while(1) {
232         sizebuf(ebuf, ebuf.d + 128);
233         len = read(efd[0], ebuf.b + ebuf.d, ebuf.s - ebuf.d);
234         if(len < 0) {
235             if(errno == EINTR)
236                 continue;
237             break;
238         } else if(len == 0) {
239             break;
240         }
241         ebuf.d += len;
242     }
243     if(ebuf.d > 0) {
244         bufadd(ebuf, 0);
245         msg = ebuf.b;
246     }
247     close(efd[0]);
248     if(waitpid(pid, &status, 0) < 0) {
249         flog(LOG_ERR, "htextauth: could not wait: %s", strerror(errno));
250         simpleerror(fd, 500, "Server Error", "An internal error occurred.");
251         buffree(ebuf);
252         return(0);
253     }
254     if(WIFEXITED(status) && (WEXITSTATUS(status) == 0))
255         rv = 1;
256     else
257         simpleerror(fd, 401, "Invalid authentication", msg);
258     buffree(ebuf);
259     return(rv);
260 }
261
262 static void usage(FILE *out)
263 {
264     fprintf(out, "usage: htextauth [-hCs] [-r REALM] AUTHCMD [ARGS...] -- CHILD [ARGS...]\n");
265 }
266
267 static void sighandler(int sig)
268 {
269 }
270
271 int main(int argc, char **argv)
272 {
273     int i, c, ret;
274     struct charvbuf cbuf;
275     struct pollfd pfd[2];
276     struct hthead *req;
277     int fd;
278     
279     while((c = getopt(argc, argv, "+hCsr:")) >= 0) {
280         switch(c) {
281         case 'h':
282             usage(stdout);
283             return(0);
284         case 'C':
285             docache = 0;
286             break;
287         case 's':
288             reqssl = 1;
289             break;
290         case 'r':
291             realm = optarg;
292             break;
293         default:
294             usage(stderr);
295             return(1);
296         }
297     }
298     bufinit(cbuf);
299     for(i = optind; i < argc; i++) {
300         if(!strcmp(argv[i], "--"))
301             break;
302         bufadd(cbuf, argv[i]);
303     }
304     if((cbuf.d == 0) || (i == argc)) {
305         usage(stderr);
306         return(1);
307     }
308     bufadd(cbuf, NULL);
309     authcmd = cbuf.b;
310     i++;
311     if(i == argc) {
312         usage(stderr);
313         return(1);
314     }
315     if((ch = stdmkchild(argv + i, NULL, NULL)) < 0) {
316         flog(LOG_ERR, "htextauth: could not fork child: %s", strerror(errno));
317         return(1);
318     }
319     signal(SIGCHLD, sighandler);
320     signal(SIGPIPE, sighandler);
321     while(1) {
322         memset(pfd, 0, sizeof(pfd));
323         pfd[0].fd = 0;
324         pfd[0].events = POLLIN;
325         pfd[1].fd = ch;
326         pfd[1].events = POLLHUP;
327         if((ret = poll(pfd, 2, -1)) < 0) {
328             if(errno != EINTR) {
329                 flog(LOG_ERR, "htextauth: error in poll: %s", strerror(errno));
330                 exit(1);
331             }
332         }
333         if(pfd[0].revents) {
334             if((fd = recvreq(0, &req)) < 0) {
335                 if(errno == 0)
336                     break;
337                 flog(LOG_ERR, "htextauth: error in recvreq: %s", strerror(errno));
338                 exit(1);
339             }
340             serve(req, fd);
341             freehthead(req);
342             close(fd);
343         }
344         if(pfd[1].revents & POLLHUP)
345             break;
346     }
347     cleancache(1);
348     return(0);
349 }