/** Called when resolver query finishes. If the DNS lookup was * successful, start the connection; otherwise notify opers of the * failure. * @param[in] vptr The struct ConfItem representing the Connect block. * @param[in] addr The resolved IP address (NULL on failure). * @param[in] h_name The name being looked up. */ static void connect_dns_callback(void* vptr, const struct irc_in_addr *addr, const char *h_name) { struct ConfItem* aconf = (struct ConfItem*) vptr; assert(aconf); aconf->dns_pending = 0; if (addr) { memcpy(&aconf->address, addr, sizeof(aconf->address)); connect_server(aconf, 0); } else sendto_opmask(0, SNO_OLDSNO, "Connect to %s failed: host lookup", aconf->name); }
/** Try to send a buffer to a client, queueing it if needed. * @param[in,out] to Client to send message to. * @param[in] buf Message to send. * @param[in] prio If non-zero, send as high priority. */ void send_buffer(struct Client* to, struct MsgBuf* buf, int prio) { assert(0 != to); assert(0 != buf); if (cli_from(to)) to = cli_from(to); if (!can_send(to)) /* * This socket has already been marked as dead */ return; if (MsgQLength(&(cli_sendQ(to))) > get_sendq(to)) { if (IsServer(to)) sendto_opmask(0, SNO_OLDSNO, "Max SendQ limit exceeded for %C: %zu > %zu", to, MsgQLength(&(cli_sendQ(to))), get_sendq(to)); dead_link(to, "Max sendQ exceeded"); return; } Debug((DEBUG_SEND, "Sending [%p] to %s", buf, cli_name(to))); #if defined(USE_SSL) if (cli_socket(to).s_ssl) prio = 0; #endif msgq_add(&(cli_sendQ(to)), buf, prio); client_add_sendq(cli_connect(to), &send_queues); update_write(to); /* * Update statistics. The following is slightly incorrect * because it counts messages even if queued, but bytes * only really sent. Queued bytes get updated in SendQueued. */ ++(cli_sendM(to)); ++(cli_sendM(&me)); /* * This little bit is to stop the sendQ from growing too large when * there is no need for it to. Thus we call send_queued() every time * 2k has been added to the queue since the last non-fatal write. * Also stops us from deliberately building a large sendQ and then * trying to flood that link with data (possible during the net * relinking done by servers with a large load). */ if (MsgQLength(&(cli_sendQ(to))) / 1024 > cli_lastsq(to)) send_queued(to); }
/** Update server start timestamps and TS offsets. * @param[in] cptr Server that just connected. * @param[in] timestamp Current time according to \a cptr. * @param[in] start_timestamp Time that \a cptr started. * @param[in] recv_time Current time as we know it. */ static void check_start_timestamp(struct Client *cptr, time_t timestamp, time_t start_timestamp, time_t recv_time) { Debug((DEBUG_DEBUG, "My start time: %Tu; other's start time: %Tu", cli_serv(&me)->timestamp, start_timestamp)); Debug((DEBUG_DEBUG, "Receive time: %Tu; received timestamp: %Tu; " "difference %ld", recv_time, timestamp, timestamp - recv_time)); if (feature_bool(FEAT_RELIABLE_CLOCK)) { if (start_timestamp < cli_serv(&me)->timestamp) cli_serv(&me)->timestamp = start_timestamp; if (IsUnknown(cptr)) cli_serv(cptr)->timestamp = TStime(); } else if (start_timestamp < cli_serv(&me)->timestamp) { sendto_opmask(0, SNO_OLDSNO, "got earlier start time: %Tu < %Tu", start_timestamp, cli_serv(&me)->timestamp); cli_serv(&me)->timestamp = start_timestamp; TSoffset += timestamp - recv_time; sendto_opmask(0, SNO_OLDSNO, "clock adjusted by adding %d", (int)(timestamp - recv_time)); } else if ((start_timestamp > cli_serv(&me)->timestamp) && IsUnknown(cptr)) { cli_serv(cptr)->timestamp = TStime(); } else if (timestamp != recv_time) { /* * Equal start times, we have a collision. Let the connected-to * server decide. This assumes leafs issue more than half of the * connection attempts. */ if (IsUnknown(cptr)) cli_serv(cptr)->timestamp = TStime(); else if (IsHandshake(cptr)) { sendto_opmask(0, SNO_OLDSNO, "clock adjusted by adding %d", (int)(timestamp - recv_time)); TSoffset += timestamp - recv_time; } } }
/** Mark a client as dead, even if they are not the current message source. * This is done by setting the DEADSOCKET flag on the user and letting the * main loop perform the actual exit logic. * @param[in,out] to Client being killed. * @param[in] notice Message for local opers. */ static void dead_link(struct Client *to, char *notice) { SetFlag(to, FLAG_DEADSOCKET); /* * If because of BUFFERPOOL problem then clean dbuf's now so that * notices don't hurt operators below. */ DBufClear(&(cli_recvQ(to))); MsgQClear(&(cli_sendQ(to))); client_drop_sendq(cli_connect(to)); /* * Keep a copy of the last comment, for later use... */ ircd_strncpy(cli_info(to), notice, REALLEN); if (!IsUser(to) && !IsUnknown(to) && !HasFlag(to, FLAG_CLOSING)) sendto_opmask(0, SNO_OLDSNO, "%s for %s", cli_info(to), cli_name(to)); Debug((DEBUG_ERROR, cli_info(to))); }
/** Handle a SERVER message from another server. * * \a parv has the following elements: * \li \a parv[1] is the server name * \li \a parv[2] is the hop count to the server * \li \a parv[3] is the start timestamp for the server * \li \a parv[4] is the link timestamp * \li \a parv[5] is the protocol version (P10 or J10) * \li \a parv[6] is the numnick mask for the server * \li \a parv[7] is a string of flags like +hs to mark hubs and services * \li \a parv[\a parc - 1] is the server description * * See @ref m_functions for discussion of the arguments. * @param[in] cptr Client that sent us the message. * @param[in] sptr Original source of message. * @param[in] parc Number of arguments. * @param[in] parv Argument vector. */ int ms_server(struct Client* cptr, struct Client* sptr, int parc, char* parv[]) { int i; char* host; struct Client* acptr; struct Client* bcptr; int hop; int ret; unsigned short prot; time_t start_timestamp; time_t timestamp; if (parc < 8) { return need_more_params(sptr, "SERVER"); return exit_client(cptr, cptr, &me, "Need more parameters"); } host = clean_servername(parv[1]); if (!host) { sendto_opmask(0, SNO_OLDSNO, "Bogus server name (%s) from %s", host, cli_name(cptr)); return exit_client_msg(cptr, cptr, &me, "Bogus server name (%s)", host); } /* * Detect protocol */ hop = atoi(parv[2]); start_timestamp = atoi(parv[3]); timestamp = atoi(parv[4]); prot = parse_protocol(parv[5]); if (!prot) return exit_client_msg(cptr, sptr, &me, "Bogus protocol (%s)", parv[5]); else if (prot < atoi(MINOR_PROTOCOL)) return exit_new_server(cptr, sptr, host, timestamp, "Incompatible protocol: %s", parv[5]); Debug((DEBUG_INFO, "Got SERVER %s with timestamp [%s] age %Tu (%Tu)", host, parv[4], start_timestamp, cli_serv(&me)->timestamp)); if (timestamp < OLDEST_TS) return exit_client_msg(cptr, sptr, &me, "Bogus timestamps (%s %s)", parv[3], parv[4]); if (parv[parc - 1][0] == '\0') return exit_client_msg(cptr, cptr, &me, "No server info specified for %s", host); ret = check_loop_and_lh(cptr, sptr, NULL, host, (parc > 7 ? parv[6] : NULL), timestamp, hop, parv[5][0] == 'J'); if (ret != 1) return ret; /* * Server is informing about a new server behind * this link. Create REMOTE server structure, * add it to list and propagate word to my other * server links... */ acptr = make_client(cptr, STAT_SERVER); make_server(acptr); cli_serv(acptr)->prot = prot; cli_serv(acptr)->timestamp = timestamp; cli_hopcount(acptr) = hop; ircd_strncpy(cli_name(acptr), host, HOSTLEN); ircd_strncpy(cli_info(acptr), parv[parc-1], REALLEN); cli_serv(acptr)->up = sptr; cli_serv(acptr)->updown = add_dlink(&(cli_serv(sptr))->down, acptr); /* Use cptr, because we do protocol 9 -> 10 translation for numeric nicks ! */ SetServerYXX(cptr, acptr, parv[6]); update_uworld_flags(cptr); if (*parv[7] == '+') set_server_flags(acptr, parv[7] + 1); Count_newremoteserver(UserStats); add_client_to_list(acptr); hAddClient(acptr); if (*parv[5] == 'J') { SetBurst(acptr); SetJunction(acptr); for (bcptr = cli_serv(acptr)->up; !IsMe(bcptr); bcptr = cli_serv(bcptr)->up) if (IsBurstOrBurstAck(bcptr)) break; if (IsMe(bcptr)) sendto_opmask(0, SNO_NETWORK, "Net junction: %s %s", cli_name(sptr), cli_name(acptr)); } /* * Old sendto_serv_but_one() call removed because we now need to send * different names to different servers (domain name matching). */ for (i = 0; i <= HighestFd; i++) { if (!(bcptr = LocalClientArray[i]) || !IsServer(bcptr) || bcptr == cptr || IsMe(bcptr)) continue; if (0 == match(cli_name(&me), cli_name(acptr))) continue; sendcmdto_one(sptr, CMD_SERVER, bcptr, "%s %d 0 %s %s %s%s +%s%s%s :%s", cli_name(acptr), hop + 1, parv[4], parv[5], NumServCap(acptr), IsHub(acptr) ? "h" : "", IsService(acptr) ? "s" : "", IsIPv6(acptr) ? "6" : "", cli_info(acptr)); } return 0; }
/** Handle a SERVER message from an unregistered connection. * * \a parv has the following elements: * \li \a parv[1] is the server name * \li \a parv[2] is the hop count to the server * \li \a parv[3] is the start timestamp for the server * \li \a parv[4] is the link timestamp * \li \a parv[5] is the protocol version (P10 or J10) * \li \a parv[6] is the numnick mask for the server * \li \a parv[7] is a string of flags like +hs to mark hubs and services * \li \a parv[\a parc - 1] is the server description * * See @ref m_functions for discussion of the arguments. * @param[in] cptr Client that sent us the message. * @param[in] sptr Original source of message. * @param[in] parc Number of arguments. * @param[in] parv Argument vector. */ int mr_server(struct Client* cptr, struct Client* sptr, int parc, char* parv[]) { char* host; struct ConfItem* aconf; struct Jupe* ajupe; unsigned int hop; int ret; unsigned short prot; time_t start_timestamp; time_t timestamp; time_t recv_time; time_t ghost; if (IsUserPort(cptr)) return exit_client_msg(cptr, cptr, &me, "Cannot connect a server to a user port"); if (parc < 8) { need_more_params(sptr, "SERVER"); return exit_client(cptr, cptr, &me, "Need more parameters"); } host = clean_servername(parv[1]); if (!host) { sendto_opmask(0, SNO_OLDSNO, "Bogus server name (%s) from %s", host, cli_name(cptr)); return exit_client_msg(cptr, cptr, &me, "Bogus server name (%s)", host); } if ((ajupe = jupe_find(host)) && JupeIsActive(ajupe)) return exit_client_msg(cptr, sptr, &me, "Juped: %s", JupeReason(ajupe)); /* check connection rules */ if (0 != conf_eval_crule(host, CRULE_ALL)) { ServerStats->is_ref++; sendto_opmask(0, SNO_OLDSNO, "Refused connection from %s.", cli_name(cptr)); return exit_client(cptr, cptr, &me, "Disallowed by connection rule"); } log_write(LS_NETWORK, L_NOTICE, LOG_NOSNOTICE, "SERVER: %s %s[%s]", host, cli_sockhost(cptr), cli_sock_ip(cptr)); /* * Detect protocol */ hop = atoi(parv[2]); start_timestamp = atoi(parv[3]); timestamp = atoi(parv[4]); prot = parse_protocol(parv[5]); if (!prot) return exit_client_msg(cptr, sptr, &me, "Bogus protocol (%s)", parv[5]); else if (prot < atoi(MINOR_PROTOCOL)) return exit_new_server(cptr, sptr, host, timestamp, "Incompatible protocol: %s", parv[5]); Debug((DEBUG_INFO, "Got SERVER %s with timestamp [%s] age %Tu (%Tu)", host, parv[4], start_timestamp, cli_serv(&me)->timestamp)); if (timestamp < OLDEST_TS || start_timestamp < OLDEST_TS) return exit_client_msg(cptr, sptr, &me, "Bogus timestamps (%s %s)", parv[3], parv[4]); /* If the server had a different name before, change it. */ if (!EmptyString(cli_name(cptr)) && (IsUnknown(cptr) || IsHandshake(cptr)) && 0 != ircd_strcmp(cli_name(cptr), host)) hChangeClient(cptr, host); ircd_strncpy(cli_name(cptr), host, HOSTLEN); ircd_strncpy(cli_info(cptr), parv[parc-1][0] ? parv[parc-1] : cli_name(&me), REALLEN); cli_hopcount(cptr) = hop; if (conf_check_server(cptr)) { ++ServerStats->is_ref; sendto_opmask(0, SNO_OLDSNO, "Received unauthorized connection from %s", cli_name(cptr)); log_write(LS_NETWORK, L_NOTICE, LOG_NOSNOTICE, "Received unauthorized " "connection from %C [%s]", cptr, ircd_ntoa(&cli_ip(cptr))); return exit_client(cptr, cptr, &me, "No Connect block"); } host = cli_name(cptr); update_load(); if (!(aconf = find_conf_byname(cli_confs(cptr), host, CONF_SERVER))) { ++ServerStats->is_ref; sendto_opmask(0, SNO_OLDSNO, "Access denied. No conf line for server %s", cli_name(cptr)); return exit_client_msg(cptr, cptr, &me, "Access denied. No conf line for server %s", cli_name(cptr)); } #if defined(USE_SSL) if (!verify_sslclifp(cptr, aconf)) { ++ServerStats->is_ref; sendto_opmask(0, SNO_OLDSNO, "Access denied (SSL fingerprint mismatch) %s", cli_name(cptr)); return exit_client_msg(cptr, cptr, &me, "No Access (SSL fingerprint mismatch) %s", cli_name(cptr)); } #endif if (*aconf->passwd && !!strcmp(aconf->passwd, cli_passwd(cptr))) { ++ServerStats->is_ref; sendto_opmask(0, SNO_OLDSNO, "Access denied (passwd mismatch) %s", cli_name(cptr)); return exit_client_msg(cptr, cptr, &me, "No Access (passwd mismatch) %s", cli_name(cptr)); } memset(cli_passwd(cptr), 0, sizeof(cli_passwd(cptr))); ret = check_loop_and_lh(cptr, sptr, &ghost, host, (parc > 7 ? parv[6] : NULL), timestamp, hop, 1); if (ret != 1) return ret; make_server(cptr); cli_serv(cptr)->timestamp = timestamp; cli_serv(cptr)->prot = prot; cli_serv(cptr)->ghost = ghost; memset(cli_privs(cptr), 255, sizeof(struct Privs)); ClrPriv(cptr, PRIV_SET); SetServerYXX(cptr, cptr, parv[6]); update_uworld_flags(cptr); if (*parv[7] == '+') set_server_flags(cptr, parv[7] + 1); recv_time = TStime(); check_start_timestamp(cptr, timestamp, start_timestamp, recv_time); ret = server_estab(cptr, aconf); if (feature_bool(FEAT_RELIABLE_CLOCK) && abs(cli_serv(cptr)->timestamp - recv_time) > 30) { sendto_opmask(0, SNO_OLDSNO, "Connected to a net with a " "timestamp-clock difference of %Td seconds! " "Used SETTIME to correct this.", timestamp - recv_time); sendcmdto_prio_one(&me, CMD_SETTIME, cptr, "%Tu :%s", TStime(), cli_name(&me)); } return ret; }
/** 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; }
/** 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 * @param cptr Client from which to read data. * @param socket_ready If non-zero, more data can be read from the client's socket. * @return Positive number on success, zero on connection-fatal failure, negative * if user is killed. */ static int read_packet(struct Client *cptr, int socket_ready) { unsigned int dolen = 0; unsigned int length = 0; if (socket_ready && !(IsUser(cptr) && DBufLength(&(cli_recvQ(cptr))) > feature_uint(FEAT_CLIENT_FLOOD))) { #if defined(USE_SSL) switch (client_recv(cptr, readbuf, sizeof(readbuf), &length)) { #else switch (os_recv_nonb(cli_fd(cptr), readbuf, sizeof(readbuf), &length)) { #endif case IO_SUCCESS: if (length) { cli_lasttime(cptr) = CurrentTime; ClearPingSent(cptr); ClrFlag(cptr, FLAG_NONL); if (cli_lasttime(cptr) > cli_since(cptr)) cli_since(cptr) = cli_lasttime(cptr); } break; case IO_BLOCKED: break; case IO_FAILURE: cli_error(cptr) = errno; /* SetFlag(cptr, FLAG_DEADSOCKET); */ return 0; } } /* * For server connections, we process as many as we can without * worrying about the time of day or anything :) */ if (length > 0 && IsServer(cptr)) return server_dopacket(cptr, readbuf, length); else if (length > 0 && (IsHandshake(cptr) || IsConnecting(cptr))) return connect_dopacket(cptr, readbuf, length); else { /* * Before we even think of parsing what we just read, stick * it on the end of the receive queue and do it when its * turn comes around. */ if (length > 0 && dbuf_put(cptr, &(cli_recvQ(cptr)), readbuf, length) == 0) return exit_client(cptr, cptr, &me, "dbuf_put fail"); if ((DBufLength(&(cli_recvQ(cptr))) > feature_uint(FEAT_CLIENT_FLOOD)) && !IsChannelService(cptr)) return exit_client(cptr, cptr, &me, "Excess Flood"); while (DBufLength(&(cli_recvQ(cptr))) && !NoNewLine(cptr) && (IsTrusted(cptr) || IsChannelService(cptr) || cli_since(cptr) - CurrentTime < 10)) { dolen = dbuf_getmsg(&(cli_recvQ(cptr)), cli_buffer(cptr), BUFSIZE); /* * Devious looking...whats it do ? well..if a client * sends a *long* message without any CR or LF, then * dbuf_getmsg fails and we pull it out using this * loop which just gets the next 512 bytes and then * deletes the rest of the buffer contents. * -avalon */ if (dolen == 0) { if (DBufLength(&(cli_recvQ(cptr))) < 510) SetFlag(cptr, FLAG_NONL); else { /* More than 512 bytes in the line - drop the input and yell * at the client. */ DBufClear(&(cli_recvQ(cptr))); send_reply(cptr, ERR_INPUTTOOLONG); } } else if (client_dopacket(cptr, dolen) == CPTR_KILLED) return CPTR_KILLED; /* * If it has become registered as a Server * then skip the per-message parsing below. */ if (IsHandshake(cptr) || IsServer(cptr)) { while (-1) { dolen = dbuf_get(&(cli_recvQ(cptr)), readbuf, sizeof(readbuf)); if (dolen <= 0) return 1; else if (dolen == 0) { if (DBufLength(&(cli_recvQ(cptr))) < 510) SetFlag(cptr, FLAG_NONL); else { DBufClear(&(cli_recvQ(cptr))); /* send_reply(cptr, ERR_INPUTTOOLONG); */ } } else if ((IsServer(cptr) && server_dopacket(cptr, readbuf, dolen) == CPTR_KILLED) || (!IsServer(cptr) && connect_dopacket(cptr, readbuf, dolen) == CPTR_KILLED)) return CPTR_KILLED; } } } /* If there's still data to process, wait 2 seconds first */ if (DBufLength(&(cli_recvQ(cptr))) && !NoNewLine(cptr) && !t_onqueue(&(cli_proc(cptr)))) { Debug((DEBUG_LIST, "Adding client process timer for %C", cptr)); cli_freeflag(cptr) |= FREEFLAG_TIMER; timer_add(&(cli_proc(cptr)), client_timer_callback, cli_connect(cptr), TT_RELATIVE, 2); } } return 1; } /** Start a connection to another server. * @param aconf Connect block data for target server. * @param by Client who requested the connection (if any). * @return Non-zero on success; zero on failure. */ int connect_server(struct ConfItem* aconf, struct Client* by) { struct Client* cptr = 0; assert(0 != aconf); if (aconf->dns_pending) { sendto_opmask(0, SNO_OLDSNO, "Server %s connect DNS pending", aconf->name); return 0; } Debug((DEBUG_NOTICE, "Connect to %s[@%s]", aconf->name, ircd_ntoa(&aconf->address.addr))); if ((cptr = FindClient(aconf->name))) { if (IsServer(cptr) || IsMe(cptr)) { sendto_opmask(0, SNO_OLDSNO, "Server %s already present from %s", aconf->name, cli_name(cli_from(cptr))); if (by && IsUser(by) && !MyUser(by)) { sendcmdto_one(&me, CMD_NOTICE, by, "%C :Server %s already present " "from %s", by, aconf->name, cli_name(cli_from(cptr))); } return 0; } else if (IsHandshake(cptr) || IsConnecting(cptr)) { if (by && IsUser(by)) { sendcmdto_one(&me, CMD_NOTICE, by, "%C :Connection to %s already in " "progress", by, cli_name(cptr)); } return 0; } } /* * If we don't know the IP# for this host and it is a hostname and * not a ip# string, then try and find the appropriate host record. */ if (!irc_in_addr_valid(&aconf->address.addr) && !ircd_aton(&aconf->address.addr, aconf->host)) { char buf[HOSTLEN + 1]; host_from_uh(buf, aconf->host, HOSTLEN); gethost_byname(buf, connect_dns_callback, aconf); aconf->dns_pending = 1; return 0; } cptr = make_client(NULL, STAT_UNKNOWN_SERVER); /* * Copy these in so we have something for error detection. */ ircd_strncpy(cli_name(cptr), aconf->name, HOSTLEN); ircd_strncpy(cli_sockhost(cptr), aconf->host, HOSTLEN); /* * Attach config entries to client here rather than in * completed_connection. This to avoid null pointer references */ attach_confs_byhost(cptr, aconf->host, CONF_SERVER); if (!find_conf_byhost(cli_confs(cptr), aconf->host, CONF_SERVER)) { sendto_opmask(0, SNO_OLDSNO, "Host %s is not enabled for " "connecting: no Connect block", aconf->name); if (by && IsUser(by) && !MyUser(by)) { sendcmdto_one(&me, CMD_NOTICE, by, "%C :Connect to host %s failed: no " "Connect block", by, aconf->name); } det_confs_butmask(cptr, 0); free_client(cptr); return 0; } /* * attempt to connect to the server in the conf line */ if (!connect_inet(aconf, cptr)) { if (by && IsUser(by) && !MyUser(by)) { sendcmdto_one(&me, CMD_NOTICE, by, "%C :Couldn't connect to %s", by, cli_name(cptr)); } det_confs_butmask(cptr, 0); free_client(cptr); return 0; } /* * NOTE: if we're here we have a valid C:Line and the client should * have started the connection and stored the remote address/port and * ip address name in itself * * The socket has been connected or connect is in progress. */ make_server(cptr); if (by && IsUser(by)) { ircd_snprintf(0, cli_serv(cptr)->by, sizeof(cli_serv(cptr)->by), "%s%s", NumNick(by)); assert(0 == cli_serv(cptr)->user); cli_serv(cptr)->user = cli_user(by); cli_user(by)->refcnt++; } else { *(cli_serv(cptr))->by = '\0'; /* strcpy(cptr->serv->by, "Auto"); */ } cli_serv(cptr)->up = &me; SetConnecting(cptr); if (cli_fd(cptr) > HighestFd) HighestFd = cli_fd(cptr); LocalClientArray[cli_fd(cptr)] = cptr; Count_newunknown(UserStats); /* Actually we lie, the connect hasn't succeeded yet, but we have a valid * cptr, so we register it now. * Maybe these two calls should be merged. */ add_client_to_list(cptr); hAddClient(cptr); /* nextping = CurrentTime; */ return (s_state(&cli_socket(cptr)) == SS_CONNECTED) ? completed_connection(cptr) : 1; } /** Find the real hostname for the host running the server (or one which * matches the server's name) and its primary IP#. Hostname is stored * in the client structure passed as a pointer. */ void init_server_identity(void) { const struct LocalConf* conf = conf_get_local(); assert(0 != conf); ircd_strncpy(cli_name(&me), conf->name, HOSTLEN); SetYXXServerName(&me, conf->numeric); } /** Process events on a client socket. * @param ev Socket event structure that has a struct Connection as * its associated data. */ static void client_sock_callback(struct Event* ev) { struct Client* cptr; struct Connection* con; char *fmt = "%s"; char *fallback = 0; assert(0 != ev_socket(ev)); assert(0 != s_data(ev_socket(ev))); con = (struct Connection*) s_data(ev_socket(ev)); assert(0 != con_client(con) || ev_type(ev) == ET_DESTROY); cptr = con_client(con); assert(0 == cptr || con == cli_connect(cptr)); switch (ev_type(ev)) { case ET_DESTROY: con_freeflag(con) &= ~FREEFLAG_SOCKET; if (!con_freeflag(con) && !cptr) free_connection(con); #if defined(USE_SSL) ssl_free(ev_socket(ev)); #endif break; case ET_CONNECT: /* socket connection completed */ if (!completed_connection(cptr) || IsDead(cptr)) fallback = cli_info(cptr); break; case ET_ERROR: /* an error occurred */ fallback = cli_info(cptr); cli_error(cptr) = ev_data(ev); /* If the OS told us we have a bad file descriptor, we should * record that for future reference. */ if (cli_error(cptr) == EBADF) cli_fd(cptr) = -1; if (s_state(&(con_socket(con))) == SS_CONNECTING) { completed_connection(cptr); /* for some reason, the os_get_sockerr() in completed_connection() * can return 0 even when ev_data(ev) indicates a real error, so * re-assign the client error here. */ cli_error(cptr) = ev_data(ev); break; } /*FALLTHROUGH*/ case ET_EOF: /* end of file on socket */ Debug((DEBUG_ERROR, "READ ERROR: fd = %d %d", cli_fd(cptr), cli_error(cptr))); SetFlag(cptr, FLAG_DEADSOCKET); if ((IsServer(cptr) || IsHandshake(cptr)) && cli_error(cptr) == 0) { exit_client_msg(cptr, cptr, &me, "Server %s closed the connection (%s)", cli_name(cptr), cli_serv(cptr)->last_error_msg); return; } else { fmt = "Read error: %s"; fallback = "EOF from client"; } break; case ET_WRITE: /* socket is writable */ ClrFlag(cptr, FLAG_BLOCKED); if (cli_listing(cptr) && MsgQLength(&(cli_sendQ(cptr))) < 2048) list_next_channels(cptr); Debug((DEBUG_SEND, "Sending queued data to %C", cptr)); send_queued(cptr); break; case ET_READ: /* socket is readable */ if (!IsDead(cptr)) { Debug((DEBUG_DEBUG, "Reading data from %C", cptr)); if (read_packet(cptr, 1) == 0) /* error while reading packet */ fallback = "EOF from client"; } break; default: assert(0 && "Unrecognized socket event in client_sock_callback()"); break; } assert(0 == cptr || 0 == cli_connect(cptr) || con == cli_connect(cptr)); if (fallback) { const char* msg = (cli_error(cptr)) ? strerror(cli_error(cptr)) : fallback; if (!msg) msg = "Unknown error"; exit_client_msg(cptr, cptr, &me, fmt, msg); } } /** Process a timer on client socket. * @param ev Timer event that has a struct Connection as its * associated data. */ static void client_timer_callback(struct Event* ev) { struct Client* cptr; struct Connection* con; assert(0 != ev_timer(ev)); assert(0 != t_data(ev_timer(ev))); assert(ET_DESTROY == ev_type(ev) || ET_EXPIRE == ev_type(ev)); con = (struct Connection*) t_data(ev_timer(ev)); assert(0 != con_client(con) || ev_type(ev) == ET_DESTROY); cptr = con_client(con); assert(0 == cptr || con == cli_connect(cptr)); if (ev_type(ev)== ET_DESTROY) { con_freeflag(con) &= ~FREEFLAG_TIMER; /* timer has expired... */ if (!con_freeflag(con) && !cptr) free_connection(con); /* client is being destroyed */ } else { Debug((DEBUG_LIST, "Client process timer for %C expired; processing", cptr)); read_packet(cptr, 0); /* read_packet will re-add timer if needed */ } assert(0 == cptr || 0 == cli_connect(cptr) || con == cli_connect(cptr)); }
/** 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; }
/** Handle a KICK message from a server connection. * * \a parv has the following elements: * \li \a parv[1] is the channel name to kick someone from * \li \a parv[2] is the numnick of the client to kick * \li \a parv[\a parc - 1] is the kick comment * * See @ref m_functions for discussion of the arguments. * @param[in] cptr Client that sent us the message. * @param[in] sptr Original source of message. * @param[in] parc Number of arguments. * @param[in] parv Argument vector. */ int ms_kick(struct Client *cptr, struct Client *sptr, int parc, char *parv[]) { struct Client *who; struct Channel *chptr; struct Membership *member = 0, *sptr_link = 0; char *name, *comment; if (parc < 3 || *parv[1] == '\0') return need_more_params(sptr, "KICK"); name = parv[1]; comment = parv[parc - 1]; /* figure out who gets kicked from what */ if (IsLocalChannel(name) || !(chptr = get_channel(sptr, name, CGT_NO_CREATE)) || !(who = findNUser(parv[2]))) return 0; /* We go ahead and pass on the KICK for users not on the channel */ member = find_member_link(chptr, who); if (member && IsZombie(member)) { /* We might get a KICK from a zombie's own server because the user * net-rode during a burst (which always generates a KICK) *and* * was kicked via another server. In that case, we must remove * the user from the channel. */ if (sptr == cli_user(who)->server) { remove_user_from_channel(who, chptr); } /* Otherwise, we treat zombies like they are not channel members. */ member = 0; } /* Send HACK notice, but not for servers in BURST */ /* 2002-10-17: Don't send HACK if the users local server is kicking them */ if (IsServer(sptr) && !IsBurstOrBurstAck(sptr) && sptr!=cli_user(who)->server) sendto_opmask(0, SNO_HACK4, "HACK: %C KICK %H %C %s", sptr, chptr, who, comment); /* Unless someone accepted it downstream (or the user isn't on the channel * here), if kicker is not on channel, or if kicker is not a channel * operator, bounce the kick */ #if defined(DDB) || defined(SERVICES) if (!IsServer(sptr) && member && cli_from(who) != cptr && !IsService(cli_user(sptr)->server) && (!(sptr_link = find_member_link(chptr, sptr)) || !(IsChanOwner(sptr_link) || IsChanOp(sptr_link)))) { #else if (!IsServer(sptr) && member && cli_from(who) != cptr && (!(sptr_link = find_member_link(chptr, sptr)) || !IsChanOp(sptr_link))) { #endif sendto_opmask(0, SNO_HACK2, "HACK: %C KICK %H %C %s", sptr, chptr, who, comment); sendcmdto_one(who, CMD_JOIN, cptr, "%H", chptr); /* Reop/revoice member */ if (IsChanOp(member) || HasVoice(member)) { struct ModeBuf mbuf; modebuf_init(&mbuf, sptr, cptr, chptr, (MODEBUF_DEST_SERVER | /* Send mode to a server */ MODEBUF_DEST_DEOP | /* Deop the source */ MODEBUF_DEST_BOUNCE)); /* And bounce the MODE */ #if defined(UNDERNET) if (IsChanOp(member)) modebuf_mode_client(&mbuf, MODE_DEL | MODE_CHANOP, who, OpLevel(member)); if (HasVoice(member)) modebuf_mode_client(&mbuf, MODE_DEL | MODE_VOICE, who, MAXOPLEVEL + 1); #else if (IsChanOp(member)) modebuf_mode_client(&mbuf, MODE_DEL | MODE_CHANOP, who, 0); if (HasVoice(member)) modebuf_mode_client(&mbuf, MODE_DEL | MODE_VOICE, who, 0); #endif modebuf_flush(&mbuf); } } else { /* Propagate kick... */ sendcmdto_serv(sptr, CMD_KICK, cptr, "%H %C :%s", chptr, who, comment); if (member) { /* and tell the channel about it */ if (IsDelayedJoin(member)) { if (MyUser(who)) sendcmdto_one(IsServer(sptr) ? &his : sptr, CMD_KICK, who, "%H %C :%s", chptr, who, comment); } else { sendcmdto_channel(IsServer(sptr) ? &his : sptr, CMD_KICK, chptr, NULL, SKIP_SERVERS, "%H %C :%s", chptr, who, comment); } make_zombie(member, who, cptr, sptr, chptr); } } return 0; }