char *Version =
"tircproxy v0.4.5, Copyleft 2000, Bjarni R. Einarsson <bre@netverjar.is>" ;
/*****************************************************************************\
*
* This is an IRC proxy server. It can be started from inetd to service
* IRC requests, or run as a stand-alone daemon. It can operate with
* or without the Linux kernel's transparent proxy feature (or the IPF
* filters for *BSD systems). DCC requests are transparently accepted,
* rewritten and proxied for the clients.
*
* Thanks go to the author of transproxy.c, this program is based on his
* code - and without it I would have written a much messier program or
* none at all. This code is distributed under the GPL.
*
* See the files README, CHANGELOG.txt and BUGS.txt for more info!
* Don't forget to edit tircproxy.h to suit your tastes!
*
* ************************************************************************* *
*
* Note to hackers, porters, etc.
*
* Please, please let me know if you find bugs in my code!
*
\*****************************************************************************/
/* The following is an example of a useful broadcast file.. the format for
* /etc/motd.irc is the same (raw IRC server output, that is).
*
* Keep things short, people have flood protection out there.. and others
* are "suppressing" the MOTD!
:admin@isp.net 999 * :This is a fake server message!
:bofh!admin@isp.net PRIVMSG #notice :The proxy server is about to be shut down!
:bofh!admin@isp.net PRIVMSG #notice :Be very afraid!!!
*/
#include <config.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/types.h>
#include <syslog.h>
#include <signal.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <errno.h>
#include <tircproxy.h>
/* "Inheritance" of config options.
*/
#ifdef PARANOID
# ifndef MIRC_DCC_KLUDGE
# define MIRC_DCC_KLUDGE 1
# endif
# ifndef NICK_LOG
# define NICK_LOG
# endif
#endif
#ifdef MIRC_DCC_KLUDGE
# warning MIRC DCC kludge active
#endif
#ifdef NICK_LOG
# warning Nickname logging active
#endif
#ifdef CDIR
# ifndef IP_TO_UID_PREFIX
# define IP_TO_UID_PREFIX CDIR CDIR_MAP "-"
# endif
#endif
/* Autoconf stuff
*/
#ifdef HAVE_CRYPT_H
# include <crypt.h>
#endif
#if (HAVE_UDB_H && USE_UDB)
# warning Using UDB style IPC for identd etc.
# include <udb.h>
# undef CDIR
# define CDIR 1
# undef CDIR_IDENT
# define CDIR_IDENT 1
# undef IP_TO_UID_PREFIX
#else
# undef USE_UDB
#endif
#ifndef INADDR_NONE
# define INADDR_NONE -1
#endif
#if !HAVE_LIBWRAP
# undef TCP_WRAPPERS
# warning Configuration via /etc/hosts.* not available
#else
# warning Configuration via /etc/hosts.* available
#endif
#ifndef IPF
# define IPF 0
#endif
#undef TRANS
#if IPF
# if HAVE_NETINET_IP_NAT_H
# include <sys/ioctl.h>
# include <net/if.h>
# include <netinet/in_systm.h>
# include <netinet/tcp.h>
# if HAVE_NETINET_IP_FIL_COMPAT_H
# include <netinet/ip_fil_compat.h>
# else
# include <netinet/ip_compat.h>
# endif
# include <netinet/ip_fil.h>
# include <netinet/ip_nat.h>
# include <netinet/ip_state.h>
# include <netinet/ip_proxy.h>
# include <netinet/ip_nat.h>
# include <netinet/ipl.h>
# include <osreldate.h>
# define TRANS 1
# ifndef IPL_NAT
# define IPL_NAT IPNAT_NAME
# endif
# warning IPF transparent proxying available
# else
# undef IPF
# define IPF 0
# warning IPF support not available
# endif
#endif
#ifndef LINUX
# define LINUX 0
#endif
#if LINUX
# ifndef HAVE_LINUX
# undef LINUX
# define LINUX 0
# warning Linux support not available
# else
# warning Linux transparent proxying available (depends on kernel)
# define TRANS 1
# endif
#endif
#if QUIZ_MODE
# warning Quiz mode available
#else
# warning Quiz mode not available
#endif
/* Some useful definitions, which we might get from the system.
*/
#ifndef MAXLOGNAME
# define MAXLOGNAME 64
#endif
#ifndef PATH_MAX
# define PATH_MAX 256
#endif
/* Do we want to use the TCP wrappers for access control?
*/
#ifdef TCP_WRAPPERS
#include "tcpd.h"
int allow_severity = 0;
int deny_severity = 0;
int hosts_ctl(char *daemon,
char *client_name,
char *client_addr,
char *client_user);
#endif
/* Typedefs.
*/
typedef unsigned long ipaddr_t;
/* Macros & definitions.
*/
#define FD_MAX(a,b) ((a) > (b) ? (a) : (b))
#define MINUTE 60
#define DEBUG_FEATURES 1
#define DEBUG_BASIC 2
#define DEBUG_TRIVIA 8
#define DEBUG_NOFORK 9
/* Function prototypes.
*/
static void usage (char *prog, char *opt);
static short get_port_num (char *portnum);
static ipaddr_t get_ip_addr (char *ipaddr);
static uid_t get_user_id (char *user);
static uid_t get_group_id (uid_t uid);
static int bind_to_port (ipaddr_t bind_ip, short bind_port,
int backlog, int dlev);
static int connect_to_server (struct sockaddr_in addr);
static void lookup_hostname (struct sockaddr_in *addr, char *hostname,
int hostlen, int needed);
static void server_main_loop (int sock);
static void trans_proxy (int sock, struct sockaddr_in *addr);
static int copy_loop (int sock, struct sockaddr_in *from_addr,
struct sockaddr_in *to_addr,
int is_server_connection);
static int copy_once (int rsock, char *rbuffer, int rbufsize,
char *rname,
int wsock, char *wlbuffer, int *wlbufpos,
char *wname,
int is_server_connection, int r_is_client,
int *read_len);
static int filtered_write (int to_sock, char *buff, int blen,
char *leftover, int *llen,
int isclient);
static int scan_line (char *line, int isclient);
static int dcc_mangle_filename (char *filename);
static int dcc_resume (int destport, int resume);
static char *proxy_dcc (int destaddr, int destport, int incoming);
static void get_user_name (struct sockaddr_in *addr);
static void change_uid (void);
#ifdef CDIR
static void tell_identd (struct sockaddr_in *, struct sockaddr_in *);
static void cleanup_identd (void);
#endif
static void broadcast (int sock, const char *filename);
static void alarm_signal (int sig);
static void hup_signal (int sig);
static void chld_signal (int sig);
static void debug_msg (int lev, int pri, const char *format, ...);
#ifdef QUIZ_MODE
static void quiz_delay_line (char *line, char **first, char ***bl);
static void quiz_dump_lines (int sock, char **first, char ***bl);
static void quiz_check_auth (char *pass);
static void quiz_msg (char *message);
static void quiz_greet (void);
#endif
/* Taken from the RFC 1459 ..
*/
#define LENGTH_NICKNAME 9
#define LENGTH_SERVERDATA 512
/* These make useful global variables..
*/
char user_nick[LENGTH_NICKNAME * 2];
char user_name[MAXLOGNAME];
char user_ident_file[PATH_MAX];
char server_tag[64], alarm_in[64];
ipaddr_t clients_ip = INADDR_NONE;
char clients_ip_s[64];
int motd_is_done = 0;
/* We need to remember some stuff to support DCC RESUME.
** See: http://www.mirc.co.uk/help/dccresum.txt
*/
#define MAX_DCC_SESSIONS 10
time_t dcc_session_time [MAX_DCC_SESSIONS];
int dcc_original_port [MAX_DCC_SESSIONS];
int dcc_proxied_port [MAX_DCC_SESSIONS];
/* Quid mode - require a password or an answer to some silly
** question before sending the users' output to the server.
*/
#ifdef QUIZ_MODE
# define QUIZ_OFF 0
# define QUIZ_ON 1
# define QUIZ_READY 2
# define QUIZ_FLUSH 3
# define QUIZ_S &from_cli_first, &from_cli_last
# define QUIZ_C &to_cli_first, &to_cli_last
char *from_cli_first = NULL;
char **from_cli_last = NULL;
char *to_cli_first = NULL;
char **to_cli_last = NULL;
char quizfile[PATH_MAX], quiz[512];
int use_unix_passwd = QUIZ_OFF;
int use_quiz_mode = QUIZ_OFF;
#endif
/* Configuration variables..
*/
ipaddr_t bind_ip = INADDR_ANY;
ipaddr_t server_ip = INADDR_NONE;
ipaddr_t visible_ip_i = INADDR_ANY;
char visible_ip_i_s[64];
ipaddr_t visible_ip_o = INADDR_ANY;
char visible_ip_o_s[64];
int throttle_seconds = -1;
int allow_dcc_send = 1;
int allow_dcc_chat = 1;
int allow_dcc_unknown = 1;
short server_port = -1;
int broadcast_flag = 0;
int not_an_irc_proxy = 0;
int use_dcc_mangling = 1;
int use_syslog = 1;
int debug_level = 0;
int use_anonymity = 0;
int anon_notval = 0;
#ifdef PARANOID
int use_paranoid = 1;
#endif
#ifdef MIRC_DCC_KLUDGE
int use_mirc_dcc_kludge = 1;
#endif
#ifdef NICK_LOG
int use_nick_log = 1;
#endif
#ifdef TCP_WRAPPERS
int use_tcp_wrappers = 1;
#endif
#ifdef CDIR
int use_cdir = 1;
#endif
#ifdef USE_UDB
struct udb_connection conn;
#endif
/* Main!
*/
int main(int argc, char **argv)
{
int arg;
int run_as_server = 0;
short bind_port = -1;
uid_t run_uid = 0;
gid_t run_gid = 0;
int sock;
struct sockaddr_in addr;
int len;
/* Parse the command line arguments.
*/
while ((arg = getopt(argc, argv, "ab:d:h?i:o:pq:r:s:t:CDHIKLMNOQRSU")) != EOF)
{
switch (arg)
{
case 'a':
use_anonymity = 1;
break;
case 'b':
bind_ip = get_ip_addr(optarg);
break;
case 'd':
#ifdef TIRC_DEBUG
debug_level = atoi(optarg);
#else
usage(argv[0],"Feature DEBUG not available.");
#endif
break;
case 'h':
case '?':
usage(argv[0], NULL);
break;
case 'i':
visible_ip_i = get_ip_addr(optarg);
break;
case 'o':
visible_ip_o = get_ip_addr(optarg);
break;
case 'p':
#ifdef QUIZ_MODE
use_quiz_mode = use_unix_passwd = QUIZ_ON;
if (getuid()) fprintf(stderr,
"Warning: Not running as root, may not be able to check passwords!\n");
#else
usage(argv[0],"Feature QUIZ_MODE not active.");
#endif
break;
case 'q':
#ifdef QUIZ_MODE
use_quiz_mode = QUIZ_ON;
strncpy(quizfile, optarg, PATH_MAX);
quizfile[PATH_MAX-1] = '\0';
#else
usage(argv[0],"Feature QUIZ_MODE not active.");
#endif
break;
case 'r':
run_uid = get_user_id(optarg);
run_gid = get_group_id(run_uid);
break;
case 's':
run_as_server = 1;
bind_port = get_port_num(optarg);
break;
case 't':
throttle_seconds = atoi(optarg);
break;
case 'C':
allow_dcc_chat = 0;
break;
case 'D':
#ifdef NICK_LOG
use_nick_log = 0;
#else
usage(argv[0],"Feature NICK_LOG not active.");
#endif
break;
case 'H':
#ifdef TCP_WRAPPERS
use_tcp_wrappers = 0;
#else
usage(argv[0],"Feature TCP_WRAPPERS not active.");
#endif
break;
case 'I':
case 'O': /* backwards compatibility ... */
#ifdef CDIR
use_cdir = 0;
#else
usage(argv[0],"Neither CDIR nor USE_UDB compiled in, sorry.");
#endif
break;
case 'K':
#ifdef MIRC_DCC_KLUDGE
use_mirc_dcc_kludge = 0;
#else
usage(argv[0],"Feature MIRC_DCC_KLUDGE not active.");
#endif
break;
case 'L':
use_syslog = 0;
break;
case 'M':
use_dcc_mangling = 0;
break;
case 'N':
not_an_irc_proxy = 1;
break;
case 'R':
#ifdef PARANOID
use_paranoid = 0;
#else
usage(argv[0],"Feature PARANOID not active.");
#endif
break;
case 'S':
allow_dcc_send = 0;
break;
case 'U':
allow_dcc_unknown = 0;
break;
}
}
/* Set a few variables to 'default' values.
*/
if ((bind_ip != INADDR_ANY) && (visible_ip_i == INADDR_ANY))
visible_ip_i = bind_ip;
*user_ident_file = *user_name = *user_nick = '\0';
/* Process the remaining command line arguments.
*/
for (; optind < argc; ++optind)
{
if (server_ip == INADDR_NONE)
{
server_ip = get_ip_addr(argv[optind]);
}
else if (server_port == -1)
{
server_port = get_port_num(argv[optind]);
}
else
{
usage(argv[0], "Extra arguments were specified.");
}
}
if (server_ip == INADDR_NONE)
{
#ifndef TRANS
usage(argv[0], "No remote server specified!");
#else
if ((visible_ip_i == INADDR_ANY) &&
(allow_dcc_send | allow_dcc_chat | allow_dcc_unknown) &&
(!not_an_irc_proxy))
{
usage(argv[0], "No internal address specified, DCC would break!");
}
fprintf(stderr,
"No remote server specified, transparent operation assumed.\n");
#endif
}
else
{
if (server_port == -1)
{
server_port = 6667;
fprintf(stderr,"No remote port specified, defaulting to 6667\n");
}
}
/* Create a "server tag" for this proxy. This is used for preventing
** loops, while allowing proxy-to-proxy operation.
*/
{
char hostname[128];
gethostname(hostname, 127);
/* server_tag is 64 bytes - this is safe */
sprintf(server_tag, "X-tircproxy[%d/%.10s]\n",
getpid(), hostname );
}
#ifdef USE_UDB
/* Aquire handles to the UDB shared memory.
*/
udb_init(UDB_ENV_BASE_KEY);
#endif
/* See if we should run as a server.
*/
if (run_as_server)
{
/* Randomize the anonymouse userid.
*/
if (use_anonymity)
{
srand(time((time_t) NULL));
anon_notval = rand();
}
/* Start by binding to the port, the child inherits this socket.
*/
sock = bind_to_port(bind_ip, bind_port, SOMAXCONN, 0);
if (sock < 0)
{
usage(argv[0], "Failed to bind to port, is another tircproxy still running?");
}
/* Start a server process. When DEBUG is defined, and the
** debug level is high, just run in the foreground.
*/
#ifdef TIRC_DEBUG
switch ((debug_level >= DEBUG_NOFORK) ? 0 : fork())
#else
switch (fork())
#endif
{
case -1:
perror("fork()");
exit(1);
case 0:
/* Open syslog for logging errors.
*/
if ((use_syslog) && (debug_level < DEBUG_NOFORK))
openlog((not_an_irc_proxy) ? "proxy" : "tircproxy",
LOG_PID, LOG_DAEMON);
/* Ignore some signals.
*/
signal(SIGHUP, SIG_IGN);
#ifdef TIRC_DEBUG
if (debug_level < DEBUG_NOFORK)
#else
signal(SIGINT, SIG_IGN);
#endif
signal(SIGQUIT, SIG_IGN);
signal(SIGTSTP, SIG_IGN);
signal(SIGCONT, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
signal(SIGALRM, alarm_signal);
if (run_gid)
{
/* Drop back to an untrusted user.
*/
setgid(run_gid);
setuid(run_uid);
/* Start a new session and group.
*/
setsid();
#ifdef HAVE_LINUX
setpgrp();
#endif
}
/* Handle the server main loop.
*/
server_main_loop(sock);
/* Should never exit.
*/
closelog();
exit(1);
}
/* Parent exits at this stage.
*/
exit(0);
}
/* Open syslog for logging errors.
*/
if ((use_syslog) && (debug_level < DEBUG_NOFORK))
openlog((not_an_irc_proxy) ? "proxy" : "tircproxy",
LOG_PID, LOG_DAEMON);
/* We are running from inetd so find the peer address.
*/
len = sizeof(addr);
if (getpeername(STDIN_FILENO, (struct sockaddr *)&addr, &len) < 0)
{
debug_msg(0, LOG_ERR, "getpeername(): %.256s", strerror(errno));
closelog();
usage(argv[0], NULL);
}
/* Randomize (well, not really..) the anonymouse userid.
*/
if (use_anonymity) anon_notval = (time((time_t) NULL) >> 17);
/* Make note of the client's IP address.
*/
clients_ip = addr.sin_addr.s_addr;
lookup_hostname(&addr, clients_ip_s, sizeof(clients_ip_s), 0);
/* Set the keepalive socket option to on.
*/
{
int on = 1;
setsockopt(STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, (char *)&on, sizeof(on));
}
/* We are running from inetd so process stdin.
*/
trans_proxy(STDIN_FILENO, &addr);
closelog();
return (0);
}
/* Print some basic help information.
*/
static void usage(char *prog, char *opt)
{
fprintf(stderr,"%s\n\n\
Usage: %s [switches..] [options..] [ircserver [ircport]]\n\
\n\
Options:\n",Version,prog);
#ifdef TIRC_DEBUG
fprintf(stderr,
" -d level Set debug level (0=nothing .. 8=everything, 9=foreground).\n");
#endif
fprintf(stderr,"\
-s port Run as a server bound to the specified port.\n\
-b ipaddr Bind to the specified ipaddr in server mode.\n\
-i ipaddr Internal IP, needed for incoming DCCs (when transparent).\n\
-o ipaddr Visible IP, used for connecting to the IRC server (and others).\n");
#ifdef QUIZ_MODE
fprintf(stderr,"\
-p Require a valid Unix password for access.\n\
-q file Read a list of 'quizzes' from the named file.\n");
#endif
fprintf(stderr,"\
-a Anonymous mode, hide as much info about the user as possible.\n\
-r user Run as the specified user in server mode.\n\
-t n Force a sleep(1) between connections under n seconds apart.\n\
Switches:\n\
-M Disable DCC SEND filename mangling/censorship.\n\
-C Disallow DCC CHAT.\n\
-S Disallow DCC file transmissions (SEND, TSEND, etc.).\n\
-U Disallow unknown DCC requests.\n\
-L Log to stderr instead of syslog.\n\
-N Not an IRC proxy, disable all neat-o IRC stuff\n");
#ifdef CDIR
fprintf(stderr,"\
-I Do not attempt to communicate with the identd.\n");
#endif
#ifdef PARANOID
fprintf(stderr,"\
-R Relaxed, allows proxy service to IPs we can't map to a UID.\n");
#endif
#ifdef NICK_LOG
fprintf(stderr,"\
-D Don't log clients' nicknames in syslog.\n");
#endif
#ifdef MIRC_DCC_KLUDGE
fprintf(stderr,"\
-K Disable the \"mIRC DCC kludge\" (read the documentation).\n");
#endif
#ifdef TCP_WRAPPERS
fprintf(stderr,"\
-H Ignore /etc/hosts.allow and /etc/hosts.deny.\n");
#endif
fprintf(stderr,"\n\
By default all features enabled at compile-time are active. In server mode\n\
the -s flag is mandatory.\n");
#ifdef TRANS
fprintf(stderr,"\n\
If no IRC server/port pair is specified the proxy assumes it is running in\n\
transparent mode. This requires support from the Linux kernel or a working\n\
ipf filter installation!\n");
#endif
if (opt)
{
fprintf(stderr,"\nERROR: %s\n", opt);
}
exit(1);
}
/* Return the port number, in network order, of the specified service.
*/
static short get_port_num(char *portnum)
{
char *digits = portnum;
struct servent *serv;
short port;
for (port = 0; isdigit(*digits); ++digits)
{
port = (port * 10) + (*digits - '0');
}
if ((*digits != '\0') || (port <= 0))
{
if ((serv = getservbyname(portnum, "tcp")) != NULL)
{
port = ntohs(serv->s_port);
}
else
{
port = -1;
}
endservent();
}
#ifdef TIRC_DEBUG
debug_msg(DEBUG_TRIVIA, LOG_DEBUG,
"Port lookup %s -> %hd\n", portnum, port);
#endif
return (port);
}
/* Return the IP address of the specified host.
*/
static ipaddr_t get_ip_addr(char *ipaddr)
{
struct hostent *host;
ipaddr_t ip;
if (((ip = inet_addr(ipaddr)) == INADDR_NONE) &&
(strcmp(ipaddr, "255.255.255.255") != 0))
{
if ((host = gethostbyname(ipaddr)) != NULL)
{
memcpy(&ip, host->h_addr, sizeof(ip));
}
endhostent();
}
#ifdef TIRC_DEBUG
debug_msg(DEBUG_BASIC, LOG_DEBUG,
"IP lookup %s -> 0x%08lx\n", ipaddr, ip);
#endif
return (ip);
}
/* Find the userid of the specified user.
*/
static uid_t get_user_id(char *user)
{
struct passwd *pw;
uid_t uid;
if ((pw = getpwnam(user)) != NULL)
{
uid = pw->pw_uid;
}
else if (*user == '#')
{
uid = (uid_t)atoi(&user[1]);
}
else
{
uid = -1;
}
#ifdef TIRC_DEBUG
debug_msg(DEBUG_BASIC, LOG_DEBUG,
"User lookup %s -> %d\n", user, uid);
#endif
endpwent();
return (uid);
}
/* Find the groupid of the specified user.
*/
static uid_t get_group_id(uid_t uid)
{
struct passwd *pw;
gid_t gid;
if ((pw = getpwuid(uid)) != NULL)
{
gid = pw->pw_gid;
}
else
{
gid = -1;
}
#ifdef TIRC_DEBUG
debug_msg(DEBUG_TRIVIA, LOG_DEBUG,
"Group lookup %d -> %d\n", uid, gid);
#endif
endpwent();
return (gid);
}
/* Bind to the specified ip and port.
*/
static int bind_to_port(ipaddr_t bind_ip, short bind_port, int backlog, int dlev)
{
struct sockaddr_in addr;
int sock;
/* Allocate a socket.
*/
if ((sock = socket(AF_INET, SOCK_STREAM,
getprotobyname("tcp")->p_proto)) < 0)
{
debug_msg(dlev, LOG_WARNING, "socket(): %d - %.256s", errno, strerror(errno));
return(-1);
}
#ifdef TIRC_DEBUG
/* Set the SO_REUSEADDR option for debugging.
*/
if (debug_level >= DEBUG_NOFORK) {
int on = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on));
}
#endif
/* Set the address to listen to.
*/
memset(&addr, '\0', sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(bind_port);
addr.sin_addr.s_addr = bind_ip;
/* Bind our socket to the above address.
*/
if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0)
{
debug_msg(dlev, LOG_WARNING, "bind(): %d - %.256s", errno, strerror(errno));
return(-1);
}
/* Establish a large listen backlog.
*/
if (listen(sock, backlog) < 0)
{
debug_msg(dlev, LOG_WARNING, "listen(): %d - %.256s", errno, strerror(errno));
return(-1);
}
return (sock);
}
/* Connect to the a server.
*/
static int connect_to_server(struct sockaddr_in addr)
{
struct sockaddr_in my_addr;
int sock;
/* Allocate a socket.
*/
if ((sock = socket(AF_INET, SOCK_STREAM,
getprotobyname("tcp")->p_proto)) < 0)
{
debug_msg(0, LOG_WARNING, "socket(): .%256s", strerror(errno));
return (-1);
}
/* Set the address to connect to, and the address to connect from.
*/
addr.sin_family = AF_INET;
memset(&my_addr, '\0', sizeof(my_addr));
my_addr.sin_family = AF_INET;
my_addr.sin_port = 0;
my_addr.sin_addr.s_addr = visible_ip_o;
/* Bind to the address we want to use.
*/
if (bind(sock, (struct sockaddr *)&my_addr, sizeof(my_addr)) < 0)
debug_msg(0, LOG_WARNING, "bind(): %d - %.256s",
errno, strerror(errno));
/* Connect our socket to the above address.
*/
if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0)
{
debug_msg(0, LOG_WARNING, "connect(): %.256s", strerror(errno));
return (-1);
}
/* Set the keepalive socket option to on.
*/
{
int on = 1;
setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (char *)&on, sizeof(on));
}
return (sock);
}
/* Translate a sockaddr_in structure into a usable ASCII hostname.
*/
static void lookup_hostname(struct sockaddr_in *addr, char *hostname,
int hostlen, int needed)
{
struct hostent *host;
if (needed)
{
/* Attempt a DNS lookup..
*/
if ((host = gethostbyaddr((char *)&addr->sin_addr,
sizeof(addr->sin_addr),
AF_INET)) != NULL)
{
strncpy(hostname, host->h_name, hostlen);
hostname[hostlen - 1] = '\0';
return;
}
}
/* Get the hostname IP in dotted decimal in case the lookup fails.
*/
strncpy(hostname, inet_ntoa(addr->sin_addr), hostlen);
hostname[hostlen - 1] = '\0';
}
/* This is the main loop when running as a server.
*/
static void server_main_loop(int sock)
{
int new_sock;
struct sockaddr_in addr;
int len;
time_t last_time, this_time;
int delay;
/* Call cleanup func on SIGCHLD.
*/
signal(SIGCHLD, chld_signal);
last_time = (time_t) 0;
delay = 0;
for (;;)
{
/* Accept an incoming connection.
*/
len = sizeof(addr);
while ((new_sock =
accept(sock, (struct sockaddr *)&addr, &len)) < 0)
{
/* Connection resets are common enough to log them as debug only.
*/
debug_msg(0, (errno == ECONNRESET ? LOG_DEBUG : LOG_ERR), "accept(): %.256s", strerror(errno));
}
/* At most send the server one connection every
** throttle_seconds seconds.
*/
this_time = time((time_t) NULL);
if (this_time <= last_time+delay+throttle_seconds)
delay++;
else delay = 0;
last_time = this_time;
/* Make note of the client's IP address.
*/
clients_ip = addr.sin_addr.s_addr;
lookup_hostname(&addr, clients_ip_s, sizeof(clients_ip_s), 0);
/* Create a new process to handle the connection.
*/
switch (fork())
{
case -1:
/* Under load conditions just ignore new connections.
*/
break;
case 0:
/* Start the proxy work in the new socket.
*/
broadcast_flag = 0;
if (delay) {
debug_msg(0, LOG_DEBUG,"Delaying connection %d seconds..",delay);
sleep(delay);
}
trans_proxy(new_sock, &addr);
close(new_sock);
closelog();
exit(0);
}
/* Close the socket as the child does the handling.
*/
close(new_sock);
}
}
/* Handle connections transparently or otherwise..
*/
static void trans_proxy(int sock, struct sockaddr_in *from_addr)
{
struct sockaddr_in to_addr;
int to_len;
#if IPF
#if __FreeBSD_version >=600024
ipfobj_t obj;
#endif
struct sockaddr_in socketin, sloc;
natlookup_t natlook;
natlookup_t *natlookp = &natlook;
int fd;
#endif
/* Give this thing 10 minutes to get started (paranoia).
*/
signal(SIGALRM, alarm_signal);
alarm(10*MINUTE);
strcpy(alarm_in, "trans_proxy");
/* Check who the client is.
*/
get_user_name(from_addr);
#ifdef TCP_WRAPPERS
if (use_tcp_wrappers)
{
char host_ip[64], host_name[64];
/* Lookup an ASCII representation of the host's IP address,
** and attempt to look up it's FQDN as well (DNS).
*/
lookup_hostname(from_addr, host_ip, sizeof(host_ip), 0);
lookup_hostname(from_addr, host_name, sizeof(host_name), 1);
/* Check if the calling host is allowed to use the proxy.
*/
if (!hosts_ctl((not_an_irc_proxy) ? "proxy" : "tircproxy",
host_name, host_ip, user_name))
{
debug_msg(0, LOG_INFO,
"Denied access to: %.32s@%.128s [%.96s]",
user_name, host_name, host_ip);
return;
}
}
#endif
/* Are we running in transparent mode?
*/
if (server_ip == INADDR_NONE)
{
/* There are two completely different ways to do this, one
** based on linux transparent proxying the other based on
** ipf transparent proyxing, only one is used
*/
#if LINUX
/* The Linux method:
**
** The first thing we do is get the IP address that the client was
** trying to connected to. Here lies part of the magic. Normally
** getsockname returns our address, but not with transparent proxying.
*/
to_len = sizeof(to_addr);
if (getsockname(sock, (struct sockaddr *)&to_addr, &to_len) < 0)
{
debug_msg(0, LOG_ERR, "getsockname(): %.256s", strerror(errno));
return;
}
#else
# if IPF
/* This is the ipf method
*/
to_len = sizeof(socketin);
if (getpeername(sock, (struct sockaddr *)&socketin, &to_len) == -1)
{
perror("getpeername");
exit(-1);
}
to_len = sizeof(socketin);
if (getsockname(sock, (struct sockaddr *)&sloc, &to_len) == -1)
{
perror("getsockname");
exit(-1);
}
#if __FreeBSD_version >=600024
bzero(&obj, sizeof(obj));
obj.ipfo_rev = IPFILTER_VERSION;
obj.ipfo_size = sizeof(natlook);
obj.ipfo_ptr = &natlook;
obj.ipfo_type = IPFOBJ_NATLOOKUP;
#endif
bzero((char *)&natlook, sizeof(natlook));
natlook.nl_outip = socketin.sin_addr;
natlook.nl_inip = sloc.sin_addr;
natlook.nl_flags = IPN_TCP;
natlook.nl_outport = socketin.sin_port;
natlook.nl_inport = sloc.sin_port;
fd = open(IPL_NAT, O_RDONLY);
#if __FreeBSD_version >=600024
if (ioctl(fd, SIOCGNATL, &obj) == -1)
#else
if (ioctl(fd, SIOCGNATL, &natlookp) == -1)
#endif
{
perror("ioctl(SIOCGNATL)");
exit(-1);
}
close(fd);
/* [ The following is WEIRD. Why the htons(ntohs()) ??
** And why the getip..(inet_ntoa()) ??? ]
*/
memset(&to_addr, '\0', sizeof(to_addr));
to_addr.sin_family = AF_INET;
to_addr.sin_port = htons(ntohs(natlook.nl_realport));
to_addr.sin_addr.s_addr = get_ip_addr(inet_ntoa(natlook.nl_realip));
# endif /* IFP */
#endif /* LINUX */
}
else
{
/* Ok, not transparent, we'll connect to the specified server.
*/
memset(&to_addr, '\0', sizeof(to_addr));
to_addr.sin_family = AF_INET;
to_addr.sin_port = htons(server_port);
to_addr.sin_addr.s_addr = server_ip;
}
/* Drop root privs if we have them.
*/
change_uid();
signal(SIGHUP, hup_signal);
debug_msg(0, LOG_DEBUG,
"Copied %d bytes, exiting.",
copy_loop(sock, from_addr, &to_addr, !not_an_irc_proxy));
#ifdef CDIR_IDENT
if (use_cdir)
cleanup_identd();
#endif
}
/* Perform the proxy activity itself.
*/
static int copy_loop(int sock, struct sockaddr_in *from_addr,
struct sockaddr_in *to_addr,
int is_server_connection)
{
int server,junk;
struct sockaddr_in ourend, ourend_i;
char blurb[64];
char from_host[64];
char to_host[64];
static char headers[16384 + 1];
static char ser_left[16384 + 1];
static char cli_left[16384 + 1];
int ser_ln = 0;
int cli_ln = 0;
int select_loop;
int max_fd;
fd_set read_fd;
int read_len;
int motd_sent = 0;
int copied_bytes = 0;
/* Allow 3 minutes for things to get properly started.
*/
signal(SIGALRM, alarm_signal);
alarm(3*MINUTE);
strcpy(alarm_in, "copy_loop: starting");
/* Lookup an ASCII representation of the host's IP address.
*/
lookup_hostname(from_addr, from_host, sizeof(from_host), 0);
lookup_hostname(to_addr, to_host, sizeof(to_host), 0);
/* Read the first couple of lines from the client, take a
** peek. Check if the server is looping.
*/
if (is_server_connection)
{
#ifdef TIRC_DEBUG
debug_msg(DEBUG_BASIC, LOG_DEBUG,
"Checking if this is a loopy server..");
#endif
server = read_len = -1;
copy_once(sock, headers, sizeof(headers), "client",
server, ser_left, &ser_ln, "server",
is_server_connection, 1,
&read_len);
if (read_len <= 0) return 0;
strncpy(blurb,headers,63);
if (strstr(blurb,server_tag) != NULL)
{
debug_msg(0, LOG_INFO, "Looping detected, terminating!");
return 0;
}
}
/* Connect to the server (or die).
*/
if ((server = connect_to_server(*to_addr)) < 0) return 0;
junk = sizeof(ourend);
strcpy(alarm_in, "copy_loop: getsockname 1");
getsockname(server, (struct sockaddr *) &ourend, &junk);
#ifdef CDIR_IDENT
/* Tell the ident server which user this connection really belongs to.
**
** Note: This may be a race between us and the remote IRC server,
** depending on the implementation of the ident server.
*/
if (use_cdir && is_server_connection) tell_identd(&ourend, to_addr);
#endif
strcpy(alarm_in, "copy_loop: getsockname 2");
getsockname(sock, (struct sockaddr *) &ourend_i, &junk);
/* Record what IP addresses we're using.
*/
visible_ip_o = ourend.sin_addr.s_addr;
lookup_hostname(&ourend, visible_ip_o_s, sizeof(visible_ip_o_s), 0);
if (visible_ip_i == INADDR_ANY)
{
visible_ip_i = ourend_i.sin_addr.s_addr;
lookup_hostname(&ourend_i, visible_ip_i_s, sizeof(visible_ip_i_s), 0);
}
/* Log the facts about the connection.
*/
debug_msg(0, LOG_INFO, "%.64s -%.64s:%d-> %.64s:%d",
from_host, user_name, ntohs(ourend.sin_port),
to_host, ntohs(to_addr->sin_port));
/* Pass the data we read just now on to the server..
*/
if (is_server_connection)
{
junk = strlen(server_tag);
copy_once(sock, server_tag, 0, "tircproxy",
server, ser_left, &ser_ln, "server",
is_server_connection, 1, &junk);
copy_once(sock, headers, sizeof(headers), "client",
server, ser_left, &ser_ln, "server",
is_server_connection, 1, &read_len);
if (read_len <= 0) return 0;
}
/* Continue by passing data back and forth between the
** client and the server (or remote end of DCC).
*/
for (;;)
{
/* Construct a select read mask from both file descriptors.
*/
FD_ZERO(&read_fd);
FD_SET(sock, &read_fd);
FD_SET(server, &read_fd);
max_fd = FD_MAX(sock, server);
/* Allow 30 minutes for the loop to complete.
*/
signal(SIGALRM, alarm_signal);
alarm(30*MINUTE);
strcpy(alarm_in, "copy_loop: waiting");
#ifdef TIRC_DEBUG
debug_msg(DEBUG_TRIVIA, LOG_DEBUG,"copy_loop: waiting");
#endif
/* Wait for some data to be read.
*/
select_loop = 1;
while (select_loop)
if (select(max_fd + 1, &read_fd, NULL, NULL, NULL) < 0)
{
if (errno != EINTR) {
#ifdef TIRC_DEBUG
debug_msg(DEBUG_BASIC, LOG_ERR,
"select(): %.256s",
strerror(errno));
#endif
close(server);
return copied_bytes;
}
} else
select_loop = 0;
/* We aren't waiting anymore..
*/
strcpy(alarm_in, "copy_loop: copying");
#ifdef TIRC_DEBUG
debug_msg(DEBUG_TRIVIA, LOG_DEBUG,"copy_loop: copying");
#endif
if (is_server_connection)
{
/* Send the user a message, if requested by admin.
*/
#ifdef BROADCAST_FILE
if (broadcast_flag) broadcast(sock, BROADCAST_FILE);
#endif
#ifdef IRC_MOTD_FILE
/* Send the user the MOTD.
*/
if ((!motd_sent) &&
(motd_is_done) &&
(is_server_connection))
{
broadcast(sock, IRC_MOTD_FILE);
motd_sent = 1;
}
#endif
}
/* See if any data can be read from the client.
*/
if (FD_ISSET(sock, &read_fd))
{
read_len = 0;
copy_once(sock, headers, sizeof(headers), "client",
server, ser_left, &ser_ln, "server",
is_server_connection, 1,
&read_len);
if (read_len < 0) return copied_bytes;
copied_bytes += read_len;
}
/* See if any data can be read from the server.
*/
if (FD_ISSET(server, &read_fd))
{
read_len = 0;
copy_once(server, headers, sizeof(headers), "server",
sock, cli_left, &cli_ln, "client",
is_server_connection, 0,
&read_len);
if (read_len < 0) return copied_bytes;
copied_bytes += read_len;
}
#ifdef QUIZ_MODE
/* Dump queued data meant for the client.
*/
if (use_quiz_mode && motd_is_done)
quiz_dump_lines(sock, QUIZ_C);
/* Dump queued data, stop quizzing if quiz mode is "flush".
*/
if ((use_quiz_mode == QUIZ_FLUSH) && (copied_bytes > 128))
{
quiz_dump_lines(server, QUIZ_S);
use_quiz_mode = QUIZ_OFF;
}
#endif
}
}
/* This function handles a single copy cycle from one socket to another.
** If *read_len <= 0, then
** It reads at most rbufsize bytes from rsock, into rbuffer.
** If *read_len >= 0, then
** It writes the contents of rbuffer to wsock.
**
** Writes are filtered if the is_server_connection is true.
** The function returns -1 or -2 on errors, or the number of read bytes on
** success.
*/
static int copy_once(int rsock, char *rbuffer, int rbufsize, char *rname,
int wsock, char *wlbuffer, int *wlbufpos, char *wname,
int is_server_connection, int r_is_client,
int *read_len)
{
int rlen;
#ifdef TIRC_DEBUG
debug_msg(DEBUG_TRIVIA, LOG_DEBUG,
"Entering copy_once (reading %s, writing %s)",
rname, wname);
#endif
rlen = *read_len;
if (rlen <= 0) rlen = read(rsock, rbuffer, rbufsize - 1);
switch (rlen)
{
case -1:
#ifdef TIRC_DEBUG
debug_msg(DEBUG_BASIC,
(errno == ECONNRESET ? LOG_DEBUG : LOG_WARNING),
"read(%.32s) failed: %.256s", rname, strerror(errno));
#endif
if (errno != EINTR)
{
close(wsock);
return -2;
}
return 0;
case 0:
#ifdef TIRC_DEBUG
debug_msg(DEBUG_TRIVIA, LOG_DEBUG, "EOF!");
#endif
close(rsock);
close(wsock);
return -1;
default:
if (*read_len >= 0)
{
if (is_server_connection)
{
if (!filtered_write(wsock, rbuffer, rlen,
wlbuffer, wlbufpos, r_is_client))
{
#ifdef TIRC_DEBUG
debug_msg(DEBUG_BASIC, LOG_WARNING,
"write(%.32s) failed: %.256s",
wname, strerror(errno));
#endif
close(rsock);
return 0;
}
}
else
{
while (write(wsock, rbuffer, rlen) < 0)
if (errno != EINTR)
{
#ifdef TIRC_DEBUG
debug_msg(DEBUG_BASIC, LOG_WARNING,
"write(%.32s) failed: %.256s",
wname, strerror(errno));
#endif
close(rsock);
return 0;
}
}
break;
}
}
#ifdef TIRC_DEBUG
debug_msg(DEBUG_TRIVIA, LOG_DEBUG,
"Leaving copy_once (rlen = %d)", rlen);
#endif
return (*read_len = rlen);
}
/* Line buffered write routine.. only dumps whole lines, and only
** after submitting them to the CTCP police.
*/
static int filtered_write(int to_sock, char *buff, int blen,
char *leftover, int *llen,
int isclient)
{
char *nl, *ol, *lp;
int l,silent;
#ifdef TIRC_DEBUG
debug_msg(DEBUG_TRIVIA, LOG_DEBUG, "Entering filtered_write");
#endif
buff[blen] = '\0'; /* Null terminate buffer for strchr */
/* Break the input into lines, parse each one and send them one at
** a time to the client.
*/
nl = ol = buff;
lp = leftover + *llen;
while ((nl = strchr(ol,'\n')) != NULL)
{
/* nl points to the first '\n' in the input.
** ol points to the beginning of the unscanned input.
** lp points to the end of the contents of 'leftover'
** which is the the fragment of a line we couldn't finish
** processing last time.
*/
nl++; /* Move nl past the '\n' */
l = nl-ol; /* l = the length of this line */
if (l > 1500) l = 1500; /* Arbitrary limits == bad! FIXME!! */
memmove(lp, ol, l); /* Append new line to leftover */
lp[l] = '\0'; /* Null terminate leftover */
/* Okay, now we have a complete line of data in the leftover
** buffer - scan it for interesting bits, and send it if it
** isn't deemed completely obscene. :)
*/
if ((silent = scan_line(leftover, isclient)) > 0)
{
while (write(to_sock, leftover,
strlen(leftover)) < 0)
if (errno != EINTR)
{
#ifdef TIRC_DEBUG
debug_msg(DEBUG_BASIC, LOG_WARNING,
"write(%s) failed: %.256s",
isclient ? "client" : "server",
strerror(errno));
#endif
close(to_sock);
return 0;
}
}
#ifdef TIRC_DEBUG
else if (silent >= 0)
debug_msg(DEBUG_FEATURES, LOG_DEBUG,
"Squelched: %.256s", leftover);
#endif
/* Maintain loop integrity..
*/
ol = nl;
lp = leftover;
}
/* Loop has terminated - if anything is left in ol, then it isn't
** a complete line we can scan, it's only a fragment.
**
** .. so we save it for next time, and return 1 for success.
*/
*llen = blen + (buff - ol);
memmove(leftover, ol, *llen);
return 1;
}
/* This function scans a single line of data for DCC constructs and other
** information we need to keep track of. The line is rewritten if necissary.
** Returns 1 if the line is "okay for writing", 0 if it is to be suppressed,
** -1 if it is to be suppressed silently.
*/
#define CTRL_A ('A'&31)
static int scan_line(char *line, int isclient)
{
char *done, *ctcp_begin, *ctcp_end, *p;
char replace[LENGTH_SERVERDATA * 2];
/* Sanity check..
*/
if (strlen(line) > LENGTH_SERVERDATA) {
debug_msg(0, LOG_ERR, "Input to scan_line() of illegal length - input not scanned!");
return(1);
}
/* Scan all CTCPs (it IS legal to embed more than one in the same,
** line, but most clients don't handle this correctly).
*/
done = line;
while (((ctcp_begin = strchr(done, CTRL_A)) != NULL) &&
((ctcp_end = strchr(ctcp_begin + 1, CTRL_A)) != NULL))
{
char ctcp_type[32];
strncpy(ctcp_type, ctcp_begin + 1, sizeof(ctcp_type));
ctcp_type[sizeof(ctcp_type) - 1] = '\0';
if ((p = strchr(ctcp_type, ' ')) != NULL) *p = '\0';
/* It's a CTCP.. but is it DCC?
*/
if (!strcasecmp(ctcp_type, "DCC"))
{
/* Okay, it's DCC.. rewrite it!
*/
int squelch = 0;
char type[32];
char arg_1[256];
char *rest;
unsigned int arg_2, arg_3, scanned;
*replace = *ctcp_begin = *ctcp_end = '\0'; /* Chop! */
ctcp_begin++;
if (sscanf(ctcp_begin, "%4s %31s %255s %u%u%n",
ctcp_type, type,
arg_1, &arg_2, &arg_3,
&scanned) >= 5)
{
#ifdef TIRC_DEBUG
debug_msg(DEBUG_FEATURES, LOG_DEBUG,
"Caught: %.256s", ctcp_begin);
#endif
rest = ctcp_begin + scanned;
if (!strcasecmp(type, "CHAT"))
{
if (!allow_dcc_chat)
#ifdef DISALLOW_DCC_CHAT
sprintf(replace,
DISALLOW_DCC_CHAT,
type, arg_1);
#else
squelch = 1;
#endif
}
else if (!strcasecmp(type, "SEND") ||
!strcasecmp(type, "TSEND") ||
!strcasecmp(type, "RESEND") ||
!strcasecmp(type, "TRESEND"))
{
if (!allow_dcc_send)
#ifdef DISALLOW_DCC_SEND
sprintf(replace,
DISALLOW_DCC_SEND,
type, arg_1);
#else
squelch = 1;
#endif
else if (!dcc_mangle_filename(arg_1))
#ifdef MANGLE_DCC_SEND
sprintf(replace,
MANGLE_DCC_SEND,
type, arg_1);
#else
squelch = 1;
#endif
}
else if (!strcasecmp(type, "RESUME"))
{
sprintf(replace,
"%cDCC %s %s %d %d%s%c",
CTRL_A,
type, arg_1,
dcc_resume(arg_2, 1),
arg_3, rest, CTRL_A);
}
else if (!strcasecmp(type, "ACCEPT"))
{
sprintf(replace,
"%cDCC %s %s %d %d%s%c",
CTRL_A,
type, arg_1,
dcc_resume(arg_2, 0),
arg_3, rest, CTRL_A);
}
/* Insert new DCC protocols here :-)
*/
else if (!allow_dcc_unknown)
#ifdef DISALLOW_DCC_SEND
sprintf(replace,
DISALLOW_DCC_FUNK,
type, arg_1);
#else
squelch = 1;
#endif
if (!*replace && !squelch)
{
/* No replacement has been set, so
** proxy this connection. :-)
*/
sprintf(replace,
"%c%s %s %s %s%s%c",
CTRL_A, ctcp_type,
type, arg_1,
proxy_dcc(arg_2, arg_3, isclient),
rest, CTRL_A);
}
}
/* Alternate (!= DCC) protocols go here.. */
/* Are we changing anything?
*/
ctcp_begin--;
if (*replace)
{
#ifdef TIRC_DEBUG
debug_msg(DEBUG_FEATURES, LOG_DEBUG,
"Sent: %.256s", replace);
#endif
done = ctcp_begin + strlen(replace); /* Mark */
strcat(replace, ctcp_end + 1);
strcpy(ctcp_begin, replace); /* Rewrite */
}
else
{
*ctcp_begin = *ctcp_end = CTRL_A; /* Unchop */
done = ctcp_end + 1; /* Mark */
if (squelch)
return(0);
}
} /* is DCC */
else if ((use_anonymity) &&
(isclient) &&
((!strcasecmp(ctcp_type, "VERSION")) ||
(!strcasecmp(ctcp_type, "FINGER")) ||
(!strcasecmp(ctcp_type, "USERINFO")) ||
(!strcasecmp(ctcp_type, "CLIENTINFO"))) &&
(!strncasecmp(line, "NOTICE", 6)))
{
strcpy(replace, CTCP_NOT_ALLOWED);
done = ctcp_begin + strlen(replace) + 2; /* Mark */
strcat(replace, ctcp_end);
strcpy(ctcp_begin + 1, replace); /* Rewrite */
}
else if (isclient)
{
/* Squelch other outgoing CTCPs containing the
** client's IP address.
*/
if (strstr(ctcp_begin, clients_ip_s) != NULL)
return (0);
done = ctcp_end + 1;
}
else
done = ctcp_end + 1;
}
/* Scan server input for the client's nickname.
*/
if (!isclient)
{
char type[128];
char arg[32];
char nuh[512];
if (sscanf(line, ":%511s %127s %31s", nuh, type, arg) == 3)
{
if ((isdigit(*type) ||
(!strcasecmp(type, "PRIVMSG"))) &&
((*arg != '&') && (*arg != '#') &&
(*arg != '%') &&
(*arg != '!') && (*arg != '*')))
{
#ifdef NICK_LOG
if (use_nick_log &&
strcasecmp(user_nick, arg))
debug_msg(0, LOG_INFO, "Nick: %.256s", arg);
#endif
strncpy(user_nick, arg, sizeof(user_nick));
user_nick[sizeof(user_nick) - 1] = '\0';
}
if ((!motd_is_done) &&
((!strcasecmp(type,"376")) ||
(!strcasecmp(type,"421"))))
motd_is_done = 1;
}
/* The server shouldn't start PINGing us until well
** after the end of the MOTD..
*/
if ((!motd_is_done) && (!strncasecmp(line,"PING",4)))
motd_is_done = 1;
}
else if (use_anonymity) /* Data is from client, anonymize */
{
if (!strncasecmp(line, "NOTICE", 6))
{
if (strstr(line, clients_ip_s) != NULL)
return (0);
}
else if (!strncasecmp(line, "USER", 4))
{
int cip = ANON_USERID;
sprintf(line,
"USER t%x anon anon :%s\n",
cip, ANON_IRCNAME);
}
}
else if (!strncasecmp(line, "NOTICE", 6)) /* Fix mIRC junk */
{
char *p;
while ((p = strstr(line, clients_ip_s)) != NULL)
{
*p = '\0';
strcpy(replace, p+strlen(clients_ip_s));
strcat(line, visible_ip_o_s);
strcat(line, replace);
}
}
/* Other spying could be done here .. */
#ifdef QUIZ_MODE
if (use_quiz_mode) {
if ((isclient) && (use_quiz_mode <= QUIZ_FLUSH))
{
char cmd[32];
char rest[512], out[512], *r;
int what, retv, fuid;
what = 0;
if (sscanf(line, "%31s %511s", cmd, rest) == 2)
{
if (!strcasecmp(cmd, "NICK")) what = 1;
else if (!strcasecmp(cmd, "USER")) what = 2;
else if (!strcasecmp(cmd, "PASS")) what = 3;
else if (!strcasecmp(cmd, "PRIVMSG")) what = 4;
else if (!strncasecmp(line, server_tag, 6))
what = 2;
r = line+strlen(cmd)+1;
if (*r == ':') r++;
strncpy(rest, r, sizeof(rest)-1);
rest[511] = '\0' ;
if ((r = strchr(rest,'\n')) != NULL) *r = '\0';
}
else if (!strncasecmp(line, server_tag, 6)) what = 2;
r = rest;
retv = -1;
switch (what)
{
/* Users get fake nicks until after
** authentication (anti-nick-kill).
*/
case 1: quiz_delay_line(line, QUIZ_S);
fuid = getpid() * visible_ip_o;
sprintf(user_nick, "}%x", fuid);
sprintf(line, "NICK :%s\n", user_nick);
sprintf(out, ":%.32s %.256s", r, line);
quiz_delay_line(out, QUIZ_C);
quiz_greet();
retv = 1;
break;
/* Allow the USER command.
*/
case 2: retv = 1;
strcat(line,"#PiNG#\n");
break;
/* Check the users' responses.
*/
case 4: if ((r = strchr(rest,' ')) != NULL)
{
*r++ = '\0';
if (*r == ':') r++;
}
if (strcasecmp(rest, QUIZ_NICK))
{
quiz_delay_line(line, QUIZ_S);
break;
}
case 3: quiz_check_auth(r);
break;
default: quiz_delay_line(line, QUIZ_S);
break;
}
return(retv);
}
}
#endif
return(1);
}
/* Mangle the given filename. Returns 1 if the filename
** is okay, 0 if not.
*/
static int dcc_mangle_filename(char *filename)
{
struct dcc_mangle_struct *dmp;
/* Check the compiled-in rules first:
*/
dmp = dcc_mangle;
while (use_dcc_mangling &&
(dmp->offered != NULL))
{
if (!strcasecmp(filename, dmp->offered))
{
if (dmp->replace != NULL)
strcpy(filename, dmp->replace);
else
return(0);
}
dmp++;
}
#ifdef TCP_WRAPPERS
/* Check if the offered file is on the
** local blacklist.
*/
if (use_tcp_wrappers)
{
if (!hosts_ctl("tircproxy_dcc_files",
"", "", filename))
return(0);
}
#endif
return(1);
}
/* Looks up valid port numbers for proxying DCC RESUME requests.
** Set resume to 0 on "DCC RESUME", 1 on "DCC ACCEPT".
*/
static int dcc_resume(int destport, int resume)
{
int i;
for (i = 0; i < MAX_DCC_SESSIONS; i++)
{
if ((resume) && (dcc_proxied_port[i] == destport))
{
return(dcc_original_port[i]);
}
else if ((!resume) && (dcc_original_port[i] == destport))
{
return(dcc_proxied_port[i]);
}
}
return (0);
}
/* This prepares to proxy a DCC session. Ut returns a pointer
** to a string containing the "<ip> <port>" reply to send to the
** client.
*/
static char *proxy_dcc(int destaddr, int destport, int incoming)
{
static char retvalue[128];
int listen_sock;
int sock;
ipaddr_t vip;
struct sockaddr_in addr;
struct sockaddr_in to_addr;
int len;
/* Mangle DCC offers below port 1024.
*/
if (destport < 1024)
{
strcpy(retvalue,"junk junk");
return retvalue;
}
#ifdef MIRC_DCC_KLUDGE
if (incoming && use_mirc_dcc_kludge) destaddr = ntohl(clients_ip);
#endif
#ifdef TCP_WRAPPERS
if (use_tcp_wrappers)
{
char host_ip[64], host_name[64];
struct sockaddr_in addr;
int okay = 1;
/* Lookup an ASCII representation of the host's IP address,
** and attempt to look up it's FQDN as well (DNS).
*/
addr.sin_addr.s_addr = htonl(destaddr);
lookup_hostname(&addr, host_ip, sizeof(host_ip), 0);
lookup_hostname(&addr, host_name, sizeof(host_name), 1);
/* Check if the calling host is allowed to initiate DCC.
*/
if (!hosts_ctl((incoming) ? "tircproxy_dcc_out" :
"tircproxy_dcc_in",
host_name, host_ip,
(incoming) ? "" : user_name))
{
okay = 0;
}
if (clients_ip != htonl(destaddr))
{
addr.sin_addr.s_addr = clients_ip;
lookup_hostname(&addr, host_ip, sizeof(host_ip), 0);
lookup_hostname(&addr, host_name, sizeof(host_name), 1);
/* Check if the client is allowed to use DCC.
*/
if (!hosts_ctl("tircproxy_dcc_in",
host_name, host_ip,
user_name))
{
okay = 0;
}
}
if (!okay)
{
debug_msg(0, LOG_INFO,
"Dropped illegal DCC from: %.128s (client %.32s@%.96s)",
host_ip, user_name,
inet_ntoa(addr.sin_addr));
strcpy(retvalue,"junk junk");
return(retvalue);
}
}
#endif
/* Choose a DCC port at random, to make hijacking harder.
** (re: BUGTRAQ, 1998, December 22 & 23)
*/
len = 10;
while ((listen_sock = bind_to_port((incoming) ? visible_ip_o : visible_ip_i,
((rand() + getpid()) % PPOOL) + 1025,
1, DEBUG_TRIVIA)) < 0)
{
if (--len < 1) {
debug_msg(0, LOG_ERR, "Failed to bind to port, dropping DCC!");
strcpy(retvalue, "ack, pthpht!");
return(retvalue);
}
}
/* call cleanup func on SIGCHLD
*/
signal(SIGCHLD, chld_signal);
if (fork())
{
int i,n;
len = sizeof(to_addr);
if (getsockname(listen_sock, (struct sockaddr *)&to_addr, &len) < 0)
{
debug_msg(0, LOG_ERR, "getsockname(): %.256s", strerror(errno));
*retvalue = '\0';
return(retvalue);
}
/* Close the socket as the child does the handling.
*/
close(listen_sock);
vip = to_addr.sin_addr.s_addr;
/* Record the port info for this connection.
*/
for (n = i = 0; i < MAX_DCC_SESSIONS; i++)
if (dcc_session_time[i] < dcc_session_time[n]) n = i;
dcc_session_time[n] = time(NULL);
dcc_original_port[n] = destport;
dcc_proxied_port[n] = ntohs(to_addr.sin_port);
#ifdef HAVE_LINUX
sprintf(retvalue,"%lu %u",
(unsigned long int) ntohl(vip), ntohs(to_addr.sin_port));
#else
sprintf(retvalue,"%u %u",
(unsigned int) ntohl(vip), ntohs(to_addr.sin_port));
#endif
return(retvalue);
}
/* Give people five minutes to accept the call..
*/
signal(SIGALRM, alarm_signal);
alarm(5*MINUTE);
strcpy(alarm_in,"proxy_dcc: accept");
/* No silly broadcasts shall mess up our DCC stuff!
*/
signal(SIGHUP, SIG_IGN);
/* Accept an incoming connection.
*/
len = sizeof(addr);
if ((sock = accept(listen_sock, (struct sockaddr *)&addr, &len)) < 0)
{
/* Connection resets are common enough to log them as debug only.
*/
debug_msg(0, (errno == ECONNRESET ? LOG_DEBUG : LOG_ERR), "accept(): %.256s", strerror(errno));
exit (0);
}
close(listen_sock);
/* Ok, here we go!
*/
memset(&to_addr, '\0', sizeof(to_addr));
to_addr.sin_family = AF_INET;
to_addr.sin_port = htons(destport);
to_addr.sin_addr.s_addr = htonl(destaddr);
/* Make sure at least *one* end of the connection
** is the original client.
*/
if ((to_addr.sin_addr.s_addr != clients_ip) &&
(addr.sin_addr.s_addr != clients_ip))
{
debug_msg(0, LOG_WARNING, "Abuse: illegal DCC request %.128s <-> %.128s !",
inet_ntoa(to_addr.sin_addr),
inet_ntoa(addr.sin_addr));
exit(0);
}
debug_msg(0, LOG_DEBUG,
"Copied %d bytes, exiting (DCC).",
copy_loop(sock, &addr, &to_addr, 0));
#ifdef CDIR_IDENT
if (use_cdir)
cleanup_identd();
#endif
close(sock);
closelog();
exit(0);
}
/* Read username from disk or shared memory, if possible.
*/
static void get_user_name(struct sockaddr_in *addr)
{
int tries = 0;
#ifdef USE_UDB
struct udb_ip_user buf;
/* People neither using the CDIR stuff nor running as root probably
* couldn't care less about ident problems.
*/
if (!use_cdir && getuid()) tries = 10;
/* Be stubborn, to decrease the chance of race conditions.
*/
while (!udb_ip_get(&(addr->sin_addr), &buf))
{
if (tries > 3)
{
debug_msg(0, LOG_WARNING,
"Address not in UDB table, ident response will be wrong!");
return;
}
else
{
tries++;
sleep(1);
}
}
#ifdef TIRC_DEBUG
debug_msg(DEBUG_TRIVIA, LOG_DEBUG, "Got username: %.256s", buf.username);
#endif
strncpy(user_name, buf.username, MAXLOGNAME);
user_name[MAXLOGNAME-1] = '\0';
return;
#endif /* USE_UDB */
#ifdef IP_TO_UID_PREFIX /* not USE_UDB */
int fd;
char ipfile[PATH_MAX];
char *cp;
/* People neither using the CDIR stuff nor running as root probably
* couldn't care less about ident problems.
*/
#ifdef CDIR_IDENT
if (!use_cdir && getuid()) tries = 10;
#endif
/* FIXME: dangerous sprintfs */
#ifdef CDIR
if (use_cdir)
# ifdef HAVE_SNPRINTF
snprintf(ipfile, sizeof(ipfile),
# else
sprintf(ipfile,
# endif
"%s%s-%s", CDIR, CDIR_MAP,
inet_ntoa(addr->sin_addr));
else
#endif
# ifdef HAVE_SNPRINTF
snprintf(ipfile, sizeof(ipfile),
# else
sprintf(ipfile,
# endif
"%s%s", IP_TO_UID_PREFIX,
inet_ntoa(addr->sin_addr));
/* Repeat until we lose the ident race.. :-)
*/
while ((fd = open(ipfile,O_RDONLY)) == -1)
{
if (tries > 5) {
debug_msg(0, LOG_WARNING,
"No %.256s file found, ident response will be wrong!",
ipfile);
return;
}
else
{
sleep(1);
}
tries++;
}
user_name[0] = user_name[MAXLOGNAME-1] = '\0';
read(fd, user_name, MAXLOGNAME-1);
close(fd);
if ((cp = strchr(user_name,'\n'))) *cp = '\0';
return;
#endif /* IP_TO_UID_PREFIX */
}
/* Change uid & gid!
*/
static void change_uid(void)
{
#if (defined IP_TO_UID_PREFIX || defined USE_UDB)
struct passwd *pw;
/* We aren't running as root, so there's nothing more we can do..
*/
if (getuid()) return;
if ((pw = getpwnam(user_name)) == NULL)
{
debug_msg(0, LOG_WARNING,
"Invalid user: %.32s, didn't changed UID/GID.",
user_name);
# ifdef PARANOID
if (use_paranoid) exit(0);
# endif
return;
}
/* Change ids..
*/
setgid(pw->pw_gid);
setuid(pw->pw_uid);
/* Start a new session and group.
*/
setsid();
# ifdef HAVE_LINUX
setpgrp();
# endif
#endif /* IP_TO_UID_PREFIX */
return;
}
/* Tell the ident server which user this connection really belongs to.
*/
#ifdef CDIR
static void tell_identd(struct sockaddr_in *us, struct sockaddr_in *them)
{
char fakeid[64], *u;
# ifndef USE_UDB
int fd;
*user_ident_file = '\0';
sprintf(user_ident_file,
"%s%s:%d-%s:%d", CDIR, CDIR_IDENT,
ntohs(us->sin_port),
inet_ntoa(them->sin_addr),
ntohs(them->sin_port));
if ((fd = creat(user_ident_file, S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH)) == -1)
{
debug_msg(0, LOG_WARNING,
"Unable to create %s, ident response will be wrong!",
user_ident_file);
*user_ident_file = '\0';
return;
}
# endif
if ((use_anonymity) || (!*user_name))
{
int cip = ANON_USERID;
sprintf(fakeid, "t%x", cip);
debug_msg(0, LOG_INFO, "Anonymized connection as %s", fakeid);
u = fakeid;
}
else
u = user_name;
# ifdef USE_UDB
memset(&conn, 0, sizeof(conn));
conn.from.sin_family = us->sin_family;
conn.from.sin_addr = us->sin_addr;
conn.from.sin_port = us->sin_port;
conn.to.sin_family = them->sin_family;
conn.to.sin_addr = them->sin_addr;
conn.to.sin_port = them->sin_port;
udb_conn_record(&conn, u, "", UDB_OVERWRITE_LRU, UDB_NOW);
# else
if (write(fd, u, strlen(u)) < 0)
debug_msg(0, LOG_WARNING,
"Unable to write to %s, ident response will be wrong!",
user_ident_file);
close(fd);
# endif
}
/* Cleanup ..
*/
static void cleanup_identd(void)
{
# ifdef USE_UDB
udb_conn_delete(&conn);
# else
if (*user_ident_file)
unlink(user_ident_file);
# endif
}
#endif /* CDIR */
/* Broadcast a message from sysadmin to proxy user.. It's the
** sysadmin's problem to format the file so the client will understand
** the message!
*/
static void broadcast(int sock, const char *filename)
{
int fd,i;
char buff[2048], *p, *np;
#ifdef HAVE_LINUX
/* man signal:
**
** Unlike BSD systems, signals under Linux are reset to their
** default values when raised.
*/
signal(SIGHUP, hup_signal);
#endif
broadcast_flag = 0;
if ((fd = open(filename,O_RDONLY)) == -1) return;
debug_msg(0, LOG_DEBUG, "Sending broadcast from: %.256s",filename);
while ((i = read(fd, buff, 2047)) > 0)
{
buff[2047] = '\0';
np = p = buff;
while ((np = strstr(p, "$N$")) != NULL)
{
if (write(sock, p, np-p) < 0)
debug_msg(0, LOG_WARNING, "Error writing to socket!");
p = np+3;
write(sock, user_nick, strlen(user_nick));
}
if (write(sock, p, (buff+i)-p) < 0)
debug_msg(0, LOG_WARNING, "Error writing to socket!");
}
close(fd);
}
/* Catch alarm signals and exit.
*/
static void alarm_signal(int sig)
{
debug_msg(0, LOG_DEBUG, "Timeout in %.256s, exiting.", alarm_in);
#ifdef CDIR_IDENT
if (use_cdir)
cleanup_identd();
#endif
exit(1);
}
/* Catch HUP signals and set broadcast flag.
*/
static void hup_signal(int sig)
{
debug_msg(0, LOG_DEBUG, "HUP signal caught - broadcasting!");
broadcast_flag = 1;
}
/* SIGCHLD handler (reap zombies)
*/
static void chld_signal (sig)
{
int status;
while (waitpid(-1,&status,WNOHANG) > 0);
#ifdef HAVE_LINUX
/* Reset the signal handler.
*/
signal(SIGCHLD,chld_signal);
#endif
}
/* Debug message handler
*/
static void debug_msg(int lev, int pri, const char *format, ...) {
va_list ap;
char msg[512];
if (lev > debug_level) return;
va_start(ap,format);
#if HAVE_VSNPRINTF
vsnprintf(msg, 511, format, ap);
#else
# if HAVE_VPRINTF
# warning Using vsprintf instead of vsnprintf!
vsprintf(msg, format, ap);
# else
# error I need vsprintf or vsnprintf!
# endif
#endif
if ((use_syslog) && (debug_level < DEBUG_NOFORK))
syslog(pri, "%s", msg);
else fprintf(stderr,"[%d]\t%s\n", getpid(), msg);
va_end(ap);
}
/* ************************************************************************* */
#ifdef QUIZ_MODE
# define QSTR(b) (b+sizeof(char *))
# define QPTR(b) *((char **) b)
/* Save a line that was destined for output for later..
*/
int quiz_delayed = 0;
static void quiz_delay_line(char *line, char **first, char ***bl)
{
char *buf;
int linelen;
quiz_delayed += linelen = strlen(line);
if (quiz_delayed > QUIZ_MEMORY_HOG) exit(1);
if (*bl == NULL) *bl = first;
**bl = buf = malloc(sizeof(char *)+linelen+1);
strcpy(QSTR(buf), line);
QPTR(buf) = NULL;
*bl = &QPTR(buf);
#ifdef TIRC_DEBUG
debug_msg(DEBUG_TRIVIA, LOG_DEBUG, "Buffered: %.256s", line);
#endif
}
/* Print a line that was destined for output for later..
*/
static void quiz_dump_lines(int sock, char **first, char ***bl)
{
char *p;
if (*first == NULL) return;
do {
while (write(sock, QSTR(*first),
strlen(QSTR(*first))) < 0)
if (errno != EINTR)
{
#ifdef TIRC_DEBUG
debug_msg(DEBUG_BASIC, LOG_WARNING,
"write(%s) failed in quiz_dump_lines: %.128s",
strerror(errno));
#endif
return;
}
#ifdef TIRC_DEBUG
debug_msg(DEBUG_TRIVIA, LOG_DEBUG,
"Flushed: %.256s", QSTR(*first));
#endif
p = QPTR(*first);
free(*first);
*first = p;
} while (*first != NULL);
*bl = first;
}
/* Check the username%password supplied by the user.
*/
int quiz_passwd_checks = 0;
static void quiz_check_auth(char *pass)
{
if (quiz_passwd_checks++ > 3) exit(1);
if (use_unix_passwd)
{
char *u, *p, *n, salt[4];
struct passwd *pw;
if (((p = strchr(pass,'%')) != NULL) ||
((p = strchr(pass,' ')) != NULL))
{
u = pass;
*p++ = '\0';
}
else if (*user_name)
{
u = user_name;
p = pass;
}
else
{
quiz_msg(QUIZ_PASSWD_BAD);
return;
}
if ((n = strchr(p,'\n')) != NULL) *n = '\0';
if ((pw = getpwnam(u)) != NULL)
{
salt[0] = (pw->pw_passwd)[0];
salt[1] = (pw->pw_passwd)[1];
salt[2] = '\0';
if (!strcmp(pw->pw_passwd, crypt(p,salt)))
{
quiz_msg(QUIZ_PASSWD_OK);
use_quiz_mode = QUIZ_FLUSH;
bzero(p,strlen(p));
return;
}
}
#ifdef TIRC_DEBUG
debug_msg(DEBUG_FEATURES, LOG_AUTH,
"Incorrect password given for %s", u);
#endif
bzero(p,strlen(p));
sleep(quiz_passwd_checks*2);
quiz_msg(QUIZ_PASSWD_BAD);
}
else /* use_unix_passwd */
{
char *b, *p, buf[512], ok;
strcpy(buf,quiz);
b = p = buf;
ok = 0;
while ((p = strchr(p,':')) != NULL)
{
*p++ = '\0';
if (!strcasecmp(pass,b)) ok = 1;
b = p;
}
if (!strcasecmp(pass,b)) ok = 1;
if (ok)
{
quiz_msg(QUIZ_PASSWD_OK);
use_quiz_mode = QUIZ_FLUSH;
}
else
{
#ifdef TIRC_DEBUG
debug_msg(DEBUG_TRIVIA, LOG_DEBUG,
"Incorrect quiz response: %s", pass);
#endif
quiz_msg(QUIZ_ANSWER_BAD);
}
} /* else */
}
/* Send the user a message..
*/
static void quiz_msg(char *message)
{
char out[512];
sprintf(out,":%.32s!irc@proxy PRIVMSG %.32s :%.256s\n",
QUIZ_NICK, user_nick, message);
quiz_delay_line(out, QUIZ_C);
}
/* Greet the user when he connects..
*/
static void quiz_greet(void)
{
FILE *fd;
int i, r;
char question[512], buff[512], *p;
if (use_unix_passwd)
{
quiz_msg(QUIZ_PASSWD_WAIT);
return;
}
/* Not using /etc/passwd, select a question from the quizfile */
srand(time((time_t) NULL));
if ((fd = fopen(quizfile,"r")) == NULL)
{
debug_msg(0, LOG_DEBUG,
"Error (%.128s) reading quiz-file: %.128s",
strerror(errno), quizfile);
quiz_msg("Error opening quiz file. Notify the proxy administrator.");
return;
}
r = 0;
while (fgets(buff, 512, fd) != NULL)
if (*buff == '!')
quiz_msg(buff+1);
else
if (*buff != '#')
{
i = rand();
if ((i > r) || (!r))
{
r = i;
strcpy(question,buff);
}
}
fclose(fd);
if ((p = strchr(question, ':')) != NULL)
{
*p++ = '\0';
quiz_msg(question);
strcpy(quiz,p);
if ((p = strchr(quiz, '\n')) != NULL) *p = '\0';
}
else
{
debug_msg(0, LOG_DEBUG, "Invalid quiz: %.256s", question);
quiz_msg("Invalid quiz in file. Notify the proxy administrator.");
}
}
#endif
syntax highlighted by Code2HTML, v. 0.9.1