htextauth: Check credentials against control characters that might mess stuff up.
[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     dec = NULL;
151     if(reqssl && (((raw = getheader(req, "X-Ash-Protocol")) == NULL) || strcmp(raw, "https"))) {
152         simpleerror(fd, 403, "Forbidden", "The requested resource must be requested over HTTPS.");
153         goto out;
154     }
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     for(p = dec; *p; p++) {
166         if(*p < 32) {
167             simpleerror(fd, 400, "Invalid request", "The authentication data is invalid.");
168             goto out;
169         }
170     }
171     if((p = strchr(dec, ':')) == NULL) {
172         simpleerror(fd, 400, "Invalid request", "The authentication data is invalid.");
173         goto out;
174     }
175     *(p++) = 0;
176     if(docache && ckcache(dec, p)) {
177         serve2(req, fd, dec);
178         goto out;
179     }
180     if(auth(req, fd, dec, p)) {
181         if(docache)
182             addcache(dec, p);
183         serve2(req, fd, dec);
184         goto out;
185     }
186     
187 out:
188     if(dec != NULL) {
189         memset(dec, 0, declen);
190         free(dec);
191     }
192     if(docache && (now - lastclean > 60))
193         cleancache(0);
194 }
195
196 static int auth(struct hthead *req, int fd, char *user, char *pass)
197 {
198     int i, rv, status;
199     ssize_t len;
200     char *msg;
201     struct charbuf ebuf;
202     pid_t pid;
203     int pfd[2], efd[2];
204     FILE *out;
205     
206     rv = 0;
207     msg = "The supplied credentials are invalid.";
208     pipe(pfd);
209     pipe(efd);
210     if((pid = fork()) < 0) {
211         flog(LOG_ERR, "htextauth: could not fork: %s", strerror(errno));
212         simpleerror(fd, 500, "Server Error", "An internal error occurred.");
213         close(pfd[0]); close(pfd[1]);
214         close(efd[0]); close(efd[1]);
215         return(0);
216     }
217     if(pid == 0) {
218         dup2(pfd[0], 0);
219         dup2(efd[1], 1);
220         for(i = 3; i < FD_SETSIZE; i++)
221             close(i);
222         execvp(authcmd[0], authcmd);
223         flog(LOG_ERR, "htextauth: could not exec %s: %s", authcmd[0], strerror(errno));
224         exit(127);
225     }
226     close(pfd[0]);
227     close(efd[1]);
228     out = fdopen(pfd[1], "w");
229     fprintf(out, "%s\n", user);
230     fprintf(out, "%s\n", pass);
231     fclose(out);
232     bufinit(ebuf);
233     while(1) {
234         sizebuf(ebuf, ebuf.d + 128);
235         len = read(efd[0], ebuf.b + ebuf.d, ebuf.s - ebuf.d);
236         if(len < 0) {
237             if(errno == EINTR)
238                 continue;
239             break;
240         } else if(len == 0) {
241             break;
242         }
243         ebuf.d += len;
244     }
245     if(ebuf.d > 0) {
246         bufadd(ebuf, 0);
247         msg = ebuf.b;
248     }
249     close(efd[0]);
250     if(waitpid(pid, &status, 0) < 0) {
251         flog(LOG_ERR, "htextauth: could not wait: %s", strerror(errno));
252         simpleerror(fd, 500, "Server Error", "An internal error occurred.");
253         buffree(ebuf);
254         return(0);
255     }
256     if(WIFEXITED(status) && (WEXITSTATUS(status) == 0))
257         rv = 1;
258     else
259         simpleerror(fd, 401, "Invalid authentication", msg);
260     buffree(ebuf);
261     return(rv);
262 }
263
264 static void usage(FILE *out)
265 {
266     fprintf(out, "usage: htextauth [-hCs] [-r REALM] AUTHCMD [ARGS...] -- CHILD [ARGS...]\n");
267 }
268
269 static void sighandler(int sig)
270 {
271 }
272
273 int main(int argc, char **argv)
274 {
275     int i, c, ret;
276     struct charvbuf cbuf;
277     struct pollfd pfd[2];
278     struct hthead *req;
279     int fd;
280     
281     while((c = getopt(argc, argv, "+hCsr:")) >= 0) {
282         switch(c) {
283         case 'h':
284             usage(stdout);
285             return(0);
286         case 'C':
287             docache = 0;
288             break;
289         case 's':
290             reqssl = 1;
291             break;
292         case 'r':
293             realm = optarg;
294             break;
295         default:
296             usage(stderr);
297             return(1);
298         }
299     }
300     bufinit(cbuf);
301     for(i = optind; i < argc; i++) {
302         if(!strcmp(argv[i], "--"))
303             break;
304         bufadd(cbuf, argv[i]);
305     }
306     if((cbuf.d == 0) || (i == argc)) {
307         usage(stderr);
308         return(1);
309     }
310     bufadd(cbuf, NULL);
311     authcmd = cbuf.b;
312     i++;
313     if(i == argc) {
314         usage(stderr);
315         return(1);
316     }
317     if((ch = stdmkchild(argv + i, NULL, NULL)) < 0) {
318         flog(LOG_ERR, "htextauth: could not fork child: %s", strerror(errno));
319         return(1);
320     }
321     signal(SIGCHLD, sighandler);
322     signal(SIGPIPE, sighandler);
323     while(1) {
324         memset(pfd, 0, sizeof(pfd));
325         pfd[0].fd = 0;
326         pfd[0].events = POLLIN;
327         pfd[1].fd = ch;
328         pfd[1].events = POLLHUP;
329         if((ret = poll(pfd, 2, -1)) < 0) {
330             if(errno != EINTR) {
331                 flog(LOG_ERR, "htextauth: error in poll: %s", strerror(errno));
332                 exit(1);
333             }
334         }
335         if(pfd[0].revents) {
336             if((fd = recvreq(0, &req)) < 0) {
337                 if(errno == 0)
338                     break;
339                 flog(LOG_ERR, "htextauth: error in recvreq: %s", strerror(errno));
340                 exit(1);
341             }
342             serve(req, fd);
343             freehthead(req);
344             close(fd);
345         }
346         if(pfd[1].revents & POLLHUP)
347             break;
348     }
349     cleancache(1);
350     return(0);
351 }