1 /*
2  * ufdbdaemon.c - URLfilterDB
3  *
4  * ufdbGuard is copyrighted (C) 2005-2020 by URLfilterDB with all rights reserved.
5  *
6  * Parts of ufdbGuard are based on squidGuard.
7  * This module is NOT based on squidGuard.
8  *
9  * RCS $Id: ufdbdaemon.c,v 1.7 2020/08/20 14:51:12 root Exp root $
10  */
11 
12 
13 #include "ufdb.h"
14 #include "ufdblib.h"
15 
16 #include <stdio.h>
17 #include <string.h>
18 #include <fcntl.h>
19 #include <unistd.h>
20 #include <stdlib.h>
21 #include <ctype.h>
22 #include <signal.h>
23 #include <errno.h>
24 #if HAVE_SYS_SYSCALL_H
25 #include <sys/syscall.h>
26 #endif
27 #include <sys/time.h>
28 #include <sys/types.h>
29 #include <sys/socket.h>
30 #include <sys/stat.h>
31 #include <pwd.h>
32 
33 #if HAVE_PCRE_COMPILE
34 #include <pcre.h>
35 #endif
36 
37 #if HAVE_PR_SET_TRACER
38 #include <sys/prctl.h>
39 #endif
40 
41 #ifndef UFDB_API_NO_THREADS
42 #include <pthread.h>
43 #endif
44 
45 #ifdef __cplusplus
46 extern "C" {
47 #endif
48 
49 #if HAVE_SETRESUID
50 int setresuid( uid_t ruid, uid_t euid, uid_t suid );
51 int getresuid( uid_t * ruid, uid_t * euid, uid_t * suid );
52 #endif
53 
54 
55 static long          num_cpus;
56 static unsigned long cpu_mask = 0UL;
57 #define UFDB_MAX_CPUS 64
58 
59 
60 #if defined(__GLIBC__)
61 #if (__GLIBC__ > 2) || (__GLIBC__ == 2  &&  __GLIBC_MINOR__ >= 4) || HAVE_PCRE_COMPILE
62 #define NEED_REGEXEC_MUTEX 0
63 #else
64 #define NEED_REGEXEC_MUTEX 1
65 #endif
66 #else
67 #define NEED_REGEXEC_MUTEX 0
68 #endif
69 
70 #if NEED_REGEXEC_MUTEX
71 pthread_mutex_t ufdb_regexec_mutex = UFDB_STATIC_MUTEX_INIT;
72 #endif
73 
74 #ifdef UFDB_DEBUG
75 pthread_mutex_t ufdb_malloc_mutex = UFDB_STATIC_MUTEX_INIT;
76 #endif
77 
78 
79 /*
80  * Linux support binding threads to a set of CPUs which prevents cache contention.
81  * Freebsd has no support, but the 5.x series is recommended for best multithreaded performance.
82  * On Solaris it is recommended to start ufdbguardd in a processor set.
83  */
ufdbSetThreadCPUaffinity(int thread_num)84 int ufdbSetThreadCPUaffinity( int thread_num )
85 {
86    if (thread_num != 0)         // prevent compiler warning
87       { ; }
88 
89 #if defined(__linux__) && defined(SYS_sched_setaffinity)
90    int retval;
91 
92    if (cpu_mask != 0UL)
93    {
94       /* TODO: use sched_setaffinity() */
95       retval = syscall( SYS_sched_setaffinity, 0, 4, &cpu_mask );
96       if (retval < 0)
97          return UFDB_API_ERR_ERRNO;
98    }
99 #endif
100 
101    return UFDB_API_OK;
102 }
103 
104 
105 /*
106  * Bind my processes and threads to a set of CPUs.
107  * This increases the cache efficiency for all programs.
108  */
ufdbSetCPU(char * CPUspec)109 int ufdbSetCPU(
110    char * CPUspec )		/* comma-separated CPU numbers (starting with CPU 0) */
111 {
112    int    cpu;
113 
114 #if defined(_SC_NPROCESSORS_ONLN)
115    num_cpus = sysconf( _SC_NPROCESSORS_ONLN );
116    /* printf( "this system has %ld CPUs\n", num_cpus ); */
117 
118 #elif defined(__linux__) && defined(SYS_sched_getaffinity)
119    /* sched_getaffinity() is buggy on linux 2.4.x so we use syscall() instead */
120    cpu = syscall( SYS_sched_getaffinity, getpid(), 4, &cpu_mask );
121    /* printf( "sched_getaffinity returned %d %08lx\n", cpu, cpu_mask ); */
122    if (cpu >= 0)
123    {
124       num_cpus = 0;
125       for (cpu = 0; cpu < UFDB_MAX_CPUS; cpu++)
126 	 if (cpu_mask & (1 << cpu))
127 	    num_cpus++;
128       /* printf( "   found %d CPUs in the cpu mask\n", num_cpus ); */
129    }
130    else
131 #else
132       num_cpus = UFDB_MAX_CPUS;
133 #endif
134 
135    cpu_mask = 0;
136    while (*CPUspec)
137    {
138       if (sscanf(CPUspec,"%d",&cpu) == 1)
139       {
140 	 if (cpu >= 0 && cpu < num_cpus)
141 	 {
142 	    cpu_mask |= (1 << cpu);
143 	 }
144 	 else
145 	    return UFDB_API_ERR_RANGE;
146       }
147       else
148          return UFDB_API_ERR_RANGE;
149 
150       /* find the next CPU number */
151       while (isdigit( (int) *CPUspec))
152          CPUspec++;
153       while (*CPUspec == ' '  ||  *CPUspec == ',')
154          CPUspec++;
155    }
156 
157    return UFDB_API_OK;
158 }
159 
160 
ufdbSetSignalHandler(int signal,void (* handler)(int))161 void ufdbSetSignalHandler( int signal, void (*handler)(int) )
162 {
163 #if HAVE_SIGACTION
164    struct sigaction act;
165 
166 #ifndef SA_NODEFER
167 #define SA_NODEFER 0
168 #endif
169 
170 #ifndef SA_NOCLDSTOP
171 #define SA_NOCLDSTOP 0
172 #endif
173 
174    act.sa_handler = handler;
175    act.sa_flags = SA_RESTART;
176    if (signal == SIGCHLD)
177       act.sa_flags |= SA_NOCLDSTOP;
178    if (signal == SIGALRM)
179       act.sa_flags |= SA_NODEFER;
180    sigemptyset( &act.sa_mask );
181    sigaction( signal, &act, NULL );
182 
183 #else
184 
185 #if HAVE_SIGNAL
186    signal( signal, handler );
187 #else
188    ufdbLogError( "ufdbSetSignalHandler: cannot set handler for signal %d", signal );
189 #endif
190 
191 #endif
192 }
193 
194 
195 static pid_t   ruid = -1;
196 static pid_t   euid;
197 
198 
UFDBdropPrivileges(const char * username)199 void UFDBdropPrivileges(
200    const char *    username )
201 {
202    struct passwd * fieldptrs;
203 #if HAVE_GETPWNAM_R
204    struct passwd   pws;
205    char            pws_fields[1024];
206 #endif
207 
208    if (ruid == -1)
209    {
210       ruid = getuid();
211       euid = geteuid();
212 
213 #ifdef UFDB_WORLD_READABLE_LOGFILES
214       /* umask 133: no x for user, no wx for group, no wx for others */
215       umask( S_IXUSR | S_IWGRP|S_IXGRP | S_IWOTH|S_IXOTH );
216 #else
217       /* umask 137: no x for user, no wx for group, no rwx for others */
218       umask( S_IXUSR | S_IWGRP|S_IXGRP | S_IROTH|S_IWOTH|S_IXOTH );
219 #endif
220    }
221 
222    if (username == NULL  ||  username[0] == '\0')
223       return;
224 
225 #if HAVE_GETPWNAM_R
226    if (getpwnam_r( username, &pws, pws_fields, sizeof(pws_fields)-1, &fieldptrs ) != 0)
227 #else
228    fieldptrs = getpwnam( username );
229    if (fieldptrs == NULL)
230 #endif
231    {
232       ufdbLogError( "Cannot get info on user '%s' so cannot change to this user  *****\n"
233       		    "User '%s' probably does not exist.",
234 		    username, username );
235       return;
236    }
237 
238    /*
239     * We have already written to the log file which may have been created as user root.
240     * and need to change the ownership of the log file to be able to continue logging...
241     */
242    if (ufdbGlobalLogfile != NULL)
243    {
244       if (fchown( fileno(ufdbGlobalLogfile), fieldptrs->pw_uid, -1 ))
245          { ; }
246    }
247 
248    if (ufdbGV.debug > 1)
249       ufdbLogMessage( "going to call seteuid(%d) and setegid(%d) to set euid to '%s'",
250                       fieldptrs->pw_uid, fieldptrs->pw_gid, username );
251 
252    if (setegid( fieldptrs->pw_gid ))
253       { ; }
254 
255 #if HAVE_SETRESUID  && 0
256    if (setresuid( fieldptrs->pw_uid, fieldptrs->pw_uid, 0 ) != 0)
257 #endif
258 
259    if (seteuid( fieldptrs->pw_uid ) != 0)
260    {
261       ufdbLogError( "Cannot drop privileges and become user '%s': %s  *****", username, strerror(errno) );
262       if (geteuid() != 0)
263 	 ufdbLogError( "I am not root so cannot drop/change privileges to user '%s'", username );
264       return;
265    }
266    else
267       ufdbLogMessage( "dropped privileges and became user '%s'", username );
268 
269 #if 0 && defined(__linux__)
270    if (ufdbGV.debug || ufdbGV.debugHttpd)
271    {
272       uid_t rid, eid, sid;
273       (void) getresuid( &rid, &eid, &sid );
274       ufdbLogMessage( "Privileges are dropped: now running as user '%s'  %d %d %d", username, rid, eid, sid );
275       #define FF "/tmp/test.creat"
276       unlink( FF );
277       creat( FF, 0666 );
278       ufdbLogMessage( "created test file %s", FF );
279    }
280 #endif
281 }
282 
283 
UFDBraisePrivileges(const char * username,const char * why)284 void UFDBraisePrivileges(
285    const char *    username,
286    const char *    why )
287 {
288    if (username == NULL  ||  *username == '\0')
289       return;
290 
291    ufdbLogMessage( "raising privileges to %s", why );
292 
293    if (seteuid( 0 ) != 0)
294       ufdbLogError( "Cannot raise privileges *****" );
295 
296 #if 0
297    {
298       uid_t rid, eid, sid;
299       (void) getresuid( &rid, &eid, &sid );
300       ufdbLogMessage( "Privileges are raised: now running with ids %d %d %d", rid, eid, sid );
301       #define FF2 "/tmp/test.raised"
302       unlink( FF2 );
303       creat( FF2, 0666 );
304       ufdbLogMessage( "created test file %s", FF );
305    }
306 #endif
307 }
308 
309 
310 /*
311  * A bitmap with IP addresses of clients is used to count the number of clients.
312  * To keep the bitmap small (2 MB) the first octet of the IP address is ignored.
313  *
314  * This code assumes that there are 8 bits per byte.
315  */
316 
317 #define N_IPBITMAP   (256U * 256U * 256U)
318 #define IPBITMAPSIZE (N_IPBITMAP / 8)
319 
320 static volatile unsigned char * IPbitmap = NULL;
321 static ufdb_mutex IPbitmapMutex = ufdb_mutex_initializer;
322 
323 /* We can receive from Squid an IP address (most common) or a FQDN.
324  * In case that we receive a FQDN, we calculate a hash and use this
325  * as a psuedo IP address.
326  */
UFDBregisterCountedIP(const char * address)327 void UFDBregisterCountedIP( const char * address )
328 {
329    unsigned char * a;
330    unsigned int    i, o1, o2, o3;
331    unsigned int    nshift;
332 
333    if (IPbitmap == (unsigned char *)NULL)
334       UFDBinitializeIPcounters();
335 
336    a = (unsigned char *) address;
337 
338    /* extremely simple way of looking if the parameter is an IPv4 address... */
339    if (*a <= '9' && *a >= '0')
340    {
341       /* first skip the first octect */
342       while (*a != '.'  && *a != '\0')
343 	 a++;
344       if (*a == '.') a++;
345 
346       o1 = 0;
347       while (*a >= '0' && *a <= '9' && *a != '\0')
348       {
349 	 o1 = o1 * 10U + (*a - '0');
350 	 a++;
351       }
352       if (*a == '.') a++;
353 
354       o2 = 0;
355       while (*a >= '0' && *a <= '9' && *a != '\0')
356       {
357 	 o2 = o2 * 10U + (*a - '0');
358 	 a++;
359       }
360       if (*a == '.') a++;
361 
362       o3 = 0;
363       while (*a >= '0' && *a <= '9' && *a != '\0')
364       {
365 	 o3 = o3 * 10U + (*a - '0');
366 	 a++;
367       }
368       o1 = (o1 << 16) + (o2 << 8) + o3;
369 
370       /* if we got a non-existent IP address, we might go out of bounds... */
371       if (o1 >= N_IPBITMAP)
372       {
373 	 o1 = N_IPBITMAP;
374       }
375    }
376    else 	/* no IPv4 a, calculate a hash */
377    {
378       o1 = 104729U;
379       while (*a != '\0')
380       {
381          o1 = (o1 << 4) + o1 + (*a - ' ') - 53U;
382 	 a++;
383       }
384       o1 = o1 % (256U * 256U * 256U);
385    }
386 
387    i = o1 / 8U;
388 
389 #if 0
390    nshift = o1 - i * 8U;
391 #endif
392    nshift = o1 & 07;
393 
394    /*
395     * To be thread-safe we should use a semaphore here.
396     * But since this counter is not critical and a lost bit-set
397     * will probably be covered by another call to this function,
398     * we choose for superfast code and skip the use of a semaphore
399     * or __sync_fetch_and_or().
400     */
401    IPbitmap[i] |= (1 << nshift);
402 }
403 
404 
UFDBinitializeIPcounters(void)405 void UFDBinitializeIPcounters( void )
406 {
407    unsigned int i;
408    unsigned char * b;
409 
410    if (IPbitmap == NULL)
411    {
412       ufdb_mutex_lock( &IPbitmapMutex );
413       if (IPbitmap == NULL)
414          IPbitmap = (unsigned char *) ufdbMallocAligned( 2*1024*1024, IPBITMAPSIZE );   // IPBITMAPSIZE is 2 MB
415       ufdb_mutex_unlock( &IPbitmapMutex );
416    }
417 
418    b = (unsigned char *) IPbitmap;
419    for (i = 0;  i < IPBITMAPSIZE;  i++)
420       b[i] = 0;
421    ufdbGV.lastIPcounterResetDate = time( NULL );
422 }
423 
424 
UFDBgetNumberOfRegisteredIPs(void)425 unsigned long UFDBgetNumberOfRegisteredIPs( void )
426 {
427    unsigned char * b;
428    unsigned long   n;
429    unsigned char   v;
430    unsigned int    i;
431 
432    b = (unsigned char *) IPbitmap;
433    if (b == NULL)
434       return 0;
435    n = 0;
436    for (i = 0;  i < IPBITMAPSIZE;  i++)
437    {
438       v = b[i];
439       while (v != 0)
440       {
441          if (v & 1)
442 	    n++;
443 	 v >>= 1;
444       }
445    }
446 
447    return n;
448 }
449 
450 
451 /*
452  * Usernames are hashed and the a bitmap for hashvalues is administered
453  * to keep track of the number of users.
454  * To support up to 50,000 users, a bitmap that supports 500,000 users
455  * is used to prevent collisions where two or more users are counted as one.
456  *
457  * This code assumes that there are 8 bits per byte.
458  */
459 
460 /* USERBITMAPSIZE is 64KB for 512*1024 hashvalues */
461 #define N_USERBITMAP	(512 * 1024)
462 #define USERBITMAPSIZE  (N_USERBITMAP / 8)
463 
464 static volatile unsigned char * UserBitmap = NULL;
465 static ufdb_mutex UserBitmapMutex = ufdb_mutex_initializer;
466 
467 /* We can receive from Squid a username or '-'.
468  * In case that we receive a username, we calculate a hashvalue and use this
469  * as an index in UserBitmap to set a bit to 1.
470  */
UFDBregisterCountedUser(const char * username)471 void UFDBregisterCountedUser( const char * username )
472 {
473    unsigned char * a;
474    unsigned int    i, o1;
475    unsigned int    nshift;
476 
477    if (UserBitmap == (unsigned char *)NULL)
478       UFDBinitializeUserCounters();
479 
480    a = (unsigned char *) username;
481 
482    o1 = 104729U;
483    while (*a != '\0')
484    {
485       o1 = (o1 << 4) + o1 + (*a - ' ') - 53U;
486       a++;
487    }
488    o1 = o1 % N_USERBITMAP;
489 
490    i = o1 / 8U;
491 
492 #if 0
493    nshift = o1 - i * 8U;
494 #endif
495    nshift = o1 & 07;
496 
497    /*
498     * To be thread-safe we should use a semaphore here.
499     * But since this counter is not critical and a lost bit-set
500     * will probably be covered by another call to this function,
501     * we choose for superfast code and skip the use of a semaphore
502     * or __sync_fetch_and_or().
503     */
504    UserBitmap[i] |= (1 << nshift);
505 }
506 
507 
UFDBinitializeUserCounters(void)508 void UFDBinitializeUserCounters( void )
509 {
510    int i;
511    unsigned char * b;
512 
513    if (UserBitmap == (unsigned char *)NULL)
514    {
515       ufdb_mutex_lock( &UserBitmapMutex );
516       if (UserBitmap == (unsigned char *)NULL)
517          UserBitmap = (unsigned char *) ufdbMallocAligned( 4096, USERBITMAPSIZE );
518       ufdb_mutex_unlock( &UserBitmapMutex );
519    }
520 
521    b = (unsigned char *) UserBitmap;
522    for (i = 0;  i < USERBITMAPSIZE;  i++)
523       b[i] = 0;
524 }
525 
526 
UFDBgetNumberOfRegisteredUsers(void)527 unsigned long UFDBgetNumberOfRegisteredUsers( void )
528 {
529    unsigned char * b;
530    unsigned long   n;
531    unsigned char   v;
532    int             i;
533 
534    b = (unsigned char *) UserBitmap;
535    if (b == NULL)
536       return 0;
537    n = 0;
538    for (i = 0;  i < USERBITMAPSIZE;  i++)
539    {
540       v = b[i];
541       while (v != 0)
542       {
543          if (v & 1)
544 	    n++;
545 	 v >>= 1;
546       }
547    }
548 
549    return n;
550 }
551 
552 
ufdbSendEmailToAdmin(int newStatus)553 void ufdbSendEmailToAdmin( int newStatus )
554 {
555    int       mx;
556    int       n;
557    time_t    now_t;
558    struct tm t;
559    struct    timeval      tv;
560    char      hostname[256+1];
561    char      line[2048];
562 
563    if (ufdbGV.emailServer == NULL  ||  ufdbGV.adminEmail == NULL)
564       return;
565 
566    if (ufdbGV.myHostname != NULL)
567       strcpy( hostname, ufdbGV.myHostname );
568    else
569    {
570       gethostname( hostname, sizeof(hostname) );
571       hostname[256] = '\0';
572    }
573 
574    mx = UFDBopenSocket( ufdbGV.emailServer, 25 );
575    if (mx < 0)
576    {
577       ufdbLogError( "cannot open connection to mail server '%s' to inform status change: %s",
578                     ufdbGV.emailServer, strerror(errno) );
579       return;
580    }
581 
582    /*
583     *  Prevent that the read() takes ages.  Use an agressive timeout.
584     */
585    tv.tv_sec = 6;
586    tv.tv_usec = 0;
587    setsockopt( mx, SOL_SOCKET, SO_RCVTIMEO, (void *) &tv, sizeof(tv) );
588    tv.tv_sec = 5;
589    tv.tv_usec = 0;
590    setsockopt( mx, SOL_SOCKET, SO_SNDTIMEO, (void *) &tv, sizeof(tv) );
591 
592    if (ufdbGV.debug)
593       ufdbLogMessage( "   mail: opened socket 25 to mail server '%s'", ufdbGV.emailServer );
594 
595    do {
596       n = read( mx, line, 2048 );
597    } while (n < 0  &&  errno == EINTR);
598    if (n < 4  ||  strncmp( line, "220", 3 ) != 0)
599    {
600       ufdbLogError( "ufdbSendEmailToAdmin: mail server %s did not send 220 welcome message: %s",
601       		    ufdbGV.emailServer, strerror(errno) );
602       close( mx );
603       return;
604    }
605    if (ufdbGV.debug > 1)
606       ufdbLogMessage( "   mail: read welcome lines from mail server '%s'", ufdbGV.emailServer );
607 
608 #if 0
609    /* The welcome message may be long and sent in two chunks...
610     * There is a 2 second timeout so no worries if there is no more input.
611     */
612    tv.tv_sec = 2;
613    tv.tv_usec = 0;
614    setsockopt( mx, SOL_SOCKET, SO_RCVTIMEO, (void *) &tv, sizeof(tv) );
615    do {
616       n = read( mx, line, 2048 );
617    } while (n < 0  &&  errno == EINTR);
618 #endif
619 
620    /*
621     * From now on we wait maximum 20 seconds for responses of the mail server
622     */
623    tv.tv_sec = 20;
624    tv.tv_usec = 0;
625    setsockopt( mx, SOL_SOCKET, SO_RCVTIMEO, (void *) &tv, sizeof(tv) );
626 
627    sprintf( line, "HELO %s\r\n", hostname );
628    n = write( mx, line, strlen(line) );
629    if (n != (int) strlen(line))
630    {
631       ufdbLogError( "ufdbSendEmailToAdmin: failed to send HELO to mail server '%s': %s",
632                     ufdbGV.emailServer, strerror(errno) );
633       close( mx );
634       return;
635    }
636    do {
637       n = read( mx, line, 2047 );
638    } while (n < 0  &&  errno == EINTR);
639    if (n < 1)
640    {
641       ufdbLogError( "ufdbSendEmailToAdmin: mail server '%s' did not send reply to HELO: %s",
642       		    ufdbGV.emailServer, strerror(errno) );
643       close( mx );
644       return;
645    }
646 
647    if (ufdbGV.debug > 1)
648    {
649       line[n] = '\0';
650       ufdbLogMessage( "   mail: done with HELO handshake with mail server '%s'\nreply is %s", ufdbGV.emailServer, line );
651    }
652 
653    sprintf( line, "MAIL FROM:<%s>\r\n", ufdbGV.senderEmail != NULL ? ufdbGV.senderEmail : ufdbGV.adminEmail );
654    n = write( mx, line, strlen(line) );
655    if (n != (int) strlen(line))
656    {
657       ufdbLogError( "ufdbSendEmailToAdmin: failed to send MAIL FROM to mail server '%s': %s",
658                     ufdbGV.emailServer, strerror(errno) );
659       close( mx );
660       return;
661    }
662    do {
663       n = read( mx, line, 2048 );
664    } while (n < 0  &&  errno == EINTR);
665    if (n < 1)
666    {
667       ufdbLogError( "ufdbSendEmailToAdmin: mail server '%s' did not send reply to MAIL FROM: %s",
668       		    ufdbGV.emailServer, strerror(errno) );
669       close( mx );
670       return;
671    }
672    if (strncmp( line, "25", 2 ) != 0)
673    {
674       char * newline;
675 
676       newline = strchr( line, '\r' );
677       if (newline == NULL)
678 	 newline = strchr( line, '\n' );
679       if (newline != NULL)
680          *newline ='\0';
681       ufdbLogError( "ufdbSendEmailToAdmin: mail server '%s' did not send reply with OK to MAIL FROM:\n%s",
682       		    ufdbGV.emailServer, line );
683       close( mx );
684       return;
685    }
686 
687    sprintf( line, "RCPT TO:<%s>\r\n", ufdbGV.adminEmail );
688    n = write( mx, line, strlen(line) );
689    if (n != (int) strlen(line))
690    {
691       ufdbLogError( "ufdbSendEmailToAdmin: failed to send RCPT TO to mail server '%s': %s",
692                     ufdbGV.emailServer, strerror(errno) );
693       close( mx );
694       return;
695    }
696    do {
697       n = read( mx, line, 2048 );
698    } while (n < 0  &&  errno == EINTR);
699    if (n < 1)
700    {
701       ufdbLogError( "ufdbSendEmailToAdmin: mail server '%s' did not send reply to RCPT TO: %s",
702       		    ufdbGV.emailServer, strerror(errno) );
703       close( mx );
704       return;
705    }
706    if (strncmp( line, "25", 2 ) != 0)
707    {
708       char * newline;
709 
710       newline = strchr( line, '\r' );
711       if (newline == NULL)
712 	 newline = strchr( line, '\n' );
713       if (newline != NULL)
714          *newline ='\0';
715       ufdbLogError( "ufdbSendEmailToAdmin: mail server '%s' did not send reply with OK to RCPT TO:\n%s",
716       		    ufdbGV.emailServer, line );
717       close( mx );
718       return;
719    }
720 
721    /* To support mail servers with plain authentication:
722     *
723     * According to RFC 2595 the client must send: [authorize-id] \0 authenticate-id \0 password.
724     * pwcheck_method has been set to sasldb for the following example.
725     *
726     * >>> AUTH PLAIN dGVzdAB0ZXN0QHdpei5leGFtcGxlLmNvbQB0RXN0NDI=
727     * 235 2.0.0 OK Authenticated
728     * Decoded:
729     * test\000test@wiz.example.com\000tEst42
730     * With patch for lib/checkpw.c or a pwcheck_method that doesn't support realms:
731     * >>> AUTH PLAIN dGVzdAB0ZXN0AHRFc3Q0Mg==
732     * Decoded:
733     * test\000test\000tEst42
734     */
735 
736    if (ufdbGV.debug > 1)
737       ufdbLogMessage( "   mail: sending DATA to mail server '%s'", ufdbGV.emailServer );
738 
739    sprintf( line, "DATA\r\n" );
740    n = write( mx, line, strlen(line) );
741    if (n != (int) strlen(line))
742    {
743       ufdbLogError( "ufdbSendEmailToAdmin: failed to send DATA to mail server '%s': %s",
744                     ufdbGV.emailServer, strerror(errno) );
745       close( mx );
746       return;
747    }
748    do {
749       n = read( mx, line, 2048 );
750    } while (n < 0  &&  errno == EINTR);
751    if (n < 1)
752    {
753       ufdbLogError( "ufdbSendEmailToAdmin: mail server '%s' did not send reply to DATA: %s",
754       		    ufdbGV.emailServer, strerror(errno) );
755       close( mx );
756       return;
757    }
758    if (strncmp( line, "354", 3 ) != 0)
759    {
760       char * newline;
761 
762       newline = strchr( line, '\r' );
763       if (newline == NULL)
764 	 newline = strchr( line, '\n' );
765       if (newline != NULL)
766          *newline ='\0';
767       ufdbLogError( "ufdbSendEmailToAdmin: mail server '%s' did not send reply with OK to DATA:\n%s",
768       		    ufdbGV.emailServer, line );
769       close( mx );
770       return;
771    }
772 
773    now_t = time( NULL );
774    gmtime_r( &now_t, &t );
775 
776    sprintf( line, "From: ufdbGuard daemon <%s>\r\n"
777 		  "To: URL filter administrator <%s>\r\n"
778                   "Subject: ufdbGuard on %s has a new status: %s\r\n"
779 		  "Date: %3.3s, %02d %3.3s %4d %02d:%02d:%02d GMT\r\n"
780 		  "X-Mailer: ufdbguardd " UFDB_VERSION
781 		  "\r\n"
782 		  "ufdbGuard with pid %d on %s has a new status: %s\n"
783 		  "database status: %s\n"
784 		  "license status: %s\n"
785 		  "configuration file: %s\n"
786 		  "version: " UFDB_VERSION "\n"
787 		  "\r\n.\r\n",
788 		  ufdbGV.senderEmail != NULL ? ufdbGV.senderEmail : ufdbGV.adminEmail,
789 		  ufdbGV.adminEmail,
790 		  hostname, ufdbStatus2string(newStatus),
791 		     &"SunMonTueWedThuFriSat"[t.tm_wday*3],
792 		     t.tm_mday,
793 		     &"JanFebMarAprMayJunJulAugSepOctNovDec"[t.tm_mon*3],
794 		     t.tm_year + 1900,
795 		     t.tm_hour, t.tm_min, t.tm_sec,
796 		  ufdbGV.pid, hostname, ufdbStatus2string(newStatus),
797 		  ufdbDBstat2string(ufdbGV.databaseStatus),
798 		  ufdbGV.licenseStatus,
799 		  ufdbGV.configFile
800 	  );
801    n = write( mx, line, strlen(line) );
802    if (n != (int) strlen(line))
803    {
804       ufdbLogError( "ufdbSendEmailToAdmin: failed to send CONTENT to mail server '%s': %s",
805                     ufdbGV.emailServer, strerror(errno) );
806       close( mx );
807       return;
808    }
809    do {
810       n = read( mx, line, 2048 );
811    } while (n < 0  &&  errno == EINTR);
812    if (n < 1)
813    {
814       ufdbLogError( "ufdbSendEmailToAdmin: mail server '%s' did not send reply to DATA END: %s",
815       		    ufdbGV.emailServer, strerror(errno) );
816       close( mx );
817       return;
818    }
819    if (strncmp( line, "25", 2 ) != 0)
820    {
821       char * newline;
822 
823       newline = strchr( line, '\r' );
824       if (newline == NULL)
825 	 newline = strchr( line, '\n' );
826       if (newline != NULL)
827          *newline ='\0';
828       ufdbLogError( "ufdbSendEmailToAdmin: mail server '%s' did not send reply with OK to DATA END:\n%s",
829       		    ufdbGV.emailServer, line );
830       close( mx );
831       return;
832    }
833 
834    if (ufdbGV.debug > 1)
835       ufdbLogMessage( "   mail: sending QUIT to %s", ufdbGV.emailServer );
836 
837    sprintf( line, "QUIT\r\n" );
838    n = write( mx, line, strlen(line) );
839    if (n != (int) strlen(line))
840    {
841       ufdbLogError( "ufdbSendEmailToAdmin: failed to send QUIT to mail server '%s': %s",
842                     ufdbGV.emailServer, strerror(errno) );
843       close( mx );
844       return;
845    }
846 #if 0
847    n = read( mx, line, 2048 );
848    /* ignore errors */
849 #endif
850    close( mx );
851 
852    ufdbLogMessage( "sent email with status update to '%s' using mail server '%s'", ufdbGV.adminEmail, ufdbGV.emailServer );
853 }
854 
855 
ufdbExecuteExternalCommand(int status)856 static void ufdbExecuteExternalCommand( int status )
857 {
858    pid_t pid;
859 
860    if (ufdbGV.externalStatusCommand == NULL)
861       return;
862 
863    ufdbLogMessage( "going to execute: %s -s %s -d %s -l %s -v %s",
864                    ufdbGV.externalStatusCommand,
865 		   ufdbStatus2string(status),
866 		   ufdbDBstat2string(ufdbGV.databaseStatus),
867 		   ufdbGV.licenseStatus,
868 		   UFDB_VERSION  );
869 
870    pid = fork();
871    if (pid == 0)	/* we are the forked child */
872    {
873       int    i;
874       const char * argv[12];
875 
876       for (i = 0; i < 2*UFDB_MAX_THREADS + 32;  i++)
877          close( i );
878       i = 0;
879       argv[i++] = ufdbGV.externalStatusCommand;
880       argv[i++] = "-s";
881       argv[i++] = ufdbStatus2string( status );
882       argv[i++] = "-d";
883       argv[i++] = ufdbDBstat2string( ufdbGV.databaseStatus );
884       argv[i++] = "-l";
885       argv[i++] = ufdbGV.licenseStatus;
886       argv[i++] = "-v";
887       argv[i++] = UFDB_VERSION;
888       argv[i] = NULL;
889       execv( ufdbGV.externalStatusCommand, (char* const*) argv );
890       _exit( 2 );       /* exit after failed execve */
891    }
892    else
893    {
894       if (ufdbGV.debug > 1)
895          ufdbLogMessage( "   %s has pid %ld", ufdbGV.externalStatusCommand, (long) pid );
896    }
897 
898    if (pid < 0)
899    {
900       ufdbLogError( "fork failed: cannot execute external status command '%s': %s",
901                     ufdbGV.externalStatusCommand, strerror(errno) );
902       return;
903    }
904 
905    /* we could do a waitpid() here but there is an other thread that does that */
906 }
907 
908 
UFDBchangeStatus(int status)909 int UFDBchangeStatus( int status )
910 {
911    int    oldStatus;
912    char * dbhome;
913    FILE * fp;
914    char   licStatFileName[1024];
915 
916    if (status == ufdbGV.status)
917       return status;
918 
919    ufdbLogMessage( "Changing daemon status to \"%s\"", ufdbStatus2string(status) );
920 
921    /* prevent changing the ERROR state into an OK state */
922    if (ufdbGV.status == UFDB_API_STATUS_FATAL_ERROR)
923    {
924       if (status == UFDB_API_STATUS_RELOAD_OK)
925          return UFDB_API_STATUS_FATAL_ERROR;
926    }
927 
928    /* update the license status */
929    dbhome = ufdbGV.databaseDirectory;
930    strcpy( licStatFileName, dbhome );
931    strcat( licStatFileName, "/" );
932    strcat( licStatFileName, UFDB_LICENSE_STATUS_FILENAME );
933    fp = fopen( licStatFileName, "r" );
934    if (fp != NULL)
935    {
936       char * newline;
937       if (fgets( ufdbGV.licenseStatus, sizeof(ufdbGV.licenseStatus)-1, fp ))
938          { ; }
939       fclose( fp );
940       if ((newline = strchr( ufdbGV.licenseStatus, '\n' )) != NULL)
941          *newline = '\0';
942       if (ufdbGV.licenseStatus[0] == '\0')
943 	 strcpy( ufdbGV.licenseStatus, "unknown" );
944    }
945    else
946       strcpy( ufdbGV.licenseStatus, "unknown" );
947 
948    ufdbSendEmailToAdmin( status );
949    ufdbExecuteExternalCommand( status );
950 
951    oldStatus = ufdbGV.status;
952    ufdbGV.status = status;
953 
954    if (ufdbGV.debug)
955       ufdbLogMessage( "UFDBchangeStatus done" );
956 
957    return oldStatus;
958 }
959 
960 
my_fast_fgets_nonl(char * s,int size,FILE * stream)961 static char * my_fast_fgets_nonl( char * s, int size, FILE * stream )
962 {
963    char * buf;
964    int    ch;
965 
966    buf = s;
967    while ((ch = getc_unlocked(stream)) != EOF  &&  --size > 0)
968    {
969       if (ch == '\n'  ||  ch == '\r')
970          break;
971       *buf++ = ch;
972    }
973    *buf = '\0';
974    if (ch == EOF  &&  buf == s)
975       return NULL;
976    return s;
977 }
978 
979 
interruptPstack(int dummy)980 static void interruptPstack( int dummy )
981 {
982    if (dummy)           // prevent compiler warning
983       { ; }
984 
985    ufdbLogError( "waited too long for output of pstack.... aborting now!" );
986    UFDBchangeStatus( UFDB_API_STATUS_FATAL_ERROR );
987    _exit( 5 );  /* pstack took too long; exit now */
988 }
989 
990 
ufdbExecutePstack(const char * reason)991 void ufdbExecutePstack( const char * reason )
992 {
993    FILE * fp;
994    char   cmd[512];
995    char   buffer[2048];
996 
997 #if 0
998    /* popen causes SIGCHLD signals but we want to ignore them */
999    ufdbSetSignalHandler( SIGCHLD, SIG_IGN );
1000 #endif
1001 
1002    ufdbSetSignalHandler( SIGALRM, interruptPstack );
1003    alarm( 15 );
1004 
1005    if (ufdbGV.userName[0] != '\0')
1006       sprintf( cmd, DEFAULT_BINDIR "/ufdb-pstack %d -U %s %s 2>&1", ufdbGV.pid, ufdbGV.userName, reason );
1007    else
1008       sprintf( cmd, DEFAULT_BINDIR "/ufdb-pstack %d %s 2>&1", ufdbGV.pid, reason );
1009 
1010    /* gdb sometimes needs root permissions, so the popen() is done as root */
1011    UFDBraisePrivileges( ufdbGV.userName, "open pipe for ufdb-pstack" );
1012 
1013 #if HAVE_PR_SET_TRACER
1014    // allow gdb process to attach to this process:
1015    if (prctl( PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0 ) != 0)
1016       { ; }
1017 #endif
1018 
1019    errno = 0;
1020    fp = popen( cmd, "r" );
1021    if (fp == NULL)
1022       ufdbLogError( "could not open pipe and execute \"%s\": %s", cmd, strerror(errno) );
1023    UFDBdropPrivileges( ufdbGV.userName );
1024 
1025    while (my_fast_fgets_nonl( buffer, 2040, fp ) != NULL)
1026    {
1027       ufdbLogMessage( "pstack  %s", buffer );
1028    }
1029    pclose( fp );
1030    ufdbLogMessage( "pstack  END" );
1031    alarm( 0 );
1032 }
1033 
1034 
1035 #ifdef __cplusplus
1036 }
1037 #endif
1038