aboutsummaryrefslogtreecommitdiffstats
path: root/protocols/yahoo/yahoo_util.h
blob: 1a033a6601f68a089dec38b574bdc11a6326e609 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
/*
 * libyahoo2: yahoo_util.h
 *
 * Copyright (C) 2002-2004, Philip S Tellis <philip.tellis AT gmx.net>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifndef __YAHOO_UTIL_H__
#define __YAHOO_UTIL_H__

#if HAVE_CONFIG_H
# include <config.h>
#endif

#if HAVE_GLIB
# include <glib.h>

# define FREE(x)        if (x) { g_free(x); x = NULL; }

# define y_new          g_new
# define y_new0         g_new0
# define y_renew        g_renew

# define y_memdup       g_memdup
# define y_strsplit     g_strsplit
# define y_strfreev     g_strfreev
# ifndef strdup
#  define strdup        g_strdup
# endif
# ifndef strncasecmp
#  define strncasecmp   g_strncasecmp
#  define strcasecmp    g_strcasecmp
# endif

# define snprintf       g_snprintf
#ifdef vsnprintf
#undef vsnprintf
#endif
# define vsnprintf      g_vsnprintf

#else

# include <stdlib.h>
# include <stdarg.h>

# define FREE(x)                if (x) { free(x); x = NULL; }

# define y_new(type, n)         (type *) malloc(sizeof(type) * (n))
# define y_new0(type, n)        (type *) calloc((n), sizeof(type))
# define y_renew(type, mem, n)  (type *) realloc(mem, n)

void *y_memdup(const void *addr, int n);
char **y_strsplit(char *str, char *sep, int nelem);
void y_strfreev(char **vector);

int strncasecmp(const char *s1, const char *s2, size_t n);
int strcasecmp(const char *s1, const char *s2);

char *strdup(const char *s);

int snprintf(char *str, size_t size, const char *format, ...);
int vsnprintf(char *str, size_t size, const char *format, va_list ap);

#endif

#ifndef TRUE
#define TRUE 1
#endif

#ifndef FALSE
#define FALSE 0
#endif

#ifndef MIN
#define MIN(x, y) ((x) < (y) ? (x) : (y))
#endif

#ifndef MAX
#define MAX(x, y) ((x) > (y) ? (x) : (y))
#endif

/*
 * The following three functions return newly allocated memory.
 * You must free it yourself
 */
char *y_string_append(char *str, char *append);
char *y_str_to_utf8(const char *in);
char *y_utf8_to_str(const char *in);

#endif
itespace */ .highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */ .highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ .highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
/****************************************************************\
*                                                                *
*  bitlbeed.c                                                    *
*                                                                *
*  A tiny daemon to allow you to run The Bee as a non-root user  *
*  (without access to /etc/inetd.conf or whatever)               *
*                                                                *
*  Copyright 2002-2004 Wilmer van der Gaast <lintux@debian.org>  *
*                                                                *
*  Licensed under the GNU General Public License                 *
*                                                                *
*  Modified by M. Dennis, 20040627                               *
\****************************************************************/

/* 
   ChangeLog:
   
   2004-06-27: Added support for AF_LOCAL (UNIX domain) sockets
               Renamed log to do_log to fix conflict warning
               Changed protocol to 0 (6 is not supported?)
               Added error check for socket()
               Added a no-fork (debug) mode
   2004-05-15: Added rate limiting
   2003-12-26: Added the SO_REUSEADDR sockopt, logging and CPU-time limiting
               for clients using setrlimit(), fixed the execv() call
   2002-11-29: Added the timeout so old child processes clean up faster
   2002-11-28: First version
*/

#define SELECT_TIMEOUT 2
#define MAX_LOG_LEN 128

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <stdarg.h>
#include <time.h>

#include <sys/wait.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/resource.h>
#include <sys/stat.h>

#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>

typedef struct settings
{
	char local;
	char debug;
	char *interface;
	signed int port;
	
	unsigned char max_conn;
	int seconds;
	
	int rate_seconds;
	int rate_times;
	int rate_ignore;
	
	char **call;
} settings_t;

