/* * Unreal Internet Relay Chat Daemon, src/s_bsd.c * Copyright (C) 1990 Jarkko Oikarinen and * University of Oulu, Computing Center * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 1, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* -- Jto -- 07 Jul 1990 * Added jlp@hamblin.byu.edu's debugtty fix */ /* -- Armin -- Jun 18 1990 * Added setdtablesize() for more socket connections */ /* -- Jto -- 13 May 1990 * Added several fixes from msa: * Better error messages * Changes in check_access * Added SO_REUSEADDR fix from zessel@informatik.uni-kl.de */ #ifndef CLEAN_COMPILE static char sccsid[] = "@(#) 2.78 2/7/94 (C) 1988 University of Oulu, \ Computing Center and Jarkko Oikarinen"; #endif #include "struct.h" #include "common.h" #include "sys.h" #include "res.h" #include "numeric.h" #include "version.h" #ifndef _WIN32 #include #include #include #include #else #include #endif #if defined(_SOLARIS) #include #endif #include "inet.h" #include #include #include #ifdef AIX # include # include #else # include "nameser.h" #endif #include "resolv.h" #include "sock.h" /* If FD_ZERO isn't define up to this point, */ #include #include "proto.h" /* define it (BSD4.2 needs this) */ #include "h.h" #ifndef NO_FDLIST #include "fdlist.h" #endif #ifdef USE_POLL #include int rr; #endif #ifdef INET6 static unsigned char minus_one[] = { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0 }; #endif #ifndef IN_LOOPBACKNET #define IN_LOOPBACKNET 0x7f #endif #ifndef INADDRSZ #define INADDRSZ sizeof(struct IN_ADDR) #define IN6ADDRSZ sizeof(struct IN_ADDR) #endif #ifndef _WIN32 #define SET_ERRNO(x) errno = x #else #define SET_ERRNO(x) WSASetLastError(x) #endif /* _WIN32 */ extern char backupbuf[8192]; aClient *local[MAXCONNECTIONS]; short LastSlot = -1; /* GLOBAL - last used slot in local */ int OpenFiles = 0; /* GLOBAL - number of files currently open */ int readcalls = 0, resfd = -1; static struct SOCKADDR_IN mysk; static struct SOCKADDR *connect_inet(ConfigItem_link *, aClient *, int *); int completed_connection(aClient *); static int check_init(aClient *, char *, size_t); #ifndef _WIN32 static void do_dns_async(); void set_sock_opts(int, aClient *); #else void set_sock_opts(int, aClient *); #endif static char readbuf[8192]; char zlinebuf[BUFSIZE]; extern char *version; extern ircstats IRCstats; #ifndef NO_FDLIST extern fdlist default_fdlist; extern fdlist busycli_fdlist; extern fdlist serv_fdlist; extern fdlist oper_fdlist; extern fdlist socks_fdlist; #endif /* * Try and find the correct name to use with getrlimit() for setting the max. * number of files allowed to be open by this process. */ #ifdef RLIMIT_FDMAX # define RLIMIT_FD_MAX RLIMIT_FDMAX #else # ifdef RLIMIT_NOFILE # define RLIMIT_FD_MAX RLIMIT_NOFILE # else # ifdef RLIMIT_OPEN_MAX # define RLIMIT_FD_MAX RLIMIT_OPEN_MAX # else # undef RLIMIT_FD_MAX # endif # endif #endif void start_of_normal_client_handshake(aClient *acptr); /* winlocal */ void add_local_client(aClient* cptr) { int i; if (LastSlot >= (MAXCONNECTIONS-1)) { Debug((DEBUG_ERROR, "add_local_client() called when LastSlot >= MAXCONNECTIONS!")); cptr->slot = -1; return; } i = 0; while (local[i]) i++; cptr->slot = i; local[cptr->slot] = cptr; if (i > LastSlot) LastSlot = i; } void remove_local_client(aClient* cptr) { if (LastSlot < 0) { Debug((DEBUG_ERROR, "remove_local_client() called when LastSlot < 0!")); cptr->slot = -1; return; } /* Keep LastSlot as the last one */ local[cptr->slot] = NULL; cptr->slot = -1; while (!local[LastSlot]) LastSlot--; } void close_connections(void) { aClient* cptr; int i = LastSlot; for ( ; i >= 0; --i) { if ((cptr = local[i]) != 0) { if (cptr->fd >= 0) { CLOSE_SOCK(cptr->fd); cptr->fd = -2; } if (cptr->authfd >= 0) { CLOSE_SOCK(cptr->authfd); cptr->authfd = -1; } } } CLOSE_SOCK(resfd); resfd = -1; OpenFiles = 0; LastSlot = -1; #ifdef _WIN32 WSACleanup(); #endif } /* ** add_local_domain() ** Add the domain to hostname, if it is missing ** (as suggested by eps@TOASTER.SFSU.EDU) */ void add_local_domain(char *hname, int size) { #ifdef RES_INIT /* try to fix up unqualified names */ if (!index(hname, '.')) { if (!(ircd_res.options & RES_INIT)) { Debug((DEBUG_DNS, "res_init()")); ircd_res_init(); } if (ircd_res.defdname[0]) { (void)strncat(hname, ".", size - 1); (void)strncat(hname, ircd_res.defdname, size - 2); } } #endif return; } /* ** Cannot use perror() within daemon. stderr is closed in ** ircd and cannot be used. And, worse yet, it might have ** been reassigned to a normal connection... */ /* ** report_error ** This a replacement for perror(). Record error to log and ** also send a copy to all *LOCAL* opers online. ** ** text is a *format* string for outputting error. It must ** contain only two '%s', the first will be replaced ** by the sockhost from the cptr, and the latter will ** be taken from sys_errlist[errno]. ** ** cptr if not NULL, is the *LOCAL* client associated with ** the error. */ void report_error(char *text, aClient *cptr) { int errtmp = ERRNO; char *host; int err, len = sizeof(err); host = (cptr) ? get_client_name(cptr, FALSE) : ""; Debug((DEBUG_ERROR, text, host, strerror(errtmp))); /* * Get the *real* error from the socket (well try to anyway..). * This may only work when SO_DEBUG is enabled but its worth the * gamble anyway. */ #ifdef SO_ERROR if (cptr && !IsMe(cptr) && cptr->fd >= 0) if (!getsockopt(cptr->fd, SOL_SOCKET, SO_ERROR, (OPT_TYPE *)&err, &len)) if (err) errtmp = err; #endif sendto_snomask(SNO_JUNK, text, host, strerror(errtmp)); ircd_log(LOG_ERROR, text,host,strerror(errtmp)); return; } void report_baderror(char *text, aClient *cptr) { #ifndef _WIN32 int errtmp = errno; /* debug may change 'errno' */ #else int errtmp = WSAGetLastError(); /* debug may change 'errno' */ #endif char *host; int err, len = sizeof(err); host = (cptr) ? get_client_name(cptr, FALSE) : ""; /* fprintf(stderr, text, host, strerror(errtmp)); fputc('\n', stderr); */ Debug((DEBUG_ERROR, text, host, strerror(errtmp))); /* * Get the *real* error from the socket (well try to anyway..). * This may only work when SO_DEBUG is enabled but its worth the * gamble anyway. */ #ifdef SO_ERROR if (cptr && !IsMe(cptr) && cptr->fd >= 0) if (!getsockopt(cptr->fd, SOL_SOCKET, SO_ERROR, (OPT_TYPE *)&err, &len)) if (err) errtmp = err; #endif sendto_umode(UMODE_OPER, text, host, strerror(errtmp)); return; } /* * inetport * * Create a socket in the AFINET domain, bind it to the port given in * 'port' and listen to it. Connections are accepted to this socket * depending on the IP# mask given by 'name'. Returns the fd of the * socket created or -1 on error. */ int inetport(aClient *cptr, char *name, int port) { static struct SOCKADDR_IN server; int ad[4], len = sizeof(server); char ipname[20]; if (BadPtr(name)) name = "*"; ad[0] = ad[1] = ad[2] = ad[3] = 0; /* * do it this way because building ip# from separate values for each * byte requires endian knowledge or some nasty messing. Also means * easy conversion of "*" 0.0.0.0 or 134.* to 134.0.0.0 :-) */ #ifndef INET6 (void)sscanf(name, "%d.%d.%d.%d", &ad[0], &ad[1], &ad[2], &ad[3]); (void)ircsprintf(ipname, "%d.%d.%d.%d", ad[0], ad[1], ad[2], ad[3]); #else if (*name == '*') ircsprintf(ipname, "::"); else ircsprintf(ipname, "%s", name); #endif if (cptr != &me) { (void)ircsprintf(cptr->sockhost, "%-.42s.%.u", name, (unsigned int)port); (void)strlcpy(cptr->name, me.name, sizeof cptr->name); } /* * At first, open a new socket */ if (cptr->fd == -1) { cptr->fd = socket(AFINET, SOCK_STREAM, 0); } if (cptr->fd < 0) { #if !defined(DEBUGMODE) && !defined(_WIN32) #endif report_error("Cannot open stream socket() %s:%s", cptr); return -1; } else if (++OpenFiles >= MAXCLIENTS) { sendto_ops("No more connections allowed (%s)", cptr->name); CLOSE_SOCK(cptr->fd); cptr->fd = -1; --OpenFiles; return -1; } set_sock_opts(cptr->fd, cptr); /* * Bind a port to listen for new connections if port is non-null, * else assume it is already open and try get something from it. */ if (port) { server.SIN_FAMILY = AFINET; /* per-port bindings, fixes /stats l */ #ifndef INET6 server.SIN_ADDR.S_ADDR = inet_addr(ipname); #else inet_pton(AFINET, ipname, server.SIN_ADDR.S_ADDR); #endif server.SIN_PORT = htons(port); /* * Try 10 times to bind the socket with an interval of 20 * seconds. Do this so we dont have to keepp trying manually * to bind. Why ? Because a port that has closed often lingers * around for a short time. * This used to be the case. Now it no longer is. * Could cause the server to hang for too long - avalon */ if (bind(cptr->fd, (struct SOCKADDR *)&server, sizeof(server)) == -1) { ircsprintf(backupbuf, "Error binding stream socket to IP %s port %i", ipname, port); strlcat(backupbuf, "- %s:%s", sizeof backupbuf); report_error(backupbuf, cptr); CLOSE_SOCK(cptr->fd); cptr->fd = -1; --OpenFiles; return -1; } } if (getsockname(cptr->fd, (struct SOCKADDR *)&server, &len)) { report_error("getsockname failed for %s:%s", cptr); CLOSE_SOCK(cptr->fd); cptr->fd = -1; --OpenFiles; return -1; } #ifndef _WIN32 if (cptr == &me) /* KLUDGE to get it work... */ { char buf[1024]; (void)ircsprintf(buf, rpl_str(RPL_MYPORTIS), me.name, "*", ntohs(server.SIN_PORT)); (void)write(0, buf, strlen(buf)); } #endif #ifdef INET6 bcopy(server.sin6_addr.s6_addr, cptr->ip.s6_addr, IN6ADDRSZ); #else cptr->ip.S_ADDR = name ? inet_addr(ipname) : me.ip.S_ADDR; #endif cptr->port = (int)ntohs(server.SIN_PORT); (void)listen(cptr->fd, LISTEN_SIZE); add_local_client(cptr); return 0; } int add_listener2(ConfigItem_listen *conf) { aClient *cptr; cptr = make_client(NULL, NULL); cptr->flags = FLAGS_LISTEN; cptr->listener = cptr; cptr->from = cptr; SetMe(cptr); strncpyzt(cptr->name, conf->ip, sizeof(cptr->name)); if (inetport(cptr, conf->ip, conf->port)) cptr->fd = -2; cptr->class = (ConfigItem_class *)conf; cptr->umodes = conf->options ? conf->options : LISTENER_NORMAL; if (cptr->fd >= 0) { cptr->umodes |= LISTENER_BOUND; conf->options |= LISTENER_BOUND; conf->listener = cptr; set_non_blocking(cptr->fd, cptr); return 1; } else { free_client(cptr); return -1; } } /* * close_listeners * * Close and free all clients which are marked as having their socket open * and in a state where they can accept connections. */ void close_listeners(void) { aClient *cptr; int i, reloop = 1; ConfigItem_listen *aconf; /* * close all 'extra' listening ports we have */ while (reloop) { reloop = 0; for (i = LastSlot; i >= 0; i--) { if (!(cptr = local[i])) continue; if (!IsMe(cptr) || cptr == &me || !IsListening(cptr)) continue; aconf = (ConfigItem_listen *) cptr->class; if (aconf->flag.temporary && (aconf->clients == 0)) { close_connection(cptr); /* need to start over because close_connection() may have ** rearranged local[]! */ reloop = 1; } } } } /* * init_sys */ void init_sys(void) { int fd; #ifndef USE_POLL #ifdef RLIMIT_FD_MAX struct rlimit limit; if (!getrlimit(RLIMIT_FD_MAX, &limit)) { if (limit.rlim_max < MAXCONNECTIONS) { (void)fprintf(stderr, "The OS enforces a limit on max open files\n"); #ifndef LONG_LONG_RLIM_T (void)fprintf(stderr, "Hard Limit: %ld MAXCONNECTIONS: %d\n", #else (void)fprintf(stderr, "Hard Limit: %lld MAXCONNECTIONS: %d\n", #endif limit.rlim_max, MAXCONNECTIONS); (void)fprintf(stderr, "Fix MAXCONNECTIONS\n"); exit(-1); } limit.rlim_cur = limit.rlim_max; /* make soft limit the max */ if (setrlimit(RLIMIT_FD_MAX, &limit) == -1) { #ifndef LONG_LONG_RLIM_T (void)fprintf(stderr, "error setting max fd's to %ld\n", #else (void)fprintf(stderr, "error setting max fd's to %lld\n", #endif limit.rlim_cur); exit(-1); } } #endif #endif #ifndef _WIN32 if (MAXCONNECTIONS > FD_SETSIZE) { fprintf(stderr, "MAXCONNECTIONS (%d) is higher than FD_SETSIZE (%d)\n", MAXCONNECTIONS, FD_SETSIZE); fprintf(stderr, "You might need to recompile the IRCd, or if you're running Linux, read the release notes\n"); exit(-1); } #endif /* Startup message pid = getpid(); pid++; fprintf(stderr, "|---------------------------------------------\n"); fprintf(stderr, "| UnrealIRCD has successfully loaded.\n"); fprintf(stderr, "| Config Directory: %s\n", DPATH); fprintf(stderr, "| MAXCONNECTIONS set at %d\n", MAXCONNECTIONS); fprintf(stderr, "| Process ID: %d\n", pid); fprintf(stderr, "|---------------------------------------------\n"); */ #if defined(PCS) || defined(SVR3) char logbuf[BUFSIZ]; (void)setvbuf(stderr, logbuf, _IOLBF, sizeof(logbuf)); #else # if defined(HPUX) (void)setvbuf(stderr, NULL, _IOLBF, 0); # else # if !defined(_SOLARIS) && !defined(_WIN32) (void)setlinebuf(stderr); # endif # endif #endif #ifndef _WIN32 for (fd = 3; fd < MAXCONNECTIONS; fd++) { (void)close(fd); } (void)close(1); if (bootopt & BOOT_TTY) /* debugging is going to a tty */ goto init_dgram; if (!(bootopt & BOOT_DEBUG)) (void)close(2); if ((bootopt & BOOT_CONSOLE) || isatty(0)) { #ifndef _AMIGA /* if (fork()) exit(0); */ #endif #ifdef TIOCNOTTY if ((fd = open("/dev/tty", O_RDWR)) >= 0) { (void)ioctl(fd, TIOCNOTTY, (char *)NULL); (void)close(fd); } #endif #if defined(HPUX) || defined(_SOLARIS) || \ defined(_POSIX_SOURCE) || defined(SVR4) || defined(SGI) (void)setsid(); #else (void)setpgrp(0, (int)getpid()); #endif (void)close(0); /* fd 0 opened by inetd */ local[0] = NULL; } init_dgram: #else close(fileno(stdin)); close(fileno(stdout)); if (!(bootopt & BOOT_DEBUG)) close(fileno(stderr)); memset(local, 0, sizeof(aClient*) * MAXCONNECTIONS); LastSlot = -1; #endif /*_WIN32*/ resfd = init_resolver(0x1f); Debug((DEBUG_DNS, "resfd %d", resfd)); return; } void write_pidfile(void) { #ifdef IRCD_PIDFILE int fd; char buff[20]; if ((fd = open(IRCD_PIDFILE, O_CREAT | O_WRONLY, 0600)) >= 0) { bzero(buff, sizeof(buff)); (void)ircsprintf(buff, "%5d\n", (int)getpid()); if (write(fd, buff, strlen(buff)) == -1) Debug((DEBUG_NOTICE, "Error writing to pid file %s", IRCD_PIDFILE)); (void)close(fd); return; } #ifdef DEBUGMODE else Debug((DEBUG_NOTICE, "Error opening pid file %s", IRCD_PIDFILE)); #endif #endif } /* * Initialize the various name strings used to store hostnames. This is set * from either the server's sockhost (if client fd is a tty or localhost) * or from the ip# converted into a string. 0 = success, -1 = fail. */ static int check_init(aClient *cptr, char *sockn, size_t size) { struct SOCKADDR_IN sk; int len = sizeof(struct SOCKADDR_IN); /* If descriptor is a tty, special checking... */ #ifndef _WIN32 if (isatty(cptr->fd)) #else if (0) #endif { strncpyzt(sockn, me.sockhost, HOSTLEN); bzero((char *)&sk, sizeof(struct SOCKADDR_IN)); } else if (getpeername(cptr->fd, (struct SOCKADDR *)&sk, &len) == -1) { report_error("connect failure: %s %s", cptr); return -1; } (void)strlcpy(sockn, (char *)Inet_si2p(&sk), size); #ifdef INET6 if (IN6_IS_ADDR_LOOPBACK(&sk.SIN_ADDR)) #else if (inet_netof(sk.SIN_ADDR) == IN_LOOPBACKNET) #endif { cptr->hostp = NULL; strncpyzt(sockn, "localhost", HOSTLEN); } bcopy((char *)&sk.SIN_ADDR, (char *)&cptr->ip, sizeof(struct IN_ADDR)); cptr->port = (int)ntohs(sk.SIN_PORT); return 0; } /* * Ordinary client access check. Look for conf lines which have the same * status as the flags passed. * 0 = Success * -1 = Access denied * -2 = Bad socket. */ int check_client(aClient *cptr) { static char sockname[HOSTLEN + 1]; struct hostent *hp = NULL; int i; ClearAccess(cptr); Debug((DEBUG_DNS, "ch_cl: check access for %s[%s]", cptr->name, inetntoa((char *)&cptr->ip))); if (check_init(cptr, sockname, sizeof(sockname))) return -2; hp = cptr->hostp; /* * Verify that the host to ip mapping is correct both ways and that * the ip#(s) for the socket is listed for the host. */ if (hp) { for (i = 0; hp->h_addr_list[i]; i++) if (!bcmp(hp->h_addr_list[i], (char *)&cptr->ip, sizeof(struct IN_ADDR))) break; if (!hp->h_addr_list[i]) { sendto_snomask(SNO_JUNK, "IP# Mismatch: %s != %s[%08x]", Inet_ia2p((struct IN_ADDR *)&cptr->ip), hp->h_name, *((unsigned long *)hp->h_addr)); hp = NULL; } } if ((i = AllowClient(cptr, hp, sockname))) { Debug((DEBUG_DNS, "ch_cl: access denied: %s[%s]", cptr->name, sockname)); return i; } Debug((DEBUG_DNS, "ch_cl: access ok: %s[%s]", cptr->name, sockname)); #ifdef INET6 if (IN6_IS_ADDR_LOOPBACK(&cptr->ip) || (cptr->ip.s6_addr[0] == mysk.sin6_addr.s6_addr[0] && cptr->ip.s6_addr[1] == mysk.sin6_addr.s6_addr[1]) /* || IN6_ARE_ADDR_SAMEPREFIX(&cptr->ip, &mysk.SIN_ADDR)) about the same, I think NOT */ ) #else if (inet_netof(cptr->ip) == IN_LOOPBACKNET || inet_netof(cptr->ip) == inet_netof(mysk.SIN_ADDR)) #endif { ircstp->is_loc++; cptr->flags |= FLAGS_LOCAL; } return 0; } /* ** completed_connection ** Complete non-blocking connect()-sequence. Check access and ** terminate connection, if trouble detected. ** ** Return TRUE, if successfully completed ** FALSE, if failed and ClientExit */ int completed_connection(aClient *cptr) { ConfigItem_link *aconf = cptr->serv ? cptr->serv->conf : NULL; extern char serveropts[]; SetHandshake(cptr); if (!aconf) { sendto_ops("Lost configuration for %s", get_client_name(cptr, FALSE)); return -1; } if (!BadPtr(aconf->connpwd)) sendto_one(cptr, "PASS :%s", aconf->connpwd); sendto_one(cptr, "PROTOCTL %s", PROTOCTL_SERVER); sendto_one(cptr, "SERVER %s 1 :U%d-%s%s-%i %s", me.name, UnrealProtocol, serveropts, extraflags ? extraflags : "", me.serv->numeric, me.info); if (!IsDead(cptr)) start_auth(cptr); return (IsDead(cptr)) ? -1 : 0; } /* ** close_connection ** Close the physical connection. This function must make ** MyConnect(cptr) == FALSE, and set cptr->from == NULL. */ void close_connection(aClient *cptr) { ConfigItem_link *aconf; #ifdef DO_REMAPPING int i, j; int empty = cptr->fd; #endif if (IsServer(cptr)) { ircstp->is_sv++; ircstp->is_sbs += cptr->sendB; ircstp->is_sbr += cptr->receiveB; ircstp->is_sks += cptr->sendK; ircstp->is_skr += cptr->receiveK; ircstp->is_sti += TStime() - cptr->firsttime; if (ircstp->is_sbs > 1023) { ircstp->is_sks += (ircstp->is_sbs >> 10); ircstp->is_sbs &= 0x3ff; } if (ircstp->is_sbr > 1023) { ircstp->is_skr += (ircstp->is_sbr >> 10); ircstp->is_sbr &= 0x3ff; } } else if (IsClient(cptr)) { ircstp->is_cl++; ircstp->is_cbs += cptr->sendB; ircstp->is_cbr += cptr->receiveB; ircstp->is_cks += cptr->sendK; ircstp->is_ckr += cptr->receiveK; ircstp->is_cti += TStime() - cptr->firsttime; if (ircstp->is_cbs > 1023) { ircstp->is_cks += (ircstp->is_cbs >> 10); ircstp->is_cbs &= 0x3ff; } if (ircstp->is_cbr > 1023) { ircstp->is_ckr += (ircstp->is_cbr >> 10); ircstp->is_cbr &= 0x3ff; } } else ircstp->is_ni++; /* * remove outstanding DNS queries. */ del_queries((char *)cptr); /* * If the connection has been up for a long amount of time, schedule * a 'quick' reconnect, else reset the next-connect cycle. * * Now just hold on a minute. We're currently doing this when a * CLIENT exits too? I don't think so! If its not a server, or * the SQUIT flag has been set, then we don't schedule a fast * reconnect. Pisses off too many opers. :-) -Cabal95 */ if (IsServer(cptr) && !(cptr->flags & FLAGS_SQUIT) && (!cptr->serv->conf->flag.temporary && (cptr->serv->conf->options & CONNECT_AUTO))) { aconf = cptr->serv->conf; /* * Reschedule a faster reconnect, if this was a automaticly * connected configuration entry. (Note that if we have had * a rehash in between, the status has been changed to * CONF_ILLEGAL). But only do this if it was a "good" link. */ aconf->hold = TStime(); aconf->hold += (aconf->hold - cptr->since > HANGONGOODLINK) ? HANGONRETRYDELAY : aconf->class->connfreq; if (nextconnect > aconf->hold) nextconnect = aconf->hold; } if (cptr->authfd >= 0) { CLOSE_SOCK(cptr->authfd); cptr->authfd = -1; --OpenFiles; } if (cptr->fd >= 0) { flush_connections(cptr); remove_local_client(cptr); #ifdef USE_SSL if (IsSSL(cptr) && cptr->ssl) { SSL_set_shutdown((SSL *)cptr->ssl, SSL_RECEIVED_SHUTDOWN); SSL_smart_shutdown((SSL *)cptr->ssl); SSL_free((SSL *)cptr->ssl); cptr->ssl = NULL; } #endif CLOSE_SOCK(cptr->fd); cptr->fd = -2; --OpenFiles; DBufClear(&cptr->sendQ); DBufClear(&cptr->recvQ); } cptr->from = NULL; /* ...this should catch them! >:) --msa */ /* * fd remap to keep local[i] filled at the bottom. */ #if 0 #ifdef DO_REMAPPING if (empty > 0) if ((j = LastSlot) > (i = empty) && (local[j]->status != STAT_LOG)) { if (dup2(j, i) == -1) return; local[i] = local[j]; local[i]->fd = i; #ifdef USE_SSL /* I didn't know the code above existed, which fucked up SSL -Stskeeps */ if ((local[i]->flags & FLAGS_SSL) && local[i]->ssl) { /* !! RISKY !! --Stskeeps */ SSL_change_fd((SSL *) local[i]->ssl, local[i]->fd); } #endif local[j] = NULL; #ifndef NO_FDLIST /* update server list */ if (IsServer(local[i])) { delfrom_fdlist(j, &busycli_fdlist); delfrom_fdlist(j, &serv_fdlist); addto_fdlist(i, &busycli_fdlist); addto_fdlist(i, &serv_fdlist); } if (IsAnOper(local[i])) { delfrom_fdlist(j, &busycli_fdlist); delfrom_fdlist(j, &oper_fdlist); addto_fdlist(i, &busycli_fdlist); addto_fdlist(i, &oper_fdlist); } #endif CLOSE_SOCK(j); --OpenFiles; } #endif #endif return; } /* ** set_sock_opts */ void set_sock_opts(int fd, aClient *cptr) { int opt; #ifdef SO_REUSEADDR opt = 1; if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (OPT_TYPE *)&opt, sizeof(opt)) < 0) report_error("setsockopt(SO_REUSEADDR) %s:%s", cptr); #endif #if defined(SO_DEBUG) && defined(DEBUGMODE) && 0 /* Solaris with SO_DEBUG writes to syslog by default */ #if !defined(_SOLARIS) || defined(HAVE_SYSLOG) opt = 1; if (setsockopt(fd, SOL_SOCKET, SO_DEBUG, (OPT_TYPE *)&opt, sizeof(opt)) < 0) report_error("setsockopt(SO_DEBUG) %s:%s", cptr); #endif /* _SOLARIS */ #endif #if defined(SO_USELOOPBACK) && !defined(_WIN32) opt = 1; if (setsockopt(fd, SOL_SOCKET, SO_USELOOPBACK, (OPT_TYPE *)&opt, sizeof(opt)) < 0) report_error("setsockopt(SO_USELOOPBACK) %s:%s", cptr); #endif #ifdef SO_RCVBUF opt = 8192; if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (OPT_TYPE *)&opt, sizeof(opt)) < 0) report_error("setsockopt(SO_RCVBUF) %s:%s", cptr); #endif #ifdef SO_SNDBUF # ifdef _SEQUENT_ /* seems that Sequent freezes up if the receving buffer is a different size * to the sending buffer (maybe a tcp window problem too). */ opt = 8192; # else opt = 8192; # endif if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (OPT_TYPE *)&opt, sizeof(opt)) < 0) report_error("setsockopt(SO_SNDBUF) %s:%s", cptr); #endif #if defined(IP_OPTIONS) && defined(IPPROTO_IP) && !defined(_WIN32) && !defined(INET6) { char *s = readbuf, *t = readbuf + sizeof(readbuf) / 2; opt = sizeof(readbuf) / 8; if (getsockopt(fd, IPPROTO_IP, IP_OPTIONS, (OPT_TYPE *)t, &opt) < 0) report_error("getsockopt(IP_OPTIONS) %s:%s", cptr); else if (opt > 0 && opt != sizeof(readbuf) / 8) { for (*readbuf = '\0'; opt > 0; opt--, s += 3) (void)ircsprintf(s, "%2.2x:", *t++); *s = '\0'; sendto_ops("Connection %s using IP opts: (%s)", get_client_name(cptr, TRUE), readbuf); } if (setsockopt(fd, IPPROTO_IP, IP_OPTIONS, (OPT_TYPE *)NULL, 0) < 0) report_error("setsockopt(IP_OPTIONS) %s:%s", cptr); } #endif } int get_sockerr(aClient *cptr) { #ifndef _WIN32 int errtmp = errno, err = 0, len = sizeof(err); #else int errtmp = WSAGetLastError(), err = 0, len = sizeof(err); #endif #ifdef SO_ERROR if (cptr->fd >= 0) if (!getsockopt(cptr->fd, SOL_SOCKET, SO_ERROR, (OPT_TYPE *)&err, &len)) if (err) errtmp = err; #endif return errtmp; } /* * set_blocking - Set the client connection into non-blocking mode. * If your system doesn't support this, you're screwed, ircd will run like * crap. * returns true (1) if successful, false (0) otherwise */ int set_blocking(int fd) { int flags; #ifdef _WIN32 int nonb; #endif #ifndef _WIN32 if ((flags = fcntl(fd, F_GETFL, 0)) < 0 || fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) < 0) #else nonb = 0; if (ioctlsocket(fd, FIONBIO, &nonb) < 0) #endif return 0; else return 1; } /* ** set_non_blocking ** Set the client connection into non-blocking mode. If your ** system doesn't support this, you can make this a dummy ** function (and get all the old problems that plagued the ** blocking version of IRC--not a problem if you are a ** lightly loaded node...) */ void set_non_blocking(int fd, aClient *cptr) { int res, nonb = 0; /* ** NOTE: consult ALL your relevant manual pages *BEFORE* changing ** these ioctl's. There are quite a few variations on them, ** as can be seen by the PCS one. They are *NOT* all the same. ** Heed this well. - Avalon. */ #ifdef NBLOCK_POSIX nonb |= O_NONBLOCK; #endif #ifdef NBLOCK_BSD nonb |= O_NDELAY; #endif #ifdef NBLOCK_SYSV /* This portion of code might also apply to NeXT. -LynX */ res = 1; if (ioctl(fd, FIONBIO, &res) < 0) { if (cptr) report_error("ioctl(fd,FIONBIO) failed for %s:%s", cptr); } #else # if !defined(_WIN32) if ((res = fcntl(fd, F_GETFL, 0)) == -1) { if (cptr) { report_error("fcntl(fd, F_GETFL) failed for %s:%s", cptr); } } else if (fcntl(fd, F_SETFL, res | nonb) == -1) { if (cptr) { report_error("fcntl(fd, F_SETL, nonb) failed for %s:%s", cptr); } } # else nonb = 1; if (ioctlsocket(fd, FIONBIO, &nonb) < 0) { if (cptr) { report_error("ioctlsocket(fd,FIONBIO) failed for %s:%s", cptr); } } # endif #endif return; } /* * Creates a client which has just connected to us on the given fd. * The sockhost field is initialized with the ip# of the host. * The client is added to the linked list of clients but isnt added to any * hash tables yuet since it doesnt have a name. */ aClient *add_connection(aClient *cptr, int fd) { aClient *acptr; ConfigItem_ban *bconf; int i, j; acptr = make_client(NULL, &me); /* Removed preliminary access check. Full check is performed in * m_server and m_user instead. Also connection time out help to * get rid of unwanted connections. */ #ifndef _WIN32 if (isatty(fd)) /* If descriptor is a tty, special checking... */ #else if (0) #endif get_sockhost(acptr, cptr->sockhost); else { struct SOCKADDR_IN addr; int len = sizeof(struct SOCKADDR_IN); if (getpeername(fd, (struct SOCKADDR *)&addr, &len) == -1) { report_error("Failed in connecting to %s :%s", cptr); add_con_refuse: ircstp->is_ref++; acptr->fd = -2; free_client(acptr); CLOSE_SOCK(fd); --OpenFiles; return NULL; } /* don't want to add "Failed in connecting to" here.. */ /* Copy ascii address to 'sockhost' just in case. Then we * have something valid to put into error messages... */ get_sockhost(acptr, Inet_si2p(&addr)); bcopy((char *)&addr.SIN_ADDR, (char *)&acptr->ip, sizeof(struct IN_ADDR)); j = 1; for (i = LastSlot; i >= 0; i--) { if (local[i] && IsUnknown(local[i]) && #ifndef INET6 local[i]->ip.S_ADDR == acptr->ip.S_ADDR) #else !bcmp(local[i]->ip.S_ADDR, cptr->ip.S_ADDR, sizeof(cptr->ip.S_ADDR))) #endif { j++; if (j > MAXUNKNOWNCONNECTIONSPERIP) { ircsprintf(zlinebuf, "ERROR :Closing Link: [%s] (Too many unknown connections from your IP)" "\r\n", inetntoa((char *)&acptr->ip)); set_non_blocking(fd, acptr); set_sock_opts(fd, acptr); send(fd, zlinebuf, strlen(zlinebuf), 0); goto add_con_refuse; } } } if ((bconf = Find_ban(acptr->sockhost, CONF_BAN_IP))) { if (bconf) { ircsprintf(zlinebuf, "ERROR :Closing Link: [%s] (You are not welcome on " "this server: %s. Email %s for more information.)\r\n", inetntoa((char *)&acptr->ip), bconf->reason ? bconf->reason : "no reason", KLINE_ADDRESS); set_non_blocking(fd, acptr); set_sock_opts(fd, acptr); send(fd, zlinebuf, strlen(zlinebuf), 0); goto add_con_refuse; } } else if (find_tkline_match_zap(acptr) != -1) { set_non_blocking(fd, acptr); set_sock_opts(fd, acptr); send(fd, zlinebuf, strlen(zlinebuf), 0); goto add_con_refuse; } acptr->port = ntohs(addr.SIN_PORT); } acptr->fd = fd; add_local_client(acptr); acptr->listener = cptr; if (!acptr->listener->class) { sendto_ops("ERROR: !acptr->listener->class"); } else { ((ConfigItem_listen *) acptr->listener->class)->clients++; } add_client_to_list(acptr); set_non_blocking(acptr->fd, acptr); set_sock_opts(acptr->fd, acptr); IRCstats.unknown++; #ifdef USE_SSL if (cptr->umodes & LISTENER_SSL) { SetSSLAcceptHandshake(acptr); Debug((DEBUG_DEBUG, "Starting SSL accept handshake for %s", acptr->sockhost)); if ((acptr->ssl = SSL_new(ctx_server)) == NULL) { goto add_con_refuse; } acptr->flags |= FLAGS_SSL; SSL_set_fd(acptr->ssl, fd); SSL_set_nonblocking(acptr->ssl); if (!ircd_SSL_accept(acptr, fd)) { Debug((DEBUG_DEBUG, "Failed SSL accept handshake in instance 1: %s", acptr->sockhost)); SSL_set_shutdown(acptr->ssl, SSL_RECEIVED_SHUTDOWN); SSL_smart_shutdown(acptr->ssl); SSL_free(acptr->ssl); goto add_con_refuse; } } else #endif start_of_normal_client_handshake(acptr); return acptr; } void start_of_normal_client_handshake(aClient *acptr) { Link lin; acptr->status = STAT_UNKNOWN; if (SHOWCONNECTINFO) { sendto_one(acptr, REPORT_DO_DNS); } lin.flags = ASYNC_CLIENT; lin.value.cptr = acptr; Debug((DEBUG_DNS, "lookup %s", acptr->sockhost)); acptr->hostp = gethost_byaddr((char *)&acptr->ip, &lin); if (!acptr->hostp) SetDNS(acptr); else { if (SHOWCONNECTINFO) sendto_one(acptr, REPORT_FIN_DNSC); } nextdnscheck = 1; start_auth(acptr); } /* ** read_packet ** ** Read a 'packet' of data from a connection and process it. Read in 8k ** chunks to give a better performance rating (for server connections). ** Do some tricky stuff for client connections to make sure they don't do ** any flooding >:-) -avalon */ #ifndef USE_POLL static int read_packet(aClient *cptr, fd_set *rfd) { int dolen = 0, length = 0, done; time_t now = TStime(); if (FD_ISSET(cptr->fd, rfd) && !(IsPerson(cptr) && DBufLength(&cptr->recvQ) > 6090)) { SET_ERRNO(0); #ifdef USE_SSL if (cptr->flags & FLAGS_SSL) length = ircd_SSL_read(cptr, readbuf, sizeof(readbuf)); else #endif length = recv(cptr->fd, readbuf, sizeof(readbuf), 0); cptr->lasttime = now; if (cptr->lasttime > cptr->since) cptr->since = cptr->lasttime; cptr->flags &= ~(FLAGS_PINGSENT | FLAGS_NONL); /* * If not ready, fake it so it isnt closed */ if (length < 0 && ERRNO == P_EWOULDBLOCK) return 1; if (length <= 0) return length; } /* ** For server connections, we process as many as we can without ** worrying about the time of day or anything :) */ if (IsServer(cptr) || IsConnecting(cptr) || IsHandshake(cptr)) { if (length > 0) if ((done = dopacket(cptr, readbuf, length))) return done; } else { /* ** Before we even think of parsing what we just read, stick ** it on the end of the receive queue and do it when its ** turn comes around. */ if (dbuf_put(&cptr->recvQ, readbuf, length) < 0) return exit_client(cptr, cptr, cptr, "dbuf_put fail"); if (IsPerson(cptr) && DBufLength(&cptr->recvQ) > CLIENT_FLOOD) { sendto_snomask(SNO_FLOOD, "*** Flood -- %s!%s@%s (%d) exceeds %d recvQ", cptr->name[0] ? cptr->name : "*", cptr->user ? cptr->user->username : "*", cptr->user ? cptr->user->realhost : "*", DBufLength(&cptr->recvQ), CLIENT_FLOOD); return exit_client(cptr, cptr, cptr, "Excess Flood"); } while (DBufLength(&cptr->recvQ) && !NoNewLine(cptr) && ((cptr->status < STAT_UNKNOWN) || (cptr->since - now < 10))) { /* ** If it has become registered as a Service or Server ** then skip the per-message parsing below. */ if (IsServer(cptr)) { dolen = dbuf_get(&cptr->recvQ, readbuf, sizeof(readbuf)); if (dolen <= 0) break; if ((done = dopacket(cptr, readbuf, dolen))) return done; break; } dolen = dbuf_getmsg(&cptr->recvQ, readbuf, sizeof(readbuf)); /* ** Devious looking...whats it do ? well..if a client ** sends a *long* message without any CR or LF, then ** dbuf_getmsg fails and we pull it out using this ** loop which just gets the next 512 bytes and then ** deletes the rest of the buffer contents. ** -avalon */ while (dolen <= 0) { if (dolen < 0) return exit_client(cptr, cptr, cptr, "dbuf_getmsg fail"); if (DBufLength(&cptr->recvQ) < 510) { cptr->flags |= FLAGS_NONL; break; } dolen = dbuf_get(&cptr->recvQ, readbuf, 511); if (dolen > 0 && DBufLength(&cptr->recvQ)) DBufClear(&cptr->recvQ); } if (dolen > 0 && (dopacket(cptr, readbuf, dolen) == FLUSH_BUFFER)) return FLUSH_BUFFER; } } return 1; } #else /* handle taking care of the client's recvq here */ static int do_client_queue(aClient *cptr) { int dolen = 0, done; while (DBufLength(&cptr->recvQ) && !NoNewLine(cptr) && ((cptr->status < STAT_UNKNOWN) || (cptr->since - now < 10))) { /* If it's become registered as a server, just parse the whole block */ if (IsServer(cptr)) { dolen = dbuf_get(&cptr->recvQ, readbuf, sizeof(readbuf)); if (dolen <= 0) break; if ((done = dopacket(cptr, readbuf, dolen))) return done; break; } #if defined(MAXBUFFERS) dolen = dbuf_getmsg(&cptr->recvQ, readbuf, rcvbufmax * sizeof(char)); #else dolen = dbuf_getmsg(&cptr->recvQ, readbuf, sizeof(readbuf)); #endif if (dolen <= 0) { if (dolen < 0) return exit_client(cptr, cptr, cptr, "dbuf_getmsg fail"); if (DBufLength(&cptr->recvQ) < 510) { cptr->flags |= FLAGS_NONL; break; } /* The buffer is full (more than 512 bytes) and it has no \n * Some user is trying to trick us. Kill their recvq. */ DBufClear(&cptr->recvQ); break; } else if (dopacket(cptr, readbuf, dolen) == FLUSH_BUFFER) return FLUSH_BUFFER; } return 1; } #define MAX_CLIENT_RECVQ 8192 /* 4 dbufs */ static int read_packet(aClient *cptr) { int length = 0, done; /* If data is ready, and the user is either not a person or * is a person and has a recvq of less than MAX_CLIENT_RECVQ, * read from this client */ if (!(IsPerson(cptr) && DBufLength(&cptr->recvQ) > MAX_CLIENT_RECVQ)) { errno = 0; #ifdef USE_SSL if (cptr->flags & FLAGS_SSL) length = ircd_SSL_read((SSL *)cptr->ssl, readbuf, sizeof(readbuf)); else #endif length = recv(cptr->fd, readbuf, sizeof(readbuf), 0); cptr->lasttime = now; if (cptr->lasttime > cptr->since) cptr->since = cptr->lasttime; cptr->flags &= ~(FLAGS_PINGSENT | FLAGS_NONL); /* * If not ready, fake it so it isnt closed */ if (length < 0 && ((ERRNO == P_EWOULDBLOCK) || ERRNO == P_EAGAIN))) return 1; if (length <= 0) return length; } /* * For server connections, we process as many as we can without * worrying about the time of day or anything :) */ if (IsServer(cptr) || IsConnecting(cptr) || IsHandshake(cptr)) { if (length > 0) if ((done = dopacket(cptr, readbuf, length))) return done; } else { /* * Before we even think of parsing what we just read, stick * it on the end of the receive queue and do it when its turn * comes around. */ if (dbuf_put(&cptr->recvQ, readbuf, length) < 0) return exit_client(cptr, cptr, cptr, "dbuf_put fail"); if (IsPerson(cptr) && #ifdef NO_OPER_FLOOD !IsAnOper(cptr) && #endif DBufLength(&cptr->recvQ) > CLIENT_FLOOD) { sendto_snomask(SNO_FLOOD, "Flood -- %s!%s@%s (%d) Exceeds %d RecvQ", cptr->name[0] ? cptr->name : "*", cptr->user ? cptr->user->username : "*", cptr->user ? cptr->user->realhost : "*", DBufLength(&cptr->recvQ), CLIENT_FLOOD); return exit_client(cptr, cptr, cptr, "Excess Flood"); } return do_client_queue(cptr); } return 1; } #endif /* * Check all connections for new connections and input data that is to be * processed. Also check for connections with data queued and whether we can * write it out. */ #ifndef USE_POLL #ifdef NO_FDLIST int read_message(time_t delay) #else int read_message(time_t delay, fdlist *listp) #endif { /* #undef FD_SET(x,y) do { if (fcntl(x, F_GETFD, &sockerr) == -1) abort(); FD_SET(x,y); } while(0) */ aClient *cptr; int nfds; struct timeval wait; #ifndef _WIN32 fd_set read_set, write_set; #else fd_set read_set, write_set, excpt_set; #endif int j; time_t delay2 = delay, now; int res, length, fd, i; int auth = 0; int sockerr; #ifndef NO_FDLIST /* if it is called with NULL we check all active fd's */ if (!listp) { listp = &default_fdlist; listp->last_entry = LastSlot == -1 ? LastSlot : LastSlot + 1; } #endif now = TStime(); for (res = 0;;) { FD_ZERO(&read_set); FD_ZERO(&write_set); #ifdef _WIN32 FD_ZERO(&excpt_set); #endif #ifdef NO_FDLIST for (i = LastSlot; i >= 0; i--) #else for (i = listp->entry[j = 1]; j <= listp->last_entry; i = listp->entry[++j]) #endif { if (!(cptr = local[i])) continue; if (IsLog(cptr)) continue; if (DoingAuth(cptr)) { auth++; Debug((DEBUG_NOTICE, "auth on %x %d", cptr, i)); if (cptr->authfd >= 0) { FD_SET(cptr->authfd, &read_set); #ifdef _WIN32 FD_SET(cptr->authfd, &excpt_set); #endif if (cptr->flags & FLAGS_WRAUTH) FD_SET(cptr->authfd, &write_set); } } if (DoingDNS(cptr) || DoingAuth(cptr) ) continue; if (IsMe(cptr) && IsListening(cptr)) { if (cptr->fd >= 0) FD_SET(cptr->fd, &read_set); } else if (!IsMe(cptr)) { if (DBufLength(&cptr->recvQ) && delay2 > 2) delay2 = 1; if ((cptr->fd >= 0) && (DBufLength(&cptr->recvQ) < 4088)) FD_SET(cptr->fd, &read_set); } if ((cptr->fd >= 0) && (DBufLength(&cptr->sendQ) || IsConnecting(cptr) || (DoList(cptr) && IsSendable(cptr)))) { FD_SET(cptr->fd, &write_set); } } #ifndef _WIN32 if (resfd >= 0) FD_SET(resfd, &read_set); #endif if (me.fd >= 0) FD_SET(me.fd, &read_set); wait.tv_sec = MIN(delay, delay2); wait.tv_usec = 0; #ifdef HPUX nfds = select(MAXCONNECTIONS, (int *)&read_set, (int *)&write_set, 0, &wait); #else # ifndef _WIN32 nfds = select(MAXCONNECTIONS, &read_set, &write_set, 0, &wait); # else nfds = select(MAXCONNECTIONS, &read_set, &write_set, &excpt_set, &wait); # endif #endif if (nfds == -1 && ((ERRNO == P_EINTR) || (ERRNO == P_ENOTSOCK))) return -1; else if (nfds >= 0) break; report_error("select %s:%s", &me); res++; if (res > 5) restart("too many select errors"); #ifndef _WIN32 sleep(10); #else Sleep(10); #endif } #ifndef _WIN32 if (resfd >= 0 && FD_ISSET(resfd, &read_set)) { Debug((DEBUG_DNS, "Doing DNS async..")); do_dns_async(); nfds--; FD_CLR(resfd, &read_set); } #endif /* * Check fd sets for the auth fd's (if set and valid!) first * because these can not be processed using the normal loops below. * -avalon */ #ifdef NO_FDLIST for (i = LastSlot; (auth > 0) && (i >= 0); i--) #else for (i = listp->entry[j = 1]; j <= listp->last_entry; i = listp->entry[++j]) #endif { if (!(cptr = local[i])) continue; if (cptr->authfd < 0) continue; auth--; #ifdef _WIN32 /* * Because of the way windows uses select(), we have to use * the exception FD set to find out when a connection is * refused. ie Auth ports and /connect's. -Cabal95 */ if (FD_ISSET(cptr->authfd, &excpt_set)) { int err, len = sizeof(err); if (getsockopt(cptr->authfd, SOL_SOCKET, SO_ERROR, (OPT_TYPE *)&err, &len) || err) { ircstp->is_abad++; closesocket(cptr->authfd); cptr->authfd = -1; --OpenFiles; cptr->flags &= ~(FLAGS_AUTH | FLAGS_WRAUTH); if (!DoingDNS(cptr)) SetAccess(cptr); if (nfds > 0) nfds--; continue; } } #endif if (nfds > 0) { if (FD_ISSET(cptr->authfd, &read_set) || FD_ISSET(cptr->authfd, &write_set)) nfds--; if ((cptr->authfd > 0) && FD_ISSET(cptr->authfd, &write_set)) { send_authports(cptr); } if ((cptr->authfd > 0) && FD_ISSET(cptr->authfd, &read_set)) { read_authports(cptr); } } } #ifdef NO_FDLIST for (i = LastSlot; i >= 0; i--) #else for (i = listp->entry[j = 1]; (j <= listp->last_entry); i = listp->entry[++j]) #endif if ((cptr = local[i]) && FD_ISSET(cptr->fd, &read_set) && IsListening(cptr)) { FD_CLR(cptr->fd, &read_set); nfds--; cptr->lasttime = TStime(); /* ** There may be many reasons for error return, but ** in otherwise correctly working environment the ** probable cause is running out of file descriptors ** (EMFILE, ENFILE or others?). The man pages for ** accept don't seem to list these as possible, ** although it's obvious that it may happen here. ** Thus no specific errors are tested at this ** point, just assume that connections cannot ** be accepted until some old is closed first. */ if ((fd = accept(cptr->fd, NULL, NULL)) < 0) { if (ERRNO != P_EWOULDBLOCK) report_error("Cannot accept connections %s:%s", cptr); break; } ircstp->is_ac++; if (++OpenFiles >= MAXCLIENTS) { ircstp->is_ref++; sendto_ops("All connections in use. (%s)", get_client_name(cptr, TRUE)); #ifndef INET6 (void)send(fd, "ERROR :All connections in use\r\n", 32, 0); #else (void)sendto(fd, "ERROR :All connections in use\r\n", 32, 0, 0, 0); #endif CLOSE_SOCK(fd); --OpenFiles; break; } /* * Use of add_connection (which never fails :) meLazy */ (void)add_connection(cptr, fd); nextping = TStime(); if (!cptr->listener) cptr->listener = &me; } #ifndef NO_FDLIST for (i = listp->entry[j = 1]; (j <= listp->last_entry); i = listp->entry[++j]) #else for (i = LastSlot; i >= 0; i--) #endif { if (!(cptr = local[i]) || IsMe(cptr)) continue; if (FD_ISSET(cptr->fd, &write_set)) { int write_err = 0; /* ** ...room for writing, empty some queue then... */ ClearBlocked(cptr); if (IsConnecting(cptr)) { #ifdef USE_SSL if ((cptr->serv) && (cptr->serv->conf->options & CONNECT_SSL)) { Debug((DEBUG_DEBUG, "ircd_SSL_client_handshake(%s)", cptr->name)); write_err = ircd_SSL_client_handshake(cptr); } else #endif write_err = completed_connection(cptr); } if (!write_err) { if (DoList(cptr) && IsSendable(cptr)) send_list(cptr, 32); (void)send_queued(cptr); } if (IsDead(cptr) || write_err) { deadsocket: if (FD_ISSET(cptr->fd, &read_set)) { nfds--; FD_CLR(cptr->fd, &read_set); } (void)exit_client(cptr, cptr, &me, ((sockerr = get_sockerr(cptr)) ? strerror(sockerr) : "Client exited")); continue; } } length = 1; /* for fall through case */ if ((!NoNewLine(cptr) || FD_ISSET(cptr->fd, &read_set)) #ifdef USE_SSL && !(IsSSLAcceptHandshake(cptr) || IsSSLConnectHandshake(cptr)) #endif ) length = read_packet(cptr, &read_set); #ifdef USE_SSL if ((cptr->ssl != NULL) && (IsSSLAcceptHandshake(cptr) || IsSSLConnectHandshake(cptr)) && FD_ISSET(cptr->fd, &read_set)) { if (!SSL_is_init_finished(cptr->ssl)) { if (IsDead(cptr) || IsSSLAcceptHandshake(cptr) ? !ircd_SSL_accept(cptr, cptr->fd) : ircd_SSL_connect(cptr) < 0) { length = -1; } } if (SSL_is_init_finished(cptr->ssl)) { if (IsSSLAcceptHandshake(cptr)) { Debug((DEBUG_ERROR, "ssl: start_of_normal_client_handshake(%s)", cptr->sockhost)); start_of_normal_client_handshake(cptr); } else { Debug((DEBUG_ERROR, "ssl: completed_connection", cptr->name)); completed_connection(cptr); } } } #endif if (length > 0) flush_connections(cptr); if ((length != FLUSH_BUFFER) && IsDead(cptr)) goto deadsocket; if ((length > 0) && (cptr->fd >= 0) && !FD_ISSET(cptr->fd, &read_set)) continue; nfds--; readcalls++; if (length > 0) continue; /* ** ...hmm, with non-blocking sockets we might get ** here from quite valid reasons, although.. why ** would select report "data available" when there ** wasn't... so, this must be an error anyway... --msa ** actually, EOF occurs when read() returns 0 and ** in due course, select() returns that fd as ready ** for reading even though it ends up being an EOF. -avalon */ Debug((DEBUG_ERROR, "READ ERROR: fd=%d, errno=%d, length=%d", length == FLUSH_BUFFER ? -2 : cptr->fd, ERRNO, length)); /* ** NOTE: if length == -2 then cptr has already been freed! */ if (length != -2 && (IsServer(cptr) || IsHandshake(cptr))) { if (length == 0) { sendto_locfailops ("Server %s closed the connection", get_client_name(cptr, FALSE)); sendto_serv_butone(&me, ":%s GLOBOPS :Server %s closed the connection", me.name, get_client_name(cptr, FALSE)); } else report_baderror("Lost connection to %s:%s", cptr); } if (length != FLUSH_BUFFER) (void)exit_client(cptr, cptr, &me, ((sockerr = get_sockerr(cptr)) ? strerror(sockerr) : "Client exited")); } return 0; } #else /* USE_POLL */ # ifdef AIX # define POLLREADFLAGS (POLLIN|POLLMSG) # endif # if defined(POLLMSG) && defined(POLLIN) && defined(POLLRDNORM) # define POLLREADFLAGS (POLLMSG|POLLIN|POLLRDNORM) # endif # if defined(POLLIN) && defined(POLLRDNORM) && !defined(POLLMSG) # define POLLREADFLAGS (POLLIN|POLLRDNORM) # endif # if defined(POLLIN) && !defined(POLLRDNORM) && !defined(POLLMSG) # define POLLREADFLAGS POLLIN # endif # if defined(POLLRDNORM) && !defined(POLLIN) && !defined(POLLMSG) # define POLLREADFLAGS POLLRDNORM # endif # if defined(POLLOUT) && defined(POLLWRNORM) # define POLLWRITEFLAGS (POLLOUT|POLLWRNORM) # else # if defined(POLLOUT) # define POLLWRITEFLAGS POLLOUT # else # if defined(POLLWRNORM) # define POLLWRITEFLAGS POLLWRNORM # endif # endif # endif # if defined(POLLERR) && defined(POLLHUP) # define POLLERRORS (POLLERR|POLLHUP) # else # define POLLERRORS POLLERR # endif # define PFD_SETR(thisfd) { CHECK_PFD(thisfd);\ pfd->events |= POLLREADFLAGS; } # define PFD_SETW(thisfd) { CHECK_PFD(thisfd);\ pfd->events |= POLLWRITEFLAGS; } # define CHECK_PFD( thisfd ) \ if ( pfd->fd != thisfd ) { \ pfd = &poll_fdarray[nbr_pfds++];\ pfd->fd = thisfd; \ pfd->events = 0; \ } #ifdef NO_FDLIST #error You cannot set NO_FDLIST and USE_POLL at same time! #else int read_message(time_t delay, fdlist *listp) #endif { aClient *cptr; int nfds; static struct pollfd poll_fdarray[MAXCONNECTIONS]; struct pollfd *pfd = poll_fdarray; struct pollfd *res_pfd = NULL; struct pollfd *socks_pfd = NULL; int nbr_pfds = 0; u_long waittime; time_t delay2 = delay; int res, length, fd; int auth, rw, socks; int sockerr; int i, j; static char errmsg[512]; static aClient *authclnts[MAXCONNECTIONS]; static aClient *socksclnts[MAXCONNECTIONS]; /* if it is called with NULL we check all active fd's */ if (!listp) { listp = &default_fdlist; listp->last_entry = LastSlot == -1 ? LastSlot : LastSlot + 1; } for (res = 0;;) { nbr_pfds = 0; pfd = poll_fdarray; pfd->fd = -1; res_pfd = NULL; socks_pfd = NULL; auth = 0; socks = 0; for (i = listp->entry[j = 1]; j <= listp->last_entry; i = listp->entry[++j]) { if (!(cptr = local[i])) continue; if (IsLog(cptr)) continue; if (DoingAuth(cptr)) { if (auth == 0) memset((char *)&authclnts, '\0', sizeof(authclnts)); auth++; Debug((DEBUG_NOTICE, "auth on %x %d", cptr, i)); PFD_SETR(cptr->authfd); if (cptr->flags & FLAGS_WRAUTH) PFD_SETW(cptr->authfd); authclnts[cptr->authfd] = cptr; continue; } if (DoingDNS(cptr) || DoingAuth(cptr) ) continue; if (IsMe(cptr) && IsListening(cptr)) { #define CONNECTFAST # ifdef CONNECTFAST /* * This is VERY bad if someone tries to send a lot of * clones to the server though, as mbuf's can't be * allocated quickly enough... - Comstud */ PFD_SETR(i); # else if (now > (cptr->lasttime + 2)) { PFD_SETR(i); } else if (delay2 > 2) delay2 = 2; # endif } else if (!IsMe(cptr)) { /* if (DBufLength(&cptr->recvQ) && delay2 > 2) delay2 = 1; */ if (DBufLength(&cptr->recvQ) < 4088) PFD_SETR(i); } length = DBufLength(&cptr->sendQ); if (DoList(cptr) && IsSendable(cptr)) { send_list(cptr, 64); length = DBufLength(&cptr->sendQ); } if (length || IsConnecting(cptr)) PFD_SETW(i); } if (resfd >= 0) { PFD_SETR(resfd); res_pfd = pfd; } if (me.socksfd >= 0) { PFD_SETR(me.socksfd); socks_pfd = pfd; } waittime = MIN(delay2, delay) * 1000; nfds = poll(poll_fdarray, nbr_pfds, waittime); if (nfds == -1 && ((errno == EINTR) || (errno == EAGAIN))) return -1; else if (nfds >= 0) break; report_error("poll %s:%s", &me); res++; if (res > 5) restart("too many poll errors"); sleep(10); } if (res_pfd && (res_pfd->revents & (POLLREADFLAGS | POLLERRORS))) { do_dns_async(); nfds--; } if (socks_pfd && (socks_pfd->revents & (POLLREADFLAGS | POLLERRORS))) { int tmpsock; nfds--; tmpsock = accept(me.socksfd, NULL, NULL); if (tmpsock >= 0) close(tmpsock); } for (pfd = poll_fdarray, i = 0; (nfds > 0) && (i < nbr_pfds); i++, pfd++) { if (!pfd->revents) continue; if (pfd == res_pfd) continue; if (pfd == socks_pfd) continue; nfds--; fd = pfd->fd; rr = pfd->revents & POLLREADFLAGS; rw = pfd->revents & POLLWRITEFLAGS; if (pfd->revents & POLLERRORS) { if (pfd->events & POLLREADFLAGS) rr++; if (pfd->events & POLLWRITEFLAGS) rw++; } if ((auth > 0) && ((cptr = authclnts[fd]) != NULL) && (cptr->authfd == fd)) { auth--; if (rr) read_authports(cptr); if (rw) send_authports(cptr); continue; } if (!(cptr = local[fd])) continue; if (rr && IsListening(cptr)) { cptr->lasttime = TStime(); /* ** There may be many reasons for error return, but ** in otherwise correctly working environment the ** probable cause is running out of file descriptors ** (EMFILE, ENFILE or others?). The man pages for ** accept don't seem to list these as possible, ** although it's obvious that it may happen here. ** Thus no specific errors are tested at this ** point, just assume that connections cannot ** be accepted until some old is closed first. */ if ((fd = accept(fd, NULL, NULL)) < 0) { report_error("Cannot accept connections %s:%s", cptr); break; } ircstp->is_ac++; if (fd >= MAXCLIENTS) { ircstp->is_ref++; sendto_ops("All connections in use. (%s)", get_client_name(cptr, TRUE)); (void)send(fd, "ERROR :All connections in use\r\n", 32, 0); (void)close(fd); break; } /* * Use of add_connection (which never fails :) meLazy */ (void)add_connection(cptr, fd); nextping = TStime(); if (!cptr->listener) cptr->listener = &me; continue; } if (IsMe(cptr)) continue; if (rw) /* socket is marked for writing.. */ { int write_err = 0; if (IsConnecting(cptr)) write_err = completed_connection(cptr); if (!write_err) (void)send_queued(cptr); if (IsDead(cptr) || write_err) { (void)exit_client(cptr, cptr, &me, ((sockerr = get_sockerr(cptr)) ? strerror(sockerr) : "Client exited")); continue; } } length = 1; /* for fall through case */ if (rr) length = read_packet(cptr); else if (IsPerson(cptr) && !NoNewLine(cptr)) length = do_client_queue(cptr); # ifdef DEBUGMODE readcalls++; # endif if (length == FLUSH_BUFFER) continue; if (IsDead(cptr)) { ircsprintf(errmsg, "Read/Dead Error: %s", strerror(get_sockerr(cptr))); exit_client(cptr, cptr, &me, errmsg); continue; } if (length > 0) continue; /* An error has occured reading from cptr, drop it. */ /* ** NOTE: if length == -2 then cptr has already been freed! */ if (length != -2 && (IsServer(cptr) || IsHandshake(cptr))) { if (length == 0) { sendto_locfailops ("Server %s closed the connection", get_client_name(cptr, FALSE)); sendto_serv_butone(&me, ":%s GLOBOPS :Server %s closed the connection", me.name, get_client_name(cptr, FALSE)); } else report_error("Lost connection to %s:%s", cptr); } if (length != FLUSH_BUFFER) (void)exit_client(cptr, cptr, &me, ((sockerr = get_sockerr(cptr)) ? strerror(sockerr) : "Client exited")); } return 0; } #endif /* * connect_server */ int connect_server(ConfigItem_link *aconf, aClient *by, struct hostent *hp) { struct SOCKADDR *svp; aClient *cptr; char *s; int errtmp, len; /* * If we dont know the IP# for this host and itis a hostname and * not a ip# string, then try and find the appropriate host record. */ if ((!aconf->ipnum.S_ADDR)) { Link lin; lin.flags = ASYNC_CONNECT; lin.value.aconf = (ListStruct *) aconf; nextdnscheck = 1; s = aconf->hostname; #ifndef INET6 if ((aconf->ipnum.S_ADDR = inet_addr(s)) == -1) #else if (!inet_pton(AF_INET6, s, aconf->ipnum.s6_addr)) #endif { #ifdef INET6 bzero(aconf->ipnum.s6_addr, IN6ADDRSZ); #else aconf->ipnum.S_ADDR = 0; #endif hp = gethost_byname(s, &lin); if (!hp) return -2; bcopy(hp->h_addr, (char *)&aconf->ipnum, sizeof(struct IN_ADDR)); } } cptr = make_client(NULL, NULL); cptr->hostp = hp; /* * Copy these in so we have something for error detection. */ strncpyzt(cptr->name, aconf->servername, sizeof(cptr->name)); strncpyzt(cptr->sockhost, aconf->hostname, HOSTLEN + 1); svp = connect_inet(aconf, cptr, &len); if (!svp) { if (cptr->fd >= 0) { CLOSE_SOCK(cptr->fd); --OpenFiles; } cptr->fd = -2; free_client(cptr); return -1; } set_non_blocking(cptr->fd, cptr); set_sock_opts(cptr->fd, cptr); #ifndef _WIN32 (void)signal(SIGALRM, dummy); if (connect(cptr->fd, svp, len) < 0 && errno != EINPROGRESS) { #else if (connect(cptr->fd, svp, len) < 0 && WSAGetLastError() != WSAEINPROGRESS && WSAGetLastError() != WSAEWOULDBLOCK) { #endif errtmp = ERRNO; report_error("Connect to host %s failed: %s", cptr); if (by && IsPerson(by) && !MyClient(by)) sendto_one(by, ":%s NOTICE %s :*** Connect to host %s failed.", me.name, by->name, cptr); CLOSE_SOCK(cptr->fd); --OpenFiles; cptr->fd = -2; free_client(cptr); SET_ERRNO(errtmp); if (ERRNO == P_EINTR) SET_ERRNO(P_ETIMEDOUT); return -1; } /* ** The socket has been connected or connect is in progress. */ (void)make_server(cptr); cptr->serv->conf = aconf; if (by && IsPerson(by)) { (void)strlcpy(cptr->serv->by, by->name, sizeof cptr->serv->by); if (cptr->serv->user) free_user(cptr->serv->user, NULL); cptr->serv->user = by->user; by->user->refcnt++; } else { (void)strlcpy(cptr->serv->by, "AutoConn.", sizeof cptr->serv->by); if (cptr->serv->user) free_user(cptr->serv->user, NULL); cptr->serv->user = NULL; } cptr->serv->up = me.name; add_local_client(cptr); cptr->listener = &me; SetConnecting(cptr); IRCstats.unknown++; get_sockhost(cptr, aconf->hostname); add_client_to_list(cptr); nextping = TStime(); return 0; } static struct SOCKADDR *connect_inet(ConfigItem_link *aconf, aClient *cptr, int *lenp) { static struct SOCKADDR_IN server; struct hostent *hp; /* * Might as well get sockhost from here, the connection is attempted * with it so if it fails its useless. */ cptr->fd = socket(AFINET, SOCK_STREAM, 0); if (cptr->fd < 0) { if (ERRNO == P_EMFILE) { sendto_realops("opening stream socket to server %s: No more sockets", get_client_name(cptr, TRUE)); return NULL; } report_error("opening stream socket to server %s:%s", cptr); return NULL; } if (++OpenFiles >= MAXCLIENTS) { sendto_realops("No more connections allowed (%s)", cptr->name); return NULL; } mysk.SIN_PORT = 0; bzero((char *)&server, sizeof(server)); server.SIN_FAMILY = AFINET; get_sockhost(cptr, aconf->hostname); server.SIN_PORT = 0; server.SIN_ADDR = me.ip; server.SIN_FAMILY = AFINET; if (aconf->bindip && strcmp("*", aconf->bindip)) { #ifndef INET6 server.SIN_ADDR.S_ADDR = inet_addr(aconf->bindip); #else inet_pton(AF_INET6, aconf->bindip, server.SIN_ADDR.S_ADDR); #endif } if (bind(cptr->fd, (struct SOCKADDR *)&server, sizeof(server)) == -1) { report_error("error binding to local port for %s:%s", cptr); return NULL; } bzero((char *)&server, sizeof(server)); server.SIN_FAMILY = AFINET; /* * By this point we should know the IP# of the host listed in the * conf line, whether as a result of the hostname lookup or the ip# * being present instead. If we dont know it, then the connect fails. */ #ifdef INET6 if (!inet_pton(AF_INET6, aconf->hostname, aconf->ipnum.s6_addr)) bcopy(minus_one, aconf->ipnum.s6_addr, IN6ADDRSZ); if (AND16(aconf->ipnum.s6_addr) == 255) #else if (isdigit(*aconf->hostname) && (aconf->ipnum.S_ADDR == -1)) aconf->ipnum.S_ADDR = inet_addr(aconf->hostname); if (aconf->ipnum.S_ADDR == -1) #endif { hp = cptr->hostp; if (!hp) { Debug((DEBUG_FATAL, "%s: unknown host", aconf->hostname)); return NULL; } bcopy(hp->h_addr, (char *)&aconf->ipnum, sizeof(struct IN_ADDR)); } bcopy((char *)&aconf->ipnum, (char *)&server.SIN_ADDR, sizeof(struct IN_ADDR)); bcopy((char *)&aconf->ipnum, (char *)&cptr->ip, sizeof(struct IN_ADDR)); server.SIN_PORT = htons(((aconf->port > 0) ? aconf->port : portnum)); *lenp = sizeof(server); return (struct SOCKADDR *)&server; } /* * do_dns_async * * Called when the fd returned from init_resolver() has been selected for * reading. */ #ifndef _WIN32 static void do_dns_async(void) #else void do_dns_async(int id) #endif { static Link ln; aClient *cptr; ConfigItem_link *aconf; struct hostent *hp; int bytes, pkts; pkts = 0; do { ln.flags = -1; #ifndef _WIN32 hp = get_res((char *)&ln); #else hp = get_res((char *)&ln, id); #endif Debug((DEBUG_DNS,"%#x = get_res(%d,%#x)", hp, ln.flags, ln.value.cptr)); switch (ln.flags) { case ASYNC_NONE : /* * no reply was processed that was outstanding or * had a client still waiting. */ break; case ASYNC_CLIENT : if ((cptr = ln.value.cptr)) { del_queries((char *)cptr); ClearDNS(cptr); cptr->hostp = hp; if (SHOWCONNECTINFO) sendto_one(cptr, cptr->hostp ? REPORT_FIN_DNS : REPORT_FAIL_DNS); if (!DoingAuth(cptr)) SetAccess(cptr); } break; case ASYNC_CONF : aconf = (ConfigItem_link *) ln.value.aconf; if (hp && aconf) bcopy(hp->h_addr, (char *)&aconf->ipnum, sizeof(struct IN_ADDR)); break; default : break; } pkts++; #ifndef _WIN32 if (ioctl(resfd, FIONREAD, &bytes) == -1) #else if (ioctlsocket(resfd, FIONREAD, &bytes) == -1) #endif bytes = 0; } while ((bytes > 0) && (pkts < 10)); }