char *Version = "tircproxy v0.4.5, Copyleft 2000, Bjarni R. Einarsson " ; /*****************************************************************************\ * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* "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 #endif #if (HAVE_UDB_H && USE_UDB) # warning Using UDB style IPC for identd etc. # include # 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 # include # include # include # if HAVE_NETINET_IP_FIL_COMPAT_H # include # else # include # endif # include # include # include # include # include # include # include # 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 " " 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