htparser: Fixed typo.
[ashd.git] / src / accesslog.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 <time.h>
26 #include <sys/time.h>
27 #include <signal.h>
28 #include <fcntl.h>
29 #include <sys/stat.h>
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
41 static int ch;
42 static char *outname = NULL;
43 static FILE *out;
44 static int flush = 1, locklog = 1;
45 static char *format;
46 static struct timeval now;
47 static volatile int reopen = 0;
48
49 static void qputs(char *sp, FILE *o)
50 {
51     unsigned char *s = (unsigned char *)sp;
52     
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)) {
63             fprintf(o, "\\x%02x", (int)*s);
64         } else {
65             fputc(*s, o);
66         }
67     }
68 }
69
70 static void logitem(struct hthead *req, char o, char *d)
71 {
72     char *h, *p;
73     char buf[1024];
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";
107         strftime(buf, sizeof(buf), d, localtime(&now.tv_sec));
108         qputs(buf, out);
109         break;
110     case 'T':
111         if(!*d)
112             d = "%a, %d %b %Y %H:%M:%S %z";
113         strftime(buf, sizeof(buf), d, gmtime(&now.tv_sec));
114         qputs(buf, out);
115         break;
116     case 's':
117         fprintf(out, "%06i", (int)now.tv_usec);
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
134 static 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
167 static void serve(struct hthead *req, int fd)
168 {
169     gettimeofday(&now, NULL);
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
177 static void sighandler(int sig)
178 {
179     if(sig == SIGHUP)
180         reopen = 1;
181 }
182
183 static 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
195 static 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
222 static void reopenlog(void)
223 {
224     FILE *new;
225     struct stat olds, news;
226     
227     if(outname == NULL) {
228         flog(LOG_WARNING, "accesslog: received SIGHUP but logging to stdout, so ignoring");
229         return;
230     }
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     }
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     }
260     fcntl(fileno(new), F_SETFD, FD_CLOEXEC);
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     }
272     fclose(out);
273     out = new;
274 }
275
276 static void usage(FILE *out)
277 {
278     fprintf(out, "usage: accesslog [-hFaL] [-f FORMAT] [-p PIDFILE] OUTFILE CHILD [ARGS...]\n");
279     fprintf(out, "       accesslog -P LOGFILE\n");
280 }
281
282 int main(int argc, char **argv)
283 {
284     int c, ret;
285     struct hthead *req;
286     int fd;
287     struct pollfd pfd[2];
288     char *pidfile;
289     FILE *pidout;
290     
291     pidfile = NULL;
292     while((c = getopt(argc, argv, "+hFaLf:p:P:")) >= 0) {
293         switch(c) {
294         case 'h':
295             usage(stdout);
296             exit(0);
297         case 'F':
298             flush = 0;
299             break;
300         case 'L':
301             locklog = 0;
302             break;
303         case 'f':
304             format = optarg;
305             break;
306         case 'P':
307             fetchpid(optarg);
308             exit(0);
309         case 'p':
310             pidfile = optarg;
311             break;
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     }
320     if(argc - optind < 2) {
321         usage(stderr);
322         exit(1);
323     }
324     if(format == NULL)
325         format = DEFFORMAT;
326     if(!strcmp(argv[optind], "-"))
327         outname = NULL;
328     else
329         outname = argv[optind];
330     if(outname == NULL) {
331         out = stdout;
332         locklog = 0;
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));
336             exit(1);
337         }
338         fcntl(fileno(out), F_SETFD, FD_CLOEXEC);
339     }
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     }
350     if((ch = stdmkchild(argv + optind + 1, NULL, NULL)) < 0) {
351         flog(LOG_ERR, "accesslog: could not fork child: %s", strerror(errno));
352         exit(1);
353     }
354     signal(SIGHUP, sighandler);
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     }
370     while(1) {
371         if(reopen) {
372             reopenlog();
373             reopen = 0;
374         }
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     }
400     fclose(out);
401     if(pidfile != NULL)
402         unlink(pidfile);
403     return(0);
404 }