13e60ff03a0076248be997e3f0338cd4a79328c1
[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
28 #ifdef HAVE_CONFIG_H
29 #include <config.h>
30 #endif
31 #include <utils.h>
32 #include <log.h>
33 #include <req.h>
34 #include <proc.h>
35
36 #define DEFFORMAT "%{%Y-%m-%d %H:%M:%S}t %m %u %A \"%G\""
37
38 static int ch;
39 static FILE *out;
40 static int flush = 1;
41 static char *format;
42 static time_t now;
43
44 static void qputs(char *s, FILE *o)
45 {
46     for(; *s; s++) {
47         if(*s == '\"') {
48             fputs("\\\"", o);
49         } else if(*s == '\\') {
50             fputs("\\\\", o);
51         } else if(*s == '\n') {
52             fputs("\\n", o);
53         } else if(*s == '\t') {
54             fputs("\\t", o);
55         } else if((*s < 32) || (*s >= 128)) {
56             fprintf(o, "\\x%02x", *s);
57         } else {
58             fputc(*s, o);
59         }
60     }
61 }
62
63 static void logitem(struct hthead *req, char o, char *d)
64 {
65     char *h, *p;
66     char buf[1024];
67     struct timeval tv;
68     
69     switch(o) {
70     case '%':
71         putc('%', out);
72         break;
73     case 'h':
74         if((h = getheader(req, d)) == NULL) {
75             putc('-', out);
76         } else {
77             qputs(h, out);
78         }
79         break;
80     case 'u':
81         qputs(req->url, out);
82         break;
83     case 'U':
84         strcpy(buf, req->url);
85         if((p = strchr(buf, '?')) != NULL)
86             *p = 0;
87         qputs(buf, out);
88         break;
89     case 'm':
90         qputs(req->method, out);
91         break;
92     case 'r':
93         qputs(req->rest, out);
94         break;
95     case 'v':
96         qputs(req->ver, out);
97         break;
98     case 't':
99         if(!*d)
100             d = "%a, %d %b %Y %H:%M:%S %z";
101         strftime(buf, sizeof(buf), d, localtime(&now));
102         qputs(buf, 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, gmtime(&now));
108         qputs(buf, out);
109         break;
110     case 's':
111         gettimeofday(&tv, NULL);
112         fprintf(out, "%06i", (int)tv.tv_usec);
113         break;
114     case 'A':
115         logitem(req, 'h', "X-Ash-Address");
116         break;
117     case 'H':
118         logitem(req, 'h', "Host");
119         break;
120     case 'R':
121         logitem(req, 'h', "Referer");
122         break;
123     case 'G':
124         logitem(req, 'h', "User-Agent");
125         break;
126     }
127 }
128
129 static void logreq(struct hthead *req)
130 {
131     char *p, *p2;
132     char d[strlen(format)];
133     char o;
134     
135     p = format;
136     while(*p) {
137         if(*p == '%') {
138             p++;
139             if(*p == '{') {
140                 p++;
141                 if((p2 = strchr(p, '}')) == NULL)
142                     continue;
143                 memcpy(d, p, p2 - p);
144                 d[p2 - p] = 0;
145                 p = p2 + 1;
146             } else {
147                 d[0] = 0;
148             }
149             o = *p++;
150             if(o == 0)
151                 break;
152             logitem(req, o, d);
153         } else {
154             fputc(*p++, out);
155         }
156     }
157     fputc('\n', out);
158     if(flush)
159         fflush(out);
160 }
161
162 static void serve(struct hthead *req, int fd)
163 {
164     now = time(NULL);
165     if(sendreq(ch, req, fd)) {
166         flog(LOG_ERR, "accesslog: could not pass request to child: %s", strerror(errno));
167         exit(1);
168     }
169     logreq(req);
170 }
171
172 static void usage(FILE *out)
173 {
174     fprintf(out, "usage: accesslog [-hFa] [-f FORMAT] OUTFILE CHILD [ARGS...]\n");
175 }
176
177 int main(int argc, char **argv)
178 {
179     int c, ret;
180     struct hthead *req;
181     int fd;
182     struct pollfd pfd[2];
183     
184     optarg = NULL;
185     while((c = getopt(argc, argv, "+hFaf:")) >= 0) {
186         switch(c) {
187         case 'h':
188             usage(stdout);
189             exit(0);
190         case 'F':
191             flush = 0;
192             break;
193         case 'f':
194             format = optarg;
195             break;
196         case 'a':
197             format = "%A - - [%{%d/%b/%Y:%H:%M:%S %z}t] \"%m %u %v\" - - \"%R\" \"%G\"";
198             break;
199         default:
200             usage(stderr);
201             exit(1);
202         }
203     }
204     if(argc - optind < 2) {
205         usage(stderr);
206         exit(1);
207     }
208     if(format == NULL)
209         format = DEFFORMAT;
210     if(!strcmp(argv[optind], "-")) {
211         out = stdout;
212     } else {
213         if((out = fopen(argv[optind], "a")) == NULL) {
214             flog(LOG_ERR, "accesslog: could not open %s for logging: %s", argv[optind], strerror(errno));
215             exit(1);
216         }
217     }
218     if((ch = stdmkchild(argv + optind + 1, NULL, NULL)) < 0) {
219         flog(LOG_ERR, "accesslog: could fork child: %s", strerror(errno));
220         exit(1);
221     }
222     while(1) {
223         memset(pfd, 0, sizeof(pfd));
224         pfd[0].fd = 0;
225         pfd[0].events = POLLIN;
226         pfd[1].fd = ch;
227         pfd[1].events = POLLHUP;
228         if((ret = poll(pfd, 2, -1)) < 0) {
229             if(errno != EINTR) {
230                 flog(LOG_ERR, "accesslog: error in poll: %s", strerror(errno));
231                 exit(1);
232             }
233         }
234         if(pfd[0].revents) {
235             if((fd = recvreq(0, &req)) < 0) {
236                 if(errno == 0)
237                     break;
238                 flog(LOG_ERR, "accesslog: error in recvreq: %s", strerror(errno));
239                 exit(1);
240             }
241             serve(req, fd);
242             freehthead(req);
243             close(fd);
244         }
245         if(pfd[1].revents & POLLHUP)
246             break;
247     }
248     return(0);
249 }