/* * Unreal Internet Relay Chat Daemon, src/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 */ #include "unrealircd.h" #include "dns.h" #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; int connect_inet(ConfigItem_link *, aClient *); void completed_connection(int, int, void *); void set_sock_opts(int, aClient *, int); void set_ipv6_opts(int); void close_listener(ConfigItem_listen *listener); static char readbuf[BUFSIZE]; char zlinebuf[BUFSIZE]; extern char *version; extern ircstats IRCstats; MODVAR time_t 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 } /* ** 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 listener_fd, int revents, void *data) { ConfigItem_listen *listener = data; int cli_fd; if ((cli_fd = fd_accept(listener->fd)) < 0) { if ((ERRNO != P_EWOULDBLOCK) && (ERRNO != P_ECONNABORTED)) { /* Trouble! accept() returns a strange error. * Previously in such a case we would just log/broadcast the error and return, * causing this message to be triggered at a rate of XYZ per second (100% CPU). * Now we close & re-start the listener. * Of course the underlying cause of this issue should be investigated, as this * is very much a workaround. */ report_baderror("Cannot accept connections %s:%s", NULL); sendto_realops("[BUG] Restarting listener on %s:%d due to fatal errors (see previous message)", listener->ip, listener->port); close_listener(listener); start_listeners(); } return; } ircstp->is_ac++; set_sock_opts(cli_fd, NULL, listener->ipv6); set_non_blocking(cli_fd, NULL); if ((++OpenFiles >= maxclients) || (cli_fd >= maxclients)) { ircstp->is_ref++; if (last_allinuse < TStime() - 15) { sendto_ops_and_log("All connections in use. ([@%s/%u])", listener->ip, listener->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(listener, cli_fd); } /* * inetport * * Create a socket, bind it to the 'ip' and 'port' and listen to it. * Returns the fd of the socket created or -1 on error. */ int inetport(ConfigItem_listen *listener, char *ip, int port, int ipv6) { if (BadPtr(ip)) ip = "*"; if (*ip == '*') { if (ipv6) ip = "::"; else ip = "0.0.0.0"; } /* At first, open a new socket */ if (listener->fd >= 0) abort(); /* Socket already exists but we are asked to create and listen on one. Bad! */ 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_and_log("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 (!unreal_bind(listener->fd, ip, port, ipv6)) { 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; } if (listen(listener->fd, LISTEN_SIZE) < 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) { 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) { 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)) { /* This error is already handled upstream: * 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 the listener. Note that this won't *free* the listen block, it * just makes it so no new clients are accepted (and marks the listener * as "not bound"). */ void close_listener(ConfigItem_listen *listener) { if (listener->fd >= 0) { ircd_log(LOG_ERROR, "IRCd no longer listening on %s:%d (%s)%s", listener->ip, listener->port, listener->ipv6 ? "IPv6" : "IPv4", listener->options & LISTENER_TLS ? " (SSL/TLS)" : ""); fd_close(listener->fd); --OpenFiles; } listener->options &= ~LISTENER_BOUND; listener->fd = -1; if (listener->ssl_ctx) { SSL_CTX_free(listener->ssl_ctx); listener->ssl_ctx = NULL; } } 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 = aconf->next; if (aconf->flag.temporary) close_listener(aconf); } } int maxclients = 1024 - CLIENTS_RESERVE; void check_user_limit(void) { #ifdef RLIMIT_FD_MAX struct rlimit limit; long m; if (!getrlimit(RLIMIT_FD_MAX, &limit)) { if (limit.rlim_max < MAXCONNECTIONS) m = limit.rlim_max; else m = MAXCONNECTIONS; /* Adjust soft limit (if necessary, which is often the case) */ if (m != limit.rlim_cur) { limit.rlim_cur = limit.rlim_max = m; if (setrlimit(RLIMIT_FD_MAX, &limit) == -1) { /* HACK: if it's mac os X then don't error... */ #ifndef OSXTIGER fprintf(stderr, "error setting maximum number of open files to %ld\n", (long)limit.rlim_cur); exit(-1); #endif // OSXTIGER } } /* This can only happen if it is due to resource limits (./Config already rejects <100) */ if (m < 100) { fprintf(stderr, "\nERROR: Your OS has a limit placed on this account.\n" "This machine only allows UnrealIRCd to handle a maximum of %ld open connections/files, which is VERY LOW.\n" "Please check with your system administrator to bump this limit.\n" "The recommended ulimit -n setting is at least 1024 and " "preferably 4096.\n" "Note that this error is often seen on small web shells that are not meant for running IRC servers.\n", m); exit(-1); } maxclients = m - CLIENTS_RESERVE; } #endif // RLIMIT_FD_MAX #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 should not see this error on Linux or FreeBSD\n"); fprintf(stderr, "You might need to recompile the IRCd and answer a lower value to the MAXCONNECTIONS question in ./Config\n"); exit(-1); } #endif #endif #ifdef _WIN32 maxclients = MAXCONNECTIONS - CLIENTS_RESERVE; #endif } void init_sys(void) { /* Create new session / set process group */ #if defined(HPUX) || defined(_SOLARIS) || \ defined(_POSIX_SOURCE) || defined(SVR4) || defined(SGI) || \ defined(OSXTIGER) || defined(__QNX__) (void)setsid(); #elif !defined(_WIN32) (void)setpgrp(0, (int)getpid()); #endif init_resolver(1); return; } /** Replace a file descriptor (*NIX only). * @param oldfd: the old FD to close and re-use * @param name: descriptive string of the old fd, eg: "stdin". * @param mode: an open() mode, such as O_WRONLY. */ void replacefd(int oldfd, char *name, int mode) { #ifndef _WIN32 int newfd = open("/dev/null", mode); if (newfd < 0) { fprintf(stderr, "Warning: could not open /dev/null\n"); return; } if (oldfd < 0) { fprintf(stderr, "Warning: could not replace %s (invalid fd)\n", name); return; } if (dup2(newfd, oldfd) < 0) { fprintf(stderr, "Warning: could not replace %s (dup2 error)\n", name); return; } #endif } /* Mass close standard file descriptors. * We used to really just close them here (or in init_sys() actually), * making the fd's available for other purposes such as internet sockets. * For safety we now dup2() them to /dev/null. This in case someone * accidentally does a fprintf(stderr,..) somewhere in the code or some * library outputs error messages to stderr (such as libc with heap * errors). We don't want any IRC client to receive such a thing! */ void close_std_descriptors(void) { #if !defined(_WIN32) && !defined(NOCLOSEFD) replacefd(fileno(stdin), "stdin", O_RDONLY); replacefd(fileno(stdout), "stdout", O_WRONLY); replacefd(fileno(stderr), "stderr", O_WRONLY); #endif } 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) { ircd_log(LOG_ERROR, "Error writing to pid file %s: %s", conf_files->pid_file, strerror(ERRNO)); return; } ircsnprintf(buff, sizeof(buff), "%5d\n", (int)getpid()); if (write(fd, buff, strlen(buff)) < 0) ircd_log(LOG_ERROR, "Error writing to pid file %s: %s", conf_files->pid_file, strerror(ERRNO)); if (close(fd) < 0) ircd_log(LOG_ERROR, "Error writing to pid file %s: %s", conf_files->pid_file, strerror(ERRNO)); #endif } /** 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. " "If you insist with insecure linking then you can set link::options::outgoing::insecure " "(NOT recommended!).", 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_and_log("Lost configuration for %s in start_server_handshake()", get_client_name(cptr, FALSE)); return; } RunHook(HOOKTYPE_SERVER_HANDSHAKE_OUT, cptr); sendto_one(cptr, NULL, "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, NULL, "__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_and_log("Lost configuration for %s", get_client_name(cptr, FALSE)); return; } if (!cptr->local->ssl && !(aconf->outgoing.options & CONNECT_INSECURE)) { sendto_one(cptr, NULL, "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 (IsTLS(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) { #if defined(IPV6_V6ONLY) int opt = 1; (void)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 socket 'fd' to non blocking mode. */ 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; } /** Returns 1 if using a loopback IP (127.0.0.1) or * using a local IP number on the same machine (effectively the same; * no network traffic travels outside this machine). */ int is_loopback_ip(char *ip) { ConfigItem_listen *e; 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; for (e = conf_listen; e; e = e->next) { if ((e->options & LISTENER_BOUND) && !strcmp(ip, e->ip)) 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 *listener, int fd) { aClient *acptr, *acptr2; ConfigItem_ban *bconf; aTKline *tk; int i, j; char *ip; int port = 0; acptr = make_client(NULL, &me); /* If listener is IPv6 then mark client (acptr) as IPv6 */ if (listener->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); } refuse_client: 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; acptr->fd = fd; /* Tag loopback connections as FLAGS_LOCAL */ if (is_loopback_ip(acptr->ip)) { ircstp->is_loc++; acptr->flags |= FLAGS_LOCAL; } j = 1; if (!find_tkl_exception(TKL_THROTTLE, acptr)) { list_for_each_entry(acptr2, &unknown_list, lclient_node) { if (!strcmp(acptr->ip,GetIP(acptr2))) { j++; if (j > iConf.max_unknown_connections_per_ip) { 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 refuse_client; } } } } if (check_banned(acptr, NO_EXIT_CLIENT) < 0) goto refuse_client; acptr->local->listener = listener; 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 ((listener->options & LISTENER_TLS) && ctx_server) { SSL_CTX *ctx = listener->ssl_ctx ? listener->ssl_ctx : ctx_server; if (ctx) { SetTLSAcceptHandshake(acptr); Debug((DEBUG_DEBUG, "Starting TLS accept handshake for %s", acptr->local->sockhost)); if ((acptr->local->ssl = SSL_new(ctx)) == NULL) { goto refuse_client; } acptr->flags |= FLAGS_TLS; SSL_set_fd(acptr->local->ssl, fd); SSL_set_nonblocking(acptr->local->ssl); SSL_set_ex_data(acptr->local->ssl, ssl_client_index, acptr); if (!ircd_SSL_accept(acptr, fd)) { Debug((DEBUG_DEBUG, "Failed TLS 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 refuse_client; } } } 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 (TLS handshake has ended) */ RunHook(HOOKTYPE_HANDSHAKE, acptr); if (!DONT_RESOLVE) { if (SHOWCONNECTINFO && !acptr->serv && !IsServersOnlyListener(acptr->local->listener)) sendto_one(acptr, NULL, "%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, NULL, "%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, NULL, "%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[READBUFSIZE]; 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 */ if (!IsPerson(cptr) && !IsServer(cptr) && (iConf.handshake_delay > 0) && (TStime() - cptr->local->firsttime < iConf.handshake_delay)) { return 0; /* we delay processing of data until set::handshake-delay is reached */ } 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; } int process_packet(aClient *cptr, char *readbuf, int length, int killsafely) { dbuf_put(&cptr->local->recvQ, readbuf, length); /* parse some of what we have (inducing fakelag, etc) */ if (parse_client_queued(cptr) == FLUSH_BUFFER) return 0; /* flood from unknown connection */ if (IsUnknown(cptr) && (DBufLength(&cptr->local->recvQ) > UNKNOWN_FLOOD_AMOUNT*1024)) { sendto_snomask(SNO_FLOOD, "Flood from unknown connection %s detected", cptr->local->sockhost); if (!killsafely) ban_flooder(cptr); else dead_link(cptr, "Flood from unknown connection"); return 0; } /* 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)); if (!killsafely) exit_client(cptr, cptr, cptr, NULL, "Excess Flood"); else dead_link(cptr, "Excess Flood"); return 0; } return 1; } void read_packet(int fd, int revents, void *data) { aClient *cptr = data; int length = 0; time_t now = TStime(); Hook *h; int processdata; /* Don't read from dead sockets */ if (IsDead(cptr)) { fd_setselect(fd, FD_SELECT_READ, NULL, cptr); return; } SET_ERRNO(0); fd_setselect(fd, FD_SELECT_READ, read_packet, cptr); fd_setselect(fd, FD_SELECT_WRITE, NULL, cptr); while (1) { if (IsTLS(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); length = -1; SET_ERRNO(P_EWOULDBLOCK); break; case SSL_ERROR_WANT_READ: fd_setselect(fd, FD_SELECT_READ, read_packet, cptr); fd_setselect(fd, FD_SELECT_WRITE, NULL, cptr); length = -1; SET_ERRNO(P_EWOULDBLOCK); 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)); ircd_log(LOG_ERROR, "Lost connection to %s: Read error", get_client_name(cptr, FALSE)); } exit_client(cptr, cptr, cptr, NULL, "Read error"); return; } cptr->local->lasttime = now; if (cptr->local->lasttime > cptr->local->since) cptr->local->since = cptr->local->lasttime; /* FIXME: Is this correct? I have my doubts. */ cptr->flags &= ~FLAGS_PINGSENT; ClearPingWarning(cptr); processdata = 1; for (h = Hooks[HOOKTYPE_RAWPACKET_IN]; h; h = h->next) { processdata = (*(h->func.intfunc))(cptr, readbuf, &length); if (processdata < 0) return; } if (processdata && !process_packet(cptr, readbuf, length, 0)) 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) || (cptr->status > STAT_UNKNOWN)) break; } while(&cptr->lclient_node != &unknown_list); } /* When auth is finished, go back and parse all prior input. */ void finish_auth(aClient *acptr) { } /** Returns 4 if 'str' is a valid IPv4 address * and 6 if 'str' is a valid IPv6 IP address. * Zero (0) is returned in any other case (eg: hostname). */ int is_valid_ip(char *str) { char scratch[64]; if (inet_pton(AF_INET, str, scratch) == 1) return 4; /* IPv4 */ if (inet_pton(AF_INET6, str, scratch) == 1) return 6; /* IPv6 */ return 0; /* not an IP address */ } /* * connect_server */ int connect_server(ConfigItem_link *aconf, aClient *by, struct hostent *hp) { aClient *cptr; char *s; #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" */ safefree(aconf->connect_ip); } /* * 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->connect_ip) { if (is_valid_ip(aconf->outgoing.hostname)) { /* link::outgoing::hostname is an IP address. No need to resolve host. */ aconf->connect_ip = strdup(aconf->outgoing.hostname); } else { /* It's a hostname, let the resolver look it up. */ int ipv4_explicit_bind = 0; if (aconf->outgoing.bind_ip && (is_valid_ip(aconf->outgoing.bind_ip) == 4)) ipv4_explicit_bind = 1; /* 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, ipv4_explicit_bind); return -2; } } cptr = make_client(NULL, &me); 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); if (!connect_inet(aconf, cptr)) { int errtmp = ERRNO; report_error("Connect to host %s failed: %s", cptr); if (by && IsPerson(by) && !MyClient(by)) sendnotice(by, "*** Connect to host %s failed.", 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_TLS) { SetTLSConnectHandshake(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; } int connect_inet(ConfigItem_link *aconf, aClient *cptr) { int len; struct hostent *hp; char *bindip; char buf[BUFSIZE]; int n; if (!aconf->connect_ip) return 0; /* handled upstream or shouldn't happen */ if (strchr(aconf->connect_ip, ':')) SetIPV6(cptr); cptr->ip = strdup(aconf->connect_ip); snprintf(buf, sizeof buf, "Outgoing connection: %s", get_client_name(cptr, TRUE)); cptr->fd = fd_socket(IsIPV6(cptr) ? AF_INET6 : AF_INET, 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 0; } report_baderror("opening stream socket to server %s:%s", cptr); return 0; } if (++OpenFiles >= maxclients) { sendto_ops_and_log("No more connections allowed (%s)", cptr->name); return 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)) { if (!unreal_bind(cptr->fd, bindip, 0, IsIPV6(cptr))) { report_baderror("Error binding to local port for %s:%s -- " "Your link::outgoing::bind-ip is probably incorrect.", cptr); return 0; } } set_non_blocking(cptr->fd, cptr); set_sock_opts(cptr->fd, cptr, IsIPV6(cptr)); return unreal_connect(cptr->fd, cptr->ip, aconf->outgoing.port, IsIPV6(cptr)); } /** Checks if the system is IPv6 capable. * IPv6 is always available at compile time (libs, headers), but the OS may * not have IPv6 enabled (or ipv6 kernel module not loaded). So we better check.. */ int ipv6_capable(void) { int s = socket(AF_INET6, SOCK_STREAM, 0); if (s < 0) return 0; /* NO ipv6 */ CLOSE_SOCK(s); return 1; /* YES */ } static void send_authports(int fd, int revents, void *data); static void read_authports(int fd, int revents, void *data); void ident_failed(aClient *cptr) { Debug((DEBUG_NOTICE, "ident_failed() for %p", cptr)); ircstp->is_abad++; if (cptr->local->authfd != -1) { fd_close(cptr->local->authfd); --OpenFiles; cptr->local->authfd = -1; } cptr->flags &= ~(FLAGS_WRAUTH | FLAGS_AUTH); if (SHOWCONNECTINFO && !cptr->serv && !IsServersOnlyListener(cptr->local->listener)) sendto_one(cptr, NULL, "%s", REPORT_FAIL_ID); if (!DoingDNS(cptr)) finish_auth(cptr); } /* * start_auth * * Flag the client to show that an attempt to contact the ident server on * the client's host. The connect and subsequently the socket are all put * into 'non-blocking' mode. Should the connect or any later phase of the * identifing process fail, it is aborted and the user is given a username * of "unknown". */ void start_auth(aClient *cptr) { int len; char buf[BUFSIZE]; /* If ident checking is disabled or it's an outgoing connect, then no ident check */ if ((IDENT_CHECK == 0) || (cptr->serv && IsHandshake(cptr))) { cptr->flags &= ~(FLAGS_WRAUTH | FLAGS_AUTH); if (!DoingDNS(cptr)) finish_auth(cptr); return; } Debug((DEBUG_NOTICE, "start_auth(%p) fd=%d, status=%d", cptr, cptr->fd, cptr->status)); snprintf(buf, sizeof buf, "identd: %s", get_client_name(cptr, TRUE)); if ((cptr->local->authfd = fd_socket(IsIPV6(cptr) ? AF_INET6 : AF_INET, SOCK_STREAM, 0, buf)) == -1) { Debug((DEBUG_ERROR, "Unable to create auth socket for %s:%s", get_client_name(cptr, TRUE), strerror(get_sockerr(cptr)))); ident_failed(cptr); return; } if (++OpenFiles >= maxclients+1) { sendto_ops("Can't allocate fd, too many connections."); fd_close(cptr->local->authfd); --OpenFiles; cptr->local->authfd = -1; return; } if (SHOWCONNECTINFO && !cptr->serv && !IsServersOnlyListener(cptr->local->listener)) sendto_one(cptr, NULL, "%s", REPORT_DO_ID); set_sock_opts(cptr->local->authfd, cptr, IsIPV6(cptr)); set_non_blocking(cptr->local->authfd, cptr); /* Bind to the IP the user got in */ unreal_bind(cptr->local->authfd, cptr->local->listener->ip, 0, IsIPV6(cptr)); /* And connect... */ if (!unreal_connect(cptr->local->authfd, cptr->ip, 113, IsIPV6(cptr))) { ident_failed(cptr); return; } cptr->flags |= (FLAGS_WRAUTH | FLAGS_AUTH); fd_setselect(cptr->local->authfd, FD_SELECT_WRITE, send_authports, cptr); return; } /* * send_authports * * Send the ident server a query giving "theirport , ourport". * The write is only attempted *once* so it is deemed to be a fail if the * entire write doesn't write all the data given. This shouldnt be a * problem since the socket should have a write buffer far greater than * this message to store it in should problems arise. -avalon */ static void send_authports(int fd, int revents, void *data) { char authbuf[32]; int ulen, tlen; aClient *cptr = data; Debug((DEBUG_NOTICE, "write_authports(%p) fd %d authfd %d stat %d", cptr, cptr->fd, cptr->local->authfd, cptr->status)); ircsnprintf(authbuf, sizeof(authbuf), "%d , %d\r\n", cptr->local->port, cptr->local->listener->port); Debug((DEBUG_SEND, "sending [%s] to auth port %s.113", authbuf, cptr->ip)); if (WRITE_SOCK(cptr->local->authfd, authbuf, strlen(authbuf)) != strlen(authbuf)) { if (ERRNO == P_EAGAIN) return; /* Not connected yet, try again later */ ident_failed(cptr); return; } cptr->flags &= ~FLAGS_WRAUTH; fd_setselect(cptr->local->authfd, FD_SELECT_READ|FD_SELECT_NOWRITE, read_authports, cptr); return; } /* * read_authports * * read the reply (if any) from the ident server we connected to. * The actual read processijng here is pretty weak - no handling of the reply * if it is fragmented by IP. */ static void read_authports(int fd, int revents, void *userdata) { char *s, *t; int len; char ruser[USERLEN + 1], system[8]; u_short remp = 0, locp = 0; aClient *cptr = userdata; *system = *ruser = '\0'; Debug((DEBUG_NOTICE, "read_authports(%p) fd %d authfd %d stat %d", cptr, cptr->fd, cptr->local->authfd, cptr->status)); /* * Nasty. Cant allow any other reads from client fd while we're * waiting on the authfd to return a full valid string. Use the * client's input buffer to buffer the authd reply. * Oh. this is needed because an authd reply may come back in more * than 1 read! -avalon */ if ((len = READ_SOCK(cptr->local->authfd, cptr->local->buffer + cptr->count, sizeof(cptr->local->buffer) - 1 - cptr->count)) >= 0) { cptr->count += len; cptr->local->buffer[cptr->count] = '\0'; } cptr->local->lasttime = TStime(); if ((len > 0) && (cptr->count != (sizeof(cptr->local->buffer) - 1)) && (sscanf(cptr->local->buffer, "%hd , %hd : USERID : %*[^:]: %10s", &remp, &locp, ruser) == 3)) { s = rindex(cptr->local->buffer, ':'); *s++ = '\0'; for (t = (rindex(cptr->local->buffer, ':') + 1); *t; t++) if (!isspace(*t)) break; strlcpy(system, t, sizeof(system)); for (t = ruser; *s && *s != '@' && (t < ruser + sizeof(ruser)); s++) if (!isspace(*s) && *s != ':') *t++ = *s; *t = '\0'; Debug((DEBUG_INFO, "auth reply ok [%s] [%s]", system, ruser)); } else if (len != 0) { if (!index(cptr->local->buffer, '\n') && !index(cptr->local->buffer, '\r')) return; Debug((DEBUG_ERROR, "local %d remote %d", locp, remp)); Debug((DEBUG_ERROR, "bad auth reply in [%s]", cptr->local->buffer)); *ruser = '\0'; } fd_close(cptr->local->authfd); --OpenFiles; cptr->local->authfd = -1; cptr->count = 0; ClearAuth(cptr); if (!DoingDNS(cptr)) finish_auth(cptr); if (len > 0) Debug((DEBUG_INFO, "ident reply: [%s]", cptr->local->buffer)); if (SHOWCONNECTINFO && !cptr->serv && !IsServersOnlyListener(cptr->local->listener)) sendto_one(cptr, NULL, "%s", REPORT_FIN_ID); if (!locp || !remp || !*ruser) { ircstp->is_abad++; return; } ircstp->is_asuc++; strlcpy(cptr->username, ruser, USERLEN + 1); cptr->flags |= FLAGS_GOTID; Debug((DEBUG_INFO, "got username [%s]", ruser)); return; }