htextauth: Fixed segfault.
[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");
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
181out:
182 if(dec != NULL) {
183 memset(dec, 0, declen);
184 free(dec);
185 }
186 if(docache && (now - lastclean > 60))
187 cleancache(0);
188}
189
190static 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
262static void usage(FILE *out)
263{
264 fprintf(out, "usage: htextauth [-hCs] [-r REALM] AUTHCMD [ARGS...] -- CHILD [ARGS...]\n");
265}
266
267static void sighandler(int sig)
268{
269}
270
271int 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}