-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhttplog.c
549 lines (470 loc) · 13.5 KB
/
httplog.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
/*
* httplog.c - An Apache logfile rollover program
*
* Copyright (c) 2001,2002 Eli Sand
*
* This source file is covered by the Free Software License (FSL), as published
* by Eli Sand. A copy of the Free Software License (FSL) should have been
* included with this file, if not, please contact the author of this software.
*
*/
#include <pwd.h>
#include <grp.h>
#include <time.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <netdb.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "defines.h"
#ifdef USE_ZLIB
# include <wait.h>
# include <zlib.h>
#endif
/* maximum characters allowed in path */
#ifndef PATH_MAX
# define PATH_MAX 512
#endif
/* maximum characters allowed from stdin */
#ifndef BUFSIZ
# define BUFSIZ 8192
#endif
#define min(x, y) ((x) < (y)) ? (x) : (y)
#ifdef USE_ZLIB
int gzip(char *);
#endif
int mkdirs(char *);
int eprintf(const char *, const char *, ...);
int parsetags(char *, size_t, const char *);
void sighandler(int);
char *getlocalfqdn(void);
int main(int argc, char *argv[]) {
int curopt = 0;
time_t epoch = time(NULL);
unsigned long bufsize = BUFSIZ;
FILE *fd = NULL;
char *buffer = NULL;
char input[BUFSIZ + 1] = {'\0'};
char tmplog[PATH_MAX + 1] = {'\0'};
char oldlog[PATH_MAX + 1] = {'\0'};
char curlog[PATH_MAX + 1] = {'\0'};
char symlnk[PATH_MAX + 1] = {'\0'};
struct tm *timeinfo;
struct group *gid = NULL;
struct passwd *uid = NULL;
struct sigaction sigact_hup;
struct sigaction sigact_term;
#ifdef USE_ZLIB
int zlib = 0;
struct sigaction sigact_chld;
/* trap SIGCHLD so we can get rid of any zombie processes */
sigact_chld.sa_handler = (void *)sighandler;
sigemptyset(&sigact_chld.sa_mask);
sigaction(SIGCHLD, &sigact_chld, NULL);
#endif
/* trap SIGHUP so we can flush our logfile */
sigact_hup.sa_handler = (void *)sighandler;
sigemptyset(&sigact_hup.sa_mask);
sigaction(SIGHUP, &sigact_hup, NULL);
/* trap SIGTERM so we can flush our logfile and exit gracefully */
sigact_term.sa_handler = (void *)sighandler;
sigemptyset(&sigact_term.sa_mask);
sigaction(SIGTERM, &sigact_term, NULL);
while ((curopt = getopt(argc, argv, "Vvzu:g:s:b:h?")) != EOF) {
switch (curopt) {
case 'V':
case 'v': {
eprintf("notice", "\b\b version %s", VERSION);
break;
}
case 'z': {
/* enable zlib compression for logfiles */
#ifdef USE_ZLIB
zlib = 1;
#else
eprintf("error", "zlib compression was not compiled into httplog");
exit(1);
#endif
break;
}
case 'u': {
/* find the user id number for the specified user */
if ((uid = getpwnam(optarg)) == NULL) {
eprintf("error", "unable to find user id number for `%s'", optarg);
exit(1);
}
break;
}
case 'g': {
/* find the group id number for the specified group */
if ((gid = getgrnam(optarg)) == NULL) {
eprintf("error", "unable to find group id number for `%s'", optarg);
exit(1);
}
break;
}
case 's': {
/* enable symlink creation */
strncpy(symlnk, optarg, PATH_MAX);
break;
}
case 'b': {
/* find out the maximum amount of ram to malloc */
if (atoi(optarg) < BUFSIZ) {
eprintf("error", "buffer size can be no less than `%d'", BUFSIZ);
exit(1);
}
bufsize = atoi(optarg);
break;
}
case 'h':
case '?':
default : {
fprintf(stderr, "usage: %s logfile [-z] [-u user] [-g group] [-s symlink] [-b buffer_size]\n", argv[0]);
fprintf(stderr, "\tFor example, in your Apache httpd.conf file, insert this line:\n");
fprintf(stderr, "\tCustomLog \"|/path/to/%s /path/to/logfiles/ex%%Y%%m%%d.log\" combined\n", argv[0]);
exit(1);
}
}
}
if ((argc - optind) < 1) {
fprintf(stderr, "usage: %s logfile [-z] [-u user] [-g group] [-s symlink] [-b buffer_size]\n", argv[0]);
fprintf(stderr, "\tFor example, in your Apache httpd.conf file, insert this line:\n");
fprintf(stderr, "\tCustomLog \"|/path/to/%s /path/to/logfiles/ex%%Y%%m%%d.log\" combined\n", argv[0]);
exit(1);
}
/* change the current group id being used by the program */
if (gid != NULL) {
if (setgid(gid->gr_gid)) {
#ifdef USE_DEBUG
eprintf("debug", strerror(errno));
#endif
eprintf("error", "unable to set group id to `%s'", optarg);
exit(1);
}
}
/* change the current user id being used by the program */
if (uid != NULL) {
if (setuid(uid->pw_uid)) {
#ifdef USE_DEBUG
eprintf("debug", strerror(errno));
#endif
eprintf("error", "unable to set user id to `%s'", optarg);
exit(1);
}
}
/* if we get here, print out a notice to stderr saying we're up and running */
eprintf("notice", "\b\b/%s configured -- resuming normal operations", VERSION);
/* parse any special & tags in the filename template */
parsetags(tmplog, PATH_MAX, argv[optind]);
/* read standard input */
/* if we didn't catch EOF from stdin, push text to file, and loop */
/* test stdin for EOF so that when we catch a SIG, we don't up and die from fgets() returning NULL */
while (!feof(stdin)) {
if (fgets(input, BUFSIZ, stdin)) {
/* parse filename given on command line for strftime formatting */
if (!(epoch = time(NULL)) || !(timeinfo = localtime(&epoch))) {
#ifdef USE_DEBUG
eprintf("debug", strerror(errno));
#endif
eprintf("error", "unable to get local time");
exit(1);
}
strftime(curlog, PATH_MAX, tmplog, timeinfo);
/* compare the last filename to the current one to see if we need to open a new logfile */
if (strcmp(oldlog, curlog)) {
if (fd) {
/* close the file and free up memory */
fclose(fd);
free(buffer);
/* reset values to ensure data integrety */
fd = NULL;
buffer = NULL;
#ifdef USE_ZLIB
/* if compression is enabled, fork off a child process for compression */
if (zlib) {
switch (fork()) {
case 0: {
exit(gzip(oldlog));
break;
}
case -1: {
#ifdef USE_DEBUG
eprintf("debug", strerror(errno));
#endif
eprintf("error", "unable to fork compression stage for logfile `%s'", curlog);
break;
}
}
}
#endif
}
/* create path to the logfile if it doesn't exist already */
mkdirs(curlog);
/* try to append to the evaluated filename, if it doesn't exist, create it */
if ((fd = fopen(curlog, "a")) == NULL) {
#ifdef USE_DEBUG
eprintf("debug", strerror(errno));
#endif
eprintf("error", "unable to open logfile `%s'", curlog);
exit(1);
}
/* allocate some memory for our own logfile buffer */
if ((buffer = malloc(bufsize + 1)) == NULL) {
#ifdef USE_DEBUG
eprintf("debug", strerror(errno));
#endif
eprintf("error", "unable to allocate memory for logfile `%s'", curlog);
fclose(fd);
exit(1);
}
memset(buffer, '\0', bufsize + 1);
/* set up our own logfile buffer type based on flush delay setting */
if (setvbuf(fd, buffer, (bufsize > BUFSIZ) ? _IOFBF : _IOLBF, bufsize) != 0) {
#ifdef USE_DEBUG
eprintf("debug", strerror(errno));
#endif
eprintf("error", "unable to initialize buffer for logfile `%s'", curlog);
fclose(fd);
free(buffer);
exit(1);
}
/* make a symlink to the current filename */
if (strlen(symlnk) > 0) {
unlink(symlnk);
mkdirs(symlnk);
if (symlink(curlog, symlnk) != 0) {
#ifdef USE_DEBUG
eprintf("debug", strerror(errno));
#endif
eprintf("error", "unable to create symlink `%s'", symlnk);
}
}
/* update the filename for the next loop comparison */
strncpy(oldlog, curlog, PATH_MAX);
}
/* push the input to the logfile */
if (fputs(input, fd) == EOF) {
#ifdef USE_DEBUG
eprintf("debug", strerror(errno));
#endif
eprintf("error", "unable to append to logfile `%s'", curlog);
fclose(fd);
free(buffer);
exit(1);
}
}
}
/* close any open file descriptors and free up the ram used by the buffer */
if (fd) {
fclose(fd);
free(buffer);
}
return(0);
}
#ifdef USE_ZLIB
int gzip(char *filename) {
FILE *fd = NULL;
gzFile gzfd = NULL;
char buffer[BUFSIZ + 1];
char gzfilename[PATH_MAX + 1];
/* zero out the buffer */
memset(buffer, '\0', BUFSIZ + 1);
/* add ".gz" to the end of the file */
if ((strlen(filename) + 3) < PATH_MAX)
sprintf(gzfilename, "%s.gz", filename);
else {
eprintf("error", "pathname for compressed file `%s.gz' is too long", filename);
return(1);
}
/* try to open the file for reading */
if ((fd = fopen(filename, "r")) == NULL) {
#ifdef USE_DEBUG
eprintf("debug", strerror(errno));
#endif
eprintf("error", "unable to open file `%s' for compression stage", filename);
return(1);
}
/* try to create the compressed file at maximum compression */
if ((gzfd = gzopen(gzfilename, "wb9")) == NULL) {
#ifdef USE_DEBUG
eprintf("debug", strerror(errno));
#endif
eprintf("error", "unable to open compressed file `%s' for compression stage", gzfilename);
fclose(fd);
return(1);
}
/* read from the uncompressed file until we hit EOF */
while (fread(buffer, sizeof(char), BUFSIZ, fd) > 0) {
/* output and compress the line we read to the compressed file */
if (!gzwrite(gzfd, buffer, BUFSIZ)) {
eprintf("error", "unable to output compressed data to compressed file `%s'", gzfilename);
gzclose(gzfd);
fclose(fd);
return(1);
}
}
/* test to see if there were any errors while reading in data */
if (ferror(fd)) {
eprintf("error", "unable to read data from file `%s'", filename);
gzclose(gzfd);
fclose(fd);
return(1);
}
/* close both files */
gzclose(gzfd);
fclose(fd);
/* delete the original uncompressed file */
if (unlink(filename) == -1) {
#ifdef USE_DEBUG
eprintf("debug", strerror(errno));
#endif
eprintf("notice", "unable to delete file `%s' after compression stage", filename);
}
return(0);
}
#endif
int mkdirs(char *pathname) {
int fd;
char *tmpdir;
char *curdir = pathname;
/* if there's no directories to create, return to main */
if (!(tmpdir = strchr(pathname, '/')))
return(0);
/* save a link to our current working directory */
fd = open(".", O_RDONLY);
/* for each directory in the path, create it if it doesn't exist */
while ((tmpdir = strchr(curdir, '/'))) {
/* isolate the next directory to create */
*tmpdir = '\0';
/* attempt to create the required directory, and cd into it */
if (curdir == pathname)
chdir("/");
else {
mkdir(curdir, 0755);
chdir(curdir);
}
/* reset pathname to it's original state */
*tmpdir = '/';
curdir = tmpdir + 1;
}
/* change back to the original base directory */
fchdir(fd);
close(fd);
return(0);
}
int eprintf(const char *type, const char *format, ...) {
va_list ap;
char errmsg[BUFSIZ + 1];
/* get the current date and time */
time_t epoch = time(NULL);
char *curtime = ctime(&epoch);
/* cut off the '\n' at the end of ctime()'s output */
curtime[strlen(curtime) - 1] = '\0';
va_start(ap, format);
vsprintf(errmsg, format, ap);
va_end(ap);
return(fprintf(stderr, "[%s] [%s] httplog: %s\n", curtime, type, errmsg));
}
int parsetags(char *output, size_t max, const char *input) {
char *outptr = output;
const char *curptr = NULL;
const char *oldptr = input;
/* search input for special tags */
while ((curptr = strchr(oldptr, '%')) != NULL) {
/* copy everything from last tag to current tag */
strncpy(outptr, oldptr, min(max - strlen(output), curptr - oldptr));
/* update output pointer */
outptr += strlen(outptr);
/* parse current tag */
switch ((char)*(curptr + 1)) {
case '1': {
char *ptr;
char *fqdn;
/* output host name */
if ((fqdn = getlocalfqdn()) != NULL) {
if ((ptr = strchr(fqdn, '.')) != NULL)
*ptr = '\0';
strncpy(outptr, fqdn, min(max - strlen(output), strlen(fqdn)));
}
else
strncpy(outptr, "none", min(max - strlen(output), 4 * sizeof(char)));
break;
}
case '2': {
char *ptr;
char *fqdn;
/* output domain name */
if ((fqdn = getlocalfqdn()) != NULL) {
if ((ptr = strchr(fqdn, '.')) != NULL)
fqdn = ptr + 1;
strncpy(outptr, fqdn, min(max - strlen(output), strlen(fqdn)));
}
else
strncpy(outptr, "none", min(max - strlen(output), 4 * sizeof(char)));
break;
}
case '3': {
char *fqdn;
/* output full domain name */
if ((fqdn = getlocalfqdn()) != NULL)
strncpy(outptr, fqdn, min(max - strlen(output), strlen(fqdn)));
else
strncpy(outptr, "none", min(max - strlen(output), 4 * sizeof(char)));
break;
}
default: {
/* copy the unknown tag to the output */
strncpy(outptr, curptr, min(max - strlen(output), 2 * sizeof(char)));
}
}
/* update tag pointers */
oldptr = curptr + 2;
/* update output pointer */
outptr += strlen(outptr);
}
/* copy everything left over from input */
strncpy(outptr, oldptr, min(max - strlen(output), strlen(oldptr)));
return(strlen(output));
}
void sighandler(int signal) {
/* handle the appropriate signal properly */
switch (signal) {
case SIGHUP: {
/* flush all data for all open streams */
eprintf("notice", "SIGHUP received. Flushing buffers");
fflush(NULL);
break;
}
case SIGTERM: {
/* flush all data for all open streams */
eprintf("notice", "SIGTERM received. Flushing buffers and exiting");
fflush(NULL);
exit(0);
break;
}
#ifdef USE_ZLIB
case SIGCHLD: {
/* free up resources tied up by any zombie child process */
waitpid(-1, NULL, WNOHANG);
break;
}
#endif
}
return;
}
char *getlocalfqdn(void) {
char hostname[256];
struct hostent *hostinfo;
gethostname(hostname, sizeof(hostname));
if ((hostinfo = gethostbyname(hostname)) != NULL)
return(hostinfo->h_name);
else
return(NULL);
}