diff options
author | Wilmer van der Gaast <wilmer@gaast.net> | 2005-11-06 19:23:18 +0100 |
---|---|---|
committer | Wilmer van der Gaast <wilmer@gaast.net> | 2005-11-06 19:23:18 +0100 |
commit | b7d3cc34f68dab7b8f7d0777711317b334fc2219 (patch) | |
tree | 6aa4d6332c96654fda79fe18993ab0e35d36a52b /utils |
Initial repository (0.99 release tree)0.99
Diffstat (limited to 'utils')
-rw-r--r-- | utils/README | 58 | ||||
-rw-r--r-- | utils/bitlbeed.c | 455 | ||||
-rwxr-xr-x | utils/centericq2bitlbee.sh | 115 | ||||
-rw-r--r-- | utils/convert_gnomeicu.txt | 7 | ||||
-rwxr-xr-x | utils/create_nicksfile.pl | 210 |
5 files changed, 845 insertions, 0 deletions
diff --git a/utils/README b/utils/README new file mode 100644 index 00000000..92b0348b --- /dev/null +++ b/utils/README @@ -0,0 +1,58 @@ +This directory contains tiny additional programs which you might just like +or need to run BitlBee: + + +* bitlbeed.c + +If you want to run BitlBee on a machine you don't have root access to, this +utility will help you. Compiling it is easy: 'gcc bitlbeed.c -o bitlbeed', +you don't need any special flags. Use 'bitlbeed -h' to get more help. + +For example, 'bitlbeed -p6669 -n1 /home/wilmer/bin/bitlbee' will start +listening on TCP port 6669 (on any interface, you might not want that!) +and connect the specified BitlBee program to this socket as soon as +someone connects. The -n1 makes sure only one person can be connected +at once. + +Of course this program can be used for other programs too, not just BitlBee. + + +* create_nicksfile.pl (Christian Friedl <christian.friedl@chello.at>) + +This program reads your ~/.licq/ configuration data and convert it to a +correct .nicks file. This program can be extended to read other contact +list file formats as well. + + +* centericq2bitlbee.sh (geno <geno@xenyon.com>) + +Converter script for CenterICQ ICQ contact lists. See the documentation +for more information. + + +* convert_gnomeicu.txt + +Not a program, but this one contains a regex which should correctly +convert GnomeICU configuration files into the BitlBee format. + + +* Dynamic MOTD for BitlBee (Geert Hauwaerts <geert@hauwaerts.be>) + +Originally, I wanted to put this program here, but Geert put it online +on his own server, with docs and stuff, so I guess it's better to put +a link here. dmotd is a little script which generates a motd with some +nice statistics, especially nice for servers with many people on it. + +See http://dmotd.hauwaerts.be/ for more information. + + +* BitlBee-specific Irssi scripts for: tab completion, typing notifica- +tions, auto-away and more, by Tijmen Ruizendaal <tijmen@fokdat.nl>. + +There are too many scripts to include them all with BitlBee (and keep +them up-to-date), so you should get them from Tijmen's site: + +http://fokdat.nl/~tijmen/software/irssi-bitlbee.html + + +Please do send your sources if you write anything useful for the Bee! diff --git a/utils/bitlbeed.c b/utils/bitlbeed.c new file mode 100644 index 00000000..b8db348e --- /dev/null +++ b/utils/bitlbeed.c @@ -0,0 +1,455 @@ +/****************************************************************\ +* * +* 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 ); +} diff --git a/utils/centericq2bitlbee.sh b/utils/centericq2bitlbee.sh new file mode 100755 index 00000000..b8c134e8 --- /dev/null +++ b/utils/centericq2bitlbee.sh @@ -0,0 +1,115 @@ +#!/bin/bash +# +# Author geno, <geno@xenyon.com> +# Date 2004-04-24 +# Version 0.1c +# + +show_help() +{ +cat << _EOF_ + +This script converts your CenterICQ contacts (AIM/ICQ) to BitlBee's contacts. +The use of this script is on you own risk. You agree by using this script. :-) + +SYNTAX: `basename $0` <protoname> [<add_proto_tag>] + + protoname - Choose the protocol you want to get your contacts from + by using "aim" or "icq" here. + + add_proto_tag - This is optional and adds a suffix to each nickname. + For an AIM contact it will look like this: geno|aim + For an ICQ contact it will be |icq , WOW! :-D + To enable this option use "on". + +NOTE: + After the conversion of one protocol is done you will find a file + called bitlbee_[protoname] in ~/.centericq . Append the content of + this file to /var/lib/bitlbee/[username].nicks . + + [username] is your username you use to talk to the BitlBee Server. + You will have to be root to edit this file! + +CREDITS: + This script was written by geno (geno@xenyon.com). + I hope it will help you to make the switch to BitlBee a bit easier. :-) + +_EOF_ +exit 0 +} + +case $1 in + "") show_help ;; + "icq") + nick_protocol="[1-9]*/" + protocol_const="3" + ;; + + "aim") + nick_protocol="a*/" + protocol_const="1" + ;; + + *) show_help ;; +esac + +# can we see CenterICQ's directory ? +if [ ! -d ~/.centericq ]; then + echo "The directory of CenterICQ (~/.centericq) was not found!" + echo "Maybe you are logged in with the wrong username." + exit 1 +fi + +# change to the center of all evil ;) +cd ~/.centericq + +# get the listing of all nicks +nick_listing=`ls -d $nick_protocol | sed 's/\ /_DuMmY_/g' | sed 's/\/_DuMmY_/\/ /g'` + +echo -e "\nConverting ...\n" + +# remove old conversion +rm -f ~/.centericq/bitlbee_$1 + +for nick_accountname in $nick_listing; do + # get rid of the slash and replace _DuMmY_ with space + nick_accountname=`echo "$nick_accountname" | sed 's/\/$//' | sed 's/_DuMmY_/\ /g'` + + # find centericq alias + nick_cicq_alias=`cat "$nick_accountname/info" | sed '46!d'` + + # if the centericq alias is the same as the account's name then + # it's not a real alias; search for account nickname + if [ "$nick_accountname" == "$nick_cicq_alias" ]; then + nick_accountalias=`cat "$nick_accountname/info" | sed '1!d'` + fi + + # save the best nickname for conversion + if [ "x$nick_accountalias" == "x" ]; then + nick="$nick_cicq_alias" + else + nick="$nick_accountalias" + fi + + # cut off the prefix 'a' of the accountname + if [ "$1" == "aim" ]; then + nick_accountname=`echo "$nick_accountname" | sed 's/^a//'` + fi + + # replace each space with an underscore (spaces are not allowed in irc nicknames) + nick=`echo "$nick" | sed 's/\ /_/g'` + + # if tags are wanted we will add them here + if [ "$2" == "on" ]; then + nick=`echo "$nick"\|$1` + fi + + # print output to std + echo "Found '$nick_accountname' with alias '$nick'" + # save output to file + echo "$nick_accountname" $protocol_const "$nick" >> ~/.centericq/bitlbee_$1 +done + +echo -e "\nYou can find this list as a file in ~/.centericq/bitlbee_$1." +echo -e "See help if you don't know what you have to do next.\n" + diff --git a/utils/convert_gnomeicu.txt b/utils/convert_gnomeicu.txt new file mode 100644 index 00000000..e2bd1377 --- /dev/null +++ b/utils/convert_gnomeicu.txt @@ -0,0 +1,7 @@ +15:03:38 zoo| wilmer: watch this: +15:03:40 zoo| cat ~/.icq/contacts.xml | sed "s/<\/user>/\n/g" | + sed "s/^.*<uin>//g" | sed "s/<\/nick>//" | sed "s/ /_/g" | + sed "s/<\/uin><nick>/ 3 /g" | grep -v -e "^<" +15:04:23 zoo| it does output your gnomeicu nicks to stdout + +Thanks to Claas Langbehn. Use this at your own risk, it's not tested by us. diff --git a/utils/create_nicksfile.pl b/utils/create_nicksfile.pl new file mode 100755 index 00000000..abd6d3d2 --- /dev/null +++ b/utils/create_nicksfile.pl @@ -0,0 +1,210 @@ +#!/usr/bin/perl +use strict; +use Getopt::Long; + + +my $conn = undef; +my %readline_funcs = ( 'licq' => \&import_readline_licq ); +my %open_funcs = ( 'licq' => \&import_open_licq ); +my %close_funcs = ( 'licq' => \&import_close_licq ); +my $funcname = undef; +my $dirname = undef; +my $filename = undef; +my $imported = 0; +my $not_imported = 0; +my $debug = 0; + +main(); +exit(0); + + +sub main { + my ($server,$port,$nick,$pass,$func,$dir,$file,$outfile); + my $dirfile; + if (($dirfile=pop @ARGV) =~ /^\-/ || !$dirfile) { + tell_usage(); + exit(0); + } + if ($dirfile =~ m|/|) { + $dirfile =~ m|^(.*)(/.+)$|; + $dir=$1; + $file=$2; + } else { + $dir=undef; + $file = $dirfile; + } + GetOptions( + 'from=s', => \$func, + 'of=s', => \$outfile, + 'debug', => \$debug + ); + if (!import_start($func,$dir,$file,$outfile,$debug)) { + tell_usage(); + } +} + +sub tell_usage { + print "Usage: create_nicksfile.pl [--from=FROM] [--of=OUTPUTFILE] [--debug] FILENAME\n"; + print " FROM defines which application we import from.\n", + print " Note that currently the only valid value for FROM is licq.\n"; + print " For further information, you might want to do perldoc create_nicksfile.pl\n"; +} + +sub import_start { + $funcname = (shift) || 'licq'; + $dirname = shift; + $filename = shift; + my $outfile = shift || 'bitlbee.nicks'; + $debug = shift; + my ($alias,$protocol,$name,$found); + open(OUT,'>'.$outfile) || die "unable to open $outfile"; + if (defined $open_funcs{$funcname}) { + if (&{$open_funcs{$funcname}}($dirname,$filename)) { + do { + ($alias,$protocol,$name,$found)=&{$readline_funcs{$funcname}}(); + print OUT "$alias $protocol $name\n" if $found; + } while ($found); + } else { + import_err('Unable to open '.$filename); + return 0; + } + } else { + import_err($funcname.' is no defined import function.'); + return 0; + } + close OUT; + &{$close_funcs{$funcname}}(); + return 1; +} + +sub import_err { + my $msg=shift; + print "\nError: $msg\n"; +} + +sub import_open_licq { + my ($dir,$name)=@_; + return open(IN,'<'.$dir.'/users.conf'); +} +sub import_close_licq { + close IN; +} +sub import_readline_licq { + my ($uin,$alias); + my $line; +GETLINE: + $line=<IN>; + if ($line) { + while ($line && $line !~ /^User\d+/) { + $line=<IN>; + } + if ($line) { + if ($line =~ /^User\d+\s*=\s*(\d+)(\.Licq)?$/) { # getting UIN + $uin=$1; + open(ALIAS,'<'.$dirname.'/users/'.$uin.'.Licq') || + open(ALIAS,'<'.$dirname.'/users/'.$uin.'.uin') || do { + warn "unable to open userfile for $uin"; + return (undef,undef,0); + }; + while (<ALIAS>) { + if (/^Alias\s*=\s*(.*)$/) { + $alias=$1; + $alias =~ s/\s+/_/g; + last; + } + } + close ALIAS; + $imported++; + return ($uin,3,$alias,1); + } else { + warn('Unknown line format: '.$line); + $not_imported++; + goto GETLINE; #### grrrr, sometimes there are negative uins in licq files... + } + } else { + return (undef,undef,0); + } + } else { + return undef; + } +} + +__END__ + +=head1 NAME + +create_nicksfile.pl - Create a valid bitlbee .nicks file + +=head1 SYNOPSIS + +create_nicksfile.pl [--from=FROM] [--of=OUTPUTFILE] [--debug] FILENAME + + FROM defines which application we import from. + Note that currently the only valid value for FROM + is licq. + + If of is missing, we write to bitlbee.nicks. + +=head1 DESCRIPTION + +We run thru the +files where the contacts reside and create +a bitlbee .nicks-file from them. + +=head1 DEPENDENCIES + +On the perlside, we need Getopt::Long. + +=head1 CAVEATS + +=head1 TODO + +&import_readline_... should take a filehandle as argument. + +Add more import functions. If you are interested, +to do so, you need to write the following functions: + +=over + +=item * + +import_open_<WHATEVER>(DIR,FILENAME) + +=item * + +import_close_<WHATEVER>() + +=item * + +import_readline_<WHATEVER>() + +=back + +and add them to the hashes + +=over + +=item * + +%readline_funcs + +=item * + +%open_funcs + +=item * + +%close_funcs + +=back + +at the top of this script. + + +=head1 AUTHORS + +Christian Friedl <vijeno@chello.at> + +Updated for the new Licq list firmat by Hugo Buddelmeijer <kmail@hugo.doemaarwat.nl> + +=cut |