/** Complete non-blocking connect()-sequence. Check access and * terminate connection, if trouble detected. * @param cptr Client to which we have connected, with all ConfItem structs attached. * @return Zero on failure (caller should exit_client()), non-zero on success. */ static int completed_connection(struct Client* cptr) { struct ConfItem *aconf; time_t newts; struct Client *acptr; int i; assert(0 != cptr); /* * get the socket status from the fd first to check if * connection actually succeeded */ if ((cli_error(cptr) = os_get_sockerr(cli_fd(cptr)))) { const char* msg = strerror(cli_error(cptr)); if (!msg) msg = "Unknown error"; sendto_opmask_butone(0, SNO_OLDSNO, "Connection failed to %s: %s", cli_name(cptr), msg); return 0; } if (!(aconf = find_conf_byname(cli_confs(cptr), cli_name(cptr), CONF_SERVER))) { sendto_opmask_butone(0, SNO_OLDSNO, "Lost Server Line for %s", cli_name(cptr)); return 0; } if (s_state(&(cli_socket(cptr))) == SS_CONNECTING) socket_state(&(cli_socket(cptr)), SS_CONNECTED); if (!EmptyString(aconf->passwd)) sendrawto_one(cptr, MSG_PASS " :%s", aconf->passwd); /* * Create a unique timestamp */ newts = TStime(); for (i = HighestFd; i > -1; --i) { if ((acptr = LocalClientArray[i]) && (IsServer(acptr) || IsHandshake(acptr))) { if (cli_serv(acptr)->timestamp >= newts) newts = cli_serv(acptr)->timestamp + 1; } } assert(0 != cli_serv(cptr)); cli_serv(cptr)->timestamp = newts; SetHandshake(cptr); /* * Make us timeout after twice the timeout for DNS look ups */ cli_lasttime(cptr) = CurrentTime; ClearPingSent(cptr); sendrawto_one(cptr, MSG_SERVER " %s 1 %Tu %Tu J%s %s%s +%s6n :%s", cli_name(&me), cli_serv(&me)->timestamp, newts, MAJOR_PROTOCOL, NumServCap(&me), feature_bool(FEAT_HUB) ? "h" : "", cli_info(&me)); return (IsDead(cptr)) ? 0 : 1; }
/** Handle a connection that has sent a valid PASS and SERVER. * @param cptr New peer server. * @param aconf Connect block for \a cptr. * @return Zero. */ int server_estab(struct Client *cptr, struct ConfItem *aconf) { struct Client* acptr = 0; const char* inpath; int i; assert(0 != cptr); assert(0 != cli_local(cptr)); inpath = cli_name(cptr); if (IsUnknown(cptr)) { if (aconf->passwd[0]) sendrawto_one(cptr, MSG_PASS " :%s", aconf->passwd); /* * Pass my info to the new server */ sendrawto_one(cptr, MSG_SERVER " %s 1 %Tu %Tu J%s %s%s +%s6 :%s", cli_name(&me), cli_serv(&me)->timestamp, cli_serv(cptr)->timestamp, MAJOR_PROTOCOL, NumServCap(&me), feature_bool(FEAT_HUB) ? "h" : "", *(cli_info(&me)) ? cli_info(&me) : "IRCers United"); } det_confs_butmask(cptr, CONF_SERVER); if (!IsHandshake(cptr)) hAddClient(cptr); SetServer(cptr); cli_handler(cptr) = SERVER_HANDLER; Count_unknownbecomesserver(UserStats); SetBurst(cptr); /* nextping = CurrentTime; */ /* * NOTE: check for acptr->user == cptr->serv->user is necessary to insure * that we got the same one... bleah */ if (cli_serv(cptr)->user && *(cli_serv(cptr))->by && (acptr = findNUser(cli_serv(cptr)->by))) { if (cli_user(acptr) == cli_serv(cptr)->user) { sendcmdto_one(&me, CMD_NOTICE, acptr, "%C :Link with %s established.", acptr, inpath); } else { /* * if not the same client, set by to empty string */ acptr = 0; *(cli_serv(cptr))->by = '\0'; } } sendto_opmask(acptr, SNO_OLDSNO, "Link with %s established.", inpath); cli_serv(cptr)->up = &me; cli_serv(cptr)->updown = add_dlink(&(cli_serv(&me))->down, cptr); sendto_opmask(0, SNO_NETWORK, "Net junction: %s %s", cli_name(&me), cli_name(cptr)); SetJunction(cptr); /* * Old sendto_serv_but_one() call removed because we now * need to send different names to different servers * (domain name matching) Send new server to other servers. */ for (i = 0; i <= HighestFd; i++) { if (!(acptr = LocalClientArray[i]) || !IsServer(acptr) || acptr == cptr || IsMe(acptr)) continue; if (!match(cli_name(&me), cli_name(cptr))) continue; sendcmdto_one(&me, CMD_SERVER, acptr, "%s 2 0 %Tu J%02u %s%s +%s%s%s :%s", cli_name(cptr), cli_serv(cptr)->timestamp, Protocol(cptr), NumServCap(cptr), IsHub(cptr) ? "h" : "", IsService(cptr) ? "s" : "", IsIPv6(cptr) ? "6" : "", cli_info(cptr)); } /* Send these as early as possible so that glined users/juped servers can * be removed from the network while the remote server is still chewing * our burst. */ gline_burst(cptr); jupe_burst(cptr); /* * Pass on my client information to the new server * * First, pass only servers (idea is that if the link gets * canceled because the server was already there, * there are no NICK's to be canceled...). Of course, * if cancellation occurs, all this info is sent anyway, * and I guess the link dies when a read is attempted...? --msa * * Note: Link cancellation to occur at this point means * that at least two servers from my fragment are building * up connection this other fragment at the same time, it's * a race condition, not the normal way of operation... */ for (acptr = &me; acptr; acptr = cli_prev(acptr)) { /* acptr->from == acptr for acptr == cptr */ if (cli_from(acptr) == cptr) continue; if (IsServer(acptr)) { const char* protocol_str; if (Protocol(acptr) > 9) protocol_str = IsBurst(acptr) ? "J" : "P"; else protocol_str = IsBurst(acptr) ? "J0" : "P0"; if (0 == match(cli_name(&me), cli_name(acptr))) continue; sendcmdto_one(cli_serv(acptr)->up, CMD_SERVER, cptr, "%s %d 0 %Tu %s%u %s%s +%s%s%s :%s", cli_name(acptr), cli_hopcount(acptr) + 1, cli_serv(acptr)->timestamp, protocol_str, Protocol(acptr), NumServCap(acptr), IsHub(acptr) ? "h" : "", IsService(acptr) ? "s" : "", IsIPv6(acptr) ? "6" : "", cli_info(acptr)); } } for (acptr = &me; acptr; acptr = cli_prev(acptr)) { /* acptr->from == acptr for acptr == cptr */ if (cli_from(acptr) == cptr) continue; if (IsUser(acptr)) { char xxx_buf[25]; char *s = umode_str(acptr); sendcmdto_one(cli_user(acptr)->server, CMD_NICK, cptr, "%s %d %Tu %s %s %s%s%s%s %s%s :%s", cli_name(acptr), cli_hopcount(acptr) + 1, cli_lastnick(acptr), cli_user(acptr)->username, cli_user(acptr)->realhost, *s ? "+" : "", s, *s ? " " : "", iptobase64(xxx_buf, &cli_ip(acptr), sizeof(xxx_buf), IsIPv6(cptr)), NumNick(acptr), cli_info(acptr)); } } /* * Last, send the BURST. * (Or for 2.9 servers: pass all channels plus statuses) */ { struct Channel *chptr; for (chptr = GlobalChannelList; chptr; chptr = chptr->next) send_channel_modes(cptr, chptr); } sendcmdto_one(&me, CMD_END_OF_BURST, cptr, ""); return 0; }
/** * Exits a client of *any* type (user, server, etc) * from this server. Also, this generates all necessary prototol * messages that this exit may cause. * * This function implicitly exits all other clients depending on * this connection. * * For convenience, this function returns a suitable value for * m_function return value: * * CPTR_KILLED if (cptr == bcptr) * 0 if (cptr != bcptr) * * This function can be called in two ways: * 1) From before or in parse(), exiting the 'cptr', in which case it was * invoked as exit_client(cptr, cptr, &me,...), causing it to always * return CPTR_KILLED. * 2) Via parse from a m_function call, in which case it was invoked as * exit_client(cptr, acptr, sptr, ...). Here 'sptr' is known; the client * that generated the message in a way that we can assume he already * did remove acptr from memory himself (or in other cases we don't mind * because he will be delinked.) Or invoked as: * exit_client(cptr, acptr/sptr, &me, ...) when WE decide this one should * be removed. * In general: No generated SQUIT or QUIT should be sent to source link * sptr->from. And CPTR_KILLED should be returned if cptr got removed (too). * * --Run * @param cptr Connection currently being handled by read_message. * @param victim Client being killed. * @param killer Client that made the decision to remove \a victim. * @param comment Reason for the exit. * @return CPTR_KILLED if cptr == bcptr, else 0. */ int exit_client(struct Client *cptr, struct Client* victim, struct Client* killer, const char* comment) { struct Client* acptr = 0; struct DLink *dlp; time_t on_for; char comment1[HOSTLEN + HOSTLEN + 2]; assert(killer); if (MyConnect(victim)) { SetFlag(victim, FLAG_CLOSING); if (feature_bool(FEAT_CONNEXIT_NOTICES) && IsUser(victim)) sendto_opmask_butone(0, SNO_CONNEXIT, "Client exiting: %s (%s@%s) [%s] [%s] <%s%s>", cli_name(victim), cli_user(victim)->username, cli_user(victim)->host, comment, ircd_ntoa(&cli_ip(victim)), NumNick(victim) /* two %s's */); update_load(); on_for = CurrentTime - cli_firsttime(victim); if (IsUser(victim) || IsUserPort(victim)) auth_send_exit(victim); if (IsUser(victim)) log_write(LS_USER, L_TRACE, 0, "%Tu %i %s@%s %s %s %s%s %s :%s", cli_firsttime(victim), on_for, cli_user(victim)->username, cli_sockhost(victim), ircd_ntoa(&cli_ip(victim)), IsAccount(victim) ? cli_username(victim) : "0", NumNick(victim), /* two %s's */ cli_name(victim), cli_info(victim)); if (victim != cli_from(killer) /* The source knows already */ && IsClient(victim)) /* Not a Ping struct or Log file */ { if (IsServer(victim) || IsHandshake(victim)) sendcmdto_one(killer, CMD_SQUIT, victim, "%s 0 :%s", cli_name(&me), comment); else if (!IsConnecting(victim)) { if (!IsDead(victim)) { if (IsServer(victim)) sendcmdto_one(killer, CMD_ERROR, victim, ":Closing Link: %s by %s (%s)", cli_name(victim), cli_name(killer), comment); else sendrawto_one(victim, MSG_ERROR " :Closing Link: %s by %s (%s)", cli_name(victim), cli_name(IsServer(killer) ? &his : killer), comment); } } if ((IsServer(victim) || IsHandshake(victim) || IsConnecting(victim)) && (killer == &me || (IsServer(killer) && (strncmp(comment, "Leaf-only link", 14) || strncmp(comment, "Non-Hub link", 12))))) { /* * Note: check user == user needed to make sure we have the same * client */ if (cli_serv(victim)->user && *(cli_serv(victim))->by && (acptr = findNUser(cli_serv(victim)->by))) { if (cli_user(acptr) == cli_serv(victim)->user) { sendcmdto_one(&me, CMD_NOTICE, acptr, "%C :Link with %s canceled: %s", acptr, cli_name(victim), comment); } else { /* * not right client, set by to empty string */ acptr = 0; *(cli_serv(victim))->by = '\0'; } } if (killer == &me) sendto_opmask_butone(acptr, SNO_OLDSNO, "Link with %s canceled: %s", cli_name(victim), comment); } } /* * Close the Client connection first. */ close_connection(victim); } if (IsServer(victim)) { if (feature_bool(FEAT_HIS_NETSPLIT)) strcpy(comment1, "*.net *.split"); else { strcpy(comment1, cli_name(cli_serv(victim)->up)); strcat(comment1, " "); strcat(comment1, cli_name(victim)); } if (IsUser(killer)) sendto_opmask_butone(killer, SNO_OLDSNO, "%s SQUIT by %s [%s]:", (cli_user(killer)->server == victim || cli_user(killer)->server == cli_serv(victim)->up) ? "Local" : "Remote", get_client_name(killer, HIDE_IP), cli_name(cli_user(killer)->server)); else if (killer != &me && cli_serv(victim)->up != killer) sendto_opmask_butone(0, SNO_OLDSNO, "Received SQUIT %s from %s :", cli_name(victim), IsServer(killer) ? cli_name(killer) : get_client_name(killer, HIDE_IP)); sendto_opmask_butone(0, SNO_NETWORK, "Net break: %C %C (%s)", cli_serv(victim)->up, victim, comment); } /* * First generate the needed protocol for the other server links * except the source: */ for (dlp = cli_serv(&me)->down; dlp; dlp = dlp->next) { if (dlp->value.cptr != cli_from(killer) && dlp->value.cptr != victim) { if (IsServer(victim)) sendcmdto_one(killer, CMD_SQUIT, dlp->value.cptr, "%s %Tu :%s", cli_name(victim), cli_serv(victim)->timestamp, comment); else if (IsUser(victim) && !HasFlag(victim, FLAG_KILLED)) sendcmdto_one(victim, CMD_QUIT, dlp->value.cptr, ":%s", comment); } } /* Then remove the client structures */ if (IsServer(victim)) exit_downlinks(victim, killer, comment1); exit_one_client(victim, comment); /* * cptr can only have been killed if it was cptr itself that got killed here, * because cptr can never have been a dependent of victim --Run */ return (cptr == victim) ? CPTR_KILLED : 0; }
/** Check for clients that have not sent a ping response recently. * Reschedules itself to run again at the appropriate time. * @param[in] ev Timer event (ignored). */ static void check_pings(struct Event* ev) { int expire = 0; int next_check = CurrentTime; int max_ping = 0; int i; assert(ET_EXPIRE == ev_type(ev)); assert(0 != ev_timer(ev)); next_check += feature_int(FEAT_PINGFREQUENCY); /* Scan through the client table */ for (i=0; i <= HighestFd; i++) { struct Client *cptr = LocalClientArray[i]; if (!cptr) continue; assert(&me != cptr); /* I should never be in the local client array! */ /* Remove dead clients. */ if (IsDead(cptr)) { exit_client(cptr, cptr, &me, cli_info(cptr)); continue; } max_ping = IsRegistered(cptr) ? client_get_ping(cptr) : feature_int(FEAT_CONNECTTIMEOUT); Debug((DEBUG_DEBUG, "check_pings(%s)=status:%s limit: %d current: %d", cli_name(cptr), IsPingSent(cptr) ? "[Ping Sent]" : "[]", max_ping, (int)(CurrentTime - cli_lasttime(cptr)))); /* If it's a server and we have not sent an AsLL lately, do so. */ if (IsServer(cptr)) { if (CurrentTime - cli_serv(cptr)->asll_last >= max_ping) { char *asll_ts; SetPingSent(cptr); cli_serv(cptr)->asll_last = CurrentTime; expire = cli_serv(cptr)->asll_last + max_ping; asll_ts = militime_float(NULL); sendcmdto_prio_one(&me, CMD_PING, cptr, "!%s %s %s", asll_ts, cli_name(cptr), asll_ts); } expire = cli_serv(cptr)->asll_last + max_ping; if (expire < next_check) next_check = expire; } /* Ok, the thing that will happen most frequently, is that someone will * have sent something recently. Cover this first for speed. * -- * If it's an unregistered client and hasn't managed to register within * max_ping then it's obviously having problems (broken client) or it's * just up to no good, so we won't skip it, even if its been sending * data to us. * -- hikari */ if ((CurrentTime-cli_lasttime(cptr) < max_ping) && IsRegistered(cptr)) { expire = cli_lasttime(cptr) + max_ping; if (expire < next_check) next_check = expire; continue; } /* Unregistered clients pingout after max_ping seconds, they don't * get given a second chance - if they were then people could not quite * finish registration and hold resources without being subject to k/g * lines */ if (!IsRegistered(cptr)) { assert(!IsServer(cptr)); /* If client authorization time has expired, ask auth whether they * should be checked again later. */ if ((CurrentTime-cli_firsttime(cptr) >= max_ping) && auth_ping_timeout(cptr)) continue; /* OK, they still have enough time left, so we'll just skip to the * next client. Set the next check to be when their time is up, if * that's before the currently scheduled next check -- hikari */ expire = cli_firsttime(cptr) + max_ping; if (expire < next_check) next_check = expire; continue; } /* Quit the client after max_ping*2 - they should have answered by now */ if (CurrentTime-cli_lasttime(cptr) >= (max_ping*2) ) { /* If it was a server, then tell ops about it. */ if (IsServer(cptr) || IsConnecting(cptr) || IsHandshake(cptr)) sendto_opmask_butone(0, SNO_OLDSNO, "No response from %s, closing link", cli_name(cptr)); exit_client_msg(cptr, cptr, &me, "Ping timeout"); continue; } if (!IsPingSent(cptr)) { /* If we haven't PINGed the connection and we haven't heard from it in a * while, PING it to make sure it is still alive. */ SetPingSent(cptr); /* If we're late in noticing don't hold it against them :) */ cli_lasttime(cptr) = CurrentTime - max_ping; if (IsUser(cptr)) sendrawto_one(cptr, MSG_PING " :%s", cli_name(&me)); else sendcmdto_prio_one(&me, CMD_PING, cptr, ":%s", cli_name(&me)); } expire = cli_lasttime(cptr) + max_ping * 2; if (expire < next_check) next_check=expire; } assert(next_check >= CurrentTime); Debug((DEBUG_DEBUG, "[%i] check_pings() again in %is", CurrentTime, next_check-CurrentTime)); timer_add(&ping_timer, check_pings, 0, TT_ABSOLUTE, next_check); }
/** Attempt to send a sequence of bytes to the connection. * As a side effect, updates \a cptr's FLAG_BLOCKED setting * and sendB/sendK fields. * @param cptr Client that should receive data. * @param buf Message buffer to send to client. * @return Negative on connection-fatal error; otherwise * number of bytes sent. */ unsigned int deliver_it(struct Client *cptr, struct MsgQ *buf) { unsigned int bytes_written = 0; unsigned int bytes_count = 0; assert(0 != cptr); #if defined(USE_SSL) switch (client_sendv(cptr, buf, &bytes_count, &bytes_written)) { #else switch (os_sendv_nonb(cli_fd(cptr), buf, &bytes_count, &bytes_written)) { #endif case IO_SUCCESS: ClrFlag(cptr, FLAG_BLOCKED); cli_sendB(cptr) += bytes_written; cli_sendB(&me) += bytes_written; /* A partial write implies that future writes will block. */ if (bytes_written < bytes_count) SetFlag(cptr, FLAG_BLOCKED); break; case IO_BLOCKED: SetFlag(cptr, FLAG_BLOCKED); break; case IO_FAILURE: cli_error(cptr) = errno; SetFlag(cptr, FLAG_DEADSOCKET); break; } return bytes_written; } /** Complete non-blocking connect()-sequence. Check access and * terminate connection, if trouble detected. * @param cptr Client to which we have connected, with all ConfItem structs attached. * @return Zero on failure (caller should exit_client()), non-zero on success. */ static int completed_connection(struct Client* cptr) { struct ConfItem *aconf; time_t newts; struct Client *acptr; int i; #if defined(USE_SSL) char *sslfp; int r; #endif assert(0 != cptr); /* * get the socket status from the fd first to check if * connection actually succeeded */ if ((cli_error(cptr) = os_get_sockerr(cli_fd(cptr)))) { const char* msg = strerror(cli_error(cptr)); if (!msg) msg = "Unknown error"; sendto_opmask(0, SNO_OLDSNO, "Connection failed to %s: %s", cli_name(cptr), msg); return 0; } if (!(aconf = find_conf_byname(cli_confs(cptr), cli_name(cptr), CONF_SERVER))) { sendto_opmask(0, SNO_OLDSNO, "Lost Server Line for %s", cli_name(cptr)); return 0; } #if defined(USE_SSL) if (aconf->flags & CONF_SSL) { r = ssl_connect(&(cli_socket(cptr))); if (r == -1) { sendto_opmask(0, SNO_OLDSNO, "Connection failed to %s: SSL error", cli_name(cptr)); return 0; } else if (r == 0) return 1; sslfp = ssl_get_fingerprint(cli_socket(cptr).s_ssl); if (sslfp) ircd_strncpy(cli_sslclifp(cptr), sslfp, BUFSIZE+1); SetSSL(cptr); } #endif if (s_state(&(cli_socket(cptr))) == SS_CONNECTING) socket_state(&(cli_socket(cptr)), SS_CONNECTED); if (!EmptyString(aconf->passwd)) sendrawto_one(cptr, MSG_PASS " :%s", aconf->passwd); /* * Create a unique timestamp */ newts = TStime(); for (i = HighestFd; i > -1; --i) { if ((acptr = LocalClientArray[i]) && (IsServer(acptr) || IsHandshake(acptr))) { if (cli_serv(acptr)->timestamp >= newts) newts = cli_serv(acptr)->timestamp + 1; } } assert(0 != cli_serv(cptr)); cli_serv(cptr)->timestamp = newts; SetHandshake(cptr); /* * Make us timeout after twice the timeout for DNS look ups */ cli_lasttime(cptr) = CurrentTime; ClearPingSent(cptr); /* TODO: NEGOCIACION envia_config_req(cptr); */ sendrawto_one(cptr, MSG_SERVER " %s 1 %Tu %Tu J%s %s%s +%s6 :%s", cli_name(&me), cli_serv(&me)->timestamp, newts, MAJOR_PROTOCOL, NumServCap(&me), feature_bool(FEAT_HUB) ? "h" : "", cli_info(&me)); #if defined(DDB) ddb_burst(cptr); #endif return (IsDead(cptr)) ? 0 : 1; } /** Close the physical connection. Side effects: MyConnect(cptr) * becomes false and cptr->from becomes NULL. * @param cptr Client to disconnect. */ void close_connection(struct Client *cptr) { struct ConfItem* aconf; if (IsServer(cptr)) { ServerStats->is_sv++; ServerStats->is_sbs += cli_sendB(cptr); ServerStats->is_sbr += cli_receiveB(cptr); ServerStats->is_sti += CurrentTime - cli_firsttime(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(cli_name(cptr), cptr, CONF_SERVER))) { /* * Reschedule a faster reconnect, if this was a automatically * connected configuration entry. (Note that if we have had * a rehash in between, the status has been changed to * CONF_ILLEGAL). But only do this if it was a "good" link. */ aconf->hold = CurrentTime; aconf->hold += ((aconf->hold - cli_since(cptr) > feature_int(FEAT_HANGONGOODLINK)) ? feature_int(FEAT_HANGONRETRYDELAY) : ConfConFreq(aconf)); /* if (nextconnect > aconf->hold) */ /* nextconnect = aconf->hold; */ } } else if (IsUser(cptr)) { ServerStats->is_cl++; ServerStats->is_cbs += cli_sendB(cptr); ServerStats->is_cbr += cli_receiveB(cptr); ServerStats->is_cti += CurrentTime - cli_firsttime(cptr); } else ServerStats->is_ni++; #if defined(USE_ZLIB) /* * Siempre es una conexion nuestra */ if (cli_connect(cptr)->zlib_negociation & ZLIB_IN) { inflateEnd(cli_connect(cptr)->comp_in); MyFree(cli_connect(cptr)->comp_in); } if (cli_connect(cptr)->zlib_negociation & ZLIB_OUT) { deflateEnd(cli_connect(cptr)->comp_out); MyFree(cli_connect(cptr)->comp_out); } #endif if (-1 < cli_fd(cptr)) { flush_connections(cptr); LocalClientArray[cli_fd(cptr)] = 0; close(cli_fd(cptr)); socket_del(&(cli_socket(cptr))); /* queue a socket delete */ cli_fd(cptr) = -1; cli_freeflag(cptr) &= ~FREEFLAG_SOCKET; } SetFlag(cptr, FLAG_DEADSOCKET); MsgQClear(&(cli_sendQ(cptr))); client_drop_sendq(cli_connect(cptr)); DBufClear(&(cli_recvQ(cptr))); memset(cli_passwd(cptr), 0, sizeof(cli_passwd(cptr))); set_snomask(cptr, 0, SNO_SET); det_confs_butmask(cptr, 0); if (cli_listener(cptr)) { release_listener(cli_listener(cptr)); cli_listener(cptr) = 0; } for ( ; HighestFd > 0; --HighestFd) { if (LocalClientArray[HighestFd]) break; } } /** Close all unregistered connections. * @param source Oper who requested the close. * @return Number of closed connections. */ int net_close_unregistered_connections(struct Client* source) { int i; struct Client* cptr; int count = 0; assert(0 != source); for (i = HighestFd; i > 0; --i) { if ((cptr = LocalClientArray[i]) && !IsRegistered(cptr)) { send_reply(source, RPL_CLOSING, get_client_name(source, HIDE_IP)); exit_client(source, cptr, &me, "Oper Closing"); ++count; } } return count; }