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