1 /*
2 * ufdbhttpd.c - URLfilterDB
3 *
4 * Copyrighted (C) 2007-2020 by URLfilterDB B.V. with all rights reserved.
5 *
6 * Parts of the ufdbGuard daemon are based on squidGuard.
7 * This module is NOT based on squidGuard.
8 *
9 * Extremely simplified http daemon.
10 * This HTTP daemon is for the ONLY purpose of serving http requests like
11 * http://HOST:port/cgi-bin/URLblocked.cgi
12 *
13 */
14
15 #include "ufdb.h"
16 #include "ufdb.h"
17 #include "ufdblib.h"
18 #include "httpserver.h"
19
20 #include <string.h>
21 #include <unistd.h>
22 #include <stdlib.h>
23 #include <signal.h>
24 #include <libgen.h>
25 #include <stdarg.h>
26 #include <errno.h>
27 #include <time.h>
28 #include <sys/time.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31
32 #ifdef __cplusplus
33 extern "C" {
34 #endif
35
36
37 int runAsDaemon = 1;
38
39 char interface[256] = "all";
40 char imagesDirectory[256] = "";
41 int portnumber = 0;
42
43 char * globalHttpdPidFile = (char *) UFDBHTTPD_PID_FILE;
44 int UFDBforceLogRotation = 0;
45 static char UFDBlogFilename[1024];
46 static size_t logfilesize = 0;
47
48
usage(void)49 static void usage( void )
50 {
51 fprintf( stderr, "usage: ufdbhttpd [-d] [-U <user>] -p <port> -I <images-directory> -l <log-directory> [-i <interface>]\n" );
52
53 fprintf( stderr, "The ufdbGuard software suite is free and Open Source Software.\n" );
54 fprintf( stderr, "Copyright (C) 2005-2019 by URLfilterDB B.V. and others.\n" );
55
56 exit( 1 );
57 }
58
59
60 /* Rotate log files.
61 * There is a race condition when multiple instances try to rotate.
62 * Therefore acquire a lock and sleep for 1 second if the lock fails.
63 */
RotateLogfile(char * filename)64 static void RotateLogfile( char * filename )
65 {
66 int i;
67 char oldfile[1024+12];
68 char newfile[1024+12];
69
70 /* rotate the log file:
71 * file.log.7 -> file.log.8
72 * file.log.6 -> file.log.7
73 * file.log.5 -> file.log.6
74 * file.log.4 -> file.log.5
75 * file.log.3 -> file.log.4
76 * file.log.2 -> file.log.3
77 * file.log.1 -> file.log.2
78 * file.log -> file.log.1
79 */
80
81 for (i = 8; i > 1; i--)
82 {
83 snprintf( newfile, sizeof(newfile), "%s.%d", filename, i );
84 snprintf( oldfile, sizeof(oldfile), "%s.%d", filename, i-1 );
85 rename( oldfile, newfile );
86 }
87 sync(); /* sync may resolve some borderline disk space issues */
88
89 snprintf( newfile, sizeof(newfile), "%s.%d", filename, 1 );
90 rename( filename, newfile );
91 }
92
93
ufdbGlobalSetLogging(int logging)94 void ufdbGlobalSetLogging( int logging )
95 {
96 if (logging) { ; } // prevent compiler warning
97 }
98
99
UFDBrotateLogfile(void)100 void UFDBrotateLogfile( void )
101 {
102 UFDBforceLogRotation = 1;
103 }
104
105
ufdbSetGlobalErrorLogFile(char * logdir,char * basename,int mutex_is_used)106 void ufdbSetGlobalErrorLogFile( char * logdir, char * basename, int mutex_is_used )
107 {
108 struct stat s;
109
110 if (mutex_is_used) { ; } // prevent compiler warning
111
112 if (ufdbGlobalLogfile != NULL)
113 {
114 fclose( ufdbGlobalLogfile );
115 ufdbGlobalLogfile = NULL;
116 }
117
118 if (logdir == NULL)
119 {
120 if (ufdbGV.logDir == NULL)
121 strcpy( UFDBlogFilename, DEFAULT_LOGDIR );
122 else
123 strcpy( UFDBlogFilename, ufdbGV.logDir );
124 }
125 else
126 strcpy( UFDBlogFilename, logdir );
127
128 strcat( UFDBlogFilename, "/" );
129
130 if (basename == NULL || basename[0] == '\0')
131 strcat( UFDBlogFilename, DEFAULT_LOGFILE );
132 else
133 {
134 strcat( UFDBlogFilename, basename );
135 strcat( UFDBlogFilename, ".log" );
136 }
137
138 if (stat(UFDBlogFilename,&s) == 0)
139 {
140 if (s.st_size > (off_t) ufdbGV.maxLogfileSize)
141 RotateLogfile( UFDBlogFilename );
142 }
143
144 ufdbGlobalLogfile = fopen( UFDBlogFilename, "a" );
145 if (ufdbGlobalLogfile == NULL)
146 {
147 ufdbLogError( "%s: can't write to logfile %s; %s (uid=%d,euid=%d)",
148 ufdbGV.progname, UFDBlogFilename, strerror(errno), getuid(), geteuid() );
149 /*
150 * We *want* a logfile, try an other directory...
151 */
152 strcpy( UFDBlogFilename, "/tmp/" );
153 strcat( UFDBlogFilename, ufdbGV.progname );
154 strcat( UFDBlogFilename, ".log" );
155 ufdbGlobalLogfile = fopen( UFDBlogFilename, "a" );
156 if (ufdbGlobalLogfile == NULL)
157 ufdbLogError( "%s: can't write to logfile %s; %s", ufdbGV.progname, UFDBlogFilename, strerror(errno) );
158 }
159
160 if (ufdbGlobalLogfile == NULL)
161 logfilesize = 0;
162 else
163 logfilesize = ftell( ufdbGlobalLogfile );
164 }
165
166
niso(time_t t,char * buf)167 static void niso(
168 time_t t,
169 char * buf )
170 {
171 time_t tp;
172 struct tm lc;
173
174 if (t == 0)
175 tp = time(NULL);
176 else
177 tp = t;
178 localtime_r( &tp, &lc );
179 snprintf( buf, 80, "%04d-%02d-%02d %02d:%02d:%02d",
180 lc.tm_year + 1900, lc.tm_mon + 1,
181 lc.tm_mday, lc.tm_hour, lc.tm_min, lc.tm_sec );
182 }
183
184
185
ufdbLog(char * msg)186 static void ufdbLog(
187 char * msg )
188 {
189 char * nl;
190 int multiline;
191 char date[80];
192 char logmsg[UFDB_MAX_URL_LENGTH+128];
193 int logmsglen;
194
195 niso( 0, date );
196
197 multiline = 0;
198 logmsg[0] = '\0';
199 logmsglen = 0;
200 while ((nl = strchr( msg, '\n' )) != NULL)
201 {
202 *nl = '\0';
203 logmsglen += sprintf( logmsg+logmsglen, "%s [%d] %s%s\n", date, ufdbGV.pid, multiline?" ":"", msg );
204 msg = nl + 1;
205 multiline = 1;
206 }
207 logmsglen += sprintf( logmsg+logmsglen, "%s [%d] %s%s\n", date, ufdbGV.pid, multiline?" ":"", msg );
208
209 if (UFDBforceLogRotation)
210 {
211 UFDBforceLogRotation = 0;
212 if (ufdbGlobalLogfile != NULL)
213 {
214 RotateLogfile( UFDBlogFilename );
215 ufdbSetGlobalErrorLogFile( NULL, NULL, 1 );
216 }
217 }
218
219 if (ufdbGlobalLogfile == NULL)
220 {
221 fputs( logmsg, stderr );
222 fflush( stderr );
223 }
224 else
225 {
226 if (logfilesize == 0)
227 logfilesize = ftell( ufdbGlobalLogfile );
228
229 /* write is *much* faster than fputs */
230 if (write( fileno(ufdbGlobalLogfile), logmsg, (size_t) logmsglen ) > 0)
231 logfilesize += logmsglen;
232
233 if (logfilesize > ufdbGV.maxLogfileSize)
234 {
235 RotateLogfile( UFDBlogFilename );
236 ufdbSetGlobalErrorLogFile( NULL, NULL, 1 );
237 logfilesize = 0;
238 }
239 }
240 }
241
242
ufdbLogError(const char * format,...)243 void ufdbLogError( const char * format, ... )
244 {
245 va_list ap;
246 char msg[UFDB_MAX_URL_LENGTH];
247
248 va_start( ap, format );
249 vsnprintf( msg, UFDB_MAX_URL_LENGTH-32, format, ap );
250 msg[UFDB_MAX_URL_LENGTH-32] = '\0';
251 va_end( ap );
252
253 ufdbLog( msg );
254 }
255
256
ufdbLogMessage(const char * format,...)257 void ufdbLogMessage( const char * format, ... )
258 {
259 va_list ap;
260 char msg[UFDB_MAX_URL_LENGTH];
261
262 va_start( ap, format );
263 vsnprintf( msg, UFDB_MAX_URL_LENGTH-32, format, ap );
264 msg[UFDB_MAX_URL_LENGTH-32] = '\0';
265 va_end( ap );
266
267 ufdbLog( msg );
268 }
269
270
ufdbLogFatalError(const char * format,...)271 void ufdbLogFatalError( const char * format, ... )
272 {
273 va_list ap;
274 char msg[UFDB_MAX_URL_LENGTH];
275 char logmsg[UFDB_MAX_URL_LENGTH+100];
276
277 va_start( ap, format );
278 vsnprintf( msg, UFDB_MAX_URL_LENGTH-48, format, ap );
279 msg[UFDB_MAX_URL_LENGTH-48] = '\0';
280 va_end( ap );
281
282 sprintf( logmsg, "*FATAL* %s *****", msg );
283 ufdbLog( logmsg );
284
285 ufdbGV.fatalError = 1;
286 }
287
288
TermHandler(int sig)289 static void TermHandler( int sig )
290 {
291 if (sig) { ; } // prevent compiler warning
292
293 ufdbLogMessage( "signal TERM received." );
294 removeHttpdPidFile();
295 exit( 0 );
296 }
297
298
mySignalHandler(int sig)299 static void mySignalHandler( int sig )
300 {
301 char sigDesc[16];
302
303 switch (sig)
304 {
305 case SIGHUP: strcpy( sigDesc, "HUP" ); break;
306 case SIGINT: strcpy( sigDesc, "INT" ); break;
307 case SIGTERM: strcpy( sigDesc, "TERM" ); break;
308 case SIGILL: strcpy( sigDesc, "ILL" ); break;
309 case SIGSEGV: strcpy( sigDesc, "SEGV" ); break;
310 default: sprintf( sigDesc, "%d", sig ); break;
311 }
312 ufdbLogMessage( "signal %s received. exiting...", sigDesc );
313
314 removeHttpdPidFile();
315 exit( 0 );
316 }
317
318
USR1signalReceived(int sig)319 static void USR1signalReceived( int sig )
320 {
321 if (sig) { ; } // prevent compileer warning
322 ufdbLogMessage( "USR1 signal received for logfile rotation" );
323 UFDBrotateLogfile();
324 ufdbLogMessage( "USR1 signal received for logfile rotation" );
325 }
326
327
closeAllFiles(void)328 static void closeAllFiles( void )
329 {
330 int fd;
331
332 /* when ufdbguardd called execv() to start ufdbhttpd, all file descriptors were still open. close them now */
333 close( 0 );
334 close( 1 );
335
336 for (fd = 3; fd < 2*UFDB_MAX_THREADS + 32; fd++)
337 {
338 close( fd );
339 }
340 }
341
342
badSignalHandler(int signum)343 static void badSignalHandler( int signum )
344 {
345 ufdbLogMessage( "badSignalHandler: received signal %d", signum );
346 ufdbExecutePstack( "ufdbhttpd received a bad signal" );
347 sleep( 1 );
348 exit( 2 );
349 }
350
351
main(int argc,char ** argv)352 int main( int argc, char ** argv )
353 {
354 int ch;
355
356 strcpy( ufdbGV.progname, "ufdbhttpd" );
357 ufdbGV.pid = getpid();
358 ufdbGlobalLogfile = NULL;
359
360 closeAllFiles();
361 ufdbSetGlobalErrorLogFile( NULL, NULL, 0 );
362
363 while ((ch = getopt(argc, argv, "Ddhi:p:I:l:L:U:V")) > 0)
364 {
365 switch (ch) {
366 case 'D': /* undocumented -D option: do not daemonize */
367 runAsDaemon = 0;
368 break;
369 case 'd':
370 ufdbGV.debug++;
371 break;
372 case 'i':
373 strcpy( interface, optarg );
374 break;
375 case 'p':
376 portnumber = atoi( optarg );
377 break;
378 case 'I':
379 strcpy( imagesDirectory, optarg );
380 break;
381 case 'l':
382 ufdbGV.logDir = ufdbStrdup( optarg );
383 break;
384 case 'L': /* undocumented -L option for alternate PID file */
385 globalHttpdPidFile = optarg;
386 break;
387 case 'U':
388 if (strlen(optarg) <= 31)
389 strcpy( ufdbGV.userName, optarg );
390 else
391 ufdbLogFatalError( "username supplied with -U option is too long" );
392 break;
393 case 'V':
394 ufdbGV.debugHttpd = 1;
395 break;
396 case '?':
397 case 'h':
398 default:
399 usage();
400 }
401 }
402
403 if (ufdbGV.logDir == NULL ||
404 imagesDirectory[0] == '\0' ||
405 portnumber <= 0)
406 {
407 ufdbLogFatalError( "%s started with incorrect parameters. aborting...", ufdbGV.progname );
408 usage();
409 exit( 1 );
410 }
411
412 if (runAsDaemon)
413 {
414 pid_t pid;
415 if ((pid = fork()) != 0) /* the parent exits */
416 {
417 exit( 0 );
418 }
419 /* ufdbhttpd is started by ufdbguardd and does not need a new session */
420 }
421 ufdbGV.pid = getpid();
422
423 ufdbSetSignalHandler( SIGPIPE, SIG_IGN );
424 ufdbSetSignalHandler( SIGUSR1, USR1signalReceived );
425 ufdbSetSignalHandler( SIGHUP, mySignalHandler );
426 ufdbSetSignalHandler( SIGINT, mySignalHandler );
427 ufdbSetSignalHandler( SIGTERM, TermHandler );
428
429 ufdbSetSignalHandler( SIGBUS, badSignalHandler );
430 ufdbSetSignalHandler( SIGILL, badSignalHandler );
431 ufdbSetSignalHandler( SIGSEGV, badSignalHandler );
432
433 ufdbSetGlobalErrorLogFile( NULL, NULL, 0 );
434
435 ufdbLogMessage( "%s " UFDB_VERSION " started\n"
436 "interface=%s port=%d images=%s",
437 ufdbGV.progname, interface, portnumber, imagesDirectory );
438 if (ufdbGV.debug)
439 ufdbLogMessage( "debug option is ON" );
440 if (ufdbGV.debugHttpd)
441 ufdbLogMessage( "HTTP debug option is ON" );
442 if (ufdbGV.userName[0] != '\0')
443 ufdbLogMessage( "dropping privileges to user %s", ufdbGV.userName );
444
445 ufdbSimulateHttpServer( interface, portnumber, ufdbGV.userName, imagesDirectory );
446
447 exit( 0 );
448 return 0; /* make compiler happy */
449 }
450
451
452 #ifdef __cplusplus
453 }
454 #endif
455
456