htparser: Be more tolerant to broken clients.
[ashd.git] / src / htextauth.c
... / ...
CommitLineData
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
38struct cache {
39 struct cache *next, *prev;
40 char *user, *pass;
41 time_t lastuse;
42};
43
44static int ch;
45static char **authcmd;
46static char *realm;
47static int docache = 1, reqssl;
48static struct cache *cache;
49static time_t now, lastclean;
50
51static int auth(struct hthead *req, int fd, char *user, char *pass);
52
53static 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
85static 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
117static 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
139static 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
152static 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
167static 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
176static 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
219out:
220 if(dec != NULL) {
221 memset(dec, 0, declen);
222 free(dec);
223 }
224 if(docache && (now - lastclean > 60))
225 cleancache(0);
226}
227
228static 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
298static void usage(FILE *out)
299{
300 fprintf(out, "usage: htextauth [-hCs] [-r REALM] AUTHCMD [ARGS...] -- CHILD [ARGS...]\n");
301}
302
303static void sighandler(int sig)
304{
305}
306
307int 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}