Scan media entities as well, not just url entities. This should expand more
[bitlbee.git] / utils / bitlbeed.c
blob82bd08798c8b4662f8030c0685c18af9a41f11b4
1 /****************************************************************\
2 * *
3 * bitlbeed.c *
4 * *
5 * A tiny daemon to allow you to run The Bee as a non-root user *
6 * (without access to /etc/inetd.conf or whatever) *
7 * *
8 * Copyright 2002-2004 Wilmer van der Gaast <wilmer@gaast.net> *
9 * *
10 * Licensed under the GNU General Public License *
11 * *
12 * Modified by M. Dennis, 20040627 *
13 \****************************************************************/
15 /*
16 ChangeLog:
18 2004-06-27: Added support for AF_LOCAL (UNIX domain) sockets
19 Renamed log to do_log to fix conflict warning
20 Changed protocol to 0 (6 is not supported?)
21 Added error check for socket()
22 Added a no-fork (debug) mode
23 2004-05-15: Added rate limiting
24 2003-12-26: Added the SO_REUSEADDR sockopt, logging and CPU-time limiting
25 for clients using setrlimit(), fixed the execv() call
26 2002-11-29: Added the timeout so old child processes clean up faster
27 2002-11-28: First version
30 #define SELECT_TIMEOUT 2
31 #define MAX_LOG_LEN 128
33 #include <stdio.h>
34 #include <unistd.h>
35 #include <string.h>
36 #include <stdlib.h>
37 #include <fcntl.h>
38 #include <stdarg.h>
39 #include <time.h>
41 #include <sys/wait.h>
42 #include <sys/time.h>
43 #include <sys/types.h>
44 #include <sys/socket.h>
45 #include <sys/resource.h>
46 #include <sys/stat.h>
48 #include <sys/un.h>
49 #include <netinet/in.h>
50 #include <arpa/inet.h>
52 typedef struct settings
54 char local;
55 char debug;
56 char *interface;
57 signed int port;
59 unsigned char max_conn;
60 int seconds;
62 int rate_seconds;
63 int rate_times;
64 int rate_ignore;
66 char **call;
67 } settings_t;
69 typedef struct ipstats
71 unsigned int ip;
73 time_t rate_start;
74 int rate_times;
75 time_t rate_ignore;
77 struct ipstats *next;
78 } ipstats_t;
80 FILE *logfile;
81 ipstats_t *ipstats;
83 settings_t *set_load( int argc, char *argv[] );
84 void do_log( char *fmt, ... );
85 ipstats_t *ip_get( char *ip_txt );
87 int main( int argc, char *argv[] )
89 const int rebind_on = 1;
90 settings_t *set;
92 int serv_fd, serv_len;
93 struct sockaddr_in serv_addr;
94 struct sockaddr_un local_addr;
96 pid_t st;
98 if( !( set = set_load( argc, argv ) ) )
99 return( 1 );
101 if( !logfile )
102 if( !( logfile = fopen( "/dev/null", "w" ) ) )
104 perror( "fopen" );
105 return( 1 );
108 fcntl( fileno( logfile ), F_SETFD, FD_CLOEXEC );
110 if( set->local )
111 serv_fd = socket( PF_LOCAL, SOCK_STREAM, 0 );
112 else
113 serv_fd = socket( PF_INET, SOCK_STREAM, 0 );
114 if( serv_fd < 0 )
116 perror( "socket" );
117 return( 1 );
119 setsockopt( serv_fd, SOL_SOCKET, SO_REUSEADDR, &rebind_on, sizeof( rebind_on ) );
120 fcntl( serv_fd, F_SETFD, FD_CLOEXEC );
121 if (set->local) {
122 local_addr.sun_family = AF_LOCAL;
123 strncpy( local_addr.sun_path, set->interface, sizeof( local_addr.sun_path ) - 1 );
124 local_addr.sun_path[sizeof( local_addr.sun_path ) - 1] = '\0';
126 /* warning - don't let untrusted users run this program if it
127 is setuid/setgid! Arbitrary file deletion risk! */
128 unlink( set->interface );
129 if( bind( serv_fd, (struct sockaddr *) &local_addr, SUN_LEN( &local_addr ) ) != 0 )
131 perror( "bind" );
132 return( 1 );
134 chmod( set->interface, S_IRWXO|S_IRWXG|S_IRWXU );
136 } else {
137 serv_addr.sin_family = AF_INET;
138 serv_addr.sin_addr.s_addr = inet_addr( set->interface );
139 serv_addr.sin_port = htons( set->port );
140 serv_len = sizeof( serv_addr );
142 if( bind( serv_fd, (struct sockaddr *) &serv_addr, serv_len ) != 0 )
144 perror( "bind" );
145 return( 1 );
149 if( listen( serv_fd, set->max_conn ) != 0 )
151 perror( "listen" );
152 return( 1 );
155 if ( ! set->debug ) {
156 st = fork();
157 if( st < 0 )
159 perror( "fork" );
160 return( 1 );
162 else if( st > 0 )
164 return( 0 );
167 setsid();
168 close( 0 );
169 close( 1 );
170 close( 2 );
173 do_log( "bitlbeed running" );
175 /* The Daemon */
176 while( 1 )
178 int cli_fd, cli_len, i, st;
179 struct sockaddr_in cli_addr;
180 struct sockaddr_un cli_local;
181 ipstats_t *ip;
182 char *cli_txt;
183 pid_t child;
185 static int running = 0;
187 fd_set rd;
188 struct timeval tm;
190 /* accept() only returns after someone connects. To clean up old
191 processes (by running waitpid()) it's better to use select()
192 with a timeout. */
193 FD_ZERO( &rd );
194 FD_SET( serv_fd, &rd );
195 tm.tv_sec = SELECT_TIMEOUT;
196 tm.tv_usec = 0;
197 if( select( serv_fd + 1, &rd, NULL, NULL, &tm ) > 0 )
199 if (set->local) {
200 cli_len = SUN_LEN( &cli_local );
201 cli_fd = accept( serv_fd, (struct sockaddr *) &cli_local, &cli_len );
202 cli_txt = "127.0.0.1";
203 } else {
204 cli_len = sizeof( cli_addr );
205 cli_fd = accept( serv_fd, (struct sockaddr *) &cli_addr, &cli_len );
206 cli_txt = inet_ntoa( cli_addr.sin_addr );
209 ip = ip_get( cli_txt );
211 if( set->rate_times == 0 || time( NULL ) > ip->rate_ignore )
213 /* We want this socket on stdout and stderr too! */
214 dup( cli_fd ); dup( cli_fd );
216 if( ( child = fork() ) == 0 )
218 if( set->seconds )
220 struct rlimit li;
222 li.rlim_cur = (rlim_t) set->seconds;
223 li.rlim_max = (rlim_t) set->seconds + 1;
224 setrlimit( RLIMIT_CPU, &li );
226 execv( set->call[0], set->call );
227 do_log( "Error while executing %s!", set->call[0] );
228 return( 1 );
231 running ++;
232 close( 0 );
233 close( 1 );
234 close( 2 );
236 do_log( "Started child process for client %s (PID=%d), got %d clients now", cli_txt, child, running );
238 if( time( NULL ) < ( ip->rate_start + set->rate_seconds ) )
240 ip->rate_times ++;
241 if( ip->rate_times >= set->rate_times )
243 do_log( "Client %s crossed the limit; ignoring for the next %d seconds", cli_txt, set->rate_ignore );
244 ip->rate_ignore = time( NULL ) + set->rate_ignore;
245 ip->rate_start = 0;
248 else
250 ip->rate_start = time( NULL );
251 ip->rate_times = 1;
254 else
256 do_log( "Ignoring connection from %s", cli_txt );
257 close( cli_fd );
261 /* If the max. number of connection is reached, don't accept
262 new connections until one expires -> Not always WNOHANG
264 Cleaning up child processes is a good idea anyway... :-) */
265 while( ( i = waitpid( 0, &st, ( ( running < set->max_conn ) || ( set->max_conn == 0 ) ) ? WNOHANG : 0 ) ) > 0 )
267 running --;
268 if( WIFEXITED( st ) )
270 do_log( "Child process (PID=%d) exited normally with status %d. %d Clients left now",
271 i, WEXITSTATUS( st ), running );
273 else if( WIFSIGNALED( st ) )
275 do_log( "Child process (PID=%d) killed by signal %d. %d Clients left now",
276 i, WTERMSIG( st ), running );
278 else
280 /* Should not happen AFAIK... */
281 do_log( "Child process (PID=%d) stopped for unknown reason, %d clients left now",
282 i, running );
287 return( 0 );
290 settings_t *set_load( int argc, char *argv[] )
292 settings_t *set;
293 int opt, i;
295 set = malloc( sizeof( settings_t ) );
296 memset( set, 0, sizeof( settings_t ) );
297 set->interface = NULL; /* will be filled in later */
298 set->port = 6667;
299 set->local = 0;
300 set->debug = 0;
302 set->rate_seconds = 600;
303 set->rate_times = 5;
304 set->rate_ignore = 900;
306 while( ( opt = getopt( argc, argv, "i:p:n:t:l:r:hud" ) ) >= 0 )
308 if( opt == 'i' )
310 set->interface = strdup( optarg );
312 else if( opt == 'p' )
314 if( ( sscanf( optarg, "%d", &i ) != 1 ) || ( i <= 0 ) || ( i > 65535 ) )
316 fprintf( stderr, "Invalid port number: %s\n", optarg );
317 return( NULL );
319 set->port = i;
321 else if( opt == 'n' )
323 if( ( sscanf( optarg, "%d", &i ) != 1 ) || ( i < 0 ) )
325 fprintf( stderr, "Invalid number of connections: %s\n", optarg );
326 return( NULL );
328 set->max_conn = i;
330 else if( opt == 't' )
332 if( ( sscanf( optarg, "%d", &i ) != 1 ) || ( i < 0 ) || ( i > 600 ) )
334 fprintf( stderr, "Invalid number of seconds: %s\n", optarg );
335 return( NULL );
337 set->seconds = i;
339 else if( opt == 'l' )
341 if( !( logfile = fopen( optarg, "a" ) ) )
343 perror( "fopen" );
344 fprintf( stderr, "Error opening logfile, giving up.\n" );
345 return( NULL );
347 setbuf( logfile, NULL );
349 else if( opt == 'r' )
351 if( sscanf( optarg, "%d,%d,%d", &set->rate_seconds, &set->rate_times, &set->rate_ignore ) != 3 )
353 fprintf( stderr, "Invalid argument to -r.\n" );
354 return( NULL );
357 else if( opt == 'u' )
358 set->local = 1;
359 else if( opt == 'd' )
360 set->debug = 1;
361 else if( opt == 'h' )
363 printf( "Usage: %s [-i <interface>] [-p <port>] [-n <num>] [-r x,y,z] ...\n"
364 " ... <command> <args...>\n"
365 "A simple inetd-like daemon to have a program listening on a TCP socket without\n"
366 "needing root access to the machine\n"
367 "\n"
368 " -i Specify the interface (by IP address) to listen on.\n"
369 " (Default: 0.0.0.0 (any interface))\n"
370 " -p Port number to listen on. (Default: 6667)\n"
371 " -n Maximum number of connections. (Default: 0 (unlimited))\n"
372 " -t Specify the maximum number of CPU seconds per process.\n"
373 " (Default: 0 (unlimited))\n"
374 " -l Specify a logfile. (Default: none)\n"
375 " -r Rate limiting: Ignore a host for z seconds when it connects for more\n"
376 " than y times in x seconds. (Default: 600,5,900. Disable: 0,0,0)\n"
377 " -u Use a local socket, by default /tmp/bitlbee (override with -i <filename>)\n"
378 " -d Don't fork for listening (for debugging purposes)\n"
379 " -h This information\n", argv[0] );
380 return( NULL );
384 if( set->interface == NULL )
385 set->interface = (set->local) ? "/tmp/bitlbee" : "0.0.0.0";
387 if( optind == argc )
389 fprintf( stderr, "Missing program parameter!\n" );
390 return( NULL );
393 /* The remaining arguments are the executable and its arguments */
394 set->call = malloc( ( argc - optind + 1 ) * sizeof( char* ) );
395 memcpy( set->call, argv + optind, sizeof( char* ) * ( argc - optind ) );
396 set->call[argc-optind] = NULL;
398 return( set );
401 void do_log( char *fmt, ... )
403 va_list params;
404 char line[MAX_LOG_LEN];
405 time_t tm;
406 int l;
408 memset( line, 0, MAX_LOG_LEN );
410 tm = time( NULL );
411 strcpy( line, ctime( &tm ) );
412 l = strlen( line );
413 line[l-1] = ' ';
415 va_start( params, fmt );
416 vsnprintf( line + l, MAX_LOG_LEN - l - 2, fmt, params );
417 va_end( params );
418 strcat( line, "\n" );
420 fprintf( logfile, "%s", line );
423 ipstats_t *ip_get( char *ip_txt )
425 unsigned int ip;
426 ipstats_t *l;
427 int p[4];
429 sscanf( ip_txt, "%d.%d.%d.%d", p + 0, p + 1, p + 2, p + 3 );
430 ip = ( p[0] << 24 ) | ( p[1] << 16 ) | ( p[2] << 8 ) | ( p[3] );
432 for( l = ipstats; l; l = l->next )
434 if( l->ip == ip )
435 return( l );
438 if( ipstats )
440 for( l = ipstats; l->next; l = l->next );
442 l->next = malloc( sizeof( ipstats_t ) );
443 l = l->next;
445 else
447 l = malloc( sizeof( ipstats_t ) );
448 ipstats = l;
450 memset( l, 0, sizeof( ipstats_t ) );
452 l->ip = ip;
454 return( l );