htparser: Handle ECONNABORTED correctly.
[ashd.git] / src / accesslog.c
CommitLineData
048ac115
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 <time.h>
26#include <sys/time.h>
f99bcc64 27#include <signal.h>
472abd3c 28#include <fcntl.h>
ca170d77 29#include <sys/stat.h>
048ac115
FT
30
31#ifdef HAVE_CONFIG_H
32#include <config.h>
33#endif
34#include <utils.h>
35#include <log.h>
36#include <req.h>
37#include <proc.h>
38
39#define DEFFORMAT "%{%Y-%m-%d %H:%M:%S}t %m %u %A \"%G\""
40
41static int ch;
f99bcc64 42static char *outname = NULL;
048ac115 43static FILE *out;
ca170d77 44static int flush = 1, locklog = 1;
048ac115 45static char *format;
af222eef 46static struct timeval now;
f99bcc64 47static volatile int reopen = 0;
048ac115 48
39522714 49static void qputs(char *sp, FILE *o)
048ac115 50{
39522714
FT
51 unsigned char *s = (unsigned char *)sp;
52
048ac115
FT
53 for(; *s; s++) {
54 if(*s == '\"') {
55 fputs("\\\"", o);
56 } else if(*s == '\\') {
57 fputs("\\\\", o);
58 } else if(*s == '\n') {
59 fputs("\\n", o);
60 } else if(*s == '\t') {
61 fputs("\\t", o);
62 } else if((*s < 32) || (*s >= 128)) {
39522714 63 fprintf(o, "\\x%02x", (int)*s);
048ac115
FT
64 } else {
65 fputc(*s, o);
66 }
67 }
68}
69
70static void logitem(struct hthead *req, char o, char *d)
71{
72 char *h, *p;
73 char buf[1024];
048ac115
FT
74
75 switch(o) {
76 case '%':
77 putc('%', out);
78 break;
79 case 'h':
80 if((h = getheader(req, d)) == NULL) {
81 putc('-', out);
82 } else {
83 qputs(h, out);
84 }
85 break;
86 case 'u':
87 qputs(req->url, out);
88 break;
89 case 'U':
90 strcpy(buf, req->url);
91 if((p = strchr(buf, '?')) != NULL)
92 *p = 0;
93 qputs(buf, out);
94 break;
95 case 'm':
96 qputs(req->method, out);
97 break;
98 case 'r':
99 qputs(req->rest, out);
100 break;
101 case 'v':
102 qputs(req->ver, out);
103 break;
104 case 't':
105 if(!*d)
106 d = "%a, %d %b %Y %H:%M:%S %z";
af222eef 107 strftime(buf, sizeof(buf), d, localtime(&now.tv_sec));
048ac115
FT
108 qputs(buf, out);
109 break;
110 case 'T':
111 if(!*d)
112 d = "%a, %d %b %Y %H:%M:%S %z";
af222eef 113 strftime(buf, sizeof(buf), d, gmtime(&now.tv_sec));
048ac115
FT
114 qputs(buf, out);
115 break;
116 case 's':
af222eef 117 fprintf(out, "%06i", (int)now.tv_usec);
048ac115
FT
118 break;
119 case 'A':
120 logitem(req, 'h', "X-Ash-Address");
121 break;
122 case 'H':
123 logitem(req, 'h', "Host");
124 break;
125 case 'R':
126 logitem(req, 'h', "Referer");
127 break;
128 case 'G':
129 logitem(req, 'h', "User-Agent");
130 break;
131 }
132}
133
134static void logreq(struct hthead *req)
135{
136 char *p, *p2;
137 char d[strlen(format)];
138 char o;
139
140 p = format;
141 while(*p) {
142 if(*p == '%') {
143 p++;
144 if(*p == '{') {
145 p++;
146 if((p2 = strchr(p, '}')) == NULL)
147 continue;
148 memcpy(d, p, p2 - p);
149 d[p2 - p] = 0;
150 p = p2 + 1;
151 } else {
152 d[0] = 0;
153 }
154 o = *p++;
155 if(o == 0)
156 break;
157 logitem(req, o, d);
158 } else {
159 fputc(*p++, out);
160 }
161 }
162 fputc('\n', out);
163 if(flush)
164 fflush(out);
165}
166
167static void serve(struct hthead *req, int fd)
168{
af222eef 169 gettimeofday(&now, NULL);
048ac115
FT
170 if(sendreq(ch, req, fd)) {
171 flog(LOG_ERR, "accesslog: could not pass request to child: %s", strerror(errno));
172 exit(1);
173 }
174 logreq(req);
175}
176
f99bcc64
FT
177static void sighandler(int sig)
178{
179 if(sig == SIGHUP)
180 reopen = 1;
181}
182
ca170d77
FT
183static int lockfile(FILE *file)
184{
185 struct flock ld;
186
187 memset(&ld, 0, sizeof(ld));
188 ld.l_type = F_WRLCK;
189 ld.l_whence = SEEK_SET;
190 ld.l_start = 0;
191 ld.l_len = 0;
192 return(fcntl(fileno(file), F_SETLK, &ld));
193}
194
195static void fetchpid(char *filename)
196{
197 int fd, ret;
198 struct flock ld;
199
200 if((fd = open(filename, O_WRONLY)) < 0) {
201 fprintf(stderr, "accesslog: %s: %s\n", filename, strerror(errno));
202 exit(1);
203 }
204 memset(&ld, 0, sizeof(ld));
205 ld.l_type = F_WRLCK;
206 ld.l_whence = SEEK_SET;
207 ld.l_start = 0;
208 ld.l_len = 0;
209 ret = fcntl(fd, F_GETLK, &ld);
210 close(fd);
211 if(ret) {
212 fprintf(stderr, "accesslog: %s: %s\n", filename, strerror(errno));
213 exit(1);
214 }
215 if(ld.l_type == F_UNLCK) {
216 fprintf(stderr, "accesslog: %s: not locked\n", filename);
217 exit(1);
218 }
219 printf("%i\n", (int)ld.l_pid);
220}
221
f99bcc64
FT
222static void reopenlog(void)
223{
224 FILE *new;
ca170d77 225 struct stat olds, news;
f99bcc64
FT
226
227 if(outname == NULL) {
228 flog(LOG_WARNING, "accesslog: received SIGHUP but logging to stdout, so ignoring");
229 return;
230 }
ca170d77
FT
231 if(locklog) {
232 if(fstat(fileno(out), &olds)) {
233 flog(LOG_ERR, "accesslog: could not stat current logfile(?!): %s", strerror(errno));
234 return;
235 }
236 if(!stat(outname, &news)) {
237 if((olds.st_dev == news.st_dev) && (olds.st_ino == news.st_ino)) {
238 /*
239 * This needs to be ignored, because if the same logfile
240 * is opened and then closed, the lock is lost. To quote
241 * the Linux fcntl(2) manpage: "This is bad." No kidding.
242 *
243 * Technically, there is a race condition here when the
244 * file has been stat'ed but not yet opened, where the old
245 * log file, having been previously renamed, changes name
246 * back to the name accesslog knows and is thus reopened
247 * regardlessly, but I think that might fit under the
248 * idiom "pathological case". It should, at least, not be
249 * a security problem.
250 */
251 flog(LOG_INFO, "accesslog: received SIGHUP, but logfile has not changed, so ignoring");
252 return;
253 }
254 }
255 }
f99bcc64
FT
256 if((new = fopen(outname, "a")) == NULL) {
257 flog(LOG_WARNING, "accesslog: could not reopen log file `%s' on SIGHUP: %s", outname, strerror(errno));
258 return;
259 }
e09f3227 260 fcntl(fileno(new), F_SETFD, FD_CLOEXEC);
ca170d77
FT
261 if(locklog) {
262 if(lockfile(new)) {
263 if((errno == EAGAIN) || (errno == EACCES)) {
264 flog(LOG_ERR, "accesslog: logfile is already locked; reverting to current log", strerror(errno));
265 fclose(new);
266 return;
267 } else {
268 flog(LOG_WARNING, "accesslog: could not lock logfile, so no lock will be held: %s", strerror(errno));
269 }
270 }
271 }
f99bcc64
FT
272 fclose(out);
273 out = new;
274}
275
048ac115
FT
276static void usage(FILE *out)
277{
ca170d77 278 fprintf(out, "usage: accesslog [-hFaL] [-f FORMAT] [-p PIDFILE] OUTFILE CHILD [ARGS...]\n");
e51483a1 279 fprintf(out, " accesslog -P LOGFILE\n");
048ac115
FT
280}
281
282int main(int argc, char **argv)
283{
284 int c, ret;
285 struct hthead *req;
286 int fd;
287 struct pollfd pfd[2];
472abd3c
FT
288 char *pidfile;
289 FILE *pidout;
048ac115 290
472abd3c 291 pidfile = NULL;
ca170d77 292 while((c = getopt(argc, argv, "+hFaLf:p:P:")) >= 0) {
048ac115
FT
293 switch(c) {
294 case 'h':
295 usage(stdout);
296 exit(0);
048ac115
FT
297 case 'F':
298 flush = 0;
299 break;
ca170d77
FT
300 case 'L':
301 locklog = 0;
302 break;
048ac115
FT
303 case 'f':
304 format = optarg;
305 break;
ca170d77
FT
306 case 'P':
307 fetchpid(optarg);
308 exit(0);
472abd3c
FT
309 case 'p':
310 pidfile = optarg;
311 break;
048ac115
FT
312 case 'a':
313 format = "%A - - [%{%d/%b/%Y:%H:%M:%S %z}t] \"%m %u %v\" - - \"%R\" \"%G\"";
314 break;
315 default:
316 usage(stderr);
317 exit(1);
318 }
319 }
9701afc5 320 if(argc - optind < 2) {
048ac115
FT
321 usage(stderr);
322 exit(1);
323 }
324 if(format == NULL)
325 format = DEFFORMAT;
f99bcc64
FT
326 if(!strcmp(argv[optind], "-"))
327 outname = NULL;
328 else
329 outname = argv[optind];
330 if(outname == NULL) {
9701afc5 331 out = stdout;
e09f3227 332 locklog = 0;
9701afc5
FT
333 } else {
334 if((out = fopen(argv[optind], "a")) == NULL) {
335 flog(LOG_ERR, "accesslog: could not open %s for logging: %s", argv[optind], strerror(errno));
048ac115
FT
336 exit(1);
337 }
e09f3227 338 fcntl(fileno(out), F_SETFD, FD_CLOEXEC);
048ac115 339 }
ca170d77
FT
340 if(locklog) {
341 if(lockfile(out)) {
342 if((errno == EAGAIN) || (errno == EACCES)) {
343 flog(LOG_ERR, "accesslog: logfile is already locked", strerror(errno));
344 exit(1);
345 } else {
346 flog(LOG_WARNING, "accesslog: could not lock logfile: %s", strerror(errno));
347 }
348 }
349 }
9701afc5 350 if((ch = stdmkchild(argv + optind + 1, NULL, NULL)) < 0) {
e3f12675 351 flog(LOG_ERR, "accesslog: could not fork child: %s", strerror(errno));
048ac115
FT
352 exit(1);
353 }
f99bcc64 354 signal(SIGHUP, sighandler);
472abd3c
FT
355 if(pidfile) {
356 if(!strcmp(pidfile, "-")) {
357 if(!outname) {
358 flog(LOG_ERR, "accesslog: cannot derive PID file name without an output file");
359 exit(1);
360 }
361 pidfile = sprintf2("%s.pid", outname);
362 }
363 if((pidout = fopen(pidfile, "w")) == NULL) {
364 flog(LOG_ERR, "accesslog: could not open PID file %s for writing: %s", pidfile);
365 exit(1);
366 }
367 fprintf(pidout, "%i\n", (int)getpid());
368 fclose(pidout);
369 }
048ac115 370 while(1) {
f99bcc64
FT
371 if(reopen) {
372 reopenlog();
373 reopen = 0;
374 }
048ac115
FT
375 memset(pfd, 0, sizeof(pfd));
376 pfd[0].fd = 0;
377 pfd[0].events = POLLIN;
378 pfd[1].fd = ch;
379 pfd[1].events = POLLHUP;
380 if((ret = poll(pfd, 2, -1)) < 0) {
381 if(errno != EINTR) {
382 flog(LOG_ERR, "accesslog: error in poll: %s", strerror(errno));
383 exit(1);
384 }
385 }
386 if(pfd[0].revents) {
387 if((fd = recvreq(0, &req)) < 0) {
388 if(errno == 0)
389 break;
390 flog(LOG_ERR, "accesslog: error in recvreq: %s", strerror(errno));
391 exit(1);
392 }
393 serve(req, fd);
394 freehthead(req);
395 close(fd);
396 }
397 if(pfd[1].revents & POLLHUP)
398 break;
399 }
f95bc205 400 fclose(out);
472abd3c
FT
401 if(pidfile != NULL)
402 unlink(pidfile);
048ac115
FT
403 return(0);
404}