typedef struct ipstats
{
	unsigned int ip;
	
	time_t rate_start;
	int rate_times;
	time_t rate_ignore;
	
	struct ipstats *next;
} ipstats_t;

FILE *logfile;
ipstats_t *ipstats;

settings_t *set_load( int argc, char *argv[] );
void do_log( char *fmt, ... );
ipstats_t *ip_get( char *ip_txt );

int main( int argc, char *argv[] )
{
	const int rebind_on = 1;
	settings_t *set;
	
	int serv_fd, serv_len;
	struct sockaddr_in serv_addr;
	struct sockaddr_un local_addr;
	
	pid_t st;
	
	if( !( set = set_load( argc, argv ) ) )
		return( 1 );
	
	if( !logfile )
		if( !( logfile = fopen( "/dev/null", "w" ) ) )
		{
			perror( "fopen" );
			return( 1 );
		}
	
	fcntl( fileno( logfile ), F_SETFD, FD_CLOEXEC );
	
	if( set->local )
		serv_fd = socket( PF_LOCAL, SOCK_STREAM, 0 );
	else
		serv_fd = socket( PF_INET, SOCK_STREAM, 0 );
	if( serv_fd < 0 )
	{
		perror( "socket" );
		return( 1 );
	}
	setsockopt( serv_fd, SOL_SOCKET, SO_REUSEADDR, &rebind_on, sizeof( rebind_on ) );
	fcntl( serv_fd, F_SETFD, FD_CLOEXEC );
	if (set->local) {
		local_addr.sun_family = AF_LOCAL;
		strncpy( local_addr.sun_path, set->interface, sizeof( local_addr.sun_path ) - 1 );
		local_addr.sun_path[sizeof( local_addr.sun_path ) - 1] = '\0';
		
		/* warning - don't let untrusted users run this program if it
		   is setuid/setgid! Arbitrary file deletion risk! */
		unlink( set->interface );
		if( bind( serv_fd, (struct sockaddr *) &local_addr, SUN_LEN( &local_addr ) ) != 0 )
		{
			perror( "bind" );
			return( 1 );
		}
		chmod( set->interface, S_IRWXO|S_IRWXG|S_IRWXU );

	} else {
		serv_addr.sin_family = AF_INET;
		serv_addr.sin_addr.s_addr = inet_addr( set->interface );
		serv_addr.sin_port = htons( set->port );
		serv_len = sizeof( serv_addr );
	
		if( bind( serv_fd, (struct sockaddr *) &serv_addr, serv_len ) != 0 )
		{
			perror( "bind" );
			return( 1 );
		}
	}
	
	if( listen( serv_fd, set->max_conn ) != 0 )
	{
		perror( "listen" );
		return( 1 );
	}
	
	if ( ! set->debug ) {
		st = fork();
		if( st < 0 )
		{
			perror( "fork" );
			return( 1 );
		}
		else if( st > 0 )
		{
			return( 0 );
		}
		
		setsid();
		close( 0 );
		close( 1 );
		close( 2 );
	}
	
	do_log( "bitlbeed running" );
	
	/* The Daemon */
	while( 1 )
	{
		int cli_fd, cli_len, i, st;
		struct sockaddr_in cli_addr;
		struct sockaddr_un cli_local;
		ipstats_t *ip;
		char *cli_txt;
		pid_t child;
		
		static int running = 0;
		
		fd_set rd;
		struct timeval tm;
		
		/* accept() only returns after someone connects. To clean up old
		   processes (by running waitpid()) it's better to use select()
		   with a timeout. */
		FD_ZERO( &rd );
		FD_SET( serv_fd, &rd );
		tm.tv_sec = SELECT_TIMEOUT;
		tm.tv_usec = 0;
		if( select( serv_fd + 1, &rd, NULL, NULL, &tm ) > 0 )
		{
			if (set->local) {
				cli_len = SUN_LEN( &cli_local );
				cli_fd = accept( serv_fd, (struct sockaddr *) &cli_local, &cli_len );
				cli_txt = "127.0.0.1";
			} else {
				cli_len = sizeof( cli_addr );
				cli_fd = accept( serv_fd, (struct sockaddr *) &cli_addr, &cli_len );
				cli_txt = inet_ntoa( cli_addr.sin_addr );
			}
			
			ip = ip_get( cli_txt );
			
			if( set->rate_times == 0 || time( NULL ) > ip->rate_ignore )
			{
				/* We want this socket on stdout and stderr too! */
				dup( cli_fd ); dup( cli_fd );
				
				if( ( child = fork() ) == 0 )
				{
					if( set->seconds )
					{
						struct rlimit li;
						
						li.rlim_cur = (rlim_t) set->seconds;
						li.rlim_max = (rlim_t) set->seconds + 1;
						setrlimit( RLIMIT_CPU, &li );
					}
					execv( set->call[0], set->call );
					do_log( "Error while executing %s!", set->call[0] );
					return( 1 );
				}
				
				running ++;
				close( 0 );
				close( 1 );
				close( 2 );
				
				do_log( "Started child process for client %s (PID=%d), got %d clients now", cli_txt, child, running );
				
				if( time( NULL ) < ( ip->rate_start + set->rate_seconds ) )
				{
					ip->rate_times ++;
					if( ip->rate_times >= set->rate_times )
					{
						do_log( "Client %s crossed the limit; ignoring for the next %d seconds", cli_txt, set->rate_ignore );
						ip->rate_ignore = time( NULL ) + set->rate_ignore;
						ip->rate_start = 0;
					}
				}
				else
				{
					ip->rate_start = time( NULL );
					ip->rate_times = 1;
				}
			}
			else
			{
				do_log( "Ignoring connection from %s", cli_txt );
				close( cli_fd );
			}
		}
		
		/* If the max. number of connection is reached, don't accept
		   new connections until one expires -> Not always WNOHANG
		   
		   Cleaning up child processes is a good idea anyway... :-) */
		while( ( i = waitpid( 0, &st, ( ( running < set->max_conn ) || ( set->max_conn == 0 ) ) ? WNOHANG : 0 ) ) > 0 )
		{
			running --;
			if( WIFEXITED( st ) )
			{
				do_log( "Child process (PID=%d) exited normally with status %d. %d Clients left now",
				     i, WEXITSTATUS( st ), running );
			}
			else if( WIFSIGNALED( st ) )
			{
				do_log( "Child process (PID=%d) killed by signal %d. %d Clients left now",
				     i, WTERMSIG( st ), running );
			}
			else
			{
				/* Should not happen AFAIK... */
				do_log( "Child process (PID=%d) stopped for unknown reason, %d clients left now",
				     i, running );
			}
		}
	}
	
	return( 0 );
}

