htextauth: Check credentials against control characters that might mess stuff up.
[ashd.git] / src / htextauth.c
CommitLineData
baaabaf7
FT
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 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
107static 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
120static 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
135static 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
144static void serve(struct hthead *req, int fd)
145{
146 char *raw, *dec, *p;
147 size_t declen;
148
149 now = time(NULL);
b4fda020 150 dec = NULL;
baaabaf7
FT
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 }
baaabaf7
FT
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");
fb9b1027
FT
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 }
baaabaf7
FT
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
187out:
188 if(dec != NULL) {
189 memset(dec, 0, declen);
190 free(dec);
191 }
192 if(docache && (now - lastclean > 60))
193 cleancache(0);
194}
195
196static 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;
baaabaf7
FT
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
264static void usage(FILE *out)
265{
266 fprintf(out, "usage: htextauth [-hCs] [-r REALM] AUTHCMD [ARGS...] -- CHILD [ARGS...]\n");
267}
268
269static void sighandler(int sig)
270{
271}
272
273int 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}