/************************************************************************ * 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 sccsid[] = "@(#)s_bsd.c 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 "patchlevel.h" #include #include #include #include #if defined(SOL20) #include #endif #if defined(UNIXPORT) && (!defined(SVR3) || defined(sgi) || \ defined(_SEQUENT_)) # include #endif #include "inet.h" #include #include #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, */ /* define it (BSD4.2 needs this) */ #include "h.h" #ifndef IN_LOOPBACKNET #define IN_LOOPBACKNET 0x7f #endif aClient *local[MAXCONNECTIONS]; int highest_fd = 0, readcalls = 0, udpfd = -1, resfd = -1; static struct sockaddr_in mysk; static void polludp(); static struct sockaddr *connect_inet PROTO((aConfItem *, aClient *, int *)); static int completed_connection PROTO((aClient *)); static int check_init PROTO((aClient *, char *)); static void do_dns_async PROTO(()), set_sock_opts PROTO((int, aClient *)); #ifdef UNIXPORT static struct sockaddr *connect_unix PROTO((aConfItem *, aClient *, int *)); static void add_unixconnection PROTO((aClient *, int)); static char unixpath[256]; #endif static char readbuf[8192]; extern char *version; extern int dorehash; extern char *last_dead_comment; #ifdef VIRTUAL_HOST struct sockaddr_in vserv; #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 extern void list_next_channels PROTO((aClient *, int)); /* ** 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 /* try to fix up unqualified names */ if (!index(hname, '.')) { if (!(_res.options & RES_INIT)) { Debug((DEBUG_DNS,"res_init()")); res_init(); } if (_res.defdname[0]) { (void)strncat(hname, ".", size-1); (void)strncat(hname, _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(text, cptr) char *text; aClient *cptr; { Reg1 int errtmp = errno; /* debug may change 'errno' */ Reg2 char *host; int err; socklen_t 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, (OPT_TYPE *)&err, &len)) if (err) errtmp = err; #endif sendto_ops(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. 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(cptr, name, port) aClient *cptr; char *name; int port; { static struct sockaddr_in server; socklen_t len = sizeof(server); int ad[4], opt; char ipname[20]; #ifdef VIRTUAL_HOST struct hostent *hep; struct in_addr bind_addr; #endif 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 :-) */ (void)sscanf(name, "%d.%d.%d.%d", &ad[0], &ad[1], &ad[2], &ad[3]); (void)sprintf(ipname, "%d.%d.%d.%d", ad[0], ad[1], ad[2], ad[3]); if (cptr != &me) { (void)sprintf(cptr->sockhost, "%-.42s.%.u", name, (unsigned int)port); (void)strcpy(cptr->name, me.name); } /* * At first, open a new socket */ if (cptr->fd == -1) { (void)alarm(2); cptr->fd = socket(AF_INET, SOCK_STREAM, 0); (void)alarm(0); if (cptr->fd < 0 && errno == EAGAIN) { sendto_ops("opening stream socket %s: No more sockets", get_client_name(cptr, TRUE)); return -1; } } if (cptr->fd < 0) { report_error("opening stream socket %s:%s", cptr); return -1; } else if (cptr->fd >= MAXCLIENTS) { sendto_ops("No more connections allowed (%s)", cptr->name); (void)close(cptr->fd); return -1; } opt = 1; setsockopt(cptr->fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); /* 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 = AF_INET; #ifdef VIRTUAL_HOST bzero((char *) &vserv, sizeof(vserv)); vserv.sin_family = AF_INET; hep = gethostbyname(me.name); if (hep && hep->h_addrtype == AF_INET && hep->h_addr_list[0] && !hep->h_addr_list[1]) { memcpy(&bind_addr, hep->h_addr_list[0], sizeof(struct in_addr)); vserv.sin_addr = server.sin_addr = bind_addr; } else { report_error("creating virtual host %s:%s", cptr); return -1; } #else server.sin_addr.s_addr = INADDR_ANY; #endif #ifdef TESTNET server.sin_port = htons(port + 10000); #else server.sin_port = htons(port); #endif /* * 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) { 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]; #ifdef TESTNET (void)sprintf(buf, rpl_str(RPL_MYPORTIS), me.name, "*", ntohs(server.sin_port) - 10000); #else (void)sprintf(buf, rpl_str(RPL_MYPORTIS), me.name, "*", ntohs(server.sin_port)); #endif (void)write(0, buf, strlen(buf)); } if (cptr->fd > highest_fd) highest_fd = cptr->fd; cptr->ip.s_addr = inet_addr(ipname); #ifdef TESTNET cptr->port = (int)ntohs(server.sin_port) - 10000; #else cptr->port = (int)ntohs(server.sin_port); #endif (void)listen(cptr->fd, 5); 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; SetMe(cptr); strncpyzt(cptr->name, aconf->host, sizeof(cptr->name)); #ifdef UNIXPORT if (*aconf->host == '/') { if (unixport(cptr, aconf->host, aconf->port)) cptr->fd = -2; } else #endif if (inetport(cptr, aconf->host, aconf->port)) cptr->fd = -2; if (cptr->fd >= 0) { cptr->confs = make_link(); cptr->confs->next = NULL; cptr->confs->value.aconf = aconf; 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; (void)alarm(2); cptr->fd = socket(AF_UNIX, SOCK_STREAM, 0); (void)alarm(0); if (cptr->fd == -1 && errno == EAGAIN) { sendto_ops( "error opening unix domain socket %s: No more sockets", get_client_name(cptr,TRUE)); return -1; } if (cptr->fd == -1) { report_error("error opening unix domain socket %s:%s", cptr); return -1; } else if (cptr->fd >= MAXCLIENTS) { sendto_ops("No more connections allowed (%s)", cptr->name); (void)close(cptr->fd); return -1; } un.sun_family = AF_UNIX; (void)mkdir(path, 0755); (void)sprintf(unixpath, "%s/%d", path, port); (void)unlink(unixpath); strncpyzt(un.sun_path, unixpath, sizeof(un.sun_path)); (void)strcpy(cptr->name, me.name); errno = 0; get_sockhost(cptr, unixpath); if (bind(cptr->fd, (struct sockaddr *)&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, 5); (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() { Reg1 aClient *cptr; Reg2 int i; Reg3 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 (!IsMe(cptr) || cptr == &me || !IsListening(cptr)) continue; aconf = cptr->confs->value.aconf; if (IsIllegal(aconf) && aconf->clients == 0) { #ifdef UNIXPORT if (IsUnixSocket(cptr)) { (void)sprintf(unixpath, "%s/%d", aconf->host, aconf->port); (void)unlink(unixpath); } #endif close_connection(cptr); } } } /* * init_sys */ void init_sys() { Reg1 int fd; #ifdef RLIMIT_FD_MAX struct rlimit limit; if (!getrlimit(RLIMIT_FD_MAX, &limit)) { # ifdef pyr if (limit.rlim_cur < MAXCONNECTIONS) #else if (limit.rlim_max < MAXCONNECTIONS) # endif { (void)fprintf(stderr,"ircd fd table too big\n"); (void)fprintf(stderr,"Hard Limit: %lu IRC max: %d\n", limit.rlim_max, MAXCONNECTIONS); (void)fprintf(stderr,"Fix MAXCONNECTIONS\n"); exit(-1); } # ifndef pyr 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 %lu\n", limit.rlim_cur); exit(-1); } # endif } #endif #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 #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(SOL20) (void)setlinebuf(stderr); # endif # endif #endif for (fd = 3; fd < MAXCONNECTIONS; fd++) { (void)close(fd); local[fd] = NULL; } local[1] = NULL; (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))) { if (fork()) exit(0); #ifdef TIOCNOTTY if ((fd = open("/dev/tty", O_RDWR)) >= 0) { (void)ioctl(fd, TIOCNOTTY, (char *)NULL); (void)close(fd); } #endif #if defined(HPUX) || defined(SOL20) || defined(DYNIXPTX) || \ defined(_POSIX_SOURCE) || defined(SVR4) (void)setsid(); #else (void)setpgrp(0, (int)getpid()); #endif (void)close(0); /* fd 0 opened by inetd */ local[0] = NULL; } init_dgram: resfd = init_resolver(0x1f); return; } void write_pidfile() { #ifdef IRCD_PIDFILE int fd; char buff[20]; if ((fd = open(IRCD_PIDFILE, 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", 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(cptr, sockn) Reg1 aClient *cptr; Reg2 char *sockn; { struct sockaddr_in sk; socklen_t 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, (struct sockaddr *)&sk, &len) == -1) { report_error("connect failure: %s %s", cptr); return -1; } (void)strcpy(sockn, (char *)inetntoa((char *)&sk.sin_addr)); if (inet_netof(sk.sin_addr) == IN_LOOPBACKNET) { cptr->hostp = NULL; strncpyzt(sockn, me.sockhost, HOSTLEN); } bcopy((char *)&sk.sin_addr, (char *)&cptr->ip, sizeof(struct in_addr)); #ifdef TESTNET cptr->port = (int)ntohs(sk.sin_port) - 10000; #else cptr->port = (int)ntohs(sk.sin_port); #endif 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(cptr) Reg1 aClient *cptr; { static char sockname[HOSTLEN+1]; Reg2 struct hostent *hp = NULL; Reg3 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)) return -2; 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. */ 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_ops("IP# Mismatch: %s != %s[%08x]", inetntoa((char *)&cptr->ip), hp->h_name, *((unsigned long *)hp->h_addr)); 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)); if (inet_netof(cptr->ip) == IN_LOOPBACKNET || IsUnixSocket(cptr) || inet_netof(cptr->ip) == inet_netof(mysk.sin_addr)) { ircstp->is_loc++; cptr->flags |= FLAGS_LOCAL; } return 0; } #define CFLAG CONF_CONNECT_SERVER #define NFLAG CONF_NOCONNECT_SERVER /* * 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; { Reg1 char *name; Reg2 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_ops("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) { Reg1 aConfItem *aconf; aconf = count_cnlines(lp); if (aconf) { Reg1 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 cant interrupt that very ** well. */ ClearAccess(cptr); 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); } int check_server(cptr, hp, c_conf, n_conf) aClient *cptr; Reg1 aConfItem *n_conf, *c_conf; Reg2 struct hostent *hp; { Reg3 const char *name; char abuff[HOSTLEN+USERLEN+2]; char sockname[HOSTLEN+1], fullname[HOSTLEN+1]; Link *lp = cptr->confs; int i; ClearAccess(cptr); 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]) { sendto_ops("IP# Mismatch: %s != %s[%08x]", inetntoa((char *)&cptr->ip), hp->h_name, *((unsigned long *)hp->h_addr)); 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)); (void)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)) { (void)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->username, 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|CONF_UWORLD); if ((c_conf->ipnum.s_addr == -1) && !IsUnixSocket(cptr)) 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)); return 0; } #undef CFLAG #undef NFLAG /* ** 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; time_t newts; aClient *acptr; int i; aconf = find_conf(cptr->confs, cptr->name, CONF_CONNECT_SERVER); if (!aconf) { sendto_ops("Lost C-Line for %s", get_client_name(cptr,FALSE)); return -1; } if (!BadPtr(aconf->passwd)) sendto_one(cptr, "PASS :%s", aconf->passwd); aconf = find_conf(cptr->confs, cptr->name, CONF_NOCONNECT_SERVER); if (!aconf) { sendto_ops("Lost N-Line for %s", get_client_name(cptr,FALSE)); return -1; } make_server(cptr); /* Create a unique timestamp */ newts = TStime(); for (i = highest_fd; i >= 0; i--) { if (!(acptr = local[i]) || (!IsServer(acptr) && !IsHandshake(acptr))) continue; if (acptr->serv->timestamp >= newts) newts = acptr->serv->timestamp + 1; } cptr->serv->timestamp = newts; SetHandshake(cptr); /* Make us timeout after twice the timeout for DNS look ups */ cptr->lasttime = now; cptr->flags |= FLAGS_PINGSENT; sendto_one(cptr, "SERVER %s 1 %lu %lu J%s :%s", my_name_for_link(me.name, aconf), me.serv->timestamp, newts, MAJOR_PROTOCOL, 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(cptr) aClient *cptr; { Reg1 aConfItem *aconf; Reg2 int i,j; int empty = cptr->fd; 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 += now - 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 += now - 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. */ if ((aconf = find_conf_exact(cptr->name, cptr->username, cptr->sockhost, CONF_CONNECT_SERVER))) { /* * 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 = now; aconf->hold += (aconf->hold - cptr->since > HANGONGOODLINK) ? HANGONRETRYDELAY : ConfConFreq(aconf); if (nextconnect > aconf->hold) nextconnect = aconf->hold; } if (cptr->authfd >= 0) (void)close(cptr->authfd); if (cptr->fd >= 0) { flush_connections(cptr->fd); local[cptr->fd] = NULL; (void)close(cptr->fd); 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 && cptr->acpt != cptr) { aconf = cptr->acpt->confs->value.aconf; if (aconf->clients > 0) aconf->clients--; if (!aconf->clients && IsIllegal(aconf)) close_connection(cptr->acpt); } } for (; highest_fd > 0; highest_fd--) if (local[highest_fd]) break; det_confs_butmask(cptr, 0); cptr->from = NULL; /* ...this should catch them! >:) --msa */ /* * fd remap to keep local[i] filled at the bottom. */ if (empty > 0) if ((j = highest_fd) > (i = empty) && (local[j]->status != STAT_LOG)) { if (dup2(j,i) == -1) return; local[i] = local[j]; local[i]->fd = i; local[j] = NULL; (void)close(j); while (!local[highest_fd]) highest_fd--; } return; } /* ** set_sock_opts */ static void set_sock_opts(fd, cptr) int fd; aClient *cptr; { socklen_t 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(SOL20) || defined(USE_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 /* SOL20 */ #endif #ifdef SO_USELOOPBACK 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) { 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)sprintf(s, "%02x:", *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(cptr) aClient *cptr; { int errtmp = errno, err = 0; socklen_t len = sizeof(err); #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_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. */ #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) 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; } /* * 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(cptr, fd) aClient *cptr; int fd; { Link lin; aClient *acptr; aConfItem *aconf = NULL; acptr = make_client(NULL); if (cptr != &me) 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 { Reg1 char *s, *t; struct sockaddr_in addr; socklen_t 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); (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... */ get_sockhost(acptr, (char *)inetntoa((char *)&addr.sin_addr)); bcopy ((char *)&addr.sin_addr, (char *)&acptr->ip, sizeof(struct in_addr)); #ifdef TESTNET acptr->port = ntohs(addr.sin_port) - 10000; #else acptr->port = ntohs(addr.sin_port); #endif /* * Check that this socket (client) is allowed to accept * connections from this IP#. */ for (s = (char *)&cptr->ip, t = (char *)&acptr->ip, len = 4; len > 0; len--, s++, t++) { if (!*s) continue; if (*s != *t) break; } if (len) goto add_con_refuse; lin.flags = ASYNC_CLIENT; lin.value.cptr = acptr; Debug((DEBUG_DNS, "lookup %s", inetntoa((char *)&addr.sin_addr))); acptr->hostp = gethost_byaddr(&acptr->ip, &lin); if (!acptr->hostp) SetDNS(acptr); nextdnscheck = 1; } if (aconf) aconf->clients++; acptr->fd = fd; if (fd > highest_fd) highest_fd = fd; local[fd] = acptr; acptr->acpt = cptr; add_client_to_list(acptr); set_non_blocking(acptr->fd, acptr); set_sock_opts(acptr->fd, acptr); start_auth(acptr); 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); if (cptr != &me) 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; 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); set_sock_opts(acptr->fd, acptr); SetAccess(acptr); return; } #endif /* ** 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, rfd) Reg1 aClient *cptr; fd_set *rfd; { Reg1 int dolen = 0, length = 0, done; if (FD_ISSET(cptr->fd, rfd) && !(IsPerson(cptr) && DBufLength(&cptr->recvQ) > 6090)) { errno = 0; 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 == -1 && ((errno == EWOULDBLOCK) || (errno == 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) || IsService(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, &me, "dbuf_put fail"); if (IsPerson(cptr) && DBufLength(&cptr->recvQ) > CLIENT_FLOOD) return exit_client(cptr, cptr, &me, "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 (IsService(cptr) || 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, &me, "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) == CPTR_KILLED)) return CPTR_KILLED; } } 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) 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 */ { Reg1 aClient *cptr; Reg2 int nfds; struct timeval wait; #ifdef pyr struct timeval nowt; u_long us; #endif fd_set read_set, write_set; time_t delay2 = delay; u_long usec = 0; int res, length, fd, i; int auth = 0, ping = 0; #ifdef NPATH note_delay(&delay); #endif #ifdef pyr (void) gettimeofday(&nowt, NULL); now = nowt.tv_sec; #endif for (res = 0;;) { FD_ZERO(&read_set); FD_ZERO(&write_set); for (i = highest_fd; i >= 0; i--) { if (!(cptr = local[i])) continue; if (IsLog(cptr)) continue; if (DoingAuth(cptr)) { auth++; Debug((DEBUG_NOTICE,"auth on %x %d", cptr, i)); FD_SET(cptr->authfd, &read_set); if (cptr->flags & FLAGS_WRAUTH) FD_SET(cptr->authfd, &write_set); } if (IsPing(cptr)) { ping++; Debug((DEBUG_NOTICE,"open ping on %x %d", cptr, i)); FD_SET(i, &read_set); if (DoPing(cptr)) { delay2 = 1; if (now > cptr->lasttime) FD_SET(i, &write_set); } else if (cptr->firsttime && now > cptr->firsttime) { FD_CLR(i, &read_set); del_queries((char *)cptr); end_ping(cptr); } continue; } if (DoingDNS(cptr) || DoingAuth(cptr)) continue; if (IsMe(cptr) && IsListening(cptr)) { if ((now > cptr->lasttime + 2)) FD_SET(i, &read_set); else if (delay2 > 2) delay2 = 2; } else if (!IsMe(cptr)) { if (DBufLength(&cptr->recvQ) && delay2 > 2) delay2 = 1; if (DBufLength(&cptr->recvQ) < 4088) FD_SET(i, &read_set); } if (DBufLength(&cptr->sendQ) || IsConnecting(cptr) || (cptr->listing && DBufLength(&cptr->sendQ) < 2048)) #ifndef pyr FD_SET(i, &write_set); #else { if (!(cptr->flags & FLAGS_BLOCKED)) FD_SET(i, &write_set); else delay2 = 0, usec = 500000; } if (now - cptr->lw.tv_sec && nowt.tv_usec - cptr->lw.tv_usec < 0) us = 1000000; else us = 0; us += nowt.tv_usec; if (us - cptr->lw.tv_usec > 500000) cptr->flags &= ~FLAGS_BLOCKED; #endif } if (udpfd >= 0) FD_SET(udpfd, &read_set); if (resfd >= 0) FD_SET(resfd, &read_set); wait.tv_sec = MIN(delay2, delay); wait.tv_usec = usec; #ifdef HPUX nfds = select(FD_SETSIZE, (int *)&read_set, (int *)&write_set, 0, &wait); #else nfds = select(FD_SETSIZE, &read_set, &write_set, 0, &wait); #endif now = time(NULL); if (nfds == -1 && errno == EINTR) return -1; else if (nfds >= 0) break; report_error("select %s:%s", &me); res++; if (res > 5) restart("too many select errors"); sleep(10); now += 10; } if (udpfd >= 0 && FD_ISSET(udpfd, &read_set)) { polludp(); nfds--; FD_CLR(udpfd, &read_set); } /* * Check fd sets for the ping fd's (if set and valid!) first * because these can not be processed using the normal loops below. * And we want them to be as fast as possible. * -Run */ for (i = highest_fd; (ping > 0) && (i >= 0); i--) { if (!(cptr = local[i])) continue; if (!IsPing(cptr)) continue; ping--; if ((nfds > 0) && FD_ISSET(cptr->fd, &read_set)) { nfds--; FD_CLR(cptr->fd, &read_set); read_ping(cptr); /* This can MyFree(cptr) ! */ } else if ((nfds > 0) && FD_ISSET(cptr->fd, &write_set)) { nfds--; cptr->lasttime = now; FD_CLR(cptr->fd, &write_set); send_ping(cptr); /* This can MyFree(cptr) ! */ } } if (resfd >= 0 && FD_ISSET(resfd, &read_set)) { do_dns_async(); nfds--; FD_CLR(resfd, &read_set); } /* * 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 */ for (i = highest_fd; (auth > 0) && (i >= 0); i--) { if (!(cptr = local[i])) continue; if (cptr->authfd < 0) continue; auth--; if ((nfds > 0) && FD_ISSET(cptr->authfd, &write_set)) { nfds--; send_authports(cptr); } else if ((nfds > 0) && FD_ISSET(cptr->authfd, &read_set)) { nfds--; read_authports(cptr); } } for (i = highest_fd; i >= 0; i--) if ((cptr = local[i]) && FD_ISSET(i, &read_set) && IsListening(cptr)) { FD_CLR(i, &read_set); nfds--; cptr->lasttime = now; /* ** 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(i, NULL, NULL)) < 0) { if (errno == EAGAIN && IsListening(cptr)) { cptr->confs->value.aconf->status = CONF_ILLEGAL; dorehash = 1; sendto_ops("Cannot accept connections on %s" ": No more sockets", get_client_name(cptr, TRUE)); } else report_error("Cannot accept connections%s: %s", NULL); break; } #if defined(USE_SYSLOG) && defined(SYSLOG_CONNECTS) { /* get an early log of all connections --dl */ static struct sockaddr_in peer; static int len; len = sizeof(peer); getpeername(fd,(struct sockaddr *)&peer,&len); syslog(LOG_DEBUG,"Conn: %s", inetntoa((char*)&(peer.sin_addr))); } #endif 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 */ #ifdef UNIXPORT if (IsUnixSocket(cptr)) add_unixconnection(cptr, fd); else #endif (void)add_connection(cptr, fd); nextping = now; if (!cptr->acpt) cptr->acpt = &me; } for (i = highest_fd; i >= 0; i--) { if (!(cptr = local[i]) || IsMe(cptr)) continue; #ifdef DEBUGMODE if (IsLog(cptr)) continue; #endif if (FD_ISSET(i, &write_set)) { int write_err = 0; nfds--; /* ** ...room for writing, empty some queue then... */ cptr->flags &= ~FLAGS_BLOCKED; if (IsConnecting(cptr)) write_err = completed_connection(cptr); if (!write_err) { if (cptr->listing && DBufLength(&cptr->sendQ) < 2048) list_next_channels(cptr, 64); send_queued(cptr); } if (IsDead(cptr) || write_err) { deadsocket: if (FD_ISSET(i, &read_set)) { nfds--; FD_CLR(i, &read_set); } (void)exit_client(cptr, cptr, &me, IsDead(cptr) ? last_dead_comment : strerror(get_sockerr(cptr))); continue; } } length = 1; /* for fall through case */ if (!NoNewLine(cptr) || FD_ISSET(i, &read_set)) length = read_packet(cptr, &read_set); if (length > 0) flush_connections(i); if ((length != CPTR_KILLED) && IsDead(cptr)) goto deadsocket; if (!FD_ISSET(i, &read_set) && length > 0) 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 %d %d", i, errno, length)); if (length == CPTR_KILLED) continue; if ((IsServer(cptr) || IsHandshake(cptr)) && errno == 0 && length == 0) (void)exit_client_msg(cptr, cptr, &me, "Server %s closed the connection", get_client_name(cptr,FALSE)); else (void)exit_client_msg(cptr, cptr, &me, "Read error to %s: %s", get_client_name(cptr,FALSE), (length < 0) ? strerror(get_sockerr(cptr)) : "EOF from client"); } return 0; } /* * connect_server */ int connect_server(aconf, by, hp) aConfItem *aconf; aClient *by; struct hostent *hp; { Reg1 struct sockaddr *svp; Reg2 aClient *cptr, *c2ptr; Reg3 char *s; int errtmp, len; Debug((DEBUG_NOTICE,"Connect to %s[%s] @%s", aconf->name, aconf->host, inetntoa((char *)&aconf->ipnum))); if ((c2ptr = find_client(aconf->name, NULL)) || (c2ptr = find_server(aconf->name, NULL))) { if (IsServer(c2ptr) || IsMe(c2ptr)) { sendto_ops("Server %s already present from %s", aconf->name, c2ptr->from->name); if (by && IsPerson(by) && !MyClient(by)) sendto_one(by, ":%s NOTICE %s :Server %s already present from %s", me.name, by->name, aconf->name, c2ptr->from->name); return -1; } else if (IsHandshake(c2ptr) || IsConnecting(c2ptr)) { if (by && IsPerson(by)) sendto_one(by, ":%s NOTICE %s :Connection to %s already in progress", me.name, by->name, get_client_name(c2ptr, TRUE)); return -1; } } /* * 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 ) #ifdef UNIXPORT && ( ( aconf->host[2] ) != '/' ) /* needed for Unix domain -- dl*/ #endif ) { Link lin; lin.flags = ASYNC_CONNECT; lin.value.aconf = aconf; nextdnscheck = 1; s = (char *)index(aconf->host, '@'); s++; /* should NEVER be NULL */ if ((aconf->ipnum.s_addr = inet_addr(s)) == -1) { aconf->ipnum.s_addr = 0; 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[2] == '/') /* (/ 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 >= 0) (void)close(cptr->fd); cptr->fd = -2; if (by && IsPerson(by) && !MyClient(by)) sendto_one(by, ":%s NOTICE %s :Couldn't connect to %s", me.name, by->name, get_client_name(cptr, TRUE)); free_client(cptr); return -1; } set_non_blocking(cptr->fd, cptr); set_sock_opts(cptr->fd, cptr); (void)signal(SIGALRM, dummy); (void)alarm(4); if (connect(cptr->fd, svp, len) < 0 && errno != EINPROGRESS) { int err = get_sockerr(cptr); errtmp = 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: %s", me.name, by->name, get_client_name(cptr, TRUE), strerror(err)); (void)close(cptr->fd); cptr->fd = -2; free_client(cptr); errno = errtmp; 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, CONF_NOCONNECT_SERVER | CONF_CONNECT_SERVER); if (!find_conf_host(cptr->confs, aconf->host, CONF_NOCONNECT_SERVER) || !find_conf_host(cptr->confs, aconf->host, CONF_CONNECT_SERVER)) { sendto_ops("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: no C/N-lines", me.name, by->name, get_client_name(cptr, TRUE)); 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); if (cptr->serv->user) free_user(cptr->serv->user, NULL); cptr->serv->user = by->user; by->user->refcnt++; } else { (void)strcpy(cptr->serv->by, "AutoConn."); if (cptr->serv->user) free_user(cptr->serv->user, NULL); cptr->serv->user = NULL; } cptr->serv->up = &me; if (cptr->fd > highest_fd) highest_fd = cptr->fd; local[cptr->fd] = cptr; cptr->acpt = &me; SetConnecting(cptr); get_sockhost(cptr, aconf->host); add_client_to_list(cptr); (void)add_to_client_hash_table(cptr->name, cptr); nextping = now; return 0; } static struct sockaddr *connect_inet(aconf, cptr, lenp) Reg1 aConfItem *aconf; Reg2 aClient *cptr; int *lenp; { static struct sockaddr_in server; Reg3 struct hostent *hp; /* * Might as well get sockhost from here, the connection is attempted * with it so if it fails its useless. */ (void)alarm(2); cptr->fd = socket(AF_INET, SOCK_STREAM, 0); (void)alarm(0); if (cptr->fd == -1 && errno == EAGAIN) { sendto_ops( "opening stream socket to server %s: No more sockets", get_client_name(cptr,TRUE)); return NULL; } if (cptr->fd == -1) { report_error("opening stream socket to server %s:%s", cptr); return NULL; } if (cptr->fd >= MAXCLIENTS) { sendto_ops("No more connections allowed (%s)", cptr->name); return NULL; } mysk.sin_port = 0; bzero((char *)&server, sizeof(server)); server.sin_family = AF_INET; get_sockhost(cptr, aconf->host); #ifdef VIRTUAL_HOST mysk.sin_addr = vserv.sin_addr; #endif /* ** 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#. */ /* No we don't bind it, not all OS's can handle connecting with ** an already bound socket, different ip# might occur anyway ** leading to a freezing select() on this side for some time. ** I had this on my Linux 1.1.88 --Run */ #ifdef VIRTUAL_HOST /* ** No, we do bind it if we have virtual host support. If we don't ** explicitly bind it, it will default to IN_ADDR_ANY and we lose ** due to the other server not allowing our base IP --smg */ if (bind(cptr->fd, (struct sockaddr *)&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 dont know it, then the connect fails. */ if (isdigit(*aconf->host) && (aconf->ipnum.s_addr == -1)) aconf->ipnum.s_addr = inet_addr(aconf->host); if (aconf->ipnum.s_addr == -1) { 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)); #ifdef TESTNET server.sin_port = htons(((aconf->port > 0) ? aconf->port : portnum) + 10000); #else server.sin_port = htons(((aconf->port > 0) ? aconf->port : portnum)); #endif *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; (void)alarm(2); cptr->fd = socket(AF_UNIX, SOCK_STREAM, 0); (void)alarm(0); if (cptr->fd == -1 && errno == EAGAIN) { sendto_ops( "Unix domain connect to host %s failed: No more sockets", get_client_name(cptr,TRUE)); return NULL; } if (cptr->fd == -1) { report_error("Unix domain connect to host %s failed: %s", cptr); return NULL; } else if (cptr->fd >= MAXCLIENTS) { sendto_ops("No more connections allowed (%s)", cptr->name); return NULL; } get_sockhost(cptr, aconf->host); /* +2 needed for working Unix domain -- dl*/ 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.name, 16); # else strncpyzt(host, me.name, 9); # endif if (ut.ut_type == USER_PROCESS) return 0; #else strncpyzt(host, (ut.ut_host[0]) ? (ut.ut_host) : me.name, 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[120]; struct tm *tp; tp = localtime(&now); 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') #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); Debug((0, "%s (%s@%s, nick %s, %s)", "FATAL: major security hack. Notify Administrator !", who->username, who->user->host, who->name, who->info)); return; } (void)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.name, 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); (void)sprintf(line, "ircd: Channel %s, by %s@%s (%s) %s\n\r", chname, who->user->username, who->user->host, who->name, who->info); (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), me.name, 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; /* ** Setup local socket structure to use for binding to. */ bzero((char *)&mysk, sizeof(mysk)); mysk.sin_family = AF_INET; if (gethostname(name,len) == -1) return; name[len] = '\0'; /* assume that a name containing '.' is a FQDN */ if (!index(name,'.')) 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; if ((hp = gethostbyname(cname)) || (hp = gethostbyname(name))) { const 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.name, tmp)) break; } if (strcasecmp(me.name, tmp)) strncpyzt(name, hp->h_name, len); else strncpyzt(name, tmp, len); bcopy(hp->h_addr, (char *)&mysk.sin_addr, sizeof(struct in_addr)); Debug((DEBUG_DEBUG,"local name is %s", get_client_name(&me,TRUE))); } return; } /* ** setup a UDP socket and listen for incoming packets */ int setup_ping() { struct sockaddr_in from; int on = 1; bzero((char *)&from, sizeof(from)); #ifdef VIRTUAL_HOST from.sin_addr = vserv.sin_addr; #else from.sin_addr.s_addr = htonl(INADDR_ANY); #endif #ifdef TESTNET from.sin_port = htons(atoi(UDP_PORT) + 10000); #else from.sin_port = htons(atoi(UDP_PORT)); #endif from.sin_family = AF_INET; if ((udpfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { Debug((DEBUG_ERROR, "socket udp : %s", strerror(errno))); return -1; } if (setsockopt(udpfd, SOL_SOCKET, SO_REUSEADDR, (OPT_TYPE *)&on, sizeof(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); udpfd = -1; return -1; } on = 0; (void) setsockopt(udpfd, SOL_SOCKET, SO_BROADCAST, (char *)&on, sizeof(on)); if (bind(udpfd, (struct sockaddr *)&from, sizeof(from))==-1) { #ifdef USE_SYSLOG syslog(LOG_ERR, "bind udp.%d fd %d : %m", from.sin_port, udpfd); #endif Debug((DEBUG_ERROR, "bind : %s", strerror(errno))); (void)close(udpfd); udpfd = -1; return -1; } if (fcntl(udpfd, F_SETFL, FNDELAY)==-1) { Debug((DEBUG_ERROR, "fcntl fndelay : %s", strerror(errno))); (void)close(udpfd); udpfd = -1; return -1; } return udpfd; } /* * max # of pings set to 15/sec. */ static void polludp() { Reg1 char *s; struct sockaddr_in from; socklen_t fromlen = sizeof(from); int n; static time_t last = 0; static int cnt = 0, mlen = 0; /* * find max length of data area of packet. */ if (!mlen) { mlen = sizeof(readbuf) - strlen(me.name) - strlen(version); mlen -= 6; if (mlen < 0) mlen = 0; } Debug((DEBUG_DEBUG,"udp poll")); n = recvfrom(udpfd, readbuf, mlen, 0, (struct sockaddr *)&from, &fromlen); if (now == last) if (++cnt > 14) return; cnt = 0; last = now; if (n == -1) { if ((errno == EWOULDBLOCK) || (errno == EAGAIN)) return; else { report_error("udp port recvfrom (%s): %s", &me); return; } } ircstp->is_udp++; if (n < 19) return; s = readbuf + n; /* * attach my name and version for the reply */ *readbuf |= 1; (void)strcpy(s, me.name); s += strlen(s)+1; (void)strcpy(s, version); s += strlen(s); (void)sendto(udpfd, readbuf, s-readbuf, 0, (struct sockaddr *)&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; 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); if (!DoingAuth(cptr)) SetAccess(cptr); cptr->hostp = hp; } 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_ops("Connect to %s failed: host lookup", (aconf) ? aconf->host : "unknown"); break; case ASYNC_PING : cptr = ln.value.cptr; del_queries((char *)cptr); if (hp) { bcopy(hp->h_addr, (char *)&cptr->ip, sizeof(struct in_addr)); if (ping_server(cptr, hp) == -1) end_ping(cptr); } else { sendto_ops("Udp ping to %s failed: host lookup", cptr->sockhost); end_ping(cptr); } 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; } }