settings_t *set_load( int argc, char *argv[] )
{
	settings_t *set;
	int opt, i;
	
	set = malloc( sizeof( settings_t ) );
	memset( set, 0, sizeof( settings_t ) );
	set->interface = NULL;		/* will be filled in later */
	set->port = 6667;
	set->local = 0;
	set->debug = 0;
	
	set->rate_seconds = 600;
	set->rate_times = 5;
	set->rate_ignore = 900;
	
	while( ( opt = getopt( argc, argv, "i:p:n:t:l:r:hud" ) ) >= 0 )
	{
		if( opt == 'i' )
		{
			set->interface = strdup( optarg );
		}
		else if( opt == 'p' )
		{
			if( ( sscanf( optarg, "%d", &i ) != 1 ) || ( i <= 0 ) || ( i > 65535 ) )
			{
				fprintf( stderr, "Invalid port number: %s\n", optarg );
				return( NULL );
			}
			set->port = i;
		}
		else if( opt == 'n' )
		{
			if( ( sscanf( optarg, "%d", &i ) != 1 ) || ( i < 0 ) )
			{
				fprintf( stderr, "Invalid number of connections: %s\n", optarg );
				return( NULL );
			}
			set->max_conn = i;
		}
		else if( opt == 't' )
		{
			if( ( sscanf( optarg, "%d", &i ) != 1 ) || ( i < 0 ) || ( i > 600 ) )
			{
				fprintf( stderr, "Invalid number of seconds: %s\n", optarg );
				return( NULL );
			}
			set->seconds = i;
		}
		else if( opt == 'l' )
		{
			if( !( logfile = fopen( optarg, "a" ) ) )
			{
				perror( "fopen" );
				fprintf( stderr, "Error opening logfile, giving up.\n" );
				return( NULL );
			}
			setbuf( logfile, NULL );
		}
		else if( opt == 'r' )
		{
			if( sscanf( optarg, "%d,%d,%d", &set->rate_seconds, &set->rate_times, &set->rate_ignore ) != 3 )
			{
				fprintf( stderr, "Invalid argument to -r.\n" );
				return( NULL );
			}
		}
		else if( opt == 'u' )
			set->local = 1;
		else if( opt == 'd' )
			set->debug = 1;
		else if( opt == 'h' )
		{
			printf( "Usage: %s [-i <interface>] [-p <port>] [-n <num>] [-r x,y,z] ...\n"
			        "          ... <command> <args...>\n"
			        "A simple inetd-like daemon to have a program listening on a TCP socket without\n"
			        "needing root access to the machine\n"
			        "\n"
			        "  -i  Specify the interface (by IP address) to listen on.\n"
			        "      (Default: 0.0.0.0 (any interface))\n"
			        "  -p  Port number to listen on. (Default: 6667)\n"
			        "  -n  Maximum number of connections. (Default: 0 (unlimited))\n"
			        "  -t  Specify the maximum number of CPU seconds per process.\n"
			        "      (Default: 0 (unlimited))\n"
			        "  -l  Specify a logfile. (Default: none)\n"
			        "  -r  Rate limiting: Ignore a host for z seconds when it connects for more\n"
			        "      than y times in x seconds. (Default: 600,5,900. Disable: 0,0,0)\n"
				"  -u  Use a local socket, by default /tmp/bitlbee (override with -i <filename>)\n"
				"  -d  Don't fork for listening (for debugging purposes)\n"
			        "  -h  This information\n", argv[0] );
			return( NULL );
		}
	}
	
	if( set->interface == NULL )
		set->interface = (set->local) ? "/tmp/bitlbee" : "0.0.0.0";
	
	if( optind == argc )
	{
		fprintf( stderr, "Missing program parameter!\n" );
		return( NULL );
	}
	
	/* The remaining arguments are the executable and its arguments */
	set->call = malloc( ( argc - optind + 1 ) * sizeof( char* ) );
	memcpy( set->call, argv + optind, sizeof( char* ) * ( argc - optind ) );
	set->call[argc-optind] = NULL;
	
	return( set );
}

