htparser: Format v4-mapped address as dotted-quad.
[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>
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
38static int ch;
39static FILE *out;
40static int flush = 1;
41static char *format;
42static time_t now;
43
44static 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
63static 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
129static 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
162static 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
172static void usage(FILE *out)
173{
9701afc5 174 fprintf(out, "usage: accesslog [-hFa] [-f FORMAT] OUTFILE CHILD [ARGS...]\n");
048ac115
FT
175}
176
177int main(int argc, char **argv)
178{
179 int c, ret;
180 struct hthead *req;
181 int fd;
182 struct pollfd pfd[2];
048ac115
FT
183
184 optarg = NULL;
9701afc5 185 while((c = getopt(argc, argv, "+hFaf:")) >= 0) {
048ac115
FT
186 switch(c) {
187 case 'h':
188 usage(stdout);
189 exit(0);
048ac115
FT
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 }
9701afc5 204 if(argc - optind < 2) {
048ac115
FT
205 usage(stderr);
206 exit(1);
207 }
208 if(format == NULL)
209 format = DEFFORMAT;
9701afc5
FT
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));
048ac115
FT
215 exit(1);
216 }
048ac115 217 }
9701afc5 218 if((ch = stdmkchild(argv + optind + 1, NULL, NULL)) < 0) {
048ac115
FT
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}