/************************************************************************ * IRC - Internet Relay Chat, ircd/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 * (sequent OS Dynix only) -- maybe select()-call must be changed ... */ /* -- 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 lint static char rcsid[] = "@(#)$Id: s_bsd.c,v 1.11 2004/07/04 10:13:15 skold Exp $"; #endif #include "os.h" #include "s_defines.h" #define S_BSD_C #include "s_externs.h" #undef S_BSD_C #ifndef IN_LOOPBACKNET #define IN_LOOPBACKNET 0x7f #endif aClient *local[MAXCONNECTIONS]; FdAry fdas, fdall; int highest_fd = 0, readcalls = 0, udpfd = -1, resfd = -1, adfd = -1; time_t timeofday; static struct SOCKADDR_IN mysk; static void polludp(); static struct SOCKADDR *connect_inet __P((aConfItem *, aClient *, int *)); static int completed_connection __P((aClient *)); static int check_init __P((aClient *, char *)); static int check_ping __P((char *, int)); static void do_dns_async __P(()); static int set_sock_opts __P((int, aClient *)); #ifdef UNIXPORT static struct SOCKADDR *connect_unix __P((aConfItem *, aClient *, int *)); static void add_unixconnection __P((aClient *, int)); static char unixpath[256]; #endif static char readbuf[READBUF_SIZE]; #define CFLAG (CONF_CONNECT_SERVER|CONF_ZCONNECT_SERVER) #define NFLAG CONF_NOCONNECT_SERVER /* * 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 /* ** add_local_domain() ** Add the domain to hostname, if it is missing ** (as suggested by eps@TOASTER.SFSU.EDU) */ void add_local_domain(hname, size) char *hname; int size; { #ifdef RES_INIT /* Remove ending dot */ if (hname[strlen(hname) - 1] == '.') { hname[strlen(hname) - 1] = 0; size++; } /* try to fix up unqualified names */ if (!index(hname, '.')) { if (!(ircd_res.options & RES_INIT)) { Debug((DEBUG_DNS,"ircd_res_init()")); ircd_res_init(); } if (ircd_res.defdname[0] && strlen(ircd_res.defdname) + 2 <= size) { (void)strcat(hname, "."); (void)strcat(hname, ircd_res.defdname); } } #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 ** by strerror(errno). ** ** cptr if not NULL, is the *LOCAL* client associated with ** the error. */ void report_error(text, cptr) char *text; aClient *cptr; { Reg int errtmp = errno; /* debug may change 'errno' */ Reg char *host; int err; SOCK_LEN_TYPE len = sizeof(err); extern char *strerror(); 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, &err, &len)) if (err) errtmp = err; #endif sendto_flag(SCH_ERROR, text, host, strerror(errtmp)); #ifdef USE_SYSLOG syslog(LOG_WARNING, text, host, strerror(errtmp)); #endif return; } /* * inetport * * Create a socket in the AF_INET domain, bind it to the port given in * 'port' and listen to it. If 'ip' has a value, use it as vif to listen. * Connections are accepted to this socket depending on the IP# mask given * by 'ipmask'. Returns the fd of the socket created or -1 on error. */ int inetport(cptr, ip, ipmask, port) aClient *cptr; char *ipmask, *ip; int port; { static struct SOCKADDR_IN server; int ad[4]; SOCK_LEN_TYPE len = sizeof(server); char ipname[20]; 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 "*" to 0.0.0.0 or 134.* to 134.0.0.0 :-) */ (void)sscanf(ipmask, "%d.%d.%d.%d", &ad[0], &ad[1], &ad[2], &ad[3]); if (ad[0]>>8 || ad[1]>>8 || ad[2]>>8 || ad[3]>>8) { sendto_flag(SCH_ERROR, "Invalid ipmask %s", ipmask); return -1; } (void)sprintf(ipname, "%d.%d.%d.%d", ad[0], ad[1], ad[2], ad[3]); (void)sprintf(cptr->sockhost, "%-.42s.%u", ip ? ip : ME, (unsigned int)port); (void)strcpy(cptr->name, ME); DupString(cptr->auth, ipname); /* * At first, open a new socket */ if (cptr->fd == -1) cptr->fd = socket(AFINET, SOCK_STREAM, 0); if (cptr->fd < 0) { report_error("opening stream socket %s:%s", cptr); return -1; } else if (cptr->fd >= MAXCLIENTS) { sendto_flag(SCH_ERROR, "No more connections allowed (%s)", cptr->name); (void)close(cptr->fd); return -1; } (void)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; #ifdef INET6 if (!ip || (!isxdigit(*ip) && *ip != ':')) server.sin6_addr = in6addr_any; else if(!inetpton(AF_INET6, ip, server.sin6_addr.s6_addr)) bcopy(minus_one, server.sin6_addr.s6_addr, IN6ADDRSZ); #else if (!ip || !isdigit(*ip)) server.sin_addr.s_addr = INADDR_ANY; else server.sin_addr.s_addr = inetaddr(ip); #endif server.SIN_PORT = htons(port); /* * Try 10 times to bind the socket with an interval of 20 * seconds. Do this so we don't have to keep 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, (SAP)&server, sizeof(server)) == -1) { report_error("binding stream socket %s:%s", cptr); (void)close(cptr->fd); return -1; } } if (getsockname(cptr->fd, (struct SOCKADDR *)&server, &len)) { report_error("getsockname failed for %s:%s",cptr); (void)close(cptr->fd); return -1; } if (cptr == &me) /* KLUDGE to get it work... */ { char buf[1024]; (void)sprintf(buf, rpl_str(RPL_MYPORTIS, "*"), ntohs(server.SIN_PORT)); (void)write(0, buf, strlen(buf)); } if (cptr->fd > highest_fd) highest_fd = cptr->fd; #ifdef INET6 bcopy(server.sin6_addr.s6_addr, cptr->ip.s6_addr, IN6ADDRSZ); #else cptr->ip.s_addr = server.sin_addr.s_addr; /* broken on linux at least*/ #endif cptr->port = port; (void)listen(cptr->fd, LISTENQUEUE); local[cptr->fd] = cptr; return 0; } /* * add_listener * * Create a new client which is essentially the stub like 'me' to be used * for a socket that is passive (listen'ing for connections to be accepted). */ int add_listener(aconf) aConfItem *aconf; { aClient *cptr; cptr = make_client(NULL); cptr->flags = FLAGS_LISTEN; cptr->acpt = cptr; cptr->from = cptr; cptr->firsttime = time(NULL); SetMe(cptr); #ifdef UNIXPORT if (*aconf->host == '/') { strncpyzt(cptr->name, aconf->host, sizeof(cptr->name)); if (unixport(cptr, aconf->host, aconf->port)) cptr->fd = -2; } else #endif if (inetport(cptr, aconf->host, aconf->name, aconf->port)) cptr->fd = -2; if (cptr->fd >= 0) { cptr->confs = make_link(); cptr->confs->next = NULL; cptr->confs->value.aconf = aconf; add_fd(cptr->fd, &fdas); add_fd(cptr->fd, &fdall); set_non_blocking(cptr->fd, cptr); } else free_client(cptr); return 0; } #ifdef UNIXPORT /* * unixport * * Create a socket and bind it to a filename which is comprised of the path * (directory where file is placed) and port (actual filename created). * Set directory permissions as rwxr-xr-x so other users can connect to the * file which is 'forced' to rwxrwxrwx (different OS's have different need of * modes so users can connect to the socket). */ int unixport(cptr, path, port) aClient *cptr; char *path; int port; { struct sockaddr_un un; if ((cptr->fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { report_error("error opening unix domain socket %s:%s", cptr); return -1; } else if (cptr->fd >= MAXCLIENTS) { sendto_flag(SCH_ERROR, "No more connections allowed (%s)", cptr->name); (void)close(cptr->fd); return -1; } un.sun_family = AF_UNIX; (void)mkdir(path, 0755); SPRINTF(unixpath, "%s/%d", path, port); (void)unlink(unixpath); strncpyzt(un.sun_path, unixpath, sizeof(un.sun_path)); (void)strcpy(cptr->name, ME); errno = 0; get_sockhost(cptr, unixpath); if (bind(cptr->fd, (SAP)&un, strlen(unixpath)+2) == -1) { report_error("error binding unix socket %s:%s", cptr); (void)close(cptr->fd); return -1; } if (cptr->fd > highest_fd) highest_fd = cptr->fd; (void)listen(cptr->fd, LISTENQUEUE); (void)chmod(path, 0755); (void)chmod(unixpath, 0777); cptr->flags |= FLAGS_UNIX; cptr->port = 0; local[cptr->fd] = cptr; return 0; } #endif /* * close_listeners * * Close and free all clients which are marked as having their socket open * and in a state where they can accept connections. Unix sockets have * the path to the socket unlinked for cleanliness. */ void close_listeners() { Reg aClient *cptr; Reg int i; Reg aConfItem *aconf; /* * close all 'extra' listening ports we have and unlink the file * name if it was a unix socket. */ for (i = highest_fd; i >= 0; i--) { if (!(cptr = local[i])) continue; if (cptr == &me || !IsListening(cptr)) continue; aconf = cptr->confs->value.aconf; if (IsIllegal(aconf) && aconf->clients == 0) { #ifdef UNIXPORT if (IsUnixSocket(cptr)) { SPRINTF(unixpath, "%s/%d", aconf->host, aconf->port); (void)unlink(unixpath); } #endif close_connection(cptr); } } } void start_iauth(rcvdsig) int rcvdsig; { #if defined(USE_IAUTH) static time_t last = 0; static char first = 1; int sp[2], fd, val; if ((bootopt & BOOT_NOIAUTH) != 0) return; if (adfd >= 0) { if (rcvdsig) sendto_flag(SCH_AUTH, "iauth is already running, restart canceled"); return; } if ((time(NULL) - last) > 90 || rcvdsig) { sendto_flag(SCH_AUTH, "Starting iauth..."); last = time(NULL); read_iauth(); /* to reset olen */ iauth_spawn += 1; } else return; if (socketpair(AF_UNIX, SOCK_STREAM, 0, sp) < 0) { sendto_flag(SCH_ERROR, "socketpair() failed!"); sendto_flag(SCH_AUTH, "Failed to restart iauth!"); } adfd = sp[0]; set_non_blocking(sp[0], NULL); set_non_blocking(sp[1], NULL); /* less to worry about in iauth */ val = IAUTH_BUFFER; if (setsockopt(sp[0], SOL_SOCKET, SO_SNDBUF, (void *) &val, sizeof(val)) < 0) sendto_flag(SCH_AUTH, "IAUTH_BUFFER too big for sp0 sndbuf, using default"); if (setsockopt(sp[1], SOL_SOCKET, SO_SNDBUF, (void *) &val, sizeof(val)) < 0) sendto_flag(SCH_AUTH, "IAUTH_BUFFER too big for sp1 sndbuf, using default"); if (setsockopt(sp[0], SOL_SOCKET, SO_RCVBUF, (void *) &val, sizeof(val)) < 0) sendto_flag(SCH_AUTH, "IAUTH_BUFFER too big for sp0 rcvbuf, using default"); if (setsockopt(sp[1], SOL_SOCKET, SO_RCVBUF, (void *) &val, sizeof(val)) < 0) sendto_flag(SCH_AUTH, "IAUTH_BUFFER too big for sp1 rcvbuf, using default"); switch (vfork()) { case -1: sendto_flag(SCH_ERROR, "vfork() failed!"); sendto_flag(SCH_AUTH, "Failed to restart iauth!"); close(sp[0]); close(sp[1]); adfd = -1; return; case 0: for (fd = 0; fd < MAXCONNECTIONS; fd++) if (fd != sp[1]) (void)close(fd); if (sp[1] != 0) { (void)dup2(sp[1], 0); close(sp[1]); } if (execl(IAUTH_PATH, IAUTH, NULL) < 0) _exit(-1); /* should really not happen.. */ default: close(sp[1]); } if (first) first = 0; else { int i; aClient *cptr; for (i = 0; i <= highest_fd; i++) { if (!(cptr = local[i])) continue; if (IsServer(cptr) || IsService(cptr)) continue; sendto_iauth("%d O", i); } } #endif } /* * init_sys */ void init_sys() { Reg int fd; #ifdef RLIMIT_FD_MAX struct rlimit limit; if (!getrlimit(RLIMIT_FD_MAX, &limit)) { if (limit.rlim_max < MAXCONNECTIONS) { (void)fprintf(stderr, "ircd fd table is too big\n"); (void)fprintf(stderr, "Hard Limit: %d IRC max: %d\n", (int) limit.rlim_max, MAXCONNECTIONS); (void)fprintf(stderr, "Fix MAXCONNECTIONS and recompile.\n"); exit(-1); } limit.rlim_cur = limit.rlim_max; /* make soft limit the max */ if (setrlimit(RLIMIT_FD_MAX, &limit) == -1) { (void)fprintf(stderr, "error setting max fd's to %d\n", (int) limit.rlim_cur); exit(-1); } } #endif #if ! USE_POLL # ifdef sequent # ifndef DYNIXPTX int fd_limit; fd_limit = setdtablesize(MAXCONNECTIONS + 1); if (fd_limit < MAXCONNECTIONS) { (void)fprintf(stderr,"ircd fd table too big\n"); (void)fprintf(stderr,"Hard Limit: %d IRC max: %d\n", fd_limit, MAXCONNECTIONS); (void)fprintf(stderr,"Fix MAXCONNECTIONS\n"); exit(-1); } # endif # endif #endif /* USE_POLL */ #if defined(PCS) || defined(DYNIXPTX) || 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(SVR4) (void)setlinebuf(stderr); # endif # endif #endif bzero((char *)&fdas, sizeof(fdas)); bzero((char *)&fdall, sizeof(fdall)); fdas.highest = fdall.highest = -1; for (fd = 3; fd < MAXCONNECTIONS; fd++) { (void)close(fd); local[fd] = NULL; } local[1] = NULL; (void) fclose(stdout); (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)) && !(bootopt & (BOOT_INETD|BOOT_OPER))) { #ifndef __CYGWIN32__ 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(SVR4) || defined(DYNIXPTX) || \ defined(_POSIX_SOURCE) || defined(SGI) (void)setsid(); #elif defined (__CYGWIN32__) (void)setpgrp(); #else (void)setpgrp(0, (int)getpid()); #endif (void)close(0); /* fd 0 opened by inetd */ local[0] = NULL; } init_dgram: resfd = init_resolver(0x1f); start_iauth(0); } void write_pidfile() { int fd; char buff[20]; (void)truncate(IRCDPID_PATH, 0); if ((fd = open(IRCDPID_PATH, O_CREAT|O_WRONLY, 0600))>=0) { bzero(buff, sizeof(buff)); (void)sprintf(buff,"%5d\n", (int)getpid()); if (write(fd, buff, strlen(buff)) == -1) Debug((DEBUG_NOTICE,"Error writing to pid file %s", IRCDPID_PATH)); (void)close(fd); return; } # ifdef DEBUGMODE else Debug((DEBUG_NOTICE,"Error opening pid file %s", IRCDPID_PATH)); # 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(cptr, sockn) Reg aClient *cptr; Reg char *sockn; { struct SOCKADDR_IN sk; SOCK_LEN_TYPE len = sizeof(struct SOCKADDR_IN); #ifdef UNIXPORT if (IsUnixSocket(cptr)) { strncpyzt(sockn, cptr->acpt->sockhost, HOSTLEN+1); get_sockhost(cptr, sockn); return 0; } #endif /* If descriptor is a tty, special checking... */ if (isatty(cptr->fd)) { strncpyzt(sockn, me.sockhost, HOSTLEN); bzero((char *)&sk, sizeof(struct SOCKADDR_IN)); } else if (getpeername(cptr->fd, (SAP)&sk, &len) == -1) { report_error("connect failure: %s %s", cptr); return -1; } #ifdef INET6 inetntop(AF_INET6, (char *)&sk.sin6_addr, sockn, MYDUMMY_SIZE); Debug((DEBUG_DNS,"sockn %x",sockn)); Debug((DEBUG_DNS,"sockn %s",sockn)); #else (void)strcpy(sockn, (char *)inetntoa((char *)&sk.sin_addr)); #endif #ifdef INET6 if (IN6_IS_ADDR_LOOPBACK(&sk.SIN_ADDR)) #else if (inetnetof(sk.SIN_ADDR) == IN_LOOPBACKNET) #endif { cptr->hostp = me.hostp; } 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 = Bad socket. * -2 = Access denied */ int check_client(cptr) Reg aClient *cptr; { static char sockname[HOSTLEN+1]; Reg struct hostent *hp = NULL; Reg int i; #ifdef INET6 Debug((DEBUG_DNS, "ch_cl: check access for %s[%s]", cptr->name, inet_ntop(AF_INET6, (char *)&cptr->ip, mydummy, MYDUMMY_SIZE))); #else Debug((DEBUG_DNS, "ch_cl: check access for %s[%s]", cptr->name, inetntoa((char *)&cptr->ip))); #endif if (check_init(cptr, sockname)) return -1; if (!IsUnixSocket(cptr)) 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. * We shouldn't check it for localhost, because hp is fake in that * case. -Toor */ if (hp && (hp != me.hostp)) { 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]) { #ifdef INET6 sendto_flag(SCH_ERROR, "IP# Mismatch: %s != %s[%08x%08x%08x%08x]", inetntop(AF_INET6, (char *)&cptr->ip, mydummy,MYDUMMY_SIZE),hp->h_name, ((unsigned long *)hp->h_addr)[0], ((unsigned long *)hp->h_addr)[1], ((unsigned long *)hp->h_addr)[2], ((unsigned long *)hp->h_addr)[3]); #else sendto_flag(SCH_ERROR, "IP# Mismatch: %s != %s[%08x]", inetntoa((char *)&cptr->ip), hp->h_name, *((unsigned long *)hp->h_addr)); #endif hp = NULL; } } if ((i = attach_Iline(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) || IsUnixSocket(cptr) || !memcmp(cptr->ip.s6_addr, mysk.sin6_addr.s6_addr, 8) /* || IN6_ARE_ADDR_SAMEPREFIX(&cptr->ip, &mysk.SIN_ADDR)) about the same, I think NOT */ ) #else if (inetnetof(cptr->ip) == IN_LOOPBACKNET || IsUnixSocket(cptr) || inetnetof(cptr->ip) == inetnetof(mysk.SIN_ADDR)) #endif { ircstp->is_loc++; cptr->flags |= FLAGS_LOCAL; } return 0; } /* * check_server_init(), check_server() * check access for a server given its name (passed in cptr struct). * Must check for all C/N lines which have a name which matches the * name given and a host which matches. A host alias which is the * same as the server name is also acceptable in the host field of a * C/N line. * 0 = Success * -1 = Access denied * -2 = Bad socket. */ int check_server_init(cptr) aClient *cptr; { Reg char *name; Reg aConfItem *c_conf = NULL, *n_conf = NULL; struct hostent *hp = NULL; Link *lp; name = cptr->name; Debug((DEBUG_DNS, "sv_cl: check access for %s[%s]", name, cptr->sockhost)); if (IsUnknown(cptr) && !attach_confs(cptr, name, CFLAG|NFLAG)) { Debug((DEBUG_DNS,"No C/N lines for %s", name)); return -1; } lp = cptr->confs; /* * We initiated this connection so the client should have a C and N * line already attached after passing through the connec_server() * function earlier. */ if (IsConnecting(cptr) || IsHandshake(cptr)) { c_conf = find_conf(lp, name, CFLAG); n_conf = find_conf(lp, name, NFLAG); if (!c_conf || !n_conf) { sendto_flag(SCH_ERROR, "Connecting Error: %s[%s]", name, cptr->sockhost); det_confs_butmask(cptr, 0); return -1; } } #ifdef UNIXPORT if (IsUnixSocket(cptr)) { if (!c_conf) c_conf = find_conf(lp, name, CFLAG); if (!n_conf) n_conf = find_conf(lp, name, NFLAG); } #endif /* ** If the servername is a hostname, either an alias (CNAME) or ** real name, then check with it as the host. Use gethostbyname() ** to check for servername as hostname. */ if (!IsUnixSocket(cptr) && !cptr->hostp) { Reg aConfItem *aconf; aconf = count_cnlines(lp); if (aconf) { Reg char *s; Link lin; /* ** Do a lookup for the CONF line *only* and not ** the server connection else we get stuck in a ** nasty state since it takes a SERVER message to ** get us here and we can't interrupt that very ** well. */ lin.value.aconf = aconf; lin.flags = ASYNC_CONF; nextdnscheck = 1; if ((s = index(aconf->host, '@'))) s++; else s = aconf->host; Debug((DEBUG_DNS,"sv_ci:cache lookup (%s)",s)); hp = gethost_byname(s, &lin); } } return check_server(cptr, hp, c_conf, n_conf, 0); } int check_server(cptr, hp, c_conf, n_conf, estab) aClient *cptr; Reg aConfItem *n_conf, *c_conf; Reg struct hostent *hp; int estab; { Reg char *name; char abuff[HOSTLEN+USERLEN+2]; char sockname[HOSTLEN+1], fullname[HOSTLEN+1]; Link *lp = cptr->confs; int i; if (check_init(cptr, sockname)) return -2; check_serverback: 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]) { #ifdef INET6 sendto_flag(SCH_ERROR, "IP# Mismatch: %s != %s[%08x%08x%08x%08x]", inetntop(AF_INET6, (char *)&cptr->ip, mydummy,MYDUMMY_SIZE),hp->h_name, ((unsigned long *)hp->h_addr)[0], ((unsigned long *)hp->h_addr)[1], ((unsigned long *)hp->h_addr)[2], ((unsigned long *)hp->h_addr)[3]); #else sendto_flag(SCH_ERROR, "IP# Mismatch: %s != %s[%08x]", inetntoa((char *)&cptr->ip), hp->h_name, *((unsigned long *)hp->h_addr)); #endif hp = NULL; } } else if (cptr->hostp) { hp = cptr->hostp; goto check_serverback; } if (hp) /* * if we are missing a C or N line from above, search for * it under all known hostnames we have for this ip#. */ for (i=0,name = hp->h_name; name ; name = hp->h_aliases[i++]) { strncpyzt(fullname, name, sizeof(fullname)); add_local_domain(fullname, HOSTLEN-strlen(fullname)); Debug((DEBUG_DNS, "sv_cl: gethostbyaddr: %s->%s", sockname, fullname)); SPRINTF(abuff, "%s@%s", cptr->username, fullname); if (!c_conf) c_conf = find_conf_host(lp, abuff, CFLAG); if (!n_conf) n_conf = find_conf_host(lp, abuff, NFLAG); if (c_conf && n_conf) { get_sockhost(cptr, fullname); break; } } name = cptr->name; /* * Check for C and N lines with the hostname portion the ip number * of the host the server runs on. This also checks the case where * there is a server connecting from 'localhost'. */ if (IsUnknown(cptr) && (!c_conf || !n_conf)) { SPRINTF(abuff, "%s@%s", cptr->username, sockname); if (!c_conf) c_conf = find_conf_host(lp, abuff, CFLAG); if (!n_conf) n_conf = find_conf_host(lp, abuff, NFLAG); } /* * Attach by IP# only if all other checks have failed. * It is quite possible to get here with the strange things that can * happen when using DNS in the way the irc server does. -avalon */ if (!hp) { if (!c_conf) c_conf = find_conf_ip(lp, (char *)&cptr->ip, cptr->username, CFLAG); if (!n_conf) n_conf = find_conf_ip(lp, (char *)&cptr->ip, cptr->username, NFLAG); } else for (i = 0; hp->h_addr_list[i]; i++) { if (!c_conf) c_conf = find_conf_ip(lp, hp->h_addr_list[i], cptr->username, CFLAG); if (!n_conf) n_conf = find_conf_ip(lp, hp->h_addr_list[i], cptr->username, NFLAG); } /* * detach all conf lines that got attached by attach_confs() */ det_confs_butmask(cptr, 0); /* * if no C or no N lines, then deny access */ if (!c_conf || !n_conf) { get_sockhost(cptr, sockname); Debug((DEBUG_DNS, "sv_cl: access denied: %s[%s@%s] c %x n %x", name, cptr->auth, cptr->sockhost, c_conf, n_conf)); return -1; } /* * attach the C and N lines to the client structure for later use. */ (void)attach_conf(cptr, n_conf); (void)attach_conf(cptr, c_conf); (void)attach_confs(cptr, name, CONF_HUB|CONF_LEAF); if (IsIllegal(n_conf) || IsIllegal(c_conf)) { sendto_flag(SCH_DEBUG, "Illegal class!"); return -2; } if (!n_conf->host || !c_conf->host) { sendto_flag(SCH_DEBUG, "Null host in class!"); return -2; } #ifdef INET6 if ((AND16(c_conf->ipnum.s6_addr) == 255) && !IsUnixSocket(cptr)) #else if ((c_conf->ipnum.s_addr == -1) && !IsUnixSocket(cptr)) #endif bcopy((char *)&cptr->ip, (char *)&c_conf->ipnum, sizeof(struct IN_ADDR)); if (!IsUnixSocket(cptr)) get_sockhost(cptr, c_conf->host); Debug((DEBUG_DNS,"sv_cl: access ok: %s[%s]", name, cptr->sockhost)); if (estab) return m_server_estab(cptr); 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 */ static int completed_connection(cptr) aClient *cptr; { aConfItem *aconf; SetHandshake(cptr); aconf = find_conf(cptr->confs, cptr->name, CFLAG); if (!aconf) { sendto_flag(SCH_NOTICE, "Lost C-Line for %s", get_client_name(cptr,FALSE)); return -1; } if (!BadPtr(aconf->passwd)) sendto_one(cptr, "PASS %s %s IRC|%s %s" #ifdef ZIP_LINKS "%s" #endif #ifdef RUSNET_IRCD "%c%c" #endif , aconf->passwd, pass_version, serveropts, #ifdef ZIP_LINKS (aconf->status == CONF_ZCONNECT_SERVER) ? "Z" : "", #endif (bootopt & BOOT_STRICTPROT) ? "P" : "" #ifdef RUSNET_IRCD , PROTO_CAPS_K, PROTO_CAPS_R #endif ); aconf = find_conf(cptr->confs, cptr->name, CONF_NOCONNECT_SERVER); if (!aconf) { sendto_flag(SCH_NOTICE, "Lost N-Line for %s", get_client_name(cptr,FALSE)); return -1; } sendto_one(cptr, "SERVER %s 1 :%s", my_name_for_link(ME, aconf->port), me.info); if (!IsDead(cptr)) { start_auth(cptr); #if defined(USE_IAUTH) /* ** This could become a bug.. but I don't think iauth needs the ** hostname/aliases in this case. -kalt */ sendto_iauth("%d d", cptr->fd); #endif } return (IsDead(cptr)) ? -1 : 0; } int hold_server(cptr) aClient *cptr; { return -1; /* needs to be fixed, don't forget virtual hosts */ #if 0 /* code and variables declarations are removed, this avoids compiler warnings */ struct SOCKADDR_IN sin; aConfItem *aconf; aClient *acptr; int fd; #ifdef RUSNET_IRCD char interface_name[256]; #endif #ifdef ZIP_LINKS /* * reconnecting will not work with compressed links, * unless someones fixes reconnect and implements what's needed * to have it work for compressed links. -krys */ return -1; #else if (!IsServer(cptr) || !(aconf = find_conf_name(cptr->name, CFLAG))) return -1; if (!aconf->port) return -1; fd = socket(AFINET, SOCK_STREAM, 0); if (fd >= MAXCLIENTS) { (void)close(fd); sendto_flag(SCH_ERROR, "Can't reconnect - all connections in use"); return -1; } #ifdef RUSNET_IRCD bzero((char *)&sin, sizeof(sin)); sin.SIN_FAMILY = AFINET; sin.SIN_PORT = htons(aconf->port); /*RusNet PL3*/ /* RUSNET CONNECT */ /* Let's check the number and try to bind to a valid interface. Will return 0 as IP (that means ALL) if not found */ rusnet_bind_interface_address(fd, (struct sockaddr_in *)&sin, interface_name); sendto_flag(SCH_NOTICE, "Binding to %s to connect", interface_name); #endif cptr->flags |= FLAGS_HELD; (void)close(cptr->fd); del_fd(cptr->fd, &fdall); del_fd(cptr->fd, &fdas); cptr->fd = -2; acptr = make_client(NULL); acptr->fd = fd; acptr->port = aconf->port; set_non_blocking(acptr->fd, acptr); (void)set_sock_opts(acptr->fd, acptr); #ifndef RUSNET_IRCD bzero((char *)&sin, sizeof(sin)); sin.SIN_FAMILY = AFINET; sin.SIN_PORT = htons(aconf->port); #endif bcopy((char *)&cptr->ip, (char *)&sin.SIN_ADDR, sizeof(cptr->ip)); bcopy((char *)&cptr->ip, (char *)&acptr->ip, sizeof(cptr->ip)); if (connect(acptr->fd, (SAP)&sin, sizeof(sin)) < 0 && errno != EINPROGRESS) { report_error("Connect to host %s failed: %s", acptr); /*buggy*/ (void)close(acptr->fd); MyFree((char *)acptr); return -1; } acptr->status = STAT_RECONNECT; if (acptr->fd > highest_fd) highest_fd = acptr->fd; add_fd(acptr->fd, &fdall); local[acptr->fd] = acptr; acptr->acpt = &me; add_client_to_list(acptr); (void)strcpy(acptr->name, cptr->name); /* broken syntax sendto_one(acptr, "PASS %s %s", aconf->passwd, pass_version); */ sendto_one(acptr, "RECONNECT %s %d", acptr->name, cptr->sendM); sendto_flag(SCH_NOTICE, "Reconnecting to %s", acptr->name); Debug((DEBUG_NOTICE, "Reconnect %s %#x via %#x %d", cptr->name, cptr, acptr, acptr->fd)); return 0; #endif #endif } /* ** close_connection ** Close the physical connection. This function must make ** MyConnect(cptr) == FALSE, and set cptr->from == NULL. */ void close_connection(cptr) aClient *cptr; { Reg aConfItem *aconf; Reg int i; #ifdef SO_LINGER struct linger sockling; sockling.l_onoff = 0; #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 += timeofday - 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 += timeofday - 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 server connection has been up for a long amount of time, * schedule a 'quick' reconnect, else reset the next-connect cycle. */ if (IsServer(cptr) && (aconf = find_conf_exact(cptr->name, cptr->username, cptr->sockhost, CFLAG))) { /* * Reschedule a faster reconnect, if this was a automatically * 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 = timeofday; aconf->hold += (aconf->hold - cptr->since > HANGONGOODLINK) ? HANGONRETRYDELAY : ConfConFreq(aconf); if (nextconnect > aconf->hold) nextconnect = aconf->hold; } if (cptr->authfd >= 0) { #ifdef SO_LINGER if (cptr->exitc == EXITC_PING) if (SETSOCKOPT(cptr->authfd, SOL_SOCKET, SO_LINGER, &sockling, sockling)) report_error("setsockopt(SO_LINGER) %s:%s", cptr); #endif (void)close(cptr->authfd); } if ((i = cptr->fd) >= 0) { #if defined(USE_IAUTH) sendto_iauth("%d D", cptr->fd); #endif flush_connections(i); if (IsServer(cptr) || IsListening(cptr)) { del_fd(i, &fdas); #ifdef ZIP_LINKS /* ** the connection might have zip data (even if ** FLAGS_ZIP is not set) */ zip_free(cptr); #endif } else if (IsClient(cptr)) { #ifdef SO_LINGER if (cptr->exitc == EXITC_PING) if (SETSOCKOPT(i, SOL_SOCKET, SO_LINGER, &sockling, sockling)) report_error("setsockopt(SO_LINGER) %s:%s", cptr); #endif } del_fd(i, &fdall); local[i] = NULL; (void)close(i); cptr->fd = -2; DBufClear(&cptr->sendQ); DBufClear(&cptr->recvQ); bzero(cptr->passwd, sizeof(cptr->passwd)); /* * clean up extra sockets from P-lines which have been * discarded. */ if (cptr->acpt != &me) { aconf = cptr->acpt->confs->value.aconf; if (aconf->clients > 0) aconf->clients--; if (!aconf->clients && IsIllegal(aconf)) close_connection(cptr->acpt); } } det_confs_butmask(cptr, 0); cptr->from = NULL; /* ...this should catch them! >:) --msa */ return; } /* ** set_sock_opts */ static int set_sock_opts(fd, cptr) int fd; aClient *cptr; { int opt, ret = 0; #ifdef SO_REUSEADDR opt = 1; if (SETSOCKOPT(fd, SOL_SOCKET, SO_REUSEADDR, &opt, opt) < 0) report_error("setsockopt(SO_REUSEADDR) %s:%s", cptr); #endif #if defined(SO_DEBUG) && defined(DEBUGMODE) && 0 /* Solaris 2.x with SO_DEBUG writes to syslog by default */ #if ! SOLARIS_2 || defined(USE_SYSLOG) opt = 1; if (SETSOCKOPT(fd, SOL_SOCKET, SO_DEBUG, &opt, opt) < 0) report_error("setsockopt(SO_DEBUG) %s:%s", cptr); #endif /* SOLARIS_2 */ #endif #if defined(SO_USELOOPBACK) && !defined(__CYGWIN32__) opt = 1; if (SETSOCKOPT(fd, SOL_SOCKET, SO_USELOOPBACK, &opt, opt) < 0) report_error("setsockopt(SO_USELOOPBACK) %s:%s", cptr); #endif #ifdef SO_RCVBUF opt = 8192; if (SETSOCKOPT(fd, SOL_SOCKET, SO_RCVBUF, &opt, 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). */ # endif opt = 8192; if (SETSOCKOPT(fd, SOL_SOCKET, SO_SNDBUF, &opt, opt) < 0) report_error("setsockopt(SO_SNDBUF) %s:%s", cptr); # ifdef SO_SNDLOWAT /* * Setting the low water mark should improve performence by avoiding * early returns from select()/poll(). It shouldn't delay sending * data, provided that io_loop() combines read_message() and * flush_fdary/connections() calls properly. -kalt * This call isn't always implemented, even when defined.. so be quiet * about errors. -kalt */ opt = 8192; SETSOCKOPT(fd, SOL_SOCKET, SO_SNDLOWAT, &opt, opt); # endif #endif #if defined(IP_OPTIONS) && defined(IPPROTO_IP) && !defined(AIX) && \ !defined(SUN_GSO_BUG) && !defined(INET6) /* * Mainly to turn off and alert us to source routing, here. * Method borrowed from Wietse Venema's TCP wrapper. */ { if (!IsUnixSocket(cptr) && !IsListening(cptr)) { u_char opbuf[256], *t = opbuf; char *s = readbuf; opt = sizeof(opbuf); if (GETSOCKOPT(fd, IPPROTO_IP, IP_OPTIONS, t, &opt) == -1) report_error("getsockopt(IP_OPTIONS) %s:%s", cptr); else if (opt > 0) { for (; opt > 0; opt--, s+= 3) (void)sprintf(s, " %02x", *t++); *s = '\0'; sendto_flag(SCH_NOTICE, "Connection %s with IP opts%s", get_client_name(cptr, TRUE), readbuf); Debug((DEBUG_NOTICE, "Connection %s with IP opts%s", get_client_name(cptr, TRUE), readbuf)); ret = -1; } } } #endif return ret; } int get_sockerr(cptr) aClient *cptr; { int errtmp = errno, err = 0; SOCK_LEN_TYPE len = sizeof(err); #ifdef SO_ERROR if (cptr->fd >= 0) if (!GETSOCKOPT(cptr->fd, SOL_SOCKET, SO_ERROR, &err, &len)) if (err) errtmp = err; #endif return errtmp; } /* ** 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(fd, cptr) 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. */ #if NBLOCK_POSIX nonb |= O_NONBLOCK; #endif #if NBLOCK_BSD nonb |= O_NDELAY; #endif #if NBLOCK_SYSV /* This portion of code might also apply to NeXT. -LynX */ res = 1; if (ioctl (fd, FIONBIO, &res) < 0) report_error("ioctl(fd,FIONBIO) failed for %s:%s", cptr); #else if ((res = fcntl(fd, F_GETFL, 0)) == -1) report_error("fcntl(fd, F_GETFL) failed for %s:%s",cptr); else if (fcntl(fd, F_SETFL, res | nonb) == -1) report_error("fcntl(fd, F_SETL, nonb) failed for %s:%s",cptr); #endif return; } #ifdef CLONE_CHECK /* * check_clones * adapted by jecete 4 IRC Ptnet */ static int check_clones(cptr) aClient *cptr; { struct abacklog { struct IN_ADDR ip; time_t PT; struct abacklog *next; }; static struct abacklog *backlog = NULL; register struct abacklog **blscn = &backlog, *blptr; register int count = 0; /* First, ditch old entries */ while (*blscn != NULL) { if ((*blscn)->PT+CLONE_PERIOD < timeofday) { blptr= *blscn; *blscn=blptr->next; MyFree(blptr); } else blscn = &(*blscn)->next; } /* Now add new item to the list */ blptr = (struct abacklog *) MyMalloc(sizeof(struct abacklog)); #ifdef INET6 bcopy(cptr->ip.s6_addr, blptr->ip.s6_addr, IN6ADDRSZ); #else blptr->ip.s_addr = cptr->ip.s_addr; #endif blptr->PT = timeofday; blptr->next = backlog; backlog = blptr; /* Count the number of entries from the same host */ blptr = backlog; while (blptr != NULL) { #ifdef INET6 if (bcmp(blptr->ip.s6_addr, cptr->ip.s6_addr, IN6ADDRSZ) == 0) #else if (blptr->ip.s_addr == cptr->ip.s_addr) #endif count++; blptr = blptr->next; } return (count); } #endif /* * 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 yet since it doesnt have a name. */ aClient *add_connection(cptr, fd) aClient *cptr; int fd; { Link lin; aClient *acptr; aConfItem *aconf = NULL; acptr = make_client(NULL); aconf = cptr->confs->value.aconf; /* 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. */ if (isatty(fd)) /* If descriptor is a tty, special checking... */ get_sockhost(acptr, cptr->sockhost); else { struct SOCKADDR_IN addr; SOCK_LEN_TYPE len = sizeof(struct SOCKADDR_IN); if (getpeername(fd, (SAP)&addr, &len) == -1) { #if defined(linux) if (errno != ENOTCONN) #endif report_error("Failed in connecting to %s :%s", cptr); add_con_refuse: ircstp->is_ref++; acptr->fd = -2; free_client(acptr); (void)close(fd); return NULL; } /* don't want to add "Failed in connecting to" here.. */ if (aconf && IsIllegal(aconf)) goto add_con_refuse; /* Copy ascii address to 'sockhost' just in case. Then we * have something valid to put into error messages... */ #ifdef INET6 inetntop(AF_INET6, (char *)&addr.sin6_addr, mydummy, MYDUMMY_SIZE); get_sockhost(acptr, (char *)mydummy); #else get_sockhost(acptr, (char *)inetntoa((char *)&addr.sin_addr)); #endif bcopy ((char *)&addr.SIN_ADDR, (char *)&acptr->ip, sizeof(struct IN_ADDR)); acptr->port = ntohs(addr.SIN_PORT); lin.flags = ASYNC_CLIENT; lin.value.cptr = acptr; lin.next = NULL; #ifdef INET6 Debug((DEBUG_DNS, "lookup %s", inet_ntop(AF_INET6, (char *)&addr.sin6_addr, mydummy, MYDUMMY_SIZE))); #else Debug((DEBUG_DNS, "lookup %s", inetntoa((char *)&addr.sin_addr))); #endif acptr->hostp = gethost_byaddr((char *)&acptr->ip, &lin); if (!acptr->hostp) SetDNS(acptr); nextdnscheck = 1; } #ifdef CLONE_CHECK if (check_clones(acptr) > CLONE_MAX) { sendto_flag(SCH_LOCAL, "Rejecting connection from %s[%s].", (acptr->hostp) ? acptr->hostp->h_name : "", acptr->sockhost); sendto_flog(acptr, " ?Clone? ", "", (acptr->hostp) ? acptr->hostp->h_name : acptr->sockhost); del_queries((char *)acptr); (void)send(fd, "ERROR :Too rapid connections from your host\r\n", 46, 0); goto add_con_refuse; } #endif acptr->fd = fd; set_non_blocking(acptr->fd, acptr); if (set_sock_opts(acptr->fd, acptr) == -1) goto add_con_refuse; if (aconf) aconf->clients++; if (fd > highest_fd) highest_fd = fd; local[fd] = acptr; add_fd(fd, &fdall); acptr->acpt = cptr; add_client_to_list(acptr); start_auth(acptr); #if defined(USE_IAUTH) if (!isatty(fd) && !DoingDNS(acptr)) { int i = 0; while (acptr->hostp->h_aliases[i]) sendto_iauth("%d A %s", acptr->fd, acptr->hostp->h_aliases[i++]); if (acptr->hostp->h_name) sendto_iauth("%d N %s",acptr->fd,acptr->hostp->h_name); else if (acptr->hostp->h_aliases[0]) sendto_iauth("%d n", acptr->fd); } #endif #ifdef RUSNET_IRCD acptr->transptr = rusnet_getptrbyport( rusnet_getclientport(acptr->acpt->fd)); #endif return acptr; } #ifdef UNIXPORT static void add_unixconnection(cptr, fd) aClient *cptr; int fd; { aClient *acptr; aConfItem *aconf = NULL; acptr = make_client(NULL); /* Copy ascii address to 'sockhost' just in case. Then we * have something valid to put into error messages... */ get_sockhost(acptr, me.sockhost); aconf = cptr->confs->value.aconf; if (aconf) { if (IsIllegal(aconf)) { ircstp->is_ref++; acptr->fd = -2; free_client(acptr); (void)close(fd); return; } else aconf->clients++; } acptr->fd = fd; if (fd > highest_fd) highest_fd = fd; local[fd] = acptr; add_fd(fd, &fdall); acptr->acpt = cptr; SetUnixSock(acptr); bcopy((char *)&me.ip, (char *)&acptr->ip, sizeof(struct IN_ADDR)); add_client_to_list(acptr); set_non_blocking(acptr->fd, acptr); (void)set_sock_opts(acptr->fd, acptr); # if defined(USE_IAUTH) /* ** iauth protocol and iauth itself should be extended to alllow ** dealing with this type of connection. */ sendto_iauth("%d O", acptr->fd); SetDoneXAuth(acptr); # endif return; } #endif /* ** read_listener ** ** Accept incoming connections, extracted from read_message() 98/12 -kalt ** Up to 10 connections will be accepted, unless SLOW_ACCEPT is defined. */ static void read_listener(cptr) aClient *cptr; { int fdnew, max = 10; #if defined(SLOW_ACCEPT) max = 1; #endif while (max--) { /* ** 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 ((fdnew = accept(cptr->fd, NULL, NULL)) < 0) { if (errno != EWOULDBLOCK) report_error("Cannot accept connection %s:%s", cptr); break; } ircstp->is_ac++; if (fdnew >= MAXCLIENTS) { ircstp->is_ref++; sendto_flag(SCH_ERROR, "All connections in use. (%s)", get_client_name(cptr, TRUE)); find_bounce(NULL, 0, fdnew); (void)send(fdnew, "ERROR :All connections in use\r\n", 32, 0); (void)close(fdnew); continue; } /* * Use of add_connection (which never fails :) meLazy * Never say never. MrMurphy visited here. -Vesa */ #ifdef UNIXPORT if (IsUnixSocket(cptr)) add_unixconnection(cptr, fdnew); else #endif if (!add_connection(cptr, fdnew)) continue; nextping = timeofday; /* isn't this abusive? -kalt */ istat.is_unknown++; } } /* ** client_packet ** ** Process data from receive buffer to client. ** Extracted from read_packet() 960804/291p3/Vesa */ static int client_packet(cptr) Reg aClient *cptr; { Reg int dolen = 0; while (DBufLength(&cptr->recvQ) && !NoNewLine(cptr) && ((cptr->status < STAT_UNKNOWN) || (cptr->since - timeofday < MAXPENALTY))) { /* ** If it has become registered as a Service or Server ** then skip the per-message parsing below. */ if (IsService(cptr) || IsServer(cptr)) { dolen = dbuf_get(&cptr->recvQ, readbuf, sizeof(readbuf)); if (dolen <= 0) break; dolen = dopacket(cptr, readbuf, dolen); if (dolen == 2 && cptr->since == cptr->lasttime) cptr->since += 5; if (dolen) return dolen; 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, &me, "dbuf_getmsg fail"); if (DBufLength(&cptr->recvQ) < 510) { /* hmm? */ cptr->flags |= FLAGS_NONL; break; } dolen = dbuf_get(&cptr->recvQ, readbuf, 511); if (dolen > 0 && DBufLength(&cptr->recvQ)) DBufClear(&cptr->recvQ); } /* Is it okay not to test for other return values? -krys */ if (dolen > 0 && (dopacket(cptr, readbuf, dolen) == FLUSH_BUFFER)) return FLUSH_BUFFER; } return 1; } /* ** 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 */ static int read_packet(cptr, msg_ready) Reg aClient *cptr; int msg_ready; { Reg int length = 0, done; if (msg_ready && !(IsPerson(cptr) && DBufLength(&cptr->recvQ) > 6090)) { errno = 0; #ifdef INET6 length = recvfrom(cptr->fd, readbuf, sizeof(readbuf), 0, 0, 0); #else length = recv(cptr->fd, readbuf, sizeof(readbuf), 0); #endif #if defined(DEBUGMODE) && defined(DEBUG_READ) if (length > 0) Debug((DEBUG_READ, "recv = %d bytes from %d[%s]:[%*.*s]\n", length, cptr->fd, cptr->name, length, length, readbuf)); #endif Debug((DEBUG_DEBUG, "Received %d(%d-%s) bytes from %d %s", length, errno, strerror(errno), cptr->fd, get_client_name(cptr, TRUE))); #ifdef RUSNET_IRCD /* Something came from the client */ rusnet_translate(cptr->transptr, RUSNET_DIR_INCOMING, readbuf, length); #endif cptr->lasttime = timeofday; 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 == -1 && ((errno == EWOULDBLOCK) || (errno == EAGAIN))) return 1; if (length <= 0) return length; } else if (msg_ready) return exit_client(cptr, cptr, &me, "EOF From Client"); /* ** 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) || IsService(cptr)) { if (length > 0) { done = dopacket(cptr, readbuf, length); if (done && done != 2) 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 (length && dbuf_put(&cptr->recvQ, readbuf, length) < 0) return exit_client(cptr, cptr, &me, "dbuf_put fail"); if (IsPerson(cptr) && DBufLength(&cptr->recvQ) > #ifdef RUSNET_IRCD (cptr->flood ? cptr->flood : CLIENT_FLOOD) #else CLIENT_FLOOD #endif ) { cptr->exitc = EXITC_FLOOD; return exit_client(cptr, cptr, &me, "Excess Flood"); } return client_packet(cptr); } return 1; } /* * 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. */ int read_message(delay, fdp, ro) time_t delay; /* Don't ever use ZERO here, unless you mean to poll and then * you have to have sleep/wait somewhere else in the code.--msa * Actually, ZERO is NOT ZERO anymore.. see below -kalt */ FdAry *fdp; int ro; { #if ! USE_POLL # define SET_READ_EVENT( thisfd ) FD_SET( thisfd, &read_set) # define SET_WRITE_EVENT( thisfd ) FD_SET( thisfd, &write_set) # define CLR_READ_EVENT( thisfd ) FD_CLR( thisfd, &read_set) # define CLR_WRITE_EVENT( thisfd ) FD_CLR( thisfd, &write_set) # define TST_READ_EVENT( thisfd ) FD_ISSET( thisfd, &read_set) # define TST_WRITE_EVENT( thisfd ) FD_ISSET( thisfd, &write_set) fd_set read_set, write_set; int highfd = -1; #else /* most of the following use pfd */ # define POLLSETREADFLAGS (POLLIN|POLLRDNORM) # define POLLREADFLAGS (POLLSETREADFLAGS|POLLHUP|POLLERR) # define POLLSETWRITEFLAGS (POLLOUT|POLLWRNORM) # define POLLWRITEFLAGS (POLLOUT|POLLWRNORM|POLLHUP|POLLERR) # define SET_READ_EVENT( thisfd ){ CHECK_PFD( thisfd );\ pfd->events |= POLLSETREADFLAGS;} # define SET_WRITE_EVENT( thisfd ){ CHECK_PFD( thisfd );\ pfd->events |= POLLSETWRITEFLAGS;} # define CLR_READ_EVENT( thisfd ) pfd->revents &= ~POLLSETREADFLAGS # define CLR_WRITE_EVENT( thisfd ) pfd->revents &= ~POLLSETWRITEFLAGS # define TST_READ_EVENT( thisfd ) pfd->revents & POLLREADFLAGS # define TST_WRITE_EVENT( thisfd ) pfd->revents & POLLWRITEFLAGS # define CHECK_PFD( thisfd ) \ if ( pfd->fd != thisfd ) { \ pfd = &poll_fdarray[nbr_pfds++];\ pfd->fd = thisfd; \ pfd->events = 0; \ } struct pollfd poll_fdarray[MAXCONNECTIONS]; struct pollfd * pfd = poll_fdarray; struct pollfd * res_pfd = NULL; struct pollfd * udp_pfd = NULL; struct pollfd * ad_pfd = NULL; aClient * authclnts[MAXCONNECTIONS]; /* mapping of auth fds to client ptrs */ int nbr_pfds = 0; #endif aClient *cptr; int nfds, ret = 0; struct timeval wait; time_t delay2 = delay; int res, length, fd, i; int auth; for (res = 0;;) { #if ! USE_POLL FD_ZERO(&read_set); FD_ZERO(&write_set); #else /* set up such that CHECK_FD works */ nbr_pfds = 0; pfd = poll_fdarray; pfd->fd = -1; res_pfd = NULL; udp_pfd = NULL; ad_pfd = NULL; #endif /* USE_POLL */ auth = 0; #if USE_POLL if ( auth == 0 ) bzero((char *) authclnts, sizeof( authclnts )); #endif for (i = fdp->highest; i >= 0; i--) { if (!(cptr = local[fd = fdp->fd[i]]) || IsLog(cptr) || IsHeld(cptr)) continue; Debug((DEBUG_L11, "fd %d cptr %#x %d %#x %s", fd, cptr, cptr->status, cptr->flags, get_client_name(cptr,TRUE))); /* authentication fd's */ if (DoingAuth(cptr)) { auth++; SET_READ_EVENT(cptr->authfd); Debug((DEBUG_NOTICE,"auth on %x %d", cptr, fd)); if (cptr->flags & FLAGS_WRAUTH) SET_WRITE_EVENT(cptr->authfd); #if USE_POLL authclnts[cptr->authfd] = cptr; #else if (cptr->authfd > highfd) highfd = cptr->authfd; #endif } /* ** if any of these is true, data won't be parsed ** so no need to check for anything! */ #if defined(USE_IAUTH) if (DoingDNS(cptr) || DoingAuth(cptr) || WaitingXAuth(cptr) || (DoingXAuth(cptr) && !(iauth_options & XOPT_EARLYPARSE))) #else if (DoingDNS(cptr) || DoingAuth(cptr)) #endif continue; #if ! USE_POLL if (fd > highfd) highfd = fd; #endif /* ** Checking for new connections is only done up to ** once per second. */ if (IsListening(cptr)) { if (timeofday > cptr->lasttime + 1 && ro == 0) { SET_READ_EVENT( fd ); } else if (delay2 > 1) delay2 = 1; continue; } /* ** This is very approximate, it should take ** cptr->since into account. -kalt */ if (DBufLength(&cptr->recvQ) && delay2 > 2) delay2 = 1; if (IsRegisteredUser(cptr)) { if (cptr->since - timeofday < MAXPENALTY+1) SET_READ_EVENT( fd ); } else if (DBufLength(&cptr->recvQ) < 4088) SET_READ_EVENT( fd ); /* ** If we have anything in the sendQ, check if there is ** room to write data. */ if (DBufLength(&cptr->sendQ) || IsConnecting(cptr) || #ifdef ZIP_LINKS ((cptr->flags & FLAGS_ZIP) && (cptr->zip->outcount > 0)) || #endif IsReconnect(cptr)) if (IsServer(cptr) || IsConnecting(cptr) || ro == 0) SET_WRITE_EVENT( fd ); } if (udpfd >= 0) { SET_READ_EVENT(udpfd); #if ! USE_POLL if (udpfd > highfd) highfd = udpfd; #else udp_pfd = pfd; #endif } if (resfd >= 0) { SET_READ_EVENT(resfd); #if ! USE_POLL if (resfd > highfd) highfd = resfd; #else res_pfd = pfd; #endif } #if defined(USE_IAUTH) if (adfd >= 0) { SET_READ_EVENT(adfd); # if ! USE_POLL if (adfd > highfd) highfd = adfd; # else ad_pfd = pfd; # endif } #endif Debug((DEBUG_L11, "udpfd %d resfd %d adfd %d", udpfd, resfd, adfd)); #if ! USE_POLL Debug((DEBUG_L11, "highfd %d", highfd)); #endif wait.tv_sec = MIN(delay2, delay); wait.tv_usec = (delay == 0) ? 200000 : 0; #if ! USE_POLL nfds = select(highfd + 1, (SELECT_FDSET_TYPE *)&read_set, (SELECT_FDSET_TYPE *)&write_set, 0, &wait); #else nfds = poll( poll_fdarray, nbr_pfds, wait.tv_sec * 1000 + wait.tv_usec/1000 ); #endif ret = nfds; if (nfds == -1 && errno == EINTR) return -1; else if (nfds >= 0) break; #if ! USE_POLL report_error("select %s:%s", &me); #else report_error("poll %s:%s", &me); #endif res++; if (res > 5) server_reboot("too many select()/poll() errors"); sleep(10); timeofday = time(NULL); } /* for(res=0;;) */ timeofday = time(NULL); if (nfds > 0 && #if ! USE_POLL resfd >= 0 && #else (pfd = res_pfd) && #endif TST_READ_EVENT(resfd)) { CLR_READ_EVENT(resfd); nfds--; do_dns_async(); } if (nfds > 0 && #if ! USE_POLL udpfd >= 0 && #else (pfd = udp_pfd) && #endif TST_READ_EVENT(udpfd)) { CLR_READ_EVENT(udpfd); nfds--; polludp(); } #if defined(USE_IAUTH) if (nfds > 0 && # if ! USE_POLL adfd >= 0 && # else (pfd = ad_pfd) && # endif TST_READ_EVENT(adfd)) { CLR_READ_EVENT(adfd); nfds--; read_iauth(); } #endif #if ! USE_POLL for (i = fdp->highest; i >= 0; i--) #else for (pfd = poll_fdarray, i = 0; i < nbr_pfds; i++, pfd++ ) #endif { #if ! USE_POLL if (!(cptr = local[fd = fdp->fd[i]])) continue; #else fd = pfd->fd; if ((cptr = authclnts[fd])) { #endif /* * check for the auth fd's */ if (auth > 0 && nfds > 0 #if ! USE_POLL && cptr->authfd >= 0 #endif ) { auth--; if (TST_WRITE_EVENT(cptr->authfd)) { nfds--; send_authports(cptr); } else if (TST_READ_EVENT(cptr->authfd)) { nfds--; read_authports(cptr); } continue; } #if USE_POLL } fd = pfd->fd; if (!(cptr = local[fd])) continue; #else fd = cptr->fd; #endif /* * accept connections */ if (TST_READ_EVENT(fd) && IsListening(cptr)) { CLR_READ_EVENT(fd); cptr->lasttime = timeofday; read_listener(cptr); continue; } if (IsMe(cptr)) continue; if (TST_WRITE_EVENT(fd)) { int write_err = 0; /* ** ...room for writing, empty some queue then... */ if (IsConnecting(cptr)) write_err = completed_connection(cptr); if (!write_err) (void)send_queued(cptr); if (IsDead(cptr) || write_err) { deadsocket: if (TST_READ_EVENT(fd)) CLR_READ_EVENT(fd); if (cptr->exitc == EXITC_SENDQ) { (void)exit_client(cptr,cptr,&me, "Max SendQ exceeded"); } else { cptr->exitc = EXITC_ERROR; (void)exit_client(cptr, cptr, &me, strerror(get_sockerr(cptr))); } continue; } } length = 1; /* for fall through case */ if (!NoNewLine(cptr) || TST_READ_EVENT(fd)) { if (!DoingAuth(cptr)) length = read_packet(cptr, TST_READ_EVENT(fd)); } readcalls++; if (length == FLUSH_BUFFER) continue; else if (length > 0) flush_connections(cptr->fd); if (IsDead(cptr)) goto deadsocket; if (length > 0) continue; /* Ghost! Unknown users are tagged in parse() since 2.9. * Let's not drop the uplink but just the ghost's message. */ if (length == -3) continue; /* ** NB: This following section has been modified to *expect* ** cptr to be valid (ie if (length == FLUSH_BUFFER) is ** above and stays there). - avalon 24/9/94 */ /* ** ...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 %d %d", cptr->fd, errno, length)); if (IsServer(cptr) || IsHandshake(cptr)) { int timeconnected = timeofday - cptr->firsttime; if (length == 0) sendto_flag(SCH_NOTICE, "Server %s closed the connection (%d, %2d:%02d:%02d)", get_client_name(cptr, FALSE), timeconnected / 86400, (timeconnected % 86400) / 3600, (timeconnected % 3600)/60, timeconnected % 60); else /* this must be for -1 */ { report_error("Lost connection to %s:%s",cptr); sendto_flag(SCH_NOTICE, "%s had been connected for %d, %2d:%02d:%02d", get_client_name(cptr, FALSE), timeconnected / 86400, (timeconnected % 86400) / 3600, (timeconnected % 3600)/60, timeconnected % 60); if (hold_server(cptr) == 0) continue; } } (void)exit_client(cptr, cptr, &me, length >= 0 ? "EOF From client" : strerror(get_sockerr(cptr))); } /* for(i) */ return ret; } /* * connect_server */ int connect_server(aconf, by, hp) aConfItem *aconf; aClient *by; struct hostent *hp; { Reg struct SOCKADDR *svp; Reg aClient *cptr, *c2ptr; Reg char *s; int i, len; #ifdef RUSNET_IRCD extern struct sockaddr_in virtual_addr; static char interface_name[256] = "[Default]"; #endif #ifdef INET6 Debug((DEBUG_NOTICE,"Connect to %s[%s] @%s", aconf->name, aconf->host, inet_ntop(AF_INET6, (char *)&aconf->ipnum, mydummy, MYDUMMY_SIZE))); #else Debug((DEBUG_NOTICE,"Connect to %s[%s] @%s", aconf->name, aconf->host, inetntoa((char *)&aconf->ipnum))); #endif if ((c2ptr = find_server(aconf->name, NULL))) { sendto_flag(SCH_NOTICE, "Server %s already present from %s", aconf->name, get_client_name(c2ptr, TRUE)); if (by && IsPerson(by) && !MyClient(by)) sendto_one(by, ":%s NOTICE %s :Server %s already present from %s", ME, by->name, aconf->name, get_client_name(c2ptr, TRUE)); return -1; } /* * If we don't know the IP# for this host and it is a hostname and * not a ip# string, then try and find the appropriate host record. */ if (!aconf->ipnum.S_ADDR && *aconf->host != '/') { Link lin; lin.flags = ASYNC_CONNECT; lin.value.aconf = aconf; nextdnscheck = 1; s = (char *)index(aconf->host, '@'); s++; /* should NEVER be NULL */ #ifdef INET6 if (!inetpton(AF_INET6, s, aconf->ipnum.s6_addr)) #else if ((aconf->ipnum.s_addr = inetaddr(s)) == -1) #endif { #ifdef INET6 bzero(aconf->ipnum.s6_addr, IN6ADDRSZ); #else aconf->ipnum.s_addr = 0; #endif hp = gethost_byname(s, &lin); Debug((DEBUG_NOTICE, "co_sv: hp %x ac %x na %s ho %s", hp, aconf, aconf->name, s)); if (!hp) return 0; bcopy(hp->h_addr, (char *)&aconf->ipnum, sizeof(struct IN_ADDR)); } } cptr = make_client(NULL); cptr->hostp = hp; /* * Copy these in so we have something for error detection. */ strncpyzt(cptr->name, aconf->name, sizeof(cptr->name)); strncpyzt(cptr->sockhost, aconf->host, HOSTLEN+1); #ifdef UNIXPORT if (*aconf->host == '/') /* (/ starts a 2), Unix domain -- dl*/ svp = connect_unix(aconf, cptr, &len); else svp = connect_inet(aconf, cptr, &len); #else svp = connect_inet(aconf, cptr, &len); #endif if (!svp) { if (cptr->fd != -1) (void)close(cptr->fd); cptr->fd = -2; free_client(cptr); return -1; } set_non_blocking(cptr->fd, cptr); #ifdef RUSNET_IRCD /*RusNet PL3*/ /* Let's check the number and try to bind to a valid interface. Non-zero return code means the failed attempt */ if (!rusnet_bind_interface_address(cptr->fd, (struct sockaddr_in *)svp, interface_name)) { sendto_flag(SCH_NOTICE, "Binding to %s to connect", interface_name); } else /* binding virtual interface from rusnet.conf failed. Fallback */ { if (bind(cptr->fd, (SAP)&mysk, sizeof(mysk)) == -1) { report_error("error binding to local port for %s:%s", cptr); return -1; } } #endif (void)set_sock_opts(cptr->fd, cptr); (void)signal(SIGALRM, dummy); (void)alarm(4); if (connect(cptr->fd, (SAP)svp, len) < 0 && errno != EINPROGRESS) { i = errno; /* other system calls may eat errno */ (void)alarm(0); 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, by->name, cptr); (void)close(cptr->fd); cptr->fd = -2; free_client(cptr); errno = i; if (errno == EINTR) errno = ETIMEDOUT; return -1; } (void)alarm(0); /* Attach config entries to client here rather than in * completed_connection. This to avoid null pointer references * when name returned by gethostbyaddr matches no C lines * (could happen in 2.6.1a when host and servername differ). * No need to check access and do gethostbyaddr calls. * There must at least be one as we got here C line... meLazy */ (void)attach_confs_host(cptr, aconf->host, CFLAG|NFLAG); if (!find_conf_host(cptr->confs, aconf->host, NFLAG) || !find_conf_host(cptr->confs, aconf->host, CFLAG)) { sendto_flag(SCH_NOTICE, "Host %s is not enabled for connecting:no C/N-line", aconf->host); if (by && IsPerson(by) && !MyClient(by)) sendto_one(by, ":%s NOTICE %s :Connect to host %s failed.", ME, by->name, cptr); det_confs_butmask(cptr, 0); (void)close(cptr->fd); cptr->fd = -2; free_client(cptr); return(-1); } /* ** The socket has been connected or connect is in progress. */ (void)make_server(cptr); if (by && IsPerson(by)) { (void)strcpy(cptr->serv->by, by->name); cptr->serv->user = by->user; by->user->refcnt++; } else (void)strcpy(cptr->serv->by, "AutoConn."); cptr->serv->up = ME; cptr->serv->nline = aconf; #ifdef RUSNET_IRCD cptr->serv->crc = gen_crc(cptr->name); #endif if (cptr->fd > highest_fd) highest_fd = cptr->fd; add_fd(cptr->fd, &fdall); local[cptr->fd] = cptr; cptr->acpt = &me; SetConnecting(cptr); get_sockhost(cptr, aconf->host); add_client_to_list(cptr); nextping = timeofday; istat.is_unknown++; return 0; } static struct SOCKADDR *connect_inet(aconf, cptr, lenp) Reg aConfItem *aconf; Reg aClient *cptr; int *lenp; { static struct SOCKADDR_IN server; Reg struct hostent *hp; aClient *acptr; int i; /* * 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 >= MAXCLIENTS) { sendto_flag(SCH_NOTICE, "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->host); if (cptr->fd == -1) { report_error("opening stream socket to server %s:%s", cptr); return NULL; } #ifndef RUSNET_IRCD /* RusNet PL3 and higher needs to perform bind later */ /* ** Bind to a local IP# (with unknown port - let unix decide) so ** we have some chance of knowing the IP# that gets used for a host ** with more than one IP#. ** With VIFs, M:line defines outgoing IP# and initialises mysk. */ if (bind(cptr->fd, (SAP)&mysk, sizeof(mysk)) == -1) { report_error("error binding to local port for %s:%s", cptr); return NULL; } #endif /* * 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 don't know it, then the connect fails. */ #ifdef INET6 if (isdigit(*aconf->host) && (AND16(aconf->ipnum.s6_addr) == 255)) if (!inetpton(AF_INET6, aconf->host,aconf->ipnum.s6_addr)) bcopy(minus_one, aconf->ipnum.s6_addr, IN6ADDRSZ); if (AND16(aconf->ipnum.s6_addr) == 255) #else if (isdigit(*aconf->host) && (aconf->ipnum.s_addr == -1)) aconf->ipnum.s_addr = inetaddr(aconf->host); if (aconf->ipnum.s_addr == -1) #endif { hp = cptr->hostp; if (!hp) { Debug((DEBUG_FATAL, "%s: unknown host", aconf->host)); 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)); cptr->port = (aconf->port > 0) ? aconf->port : portnum; server.SIN_PORT = htons(cptr->port); /* * Look for a duplicate IP#,port pair among already open connections * (This caters for unestablished connections). */ for (i = highest_fd; i >= 0; i--) if ((acptr = local[i]) && !bcmp((char *)&cptr->ip, (char *)&acptr->ip, sizeof(cptr->ip)) && server.SIN_PORT == acptr->port) return NULL; *lenp = sizeof(server); return (struct SOCKADDR *)&server; } #ifdef UNIXPORT /* connect_unix * * Build a socket structure for cptr so that it can connet to the unix * socket defined by the conf structure aconf. */ static struct SOCKADDR *connect_unix(aconf, cptr, lenp) aConfItem *aconf; aClient *cptr; int *lenp; { static struct sockaddr_un sock; if ((cptr->fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { report_error("Unix domain connect to host %s failed: %s", cptr); return NULL; } else if (cptr->fd >= MAXCLIENTS) { sendto_flag(SCH_NOTICE, "No more connections allowed (%s)", cptr->name); return NULL; } get_sockhost(cptr, aconf->host); strncpyzt(sock.sun_path, aconf->host + 2, sizeof(sock.sun_path)); sock.sun_family = AF_UNIX; *lenp = strlen(sock.sun_path) + 2; SetUnixSock(cptr); return (struct sockaddr *)&sock; } #endif /* * The following section of code performs summoning of users to irc. */ #if defined(ENABLE_SUMMON) || defined(ENABLE_USERS) int utmp_open() { #ifdef O_NOCTTY return (open(UTMP, O_RDONLY|O_NOCTTY)); #else return (open(UTMP, O_RDONLY)); #endif } int utmp_read(fd, name, line, host, hlen) int fd, hlen; char *name, *line, *host; { struct utmp ut; while (read(fd, (char *)&ut, sizeof (struct utmp)) == sizeof (struct utmp)) { strncpyzt(name, ut.ut_name, 9); strncpyzt(line, ut.ut_line, 10); #ifdef USER_PROCESS # if defined(HPUX) || defined(AIX) strncpyzt(host,(ut.ut_host[0]) ? (ut.ut_host) : ME, 16); # else strncpyzt(host, ME, 9); # endif if (ut.ut_type == USER_PROCESS) return 0; #else strncpyzt(host, (ut.ut_host[0]) ? (ut.ut_host) : ME, hlen); if (ut.ut_name[0]) return 0; #endif } return -1; } int utmp_close(fd) int fd; { return(close(fd)); } #ifdef ENABLE_SUMMON void summon(who, namebuf, linebuf, chname) aClient *who; char *namebuf, *linebuf, *chname; { static char wrerr[] = "NOTICE %s :Write error. Couldn't summon."; int fd; char line[512]; struct tm *tp; tp = localtime(&timeofday); if (strlen(linebuf) > (size_t) 9) { sendto_one(who,"NOTICE %s :Serious fault in SUMMON.", who->name); sendto_one(who, "NOTICE %s :linebuf too long. Inform Administrator", who->name); return; } /* * Following line added to prevent cracking to e.g. /dev/kmem if * UTMP is for some silly reason writable to everyone... */ if ((linebuf[0] != 't' || linebuf[1] != 't' || linebuf[2] != 'y') && (linebuf[0] != 'c' || linebuf[1] != 'o' || linebuf[2] != 'n') && (linebuf[0] != 'p' || linebuf[1] != 't' || linebuf[2] != 's') #ifdef HPUX && (linebuf[0] != 'p' || linebuf[1] != 't' || linebuf[2] != 'y' || linebuf[3] != '/') #endif ) { sendto_one(who, "NOTICE %s :Looks like mere mortal souls are trying to", who->name); sendto_one(who,"NOTICE %s :enter the twilight zone... ", who->name); #ifdef RUSNET_IRCD Debug((0, "%s (%s@%s, nick %s, %s)", "FATAL: major security hack. Notify Administrator !", who->username, who->sockhost, who->name, who->info)); #else Debug((0, "%s (%s@%s, nick %s, %s)", "FATAL: major security hack. Notify Administrator !", who->username, who->user->host, who->name, who->info)); #endif return; } SPRINTF(line,"/dev/%s", linebuf); (void)alarm(5); #ifdef O_NOCTTY if ((fd = open(line, O_WRONLY | O_NDELAY | O_NOCTTY)) == -1) #else if ((fd = open(line, O_WRONLY | O_NDELAY)) == -1) #endif { (void)alarm(0); sendto_one(who, "NOTICE %s :%s seems to have disabled summoning...", who->name, namebuf); return; } #if !defined(O_NOCTTY) && defined(TIOCNOTTY) (void)ioctl(fd, TIOCNOTTY, NULL); #endif (void)alarm(0); (void)sprintf(line,"\n\r\007Message from IRC_Daemon@%s at %d:%02d\n\r", ME, tp->tm_hour, tp->tm_min); if (write(fd, line, strlen(line)) != strlen(line)) { (void)alarm(0); (void)close(fd); sendto_one(who, wrerr, who->name); return; } (void)alarm(0); (void)strcpy(line, "ircd: You are being summoned to Internet Relay \ Chat on\n\r"); (void)alarm(5); if (write(fd, line, strlen(line)) != strlen(line)) { (void)alarm(0); (void)close(fd); sendto_one(who, wrerr, who->name); return; } (void)alarm(0); #ifdef RUSNET_IRCD SPRINTF(line, "ircd: Channel %s, by %s@%s (%s) %s\n\r", chname, who->user->username, who->sockhost, who->name, who->info); #else SPRINTF(line, "ircd: Channel %s, by %s@%s (%s) %s\n\r", chname, who->user->username, who->user->host, who->name, who->info); #endif (void)alarm(5); if (write(fd, line, strlen(line)) != strlen(line)) { (void)alarm(0); (void)close(fd); sendto_one(who, wrerr, who->name); return; } (void)alarm(0); (void)strcpy(line,"ircd: Respond with irc\n\r"); (void)alarm(5); if (write(fd, line, strlen(line)) != strlen(line)) { (void)alarm(0); (void)close(fd); sendto_one(who, wrerr, who->name); return; } (void)close(fd); (void)alarm(0); sendto_one(who, rpl_str(RPL_SUMMONING, who->name), namebuf); return; } # endif #endif /* ENABLE_SUMMON */ /* ** find the real hostname for the host running the server (or one which ** matches the server's name) and its primary IP#. Hostname is stored ** in the client structure passed as a pointer. */ void get_my_name(cptr, name, len) aClient *cptr; char *name; int len; { static char tmp[HOSTLEN+1]; struct hostent *hp; char *cname = cptr->name; aConfItem *aconf; #ifdef HAVE_GETIPNODEBYNAME int error_num1, error_num2; struct hostent *hp1, *hp2; #endif /* ** Setup local socket structure to use for binding to. */ bzero((char *)&mysk, sizeof(mysk)); mysk.SIN_FAMILY = AFINET; if ((aconf = find_me())->passwd && isdigit(*aconf->passwd)) #ifdef INET6 if(!inetpton(AF_INET6, aconf->passwd, mysk.sin6_addr.s6_addr)) bcopy(minus_one, mysk.sin6_addr.s6_addr, IN6ADDRSZ); #else mysk.sin_addr.s_addr = inetaddr(aconf->passwd); #endif if (gethostname(name, len) == -1) return; name[len] = '\0'; add_local_domain(name, len - strlen(name)); /* ** If hostname gives another name than cname, then check if there is ** a CNAME record for cname pointing to hostname. If so accept ** cname as our name. meLazy */ if (BadPtr(cname)) return; #ifdef HAVE_GETIPNODEBYNAME hp1 = getipnodebyname(cname, AF_INET6, AI_DEFAULT, &error_num1); hp2 = getipnodebyname(name, AF_INET6, AI_DEFAULT, &error_num2); if (! error_num1) hp=hp1; else hp=hp2; if ((! error_num1) || (! error_num2)) #else if ((hp = gethostbyname(cname)) || (hp = gethostbyname(name))) #endif { char *hname; int i = 0; for (hname = hp->h_name; hname; hname = hp->h_aliases[i++]) { strncpyzt(tmp, hname, sizeof(tmp)); add_local_domain(tmp, sizeof(tmp) - strlen(tmp)); /* ** Copy the matching name over and store the ** 'primary' IP# as 'myip' which is used ** later for making the right one is used ** for connecting to other hosts. */ if (!strcasecmp(ME, tmp)) break; } if (strcasecmp(ME, tmp)) strncpyzt(name, hp->h_name, len); else strncpyzt(name, tmp, len); if (!aconf->passwd) bcopy(hp->h_addr, (char *)&mysk.SIN_ADDR, sizeof(struct IN_ADDR)); Debug((DEBUG_DEBUG,"local name is %s", get_client_name(&me,TRUE))); } #ifdef HAVE_GETIPNODEBYNAME freehostent(hp1); freehostent(hp2); #endif return; } /* ** setup a UDP socket and listen for incoming packets */ int setup_ping(aconf) aConfItem *aconf; { struct SOCKADDR_IN from; int on = 1; if (udpfd != -1) return udpfd; bzero((char *)&from, sizeof(from)); if (aconf->passwd && isdigit(*aconf->passwd)) #ifdef INET6 { if (!inetpton(AF_INET6, aconf->passwd,from.sin6_addr.s6_addr)) bcopy(minus_one, from.sin6_addr.s6_addr, IN6ADDRSZ); } #else from.sin_addr.s_addr = inetaddr(aconf->passwd); #endif else #ifdef INET6 from.SIN_ADDR = in6addr_any; #else from.sin_addr.s_addr = htonl(INADDR_ANY); /* hmmpf */ #endif from.SIN_PORT = htons((u_short) aconf->port); from.SIN_FAMILY = AFINET; if ((udpfd = socket(AFINET, SOCK_DGRAM, 0)) == -1) { Debug((DEBUG_ERROR, "socket udp : %s", strerror(errno))); return -1; } if (SETSOCKOPT(udpfd, SOL_SOCKET, SO_REUSEADDR, &on, on) == -1) { #ifdef USE_SYSLOG syslog(LOG_ERR, "setsockopt udp fd %d : %m", udpfd); #endif Debug((DEBUG_ERROR, "setsockopt so_reuseaddr : %s", strerror(errno))); (void)close(udpfd); return udpfd = -1; } on = 0; (void) SETSOCKOPT(udpfd, SOL_SOCKET, SO_BROADCAST, &on, on); if (bind(udpfd, (SAP)&from, sizeof(from))==-1) { #ifdef USE_SYSLOG syslog(LOG_ERR, "bind udp.%d fd %d : %m", ntohs(from.SIN_PORT), udpfd); #endif Debug((DEBUG_ERROR, "bind : %s", strerror(errno))); (void)close(udpfd); return udpfd = -1; } if (fcntl(udpfd, F_SETFL, FNDELAY)==-1) { Debug((DEBUG_ERROR, "fcntl fndelay : %s", strerror(errno))); (void)close(udpfd); return udpfd = -1; } Debug((DEBUG_INFO, "udpfd = %d, port %d", udpfd,ntohs(from.SIN_PORT))); return udpfd; } void send_ping(aconf) aConfItem *aconf; { Ping pi; struct SOCKADDR_IN sin; aCPing *cp = aconf->ping; #ifdef INET6 if (!aconf->ipnum.s6_addr || AND16(aconf->ipnum.s6_addr) == 255 || !cp->port) #else if (!aconf->ipnum.s_addr || aconf->ipnum.s_addr == -1 || !cp->port) #endif return; if (aconf->class->conFreq == 0) /* avoid flooding */ return; pi.pi_cp = aconf; pi.pi_id = htonl(PING_CPING); pi.pi_seq = cp->lseq++; cp->seq++; /* * Only recognise stats from the last 20 minutes as significant... * Try and fake sliding along a "window" here. */ if (cp->seq > 1 && cp->seq * aconf->class->conFreq > 1200) { if (cp->recvd) { cp->ping -= (cp->ping / cp->recvd); if (cp->recvd == cp->seq) cp->recvd--; } else cp->ping = 0; cp->seq--; } bzero((char *)&sin, sizeof(sin)); #ifdef INET6 bcopy(aconf->ipnum.s6_addr, sin.sin6_addr.s6_addr, IN6ADDRSZ); #else sin.sin_addr.s_addr = aconf->ipnum.s_addr; #endif sin.SIN_PORT = htons(cp->port); sin.SIN_FAMILY = AFINET; (void)gettimeofday(&pi.pi_tv, NULL); #ifdef INET6 Debug((DEBUG_SEND,"Send ping to %s,%d fd %d, %d bytes", inet_ntop(AF_INET6, (char *)&aconf->ipnum,mydummy,MYDUMMY_SIZE), cp->port, udpfd, sizeof(pi))); #else Debug((DEBUG_SEND,"Send ping to %s,%d fd %d, %d bytes", inetntoa((char *)&aconf->ipnum), cp->port, udpfd, sizeof(pi))); #endif (void)sendto(udpfd, (char *)&pi, sizeof(pi), 0,(SAP)&sin,sizeof(sin)); } static int check_ping(buf, len) char *buf; int len; { Ping pi; aConfItem *aconf; struct timeval tv; double d; aCPing *cp = NULL; u_long rtt; (void)gettimeofday(&tv, NULL); if (len < sizeof(pi) + 8) return -1; bcopy(buf, (char *)&pi, sizeof(pi)); /* ensure nice byte align. */ for (aconf = conf; aconf; aconf = aconf->next) if (pi.pi_cp == aconf && (cp = aconf->ping)) break; if (!aconf || match(aconf->name, buf + sizeof(pi))) return -1; cp->recvd++; cp->lrecvd++; rtt = ((tv.tv_sec - pi.pi_tv.tv_sec) * 1000 + (tv.tv_usec - pi.pi_tv.tv_usec) / 1000); cp->ping += rtt; cp->rtt += rtt; if (cp->rtt > 1000000) { cp->ping = (cp->rtt /= cp->lrecvd); cp->recvd = cp->lrecvd = 1; cp->seq = cp->lseq = 1; } d = (double)cp->recvd / (double)cp->seq; d = pow(d, (double)20.0); d = (double)cp->ping / (double)cp->recvd / d; if (d > 10000.0) d = 10000.0; aconf->pref = (int) (d * 100.0); return 0; } /* * max # of pings set to 15/sec. */ static void polludp() { static time_t last = 0; static int cnt = 0, mlen = 0, lasterr = 0; Reg char *s; struct SOCKADDR_IN from; Ping pi; int n; SOCK_LEN_TYPE fromlen = sizeof(from); /* * find max length of data area of packet. */ if (!mlen) { mlen = sizeof(readbuf) - strlen(ME) - strlen(version); mlen -= 6; if (mlen < 0) mlen = 0; } Debug((DEBUG_DEBUG,"udp poll")); memset(&from, 0, fromlen); n = recvfrom(udpfd, readbuf, mlen, 0, (SAP)&from, &fromlen); if (n == -1) { ircstp->is_udperr++; if ((errno == EWOULDBLOCK) || (errno == EAGAIN)) return; else { #if 0 /* seems to create more confusion than it's worth */ char buf[100]; sprintf(buf, "udp port recvfrom() from %s to %%s: %%s", #ifdef INET6 from.sin6_addr.s6_addr #else from.sin_addr.s_addr #endif == 0 ? "unknown" : #ifdef INET6 inetntop(AF_INET6, (char *)&from.sin6_addr, mydummy, MYDUMMY_SIZE) #else inetntoa((char *)&from.sin_addr) #endif ); report_error(buf, &me); #endif return; } } if (timeofday == last) { if (++cnt > 14) { if (timeofday > lasterr + 30) { sendto_flag(SCH_NOTICE, "udp packet dropped: %d bytes from %s.%d", #ifdef INET6 n, inetntop(AF_INET6, (char *)&from.sin6_addr, mydummy, MYDUMMY_SIZE), #else n,inetntoa((char *)&from.sin_addr), #endif ntohs(from.SIN_PORT)); lasterr = timeofday; } ircstp->is_udpdrop++; return; } } else cnt = 0, last = timeofday; #ifdef INET6 Debug((DEBUG_NOTICE, "udp (%d) %d bytes from %s,%d", cnt, n, inet_ntop(AF_INET6, (char *)&from.sin6_addr, mydummy, MYDUMMY_SIZE), ntohs(from.SIN_PORT))); #else Debug((DEBUG_NOTICE, "udp (%d) %d bytes from %s,%d", cnt, n, inetntoa((char *)&from.sin_addr), ntohs(from.SIN_PORT))); #endif readbuf[n] = '\0'; ircstp->is_udpok++; if (n < 8) return; bcopy(s = readbuf, (char *)&pi, MIN(n, sizeof(pi))); pi.pi_id = ntohl(pi.pi_id); Debug((DEBUG_INFO, "\tpi_id %#x pi_seq %d pi_cp %#x", pi.pi_id, pi.pi_seq, pi.pi_cp)); if ((pi.pi_id == (PING_CPING|PING_REPLY) || pi.pi_id == (PING_CPING|(PING_REPLY << 24))) && n >= sizeof(pi)) { check_ping(s, n); return; } else if (pi.pi_id & PING_REPLY) return; /* * attach my name and version for the reply */ pi.pi_id |= PING_REPLY; pi.pi_id = htonl(pi.pi_id); bcopy((char *)&pi, s, MIN(n, sizeof(pi))); s += n; (void)strcpy(s, ME); s += strlen(s)+1; (void)strcpy(s, version); s += strlen(s); (void)sendto(udpfd, readbuf, s-readbuf, 0, (SAP)&from ,sizeof(from)); return; } /* * do_dns_async * * Called when the fd returned from init_resolver() has been selected for * reading. */ static void do_dns_async() { static Link ln; aClient *cptr; aConfItem *aconf; struct hostent *hp; int bytes, pkts; pkts = 0; do { ln.flags = -1; hp = get_res((char *)&ln); 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 defined(USE_IAUTH) if (hp) { int i = 0; while (hp->h_aliases[i]) sendto_iauth("%d A %s", cptr->fd, hp->h_aliases[i++]); if (hp->h_name) sendto_iauth("%d N %s", cptr->fd, hp->h_name); else if (hp->h_aliases[0]) sendto_iauth("%d n", cptr->fd); } else sendto_iauth("%d d", cptr->fd); if (iauth_options & XOPT_EXTWAIT) cptr->lasttime = timeofday; #endif } break; case ASYNC_CONNECT : aconf = ln.value.aconf; if (hp && aconf) { bcopy(hp->h_addr, (char *)&aconf->ipnum, sizeof(struct IN_ADDR)); (void)connect_server(aconf, NULL, hp); } else sendto_flag(SCH_ERROR, "Connect to %s failed: host lookup", (aconf) ? aconf->host : "unknown"); break; case ASYNC_CONF : aconf = ln.value.aconf; if (hp && aconf) bcopy(hp->h_addr, (char *)&aconf->ipnum, sizeof(struct IN_ADDR)); break; default : break; } pkts++; if (ioctl(resfd, FIONREAD, &bytes) == -1) bytes = 0; } while ((bytes > 0) && (pkts < 10)); }