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