void do_log( char *fmt, ... )
{
	va_list params;
	char line[MAX_LOG_LEN];
	time_t tm;
	int l;
	
	memset( line, 0, MAX_LOG_LEN );
	
	tm = time( NULL );
	strcpy( line, ctime( &tm ) );
	l = strlen( line );
	line[l-1] = ' ';
	
	va_start( params, fmt );
	vsnprintf( line + l, MAX_LOG_LEN - l - 2, fmt, params );
	va_end( params );
	strcat( line, "\n" );
	
	fprintf( logfile, "%s", line );
}

ipstats_t *ip_get( char *ip_txt )
{
	unsigned int ip;
	ipstats_t *l;
	int p[4];
	
	sscanf( ip_txt, "%d.%d.%d.%d", p + 0, p + 1, p + 2, p + 3 );
	ip = ( p[0] << 24 ) | ( p[1] << 16 ) | ( p[2] << 8 ) | ( p[3] );
	
	for( l = ipstats; l; l = l->next )
	{
		if( l->ip == ip )
			return( l );
	}
	
	if( ipstats )
	{
		for( l = ipstats; l->next; l = l->next );
		
		l->next = malloc( sizeof( ipstats_t ) );
		l = l->next;
	}
	else
	{
		l = malloc( sizeof( ipstats_t ) );
		ipstats = l;
	}
	memset( l, 0, sizeof( ipstats_t ) );
	
	l->ip = ip;
	
	return( l );
}