/* * Unreal Internet Relay Chat Daemon, src/s_bsd.c * Copyright (C) 1990 Jarkko Oikarinen and * University of Oulu, Computing Center * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 1, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* -- Jto -- 07 Jul 1990 * Added jlp@hamblin.byu.edu's debugtty fix */ /* -- Armin -- Jun 18 1990 * Added setdtablesize() for more socket connections */ /* -- Jto -- 13 May 1990 * Added several fixes from msa: * Better error messages * Changes in check_access * Added SO_REUSEADDR fix from zessel@informatik.uni-kl.de */ /* 2.78 2/7/94 (C) 1988 University of Oulu, Computing Center and Jarkko Oikarinen */ #ifdef _WIN32 #include #endif #include "struct.h" #include "common.h" #include "sys.h" #include "res.h" #include "numeric.h" #include "version.h" #ifndef _WIN32 #include #include #include #include #else #include #endif #if defined(_SOLARIS) #include #endif #include "inet.h" #include #include #include #include "sock.h" /* If FD_ZERO isn't define up to this point, */ #include #ifndef _WIN32 #include #endif #include "proto.h" /* define it (BSD4.2 needs this) */ #include "h.h" #include "fdlist.h" #ifdef INET6 static unsigned char minus_one[] = { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0 }; #endif #ifndef IN_LOOPBACKNET #define IN_LOOPBACKNET 0x7f #endif #ifndef INADDRSZ #define INADDRSZ sizeof(struct IN_ADDR) #define IN6ADDRSZ sizeof(struct IN_ADDR) #endif #ifndef _WIN32 #define SET_ERRNO(x) errno = x #else #define SET_ERRNO(x) WSASetLastError(x) #endif /* _WIN32 */ #ifndef SOMAXCONN # define LISTEN_SIZE (5) #else # define LISTEN_SIZE (SOMAXCONN) #endif extern char backupbuf[8192]; int OpenFiles = 0; /* GLOBAL - number of files currently open */ int readcalls = 0; static struct SOCKADDR_IN mysk; static struct SOCKADDR *connect_inet(ConfigItem_link *, aClient *, int *); void completed_connection(int, int, void *); static int check_init(aClient *, char *, size_t); void set_sock_opts(int, aClient *, int); void set_ipv6_opts(int); static char readbuf[BUFSIZE]; char zlinebuf[BUFSIZE]; extern char *version; extern ircstats IRCstats; MODVAR TS last_allinuse = 0; #ifdef USE_LIBCURL extern void url_do_transfers_async(void); #endif /* * Try and find the correct name to use with getrlimit() for setting the max. * number of files allowed to be open by this process. */ #ifdef RLIMIT_FDMAX # define RLIMIT_FD_MAX RLIMIT_FDMAX #else # ifdef RLIMIT_NOFILE # define RLIMIT_FD_MAX RLIMIT_NOFILE # else # ifdef RLIMIT_OPEN_MAX # define RLIMIT_FD_MAX RLIMIT_OPEN_MAX # else # undef RLIMIT_FD_MAX # endif # endif #endif void start_of_normal_client_handshake(aClient *acptr); void proceed_normal_client_handshake(aClient *acptr, struct hostent *he); /* winlocal */ void close_connections(void) { aClient* cptr; list_for_each_entry(cptr, &lclient_list, lclient_node) { if (cptr->fd >= 0) { fd_close(cptr->fd); cptr->fd = -2; } } list_for_each_entry(cptr, &unknown_list, lclient_node) { if (cptr->fd >= 0) { fd_close(cptr->fd); cptr->fd = -2; } if (cptr->local->authfd >= 0) { fd_close(cptr->local->authfd); cptr->fd = -1; } } close_listeners(); OpenFiles = 0; #ifdef _WIN32 WSACleanup(); #endif } /* ** add_local_domain() ** Add the domain to hostname, if it is missing ** (as suggested by eps@TOASTER.SFSU.EDU) */ void add_local_domain(char *hname, int size) { #if 0 /* try to fix up unqualified names */ if (!index(hname, '.')) { if (!(ircd_res.options & RES_INIT)) { Debug((DEBUG_DNS, "res_init()")); ircd_res_init(); } if (ircd_res.defdname[0]) { (void)strncat(hname, ".", size - 1); (void)strncat(hname, ircd_res.defdname, size - 2); } } #endif return; } /* ** Cannot use perror() within daemon. stderr is closed in ** ircd and cannot be used. And, worse yet, it might have ** been reassigned to a normal connection... */ /* ** report_error ** This a replacement for perror(). Record error to log and ** also send a copy to all *LOCAL* opers online. ** ** text is a *format* string for outputting error. It must ** contain only two '%s', the first will be replaced ** by the sockhost from the cptr, and the latter will ** be taken from strerror(errno). ** ** cptr if not NULL, is the *LOCAL* client associated with ** the error. */ void report_error(char *text, aClient *cptr) { int errtmp = ERRNO, origerr = ERRNO; char *host, xbuf[256]; int err, len = sizeof(err), n; 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 if (origerr != errtmp) { /* Socket error is different than original error, * some tricks are needed because of 2x strerror() (or at least * according to the man page) -- Syzop. */ snprintf(xbuf, 200, "[syserr='%s'", STRERROR(origerr)); n = strlen(xbuf); snprintf(xbuf+n, 256-n, ", sockerr='%s']", STRERROR(errtmp)); sendto_snomask(SNO_JUNK, text, host, xbuf); ircd_log(LOG_ERROR, text, host, xbuf); } else { sendto_snomask(SNO_JUNK, text, host, STRERROR(errtmp)); ircd_log(LOG_ERROR, text,host,STRERROR(errtmp)); } return; } void report_baderror(char *text, aClient *cptr) { #ifndef _WIN32 int errtmp = errno; /* debug may change 'errno' */ #else int errtmp = WSAGetLastError(); /* debug may change 'errno' */ #endif char *host; int err, len = sizeof(err); host = (cptr) ? get_client_name(cptr, FALSE) : ""; /* fprintf(stderr, text, host, strerror(errtmp)); fputc('\n', stderr); */ Debug((DEBUG_ERROR, text, host, STRERROR(errtmp))); /* * Get the *real* error from the socket (well try to anyway..). * This may only work when SO_DEBUG is enabled but its worth the * gamble anyway. */ #ifdef SO_ERROR if (cptr && !IsMe(cptr) && cptr->fd >= 0) if (!getsockopt(cptr->fd, SOL_SOCKET, SO_ERROR, (OPT_TYPE *)&err, &len)) if (err) errtmp = err; #endif sendto_umode(UMODE_OPER, text, host, STRERROR(errtmp)); ircd_log(LOG_ERROR, text, host, STRERROR(errtmp)); return; } /** Accept an incoming client. */ static void listener_accept(int fd, int revents, void *data) { ConfigItem_listen *cptr = data; int cli_fd; if ((cli_fd = fd_accept(cptr->fd)) < 0) { if ((ERRNO != P_EWOULDBLOCK) && (ERRNO != P_ECONNABORTED)) report_baderror("Cannot accept connections %s:%s", NULL); return; } ircstp->is_ac++; set_sock_opts(fd, NULL, cptr->ipv6); set_non_blocking(fd, NULL); if ((++OpenFiles >= MAXCLIENTS) || (fd >= MAXCLIENTS)) { ircstp->is_ref++; if (last_allinuse < TStime() - 15) { sendto_realops("All connections in use. ([@%s/%u])", cptr->ip, cptr->port); last_allinuse = TStime(); } (void)send(cli_fd, "ERROR :All connections in use\r\n", 31, 0); fd_close(cli_fd); --OpenFiles; return; } /* add_connection() may fail. we just don't care. */ (void)add_connection(cptr, cli_fd); } /* * inetport * * Create a socket in the AFINET domain, bind it to the port given in * 'port' and listen to it. Connections are accepted to this socket * depending on the IP# mask given by 'name'. Returns the fd of the * socket created or -1 on error. */ int inetport(ConfigItem_listen *listener, char *ip, int port, int ipv6) { int result; if (BadPtr(ip)) ip = "*"; if (*ip == '*') { if (ipv6) ip = "::"; else ip = "0.0.0.0"; } ircd_log(LOG_ERROR, "inetport() for %s:%d (%s)", ip, port, ipv6 ? "IPv6" : "IPv4"); /* At first, open a new socket */ if (listener->fd != -1) abort(); /* there was a (reverse check) for this. let's see if this ever happened :) */ if (port == 0) abort(); /* Impossible as well, right? */ listener->fd = fd_socket(ipv6 ? AF_INET6 : AF_INET, SOCK_STREAM, 0, "Listener socket"); if (listener->fd < 0) { report_baderror("Cannot open stream socket() %s:%s", NULL); return -1; } if (++OpenFiles >= MAXCLIENTS) { sendto_ops("No more connections allowed (%s)", listener->ip); fd_close(listener->fd); listener->fd = -1; --OpenFiles; return -1; } set_sock_opts(listener->fd, NULL, ipv6); set_non_blocking(listener->fd, NULL); if (ipv6) { struct sockaddr_in6 server; memset(&server, 0, sizeof(server)); server.sin6_family = AF_INET6; server.sin6_port = htons(port); result = inet_pton(AF_INET6, ip, &server.sin6_addr.s6_addr); if (result != 1) goto binderr; result = bind(listener->fd, (struct sockaddr *)&server, sizeof(server)); } else { struct sockaddr_in server; memset(&server, 0, sizeof(server)); server.sin_family = AF_INET; server.sin_port = htons(port); result = inet_pton(AF_INET, ip, &server.sin_addr.s_addr); if (result != 1) goto binderr; result = bind(listener->fd, (struct sockaddr *)&server, sizeof(server)); } if (result < 0) { binderr: ircsnprintf(backupbuf, sizeof(backupbuf), "Error binding stream socket to IP %s port %i", ip, port); strlcat(backupbuf, " - %s:%s", sizeof backupbuf); report_baderror(backupbuf, NULL); fd_close(listener->fd); listener->fd = -1; --OpenFiles; return -1; } result = listen(listener->fd, LISTEN_SIZE); if (result < 0) { report_error("listen failed for %s:%s", NULL); fd_close(listener->fd); listener->fd = -1; --OpenFiles; return -1; } #ifdef TCP_DEFER_ACCEPT if ((listener->options & LISTENER_DEFER_ACCEPT) && !result) { int yes = 1; (void)setsockopt(listener->fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &yes, sizeof(int)); } #endif #ifdef SO_ACCEPTFILTER if ((listener->options & LISTENER_DEFER_ACCEPT) && !result) { struct accept_filter_arg afa; memset(&afa, '\0', sizeof afa); strlcpy(afa.af_name, "dataready", sizeof afa.af_name); (void)setsockopt(listener->fd, SOL_SOCKET, SO_ACCEPTFILTER, &afa, sizeof afa); } #endif /* ircd_log(LOG_ERROR, "FD #%d: Listener on %s:%d", listener->fd, ipname, port); */ fd_setselect(listener->fd, FD_SELECT_READ, listener_accept, listener); return 0; } int add_listener2(ConfigItem_listen *conf) { if (inetport(conf, conf->ip, conf->port, conf->ipv6)) { ircd_log(LOG_ERROR, "inetport failed for %s:%u", conf->ip, conf->port); conf->fd = -2; } if (conf->fd >= 0) { conf->options |= LISTENER_BOUND; return 1; } else { conf->fd = -1; return -1; } } /* * close_listeners * * Close and free all clients which are marked as having their socket open * and in a state where they can accept connections. */ void close_listener(ConfigItem_listen *listener) { fd_close(listener->fd); listener->options &= ~LISTENER_BOUND; listener->fd = -1; } void close_listeners(void) { aClient *cptr; ConfigItem_listen *aconf, *aconf_next; /* * close all 'extra' listening ports we have */ for (aconf = conf_listen; aconf != NULL; aconf = aconf_next) { aconf_next = (ConfigItem_listen *) aconf->next; if (aconf->flag.temporary && (aconf->clients == 0)) close_listener(aconf); } } /* * init_sys */ void init_sys(void) { int fd, i; #ifdef RLIMIT_FD_MAX struct rlimit limit; if (!getrlimit(RLIMIT_FD_MAX, &limit)) { if (limit.rlim_max < MAXCONNECTIONS) { (void)fprintf(stderr, "The OS enforces a limit on max open files\n"); #ifndef LONG_LONG_RLIM_T (void)fprintf(stderr, "Hard Limit: %ld MAXCONNECTIONS: %d\n", #else (void)fprintf(stderr, "Hard Limit: %lld MAXCONNECTIONS: %d\n", #endif limit.rlim_max, MAXCONNECTIONS); (void)fprintf(stderr, "Fix MAXCONNECTIONS\n"); exit(-1); } limit.rlim_cur = limit.rlim_max; /* make soft limit the max */ if (setrlimit(RLIMIT_FD_MAX, &limit) == -1) { /* HACK: if it's mac os X then don't error... */ #ifndef OSXTIGER #ifndef LONG_LONG_RLIM_T (void)fprintf(stderr, "error setting max fd's to %ld\n", #else (void)fprintf(stderr, "error setting max fd's to %lld\n", #endif limit.rlim_cur); exit(-1); #endif } } #endif #ifndef _WIN32 #ifdef BACKEND_SELECT if (MAXCONNECTIONS > FD_SETSIZE) { fprintf(stderr, "MAXCONNECTIONS (%d) is higher than FD_SETSIZE (%d)\n", MAXCONNECTIONS, FD_SETSIZE); fprintf(stderr, "You might need to recompile the IRCd, or if you're running Linux, read the release notes\n"); exit(-1); } #endif #endif #if defined(PCS) || defined(SVR3) char logbuf[BUFSIZ]; (void)setvbuf(stderr, logbuf, _IOLBF, sizeof(logbuf)); #else # if defined(HPUX) (void)setvbuf(stderr, NULL, _IOLBF, 0); # else # if !defined(_SOLARIS) && !defined(_WIN32) (void)setlinebuf(stderr); # endif # endif #endif #ifndef _WIN32 #ifdef HAVE_SYSLOG closelog(); /* temporary close syslog, as we mass close() fd's below... */ #endif if (bootopt & BOOT_TTY) /* debugging is going to a tty */ goto init_dgram; #ifndef NOCLOSEFD if (!(bootopt & BOOT_DEBUG)) (void)close(2); #endif if ((bootopt & BOOT_CONSOLE) || isatty(0)) { #ifndef _AMIGA /* if (fork()) exit(0); */ #endif #ifdef TIOCNOTTY if ((fd = open("/dev/tty", O_RDWR)) >= 0) { (void)ioctl(fd, TIOCNOTTY, (char *)NULL); #ifndef NOCLOSEFD (void)close(fd); #endif } #endif #if defined(HPUX) || defined(_SOLARIS) || \ defined(_POSIX_SOURCE) || defined(SVR4) || defined(SGI) || \ defined(OSXTIGER) || defined(__QNX__) (void)setsid(); #else (void)setpgrp(0, (int)getpid()); #endif #ifndef NOCLOSEFD (void)close(0); /* fd 0 opened by inetd */ #endif } init_dgram: #else #ifndef NOCLOSEFD close(fileno(stdin)); close(fileno(stdout)); if (!(bootopt & BOOT_DEBUG)) close(fileno(stderr)); #endif #ifdef HAVE_SYSLOG openlog("ircd", LOG_PID | LOG_NDELAY, LOG_DAEMON); /* reopened now */ #endif #endif /*_WIN32*/ #ifndef CHROOTDIR init_resolver(1); #endif return; } void write_pidfile(void) { #ifdef IRCD_PIDFILE int fd; char buff[20]; if ((fd = open(conf_files->pid_file, O_CREAT | O_WRONLY, 0600)) >= 0) { bzero(buff, sizeof(buff)); (void)ircsnprintf(buff, sizeof(buff), "%5d\n", (int)getpid()); if (write(fd, buff, strlen(buff)) == -1) Debug((DEBUG_NOTICE, "Error writing to pid file %s", conf_files->pid_file)); (void)close(fd); return; } #ifdef DEBUGMODE else Debug((DEBUG_NOTICE, "Error opening pid file %s", conf_files->pid_file)); #endif #endif } /* This used to initialize the various name strings used to store hostnames. * But nowadays this takes place much earlier (in add_connection?). * It's mainly used for "localhost" and WEBIRC magic only now... */ static int check_init(aClient *cptr, char *sockn, size_t size) { strlcpy(sockn, cptr->local->sockhost, HOSTLEN); RunHookReturnInt3(HOOKTYPE_CHECK_INIT, cptr, sockn, size, ==0); /* Some silly hack to convert 127.0.0.1 and such into 'localhost' */ if (IsLocal(cptr)) { if (cptr->local->hostp) { unreal_free_hostent(cptr->local->hostp); cptr->local->hostp = NULL; } strlcpy(sockn, "localhost", HOSTLEN); } return 0; } /* * Ordinary client access check. Look for conf lines which have the same * status as the flags passed. * 0 = Success * -1 = Access denied * -2 = Bad socket. */ int check_client(aClient *cptr, char *username) { static char sockname[HOSTLEN + 1]; struct hostent *hp = NULL; int i; ClearAccess(cptr); Debug((DEBUG_DNS, "ch_cl: check access for %s[%s]", cptr->name, cptr->local->sockhost)); if (check_init(cptr, sockname, sizeof(sockname))) return -2; hp = cptr->local->hostp; if ((i = AllowClient(cptr, hp, sockname, username))) return i; Debug((DEBUG_DNS, "ch_cl: access ok: %s[%s]", cptr->name, sockname)); return 0; } /** Reject an insecure (outgoing) server link that isn't SSL/TLS. * This function is void and not int because it can be called from other void functions */ void reject_insecure_server(aClient *cptr) { sendto_umode(UMODE_OPER, "Could not link with server %s with SSL/TLS enabled. " "Please check logs on the other side of the link and make sure the other IRCd " "is compiled with SSL support enabled. " "If you insist with insecure linking then you can set link::options::outgoing::insecure", cptr->name); dead_link(cptr, "Rejected link without SSL/TLS"); } void start_server_handshake(aClient *cptr) { ConfigItem_link *aconf = cptr->serv ? cptr->serv->conf : NULL; if (!aconf) { /* Should be impossible. */ sendto_ops("Lost configuration for %s in start_server_handshake()", get_client_name(cptr, FALSE)); return; } RunHook(HOOKTYPE_SERVER_HANDSHAKE_OUT, cptr); sendto_one(cptr, "PASS :%s", (aconf->auth->type == AUTHTYPE_PLAINTEXT) ? aconf->auth->data : "*"); send_protoctl_servers(cptr, 0); send_proto(cptr, aconf); if (NEW_LINKING_PROTOCOL) { /* Sending SERVER message moved to m_protoctl, so it's send after the first PROTOCTL * we receive from the remote server. Of course, this assumes that the remote server * to which we are connecting will at least send one PROTOCTL... but since it's an * outgoing connect, we can safely assume it's a remote UnrealIRCd server (or some * other advanced server..). -- Syzop */ /* Use this nasty hack, to make 3.2.9<->pre-3.2.9 linking work */ sendto_one(cptr, "__PANGPANG__"); } else { send_server_message(cptr); } } /* ** 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 */ void completed_connection(int fd, int revents, void *data) { aClient *cptr = data; ConfigItem_link *aconf = cptr->serv ? cptr->serv->conf : NULL; if (IsHandshake(cptr)) { /* Due to delayed ircd_SSL_connect call */ start_server_handshake(cptr); fd_setselect(fd, FD_SELECT_READ, read_packet, cptr); return; } SetHandshake(cptr); if (!aconf) { sendto_ops("Lost configuration for %s", get_client_name(cptr, FALSE)); return; } if (!cptr->local->ssl && !(aconf->outgoing.options & CONNECT_INSECURE)) { sendto_one(cptr, "STARTTLS"); } else { start_server_handshake(cptr); } if (!IsDead(cptr)) start_auth(cptr); fd_setselect(fd, FD_SELECT_READ, read_packet, cptr); } /* ** close_connection ** Close the physical connection. This function must make ** MyConnect(cptr) == FALSE, and set cptr->from == NULL. */ void close_connection(aClient *cptr) { ConfigItem_link *aconf; #ifdef DO_REMAPPING int i, j; int empty = cptr->fd; #endif if (IsServer(cptr)) { ircstp->is_sv++; ircstp->is_sbs += cptr->local->sendB; ircstp->is_sbr += cptr->local->receiveB; ircstp->is_sks += cptr->local->sendK; ircstp->is_skr += cptr->local->receiveK; ircstp->is_sti += TStime() - cptr->local->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->local->sendB; ircstp->is_cbr += cptr->local->receiveB; ircstp->is_cks += cptr->local->sendK; ircstp->is_ckr += cptr->local->receiveK; ircstp->is_cti += TStime() - cptr->local->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. */ unrealdns_delreq_bycptr(cptr); if (cptr->local->authfd >= 0) { fd_close(cptr->local->authfd); cptr->local->authfd = -1; --OpenFiles; } if (cptr->fd >= 0) { send_queued(cptr); if (IsSSL(cptr) && cptr->local->ssl) { SSL_set_shutdown(cptr->local->ssl, SSL_RECEIVED_SHUTDOWN); SSL_smart_shutdown(cptr->local->ssl); SSL_free(cptr->local->ssl); cptr->local->ssl = NULL; } fd_close(cptr->fd); cptr->fd = -2; --OpenFiles; DBufClear(&cptr->local->sendQ); DBufClear(&cptr->local->recvQ); } cptr->from = NULL; /* ...this should catch them! >:) --msa */ return; } void set_ipv6_opts(int fd) { ircd_log(LOG_ERROR, "set_ipv6_opts(%d)", fd); #if defined(INET6) && defined(IPV6_V6ONLY) int opt = 1; setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (OPT_TYPE *)&opt, sizeof(opt)); #endif } /* ** set_sock_opts */ void set_sock_opts(int fd, aClient *cptr, int ipv6) { int opt; if (ipv6) set_ipv6_opts(fd); #ifdef SO_REUSEADDR opt = 1; if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (OPT_TYPE *)&opt, sizeof(opt)) < 0) report_error("setsockopt(SO_REUSEADDR) %s:%s", cptr); #endif #if defined(SO_DEBUG) && defined(DEBUGMODE) && 0 /* Solaris with SO_DEBUG writes to syslog by default */ #if !defined(_SOLARIS) || defined(HAVE_SYSLOG) opt = 1; if (setsockopt(fd, SOL_SOCKET, SO_DEBUG, (OPT_TYPE *)&opt, sizeof(opt)) < 0) report_error("setsockopt(SO_DEBUG) %s:%s", cptr); #endif /* _SOLARIS */ #endif #if defined(SO_USELOOPBACK) && !defined(_WIN32) opt = 1; if (setsockopt(fd, SOL_SOCKET, SO_USELOOPBACK, (OPT_TYPE *)&opt, sizeof(opt)) < 0) report_error("setsockopt(SO_USELOOPBACK) %s:%s", cptr); #endif #ifdef SO_RCVBUF opt = 8192; if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (OPT_TYPE *)&opt, sizeof(opt)) < 0) report_error("setsockopt(SO_RCVBUF) %s:%s", cptr); #endif #ifdef SO_SNDBUF # ifdef _SEQUENT_ /* seems that Sequent freezes up if the receving buffer is a different size * to the sending buffer (maybe a tcp window problem too). */ opt = 8192; # else opt = 8192; # endif if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (OPT_TYPE *)&opt, sizeof(opt)) < 0) report_error("setsockopt(SO_SNDBUF) %s:%s", cptr); #endif } int get_sockerr(aClient *cptr) { #ifndef _WIN32 int errtmp = errno, err = 0, len = sizeof(err); #else int errtmp = WSAGetLastError(), err = 0, len = sizeof(err); #endif #ifdef SO_ERROR if (cptr->fd >= 0) if (!getsockopt(cptr->fd, SOL_SOCKET, SO_ERROR, (OPT_TYPE *)&err, &len)) if (err) errtmp = err; #endif return errtmp; } /* * set_blocking - Set the client connection into non-blocking mode. * If your system doesn't support this, you're screwed, ircd will run like * crap. * returns true (1) if successful, false (0) otherwise */ int set_blocking(int fd) { int flags; #ifdef _WIN32 int nonb; #endif #ifndef _WIN32 if ((flags = fcntl(fd, F_GETFL, 0)) < 0 || fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) < 0) #else nonb = 0; if (ioctlsocket(fd, FIONBIO, &nonb) < 0) #endif return 0; else return 1; } /* ** set_non_blocking ** Set the client connection into non-blocking mode. If your ** system doesn't support this, you can make this a dummy ** function (and get all the old problems that plagued the ** blocking version of IRC--not a problem if you are a ** lightly loaded node...) */ void set_non_blocking(int fd, aClient *cptr) { int res, nonb = 0; /* ** NOTE: consult ALL your relevant manual pages *BEFORE* changing ** these ioctl's. There are quite a few variations on them, ** as can be seen by the PCS one. They are *NOT* all the same. ** Heed this well. - Avalon. */ #ifdef NBLOCK_POSIX nonb |= O_NONBLOCK; #endif #ifdef NBLOCK_BSD nonb |= O_NDELAY; #endif #ifdef NBLOCK_SYSV /* This portion of code might also apply to NeXT. -LynX */ res = 1; if (ioctl(fd, FIONBIO, &res) < 0) { if (cptr) report_error("ioctl(fd,FIONBIO) failed for %s:%s", cptr); } #else # if !defined(_WIN32) if ((res = fcntl(fd, F_GETFL, 0)) == -1) { if (cptr) { report_error("fcntl(fd, F_GETFL) failed for %s:%s", cptr); } } else if (fcntl(fd, F_SETFL, res | nonb) == -1) { if (cptr) { report_error("fcntl(fd, F_SETL, nonb) failed for %s:%s", cptr); } } # else nonb = 1; if (ioctlsocket(fd, FIONBIO, &nonb) < 0) { if (cptr) { report_error("ioctlsocket(fd,FIONBIO) failed for %s:%s", cptr); } } # endif #endif return; } int is_loopback_ip(char *ip) { if (!strcmp(ip, "127.0.0.1") || !strcmp(ip, "0:0:0:0:0:0:0:1") || !strcmp(ip, "0:0:0:0:0:ffff:127.0.0.1")) return 1; return 0; } char *getpeerip(aClient *acptr, int fd, int *port) { static char ret[HOSTLEN+1]; if (IsIPV6(acptr)) { struct sockaddr_in6 addr; int len = sizeof(addr); if (getpeername(fd, (struct SOCKADDR *)&addr, &len) < 0) return NULL; *port = ntohs(addr.sin6_port); return inetntop(AF_INET6, &addr.sin6_addr.s6_addr, ret, sizeof(ret)); } else { struct sockaddr_in addr; int len = sizeof(addr); if (getpeername(fd, (struct SOCKADDR *)&addr, &len) < 0) return NULL; *port = ntohs(addr.sin_port); return inetntop(AF_INET, &addr.sin_addr.s_addr, ret, sizeof(ret)); } } /* * 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(ConfigItem_listen *cptr, int fd) { aClient *acptr, *acptr2; ConfigItem_ban *bconf; int i, j; char *ip; int port = 0; acptr = make_client(NULL, &me); /* If listener (cptr) is IPv6 then mark client (acptr) as IPv6 */ if (cptr->ipv6) SetIPV6(acptr); ip = getpeerip(acptr, fd, &port); if (!ip) { /* On Linux 2.4 and FreeBSD the socket may just have been disconnected * so it's not a serious error and can happen quite frequently -- Syzop */ if (ERRNO != P_ENOTCONN) { report_error("Failed to accept new client %s :%s", acptr); } add_con_refuse: ircstp->is_ref++; acptr->fd = -2; free_client(acptr); fd_close(fd); --OpenFiles; return NULL; } /* Fill in sockhost & ip ASAP */ set_sockhost(acptr, ip); acptr->ip = strdup(ip); acptr->local->port = port; /* Tag loopback connections as FLAGS_LOCAL */ if (is_loopback_ip(acptr->ip)) { ircstp->is_loc++; acptr->flags |= FLAGS_LOCAL; } j = 1; list_for_each_entry(acptr2, &unknown_list, lclient_node) { if (!strcmp(acptr->ip,GetIP(acptr2))) { j++; if (j > MAXUNKNOWNCONNECTIONSPERIP) { ircsnprintf(zlinebuf, sizeof(zlinebuf), "ERROR :Closing Link: [%s] (Too many unknown connections from your IP)" "\r\n", acptr->ip); (void)send(fd, zlinebuf, strlen(zlinebuf), 0); goto add_con_refuse; } } } if ((bconf = Find_ban(acptr, acptr->ip, CONF_BAN_IP))) { if (bconf) { ircsnprintf(zlinebuf, sizeof(zlinebuf), "ERROR :Closing Link: [%s] (You are not welcome on " "this server: %s. Email %s for more information.)\r\n", acptr->ip, bconf->reason ? bconf->reason : "no reason", KLINE_ADDRESS); (void)send(fd, zlinebuf, strlen(zlinebuf), 0); goto add_con_refuse; } } else if (find_tkline_match_zap(acptr) != -1) { (void)send(fd, zlinebuf, strlen(zlinebuf), 0); goto add_con_refuse; } else { int val; if (!(val = throttle_can_connect(acptr))) { ircsnprintf(zlinebuf, sizeof(zlinebuf), "ERROR :Closing Link: [%s] (Throttled: Reconnecting too fast) -" "Email %s for more information.\r\n", acptr->ip, KLINE_ADDRESS); (void)send(fd, zlinebuf, strlen(zlinebuf), 0); goto add_con_refuse; } else if (val == 1) add_throttling_bucket(acptr); } acptr->fd = fd; acptr->local->listener = cptr; if (acptr->local->listener != NULL) acptr->local->listener->clients++; add_client_to_list(acptr); IRCstats.unknown++; acptr->status = STAT_UNKNOWN; list_add(&acptr->lclient_node, &unknown_list); if ((cptr->options & LISTENER_SSL) && ctx_server) { SetSSLAcceptHandshake(acptr); Debug((DEBUG_DEBUG, "Starting SSL accept handshake for %s", acptr->local->sockhost)); if ((acptr->local->ssl = SSL_new(ctx_server)) == NULL) { goto add_con_refuse; } acptr->flags |= FLAGS_SSL; SSL_set_fd(acptr->local->ssl, fd); SSL_set_nonblocking(acptr->local->ssl); if (!ircd_SSL_accept(acptr, fd)) { Debug((DEBUG_DEBUG, "Failed SSL accept handshake in instance 1: %s", acptr->local->sockhost)); SSL_set_shutdown(acptr->local->ssl, SSL_RECEIVED_SHUTDOWN); SSL_smart_shutdown(acptr->local->ssl); SSL_free(acptr->local->ssl); goto add_con_refuse; } } else start_of_normal_client_handshake(acptr); return acptr; } static int dns_special_flag = 0; /* This is for an "interesting" race condition very ugly. */ void start_of_normal_client_handshake(aClient *acptr) { struct hostent *he; acptr->status = STAT_UNKNOWN; /* reset, to be sure (SSL handshake has ended) */ RunHook(HOOKTYPE_HANDSHAKE, acptr); if (!DONT_RESOLVE) { if (SHOWCONNECTINFO && !acptr->serv && !IsServersOnlyListener(acptr->local->listener)) sendto_one(acptr, "%s", REPORT_DO_DNS); dns_special_flag = 1; he = unrealdns_doclient(acptr); dns_special_flag = 0; if (acptr->local->hostp) goto doauth; /* Race condition detected, DNS has been done, continue with auth */ if (!he) { /* Resolving in progress */ SetDNS(acptr); } else { /* Host was in our cache */ acptr->local->hostp = he; if (SHOWCONNECTINFO && !acptr->serv && !IsServersOnlyListener(acptr->local->listener)) sendto_one(acptr, "%s", REPORT_FIN_DNSC); } } doauth: start_auth(acptr); fd_setselect(acptr->fd, FD_SELECT_READ, read_packet, acptr); } void proceed_normal_client_handshake(aClient *acptr, struct hostent *he) { ClearDNS(acptr); acptr->local->hostp = he; if (SHOWCONNECTINFO && !acptr->serv && !IsServersOnlyListener(acptr->local->listener)) sendto_one(acptr, "%s", acptr->local->hostp ? REPORT_FIN_DNS : REPORT_FAIL_DNS); if (!dns_special_flag && !DoingAuth(acptr)) finish_auth(acptr); } /* ** read_packet ** ** Read a 'packet' of data from a connection and process it. Read in 8k ** chunks to give a better performance rating (for server connections). ** Do some tricky stuff for client connections to make sure they don't do ** any flooding >:-) -avalon ** If 'doread' is set to 0 then we don't actually read (no recv()), ** however we still check if we need to dequeue anything from the recvQ. ** This is necessary, since we may have put something on the recvQ due ** to fake lag. -- Syzop ** With new I/O code, things work differently. Surprise! ** read_one_packet() reads packets in and dumps them as quickly as ** possible into the client's DBuf. Then we parse data out of the DBuf, ** after we're done reading crap. ** -- nenolod */ static int parse_client_queued(aClient *cptr) { int dolen = 0; int allow_read; int done; time_t now = TStime(); char buf[BUFSIZE]; if (DoingDNS(cptr)) return 0; /* we delay processing of data until the host is resolved */ if (DoingAuth(cptr)) return 0; /* we delay processing of data until identd has replied */ while (DBufLength(&cptr->local->recvQ) && ((cptr->status < STAT_UNKNOWN) || (cptr->local->since - now < 10))) { dolen = dbuf_getmsg(&cptr->local->recvQ, buf); if (dolen == 0) return 0; if (dopacket(cptr, buf, dolen) == FLUSH_BUFFER) return FLUSH_BUFFER; } return 0; } void read_packet(int fd, int revents, void *data) { aClient *cptr = data; int length = 0; time_t now = TStime(); Hook *h; SET_ERRNO(0); fd_setselect(fd, FD_SELECT_READ, read_packet, cptr); fd_setselect(fd, FD_SELECT_WRITE, NULL, cptr); while (1) { if (IsSSL(cptr) && cptr->local->ssl != NULL) { length = SSL_read(cptr->local->ssl, readbuf, sizeof(readbuf)); if (length < 0) { int err = SSL_get_error(cptr->local->ssl, length); switch (err) { case SSL_ERROR_WANT_WRITE: fd_setselect(fd, FD_SELECT_READ, NULL, cptr); fd_setselect(fd, FD_SELECT_WRITE, read_packet, cptr); break; case SSL_ERROR_WANT_READ: fd_setselect(fd, FD_SELECT_READ, read_packet, cptr); fd_setselect(fd, FD_SELECT_WRITE, NULL, cptr); break; case SSL_ERROR_SYSCALL: break; case SSL_ERROR_SSL: if (ERRNO == P_EAGAIN) break; default: /*length = 0; SET_ERRNO(0); ^^ why this? we should error. -- todo: is errno correct? */ break; } } } else length = recv(cptr->fd, readbuf, sizeof(readbuf), 0); if (length <= 0) { if (length < 0 && (ERRNO == P_EWOULDBLOCK || ERRNO == P_EAGAIN || ERRNO == P_EINTR)) return; if (IsServer(cptr) || cptr->serv) /* server or outgoing connection */ sendto_umode_global(UMODE_OPER, "Lost connection to %s: Read error", get_client_name(cptr, FALSE)); exit_client(cptr, cptr, cptr, "Read error"); return; } cptr->local->lasttime = now; if (cptr->local->lasttime > cptr->local->since) cptr->local->since = cptr->local->lasttime; cptr->flags &= ~(FLAGS_PINGSENT | FLAGS_NONL); for (h = Hooks[HOOKTYPE_RAWPACKET_IN]; h; h = h->next) { int v = (*(h->func.intfunc))(cptr, readbuf, &length); if (v <= 0) return; } dbuf_put(&cptr->local->recvQ, readbuf, length); /* parse some of what we have (inducing fakelag, etc) */ if (!(DoingDNS(cptr) || DoingAuth(cptr))) if (parse_client_queued(cptr) == FLUSH_BUFFER) return; /* excess flood check */ if (IsPerson(cptr) && DBufLength(&cptr->local->recvQ) > get_recvq(cptr)) { sendto_snomask(SNO_FLOOD, "*** Flood -- %s!%s@%s (%d) exceeds %d recvQ", cptr->name[0] ? cptr->name : "*", cptr->user ? cptr->user->username : "*", cptr->user ? cptr->user->realhost : "*", DBufLength(&cptr->local->recvQ), get_recvq(cptr)); exit_client(cptr, cptr, cptr, "Excess Flood"); return; } /* bail on short read! */ if (length < sizeof(readbuf)) return; } } /* Process input from clients that may have been deliberately delayed due to fake lag */ void process_clients(void) { aClient *cptr; /* Problem: * 1) When 'cptr' exits we can't check 'current_element->next' since this * has been freed. * 2) We can't use list_for_each_entry_safe() which would take care of #1 * (like if 'cptr' exited). This is because it would set * next = current_element->next, however parse_client_queued may * potentially kill 'next' (eg /KILL user) so then we would follow * an invalid pointer. * So I'm just re-running the loop. We could use some kind of 'tagging' * to mark already processed clients, however parse_client_queued() already * takes care not to read (fake) lagged up clients, and we don't actually * read/recv anything, so clients in the beginning of the list won't * benefit/get higher prio. * Another alternative is not to run the loop again, but that WOULD be * unfair to clients later in the list which wouldn't be processed then * under a heavy (kill) load scenario. * I think the chosen solution is best, though it remains silly. -- Syzop */ do { list_for_each_entry(cptr, &lclient_list, lclient_node) if ((cptr->fd >= 0) && DBufLength(&cptr->local->recvQ)) if (parse_client_queued(cptr) == FLUSH_BUFFER) break; } while(&cptr->lclient_node != &lclient_list); /* For unknown_list we also have to take into account the unknown->client transition */ do { list_for_each_entry(cptr, &unknown_list, lclient_node) if ((cptr->fd >= 0) && DBufLength(&cptr->local->recvQ)) if ((parse_client_queued(cptr) == FLUSH_BUFFER) || !IsUnknown(cptr)) break; } while(&cptr->lclient_node != &unknown_list); } /* When auth is finished, go back and parse all prior input. */ void finish_auth(aClient *acptr) { SetAccess(acptr); parse_client_queued(acptr); } /* * connect_server */ int connect_server(ConfigItem_link *aconf, aClient *by, struct hostent *hp) { struct SOCKADDR *svp; aClient *cptr; char *s; int errtmp, len; #ifdef DEBUGMODE sendto_realops("connect_server() called with aconf %p, refcount: %d, TEMP: %s", aconf, aconf->refcount, aconf->flag.temporary ? "YES" : "NO"); #endif if (!aconf->outgoing.hostname) return -1; /* This is an incoming-only link block. Caller shouldn't call us. */ if (!hp) { /* Remove "cache" */ memset(&aconf->ipnum, '\0', sizeof(struct IN_ADDR)); } /* * 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 (!WHOSTENTP(aconf->ipnum.S_ADDR)) { s = aconf->outgoing.hostname; #ifndef INET6 if ((aconf->ipnum.S_ADDR = inet_addr(s)) == -1) #else if (!inet_pton(AF_INET6, s, aconf->ipnum.s6_addr)) #endif { #ifdef INET6 bzero(aconf->ipnum.s6_addr, IN6ADDRSZ); #else aconf->ipnum.S_ADDR = 0; #endif /* We need this 'aconf->refcount++' or else there's a race condition between * starting resolving the host and the result of the resolver (we could * REHASH in that timeframe) leading to an invalid (freed!) 'aconf'. * -- Syzop, bug #0003689. */ aconf->refcount++; unrealdns_gethostbyname_link(aconf->outgoing.hostname, aconf); return -2; } } cptr = make_client(NULL, NULL); cptr->local->hostp = hp; /* * Copy these in so we have something for error detection. */ strlcpy(cptr->name, aconf->servername, sizeof(cptr->name)); strlcpy(cptr->local->sockhost, aconf->outgoing.hostname, HOSTLEN + 1); svp = connect_inet(aconf, cptr, &len); if (!svp) { if (cptr->fd >= 0) { fd_close(cptr->fd); --OpenFiles; } cptr->fd = -2; free_client(cptr); return -1; } set_non_blocking(cptr->fd, cptr); set_sock_opts(cptr->fd, cptr, 1); // XXX/FIXME!!!!! always ipv6 atm. #ifndef _WIN32 if (connect(cptr->fd, svp, len) < 0 && errno != EINPROGRESS) #else if (connect(cptr->fd, svp, len) < 0 && WSAGetLastError() != WSAEINPROGRESS && WSAGetLastError() != WSAEWOULDBLOCK) #endif { errtmp = ERRNO; report_error("Connect to host %s failed: %s", cptr); if (by && IsPerson(by) && !MyClient(by)) sendto_one(by, ":%s NOTICE %s :*** Connect to host %s failed.", me.name, by->name, cptr->name); fd_close(cptr->fd); --OpenFiles; cptr->fd = -2; free_client(cptr); SET_ERRNO(errtmp); if (ERRNO == P_EINTR) SET_ERRNO(P_ETIMEDOUT); return -1; } /* ** The socket has been connected or connect is in progress. */ (void)make_server(cptr); cptr->serv->conf = aconf; cptr->serv->conf->refcount++; #ifdef DEBUGMODE sendto_realops("connect_server() CONTINUED (%s:%d), aconf %p, refcount: %d, TEMP: %s", __FILE__, __LINE__, aconf, aconf->refcount, aconf->flag.temporary ? "YES" : "NO"); #endif Debug((DEBUG_ERROR, "reference count for %s (%s) is now %d", cptr->name, cptr->serv->conf->servername, cptr->serv->conf->refcount)); if (by && IsPerson(by)) { (void)strlcpy(cptr->serv->by, by->name, sizeof cptr->serv->by); if (cptr->serv->user) free_user(cptr->serv->user, NULL); cptr->serv->user = by->user; by->user->refcnt++; } else { (void)strlcpy(cptr->serv->by, "AutoConn.", sizeof cptr->serv->by); if (cptr->serv->user) free_user(cptr->serv->user, NULL); cptr->serv->user = NULL; } cptr->serv->up = me.name; SetConnecting(cptr); SetOutgoing(cptr); IRCstats.unknown++; list_add(&cptr->lclient_node, &unknown_list); set_sockhost(cptr, aconf->outgoing.hostname); add_client_to_list(cptr); if (aconf->outgoing.options & CONNECT_SSL) { SetSSLConnectHandshake(cptr); fd_setselect(cptr->fd, FD_SELECT_WRITE, ircd_SSL_client_handshake, cptr); } else fd_setselect(cptr->fd, FD_SELECT_WRITE, completed_connection, cptr); return 0; } static struct SOCKADDR *connect_inet(ConfigItem_link *aconf, aClient *cptr, int *lenp) { static struct SOCKADDR_IN server; struct hostent *hp; char *bindip; char buf[BUFSIZE]; /* * Might as well get sockhost from here, the connection is attempted * with it so if it fails its useless. */ snprintf(buf, sizeof buf, "Outgoing connection: %s", get_client_name(cptr, TRUE)); cptr->fd = fd_socket(AFINET, SOCK_STREAM, 0, buf); if (cptr->fd < 0) { if (ERRNO == P_EMFILE) { sendto_realops("opening stream socket to server %s: No more sockets", get_client_name(cptr, TRUE)); return NULL; } report_baderror("opening stream socket to server %s:%s", cptr); return NULL; } if (++OpenFiles >= MAXCLIENTS) { sendto_realops("No more connections allowed (%s)", cptr->name); return NULL; } mysk.SIN_PORT = 0; set_sockhost(cptr, aconf->outgoing.hostname); if (!aconf->outgoing.bind_ip && iConf.link_bindip) bindip = iConf.link_bindip; else bindip = aconf->outgoing.bind_ip; if (bindip && strcmp("*", bindip)) { bzero((char *)&server, sizeof(server)); server.SIN_FAMILY = AFINET; server.SIN_PORT = 0; #ifndef INET6 server.SIN_ADDR.S_ADDR = inet_addr(bindip); #else inet_pton(AF_INET6, bindip, server.SIN_ADDR.S_ADDR); #endif if (bind(cptr->fd, (struct SOCKADDR *)&server, sizeof(server)) == -1) { report_baderror("error binding to local port for %s:%s", cptr); return NULL; } } bzero((char *)&server, sizeof(server)); server.SIN_FAMILY = AFINET; /* * By this point we should know the IP# of the host listed in the * conf line, whether as a result of the hostname lookup or the ip# * being present instead. If we dont know it, then the connect fails. */ #ifdef INET6 if (!WHOSTENTP(aconf->ipnum.S_ADDR) && !inet_pton(AF_INET6, aconf->outgoing.hostname, aconf->ipnum.s6_addr)) bcopy(minus_one, aconf->ipnum.s6_addr, IN6ADDRSZ); /* IP->struct failed: make invalid */ if (AND16(aconf->ipnum.s6_addr) == 255) #else if (isdigit(*aconf->outgoing.hostname) && (aconf->ipnum.S_ADDR == -1)) aconf->ipnum.S_ADDR = inet_addr(aconf->outgoing.hostname); if (aconf->ipnum.S_ADDR == -1) #endif { hp = cptr->local->hostp; if (!hp) { Debug((DEBUG_FATAL, "%s: unknown host", aconf->outgoing.hostname)); return NULL; } bcopy(hp->h_addr, (char *)&aconf->ipnum, sizeof(struct IN_ADDR)); } bcopy((char *)&aconf->ipnum, (char *)&server.SIN_ADDR, sizeof(struct IN_ADDR)); bcopy((char *)&aconf->ipnum, (char *)&cptr->local->ip, sizeof(struct IN_ADDR)); cptr->ip = strdup(Inet_ia2p(&cptr->local->ip)); /* can't fail.. can it? */ server.SIN_PORT = htons(((aconf->outgoing.port > 0) ? aconf->outgoing.port : portnum)); *lenp = sizeof(server); return (struct SOCKADDR *)&server; }