/** Get client config structure for a particular client. * @param[in] acptr Client to check. * @return Pointer to the ConfItem to which \a acptr belongs. */ struct ConfItem* get_client_conf(struct Client *acptr) { struct SLink *tmp; /* Return the most recent(first on LL) client class... */ if (acptr && !IsMe(acptr) && (cli_confs(acptr))) for (tmp = cli_confs(acptr); tmp; tmp = tmp->next) { if (tmp->value.aconf && (tmp->value.aconf->status & CONF_CLIENT)) return tmp->value.aconf; } return NULL; }
/** Get connection class name for a particular client. * @param[in] acptr Client to check. * @return Name of connection class to which \a acptr belongs. */ char* get_client_class(struct Client *acptr) { struct SLink *tmp; struct ConnectionClass *cl; /* Return the most recent(first on LL) client class... */ if (acptr && !IsMe(acptr) && (cli_confs(acptr))) for (tmp = cli_confs(acptr); tmp; tmp = tmp->next) { if (tmp->value.aconf && (cl = tmp->value.aconf->conn_class)) return ConClass(cl); } return "(null-class)"; }
/** Disassociate configuration from the client. * @param cptr Client to operate on. * @param aconf ConfItem to detach. */ static void detach_conf(struct Client* cptr, struct ConfItem* aconf) { struct SLink** lp; struct SLink* tmp; assert(0 != aconf); assert(0 != cptr); assert(0 < aconf->clients); lp = &(cli_confs(cptr)); while (*lp) { if ((*lp)->value.aconf == aconf) { if (aconf->conn_class && (aconf->status & CONF_CLIENT_MASK) && ConfLinks(aconf) > 0) --ConfLinks(aconf); assert(0 < aconf->clients); if (0 == --aconf->clients && IsIllegal(aconf)) free_conf(aconf); tmp = *lp; *lp = tmp->next; free_link(tmp); return; } lp = &((*lp)->next); } }
/** 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; }
/** Check whether a particular ConfItem is already attached to a * Client. * @param aconf ConfItem to search for * @param cptr Client to check * @return Non-zero if \a aconf is attached to \a cptr, zero if not. */ static int is_attached(struct ConfItem *aconf, struct Client *cptr) { struct SLink *lp; for (lp = cli_confs(cptr); lp; lp = lp->next) { if (lp->value.aconf == aconf) return 1; } return 0; }
/** Associate a specific configuration entry to a *local* client (this * is the one which used in accepting the connection). Note, that this * automatically changes the attachment if there was an old one... * @param cptr Client to attach \a aconf to * @param aconf ConfItem to attach * @return Authorization check result. */ enum AuthorizationCheckResult attach_conf(struct Client *cptr, struct ConfItem *aconf) { struct SLink *lp; if (is_attached(aconf, cptr)) return ACR_ALREADY_AUTHORIZED; if (IsIllegal(aconf)) return ACR_NO_AUTHORIZATION; if ((aconf->status & (CONF_OPERATOR | CONF_CLIENT)) && ConfLinks(aconf) >= ConfMaxLinks(aconf) && ConfMaxLinks(aconf) > 0) return ACR_TOO_MANY_IN_CLASS; /* Use this for printing error message */ lp = make_link(); lp->next = cli_confs(cptr); lp->value.aconf = aconf; cli_confs(cptr) = lp; ++aconf->clients; if (aconf->status & CONF_CLIENT_MASK) ConfLinks(aconf)++; return ACR_OK; }
/** Remove all conf entries from the client except those which match * the status field mask. * @param cptr Client to operate on. * @param mask ConfItem types to keep. */ void det_confs_butmask(struct Client* cptr, int mask) { struct SLink* link; struct SLink* next; assert(0 != cptr); for (link = cli_confs(cptr); link; link = next) { next = link->next; if ((link->value.aconf->status & mask) == 0) detach_conf(cptr, link->value.aconf); } }
/** Find the default usermode for a client. * @param[in] sptr Client to find default usermode for. * @return Pointer to usermode string (or NULL, if there is no default). */ const char* client_get_default_umode(const struct Client* sptr) { struct ConfItem* aconf; struct SLink* link; assert(cli_verify(sptr)); for (link = cli_confs(sptr); link; link = link->next) { aconf = link->value.aconf; if ((aconf->status & CONF_CLIENT) && ConfUmode(aconf)) return ConfUmode(aconf); } return NULL; }
/** Return maximum SendQ length for a client. * @param[in] cptr Local client to check. * @return Number of bytes allowed in SendQ for \a cptr. */ unsigned int get_sendq(struct Client *cptr) { assert(0 != cptr); assert(0 != cli_local(cptr)); if (cli_max_sendq(cptr)) return cli_max_sendq(cptr); else if (cli_confs(cptr)) { struct SLink* tmp; struct ConnectionClass* cl; for (tmp = cli_confs(cptr); tmp; tmp = tmp->next) { if (!tmp->value.aconf || !(cl = tmp->value.aconf->conn_class)) continue; if (ConClass(cl) != NULL) { cli_max_sendq(cptr) = MaxSendq(cl); return cli_max_sendq(cptr); } } } return feature_uint(FEAT_DEFAULTMAXSENDQLENGTH); }
/** Find the shortest non-zero ping time attached to a client. * If all attached ping times are zero, return the value for * FEAT_PINGFREQUENCY. * @param[in] acptr Client to find ping time for. * @return Ping time in seconds. */ int client_get_ping(const struct Client* acptr) { int ping = 0; struct ConfItem* aconf; struct SLink* link; assert(cli_verify(acptr)); for (link = cli_confs(acptr); link; link = link->next) { aconf = link->value.aconf; if (aconf->status & (CONF_CLIENT | CONF_SERVER)) { int tmp = get_conf_ping(aconf); if (0 < tmp && (ping > tmp || !ping)) ping = tmp; } } if (0 == ping) ping = feature_int(FEAT_PINGFREQUENCY); Debug((DEBUG_DEBUG, "Client %s Ping %d", cli_name(acptr), ping)); return ping; }
/** Check whether the introduction of a new server would cause a loop * or be disallowed by leaf and hub configuration directives. * @param[in] cptr Neighbor who sent the message. * @param[in] sptr Client that originated the message. * @param[out] ghost If non-NULL, receives ghost timestamp for new server. * @param[in] host Name of new server. * @param[in] numnick Numnick mask of new server. * @param[in] timestamp Claimed link timestamp of new server. * @param[in] hop Number of hops to the new server. * @param[in] junction Non-zero if the new server is still bursting. * @return CPTR_KILLED if \a cptr was SQUIT. 0 if some other server * was SQUIT. 1 if the new server is allowed. */ static int check_loop_and_lh(struct Client* cptr, struct Client *sptr, time_t *ghost, const char *host, const char *numnick, time_t timestamp, unsigned int hop, int junction) { struct Client* acptr; struct Client* LHcptr = NULL; struct ConfItem* lhconf; enum lh_type active_lh_line = ALLOWED; int ii; if (ghost) *ghost = 0; /* * Calculate type of connect limit and applicable config item. */ lhconf = find_conf_byname(cli_confs(cptr), cli_name(cptr), CONF_SERVER); assert(lhconf != NULL); if (ghost) { if (!feature_bool(FEAT_HUB)) for (ii = 0; ii <= HighestFd; ii++) if (LocalClientArray[ii] && IsServer(LocalClientArray[ii])) { active_lh_line = I_AM_NOT_HUB; break; } } else if (hop > lhconf->maximum) { /* Because "maximum" should be 0 for non-hub links, check whether * there is a hub mask -- if not, complain that the server isn't * allowed to hub. */ active_lh_line = lhconf->hub_limit ? MAX_HOPS_EXCEEDED : NOT_ALLOWED_TO_HUB; } else if (lhconf->hub_limit && match(lhconf->hub_limit, host)) { struct Client *ac3ptr; active_lh_line = NOT_ALLOWED_TO_HUB; if (junction) for (ac3ptr = sptr; ac3ptr != &me; ac3ptr = cli_serv(ac3ptr)->up) if (IsJunction(ac3ptr)) { LHcptr = ac3ptr; break; } } /* * We want to find IsConnecting() and IsHandshake() too, * use FindClient(). * The second finds collisions with numeric representation of existing * servers - these shouldn't happen anymore when all upgraded to 2.10. * -- Run */ while ((acptr = FindClient(host)) || (numnick && (acptr = FindNServer(numnick)))) { /* * This link is trying feed me a server that I already have * access through another path * * Do not allow Uworld to do this. * Do not allow servers that are juped. * Do not allow servers that have older link timestamps * then this try. * Do not allow servers that use the same numeric as an existing * server, but have a different name. * * If my ircd.conf sucks, I can try to connect to myself: */ if (acptr == &me) return exit_client_msg(cptr, cptr, &me, "nick collision with me (%s), check server number in M:?", host); /* * Detect wrong numeric. */ if (0 != ircd_strcmp(cli_name(acptr), host)) { sendcmdto_serv(&me, CMD_WALLOPS, cptr, ":SERVER Numeric Collision: %s != %s", cli_name(acptr), host); return exit_client_msg(cptr, cptr, &me, "NUMERIC collision between %s and %s." " Is your server numeric correct ?", host, cli_name(acptr)); } /* * Kill our try, if we had one. */ if (IsConnecting(acptr)) { if (active_lh_line == ALLOWED && exit_client(cptr, acptr, &me, "Just connected via another link") == CPTR_KILLED) return CPTR_KILLED; /* * We can have only ONE 'IsConnecting', 'IsHandshake' or * 'IsServer', because new 'IsConnecting's are refused to * the same server if we already had it. */ break; } /* * Avoid other nick collisions... * This is a doubtful test though, what else would it be * when it has a server.name ? */ else if (!IsServer(acptr) && !IsHandshake(acptr)) return exit_client_msg(cptr, cptr, &me, "Nickname %s already exists!", host); /* * Our new server might be a juped server: */ else if (IsServer(acptr) && (0 == ircd_strncmp(cli_info(acptr), "JUPE", 4))) { if (!IsServer(sptr)) return exit_client(cptr, sptr, &me, cli_info(acptr)); sendcmdto_serv(&me, CMD_WALLOPS, cptr, ":Received :%s SERVER %s from %s !?!", NumServ(cptr), host, cli_name(cptr)); return exit_new_server(cptr, sptr, host, timestamp, "%s", cli_info(acptr)); } /* * Of course we find the handshake this link was before :) */ else if (IsHandshake(acptr) && acptr == cptr) break; /* * Here we have a server nick collision... * We don't want to kill the link that was last /connected, * but we neither want to kill a good (old) link. * Therefor we kill the second youngest link. */ if (1) { struct Client* c2ptr = 0; struct Client* c3ptr = acptr; struct Client* ac2ptr; struct Client* ac3ptr; /* Search youngest link: */ for (ac3ptr = acptr; ac3ptr != &me; ac3ptr = cli_serv(ac3ptr)->up) if (cli_serv(ac3ptr)->timestamp > cli_serv(c3ptr)->timestamp) c3ptr = ac3ptr; if (IsServer(sptr)) { for (ac3ptr = sptr; ac3ptr != &me; ac3ptr = cli_serv(ac3ptr)->up) if (cli_serv(ac3ptr)->timestamp > cli_serv(c3ptr)->timestamp) c3ptr = ac3ptr; } if (timestamp > cli_serv(c3ptr)->timestamp) { c3ptr = 0; c2ptr = acptr; /* Make sure they differ */ } /* Search second youngest link: */ for (ac2ptr = acptr; ac2ptr != &me; ac2ptr = cli_serv(ac2ptr)->up) if (ac2ptr != c3ptr && cli_serv(ac2ptr)->timestamp > (c2ptr ? cli_serv(c2ptr)->timestamp : timestamp)) c2ptr = ac2ptr; if (IsServer(sptr)) { for (ac2ptr = sptr; ac2ptr != &me; ac2ptr = cli_serv(ac2ptr)->up) if (ac2ptr != c3ptr && cli_serv(ac2ptr)->timestamp > (c2ptr ? cli_serv(c2ptr)->timestamp : timestamp)) c2ptr = ac2ptr; } if (c3ptr && timestamp > (c2ptr ? cli_serv(c2ptr)->timestamp : timestamp)) c2ptr = 0; /* If timestamps are equal, decide which link to break * by name. */ if ((c2ptr ? cli_serv(c2ptr)->timestamp : timestamp) == (c3ptr ? cli_serv(c3ptr)->timestamp : timestamp)) { const char *n2, *n2up, *n3, *n3up; if (c2ptr) { n2 = cli_name(c2ptr); n2up = MyConnect(c2ptr) ? cli_name(&me) : cli_name(cli_serv(c2ptr)->up); } else { n2 = host; n2up = IsServer(sptr) ? cli_name(sptr) : cli_name(&me); } if (c3ptr) { n3 = cli_name(c3ptr); n3up = MyConnect(c3ptr) ? cli_name(&me) : cli_name(cli_serv(c3ptr)->up); } else { n3 = host; n3up = IsServer(sptr) ? cli_name(sptr) : cli_name(&me); } if (strcmp(n2, n2up) > 0) n2 = n2up; if (strcmp(n3, n3up) > 0) n3 = n3up; if (strcmp(n3, n2) > 0) { ac2ptr = c2ptr; c2ptr = c3ptr; c3ptr = ac2ptr; } } /* Now squit the second youngest link: */ if (!c2ptr) return exit_new_server(cptr, sptr, host, timestamp, "server %s already exists and is %ld seconds younger.", host, (long)cli_serv(acptr)->timestamp - (long)timestamp); else if (cli_from(c2ptr) == cptr || IsServer(sptr)) { struct Client *killedptrfrom = cli_from(c2ptr); if (active_lh_line != ALLOWED) { /* * If the L: or H: line also gets rid of this link, * we sent just one squit. */ if (LHcptr && a_kills_b_too(LHcptr, c2ptr)) break; /* * If breaking the loop here solves the L: or H: * line problem, we don't squit that. */ if (cli_from(c2ptr) == cptr || (LHcptr && a_kills_b_too(c2ptr, LHcptr))) active_lh_line = ALLOWED; else { /* * If we still have a L: or H: line problem, * we prefer to squit the new server, solving * loop and L:/H: line problem with only one squit. */ LHcptr = 0; break; } } /* * If the new server was introduced by a server that caused a * Ghost less then 20 seconds ago, this is probably also * a Ghost... (20 seconds is more then enough because all * SERVER messages are at the beginning of a net.burst). --Run */ if (CurrentTime - cli_serv(cptr)->ghost < 20) { killedptrfrom = cli_from(acptr); if (exit_client(cptr, acptr, &me, "Ghost loop") == CPTR_KILLED) return CPTR_KILLED; } else if (exit_client_msg(cptr, c2ptr, &me, "Loop <-- %s (new link is %ld seconds younger)", host, (c3ptr ? (long)cli_serv(c3ptr)->timestamp : timestamp) - (long)cli_serv(c2ptr)->timestamp) == CPTR_KILLED) return CPTR_KILLED; /* * Did we kill the incoming server off already ? */ if (killedptrfrom == cptr) return 0; } else { if (active_lh_line != ALLOWED) { if (LHcptr && a_kills_b_too(LHcptr, acptr)) break; if (cli_from(acptr) == cptr || (LHcptr && a_kills_b_too(acptr, LHcptr))) active_lh_line = ALLOWED; else { LHcptr = 0; break; } } /* * We can't believe it is a lagged server message * when it directly connects to us... * kill the older link at the ghost, rather then * at the second youngest link, assuming it isn't * a REAL loop. */ if (ghost) *ghost = CurrentTime; /* Mark that it caused a ghost */ if (exit_client(cptr, acptr, &me, "Ghost") == CPTR_KILLED) return CPTR_KILLED; break; } } } if (active_lh_line != ALLOWED) { if (!LHcptr) LHcptr = sptr; if (active_lh_line == MAX_HOPS_EXCEEDED) { return exit_client_msg(cptr, LHcptr, &me, "Maximum hops exceeded for %s at %s", cli_name(cptr), host); } else if (active_lh_line == NOT_ALLOWED_TO_HUB) { return exit_client_msg(cptr, LHcptr, &me, "%s is not allowed to hub for %s", cli_name(cptr), host); } else /* I_AM_NOT_HUB */ { ServerStats->is_ref++; return exit_client(cptr, LHcptr, &me, "I'm a leaf, define the HUB feature"); } } return 1; }
/** Check access for a server given its name (passed in cptr struct). * Must check for all C/N lines which have a name which matches the * name given and a host which matches. A host alias which is the * same as the server name is also acceptable in the host field of a * C/N line. * @param cptr Peer server to check. * @return 0 if accepted, -1 if access denied. */ int conf_check_server(struct Client *cptr) { struct ConfItem* c_conf = NULL; struct SLink* lp; Debug((DEBUG_DNS, "sv_cl: check access for %s[%s]", cli_name(cptr), cli_sockhost(cptr))); if (IsUnknown(cptr) && !attach_confs_byname(cptr, cli_name(cptr), CONF_SERVER)) { Debug((DEBUG_DNS, "No C/N lines for %s", cli_sockhost(cptr))); return -1; } lp = cli_confs(cptr); /* * We initiated this connection so the client should have a C and N * line already attached after passing through the connect_server() * function earlier. */ if (IsConnecting(cptr) || IsHandshake(cptr)) { c_conf = find_conf_byname(lp, cli_name(cptr), CONF_SERVER); if (!c_conf) { sendto_opmask_butone(0, SNO_OLDSNO, "Connect Error: lost Connect block for %s", cli_name(cptr)); det_confs_butmask(cptr, 0); return -1; } } /* Try finding the Connect block by DNS name and IP next. */ if (!c_conf && !(c_conf = find_conf_byhost(lp, cli_sockhost(cptr), CONF_SERVER))) c_conf = find_conf_byip(lp, &cli_ip(cptr), CONF_SERVER); /* * Attach by IP# only if all other checks have failed. * It is quite possible to get here with the strange things that can * happen when using DNS in the way the irc server does. -avalon */ if (!c_conf) c_conf = find_conf_byip(lp, &cli_ip(cptr), CONF_SERVER); /* * detach all conf lines that got attached by attach_confs() */ det_confs_butmask(cptr, 0); /* * if no Connect block, then deny access */ if (!c_conf) { Debug((DEBUG_DNS, "sv_cl: access denied: %s[%s@%s]", cli_name(cptr), cli_username(cptr), cli_sockhost(cptr))); return -1; } /* * attach the Connect block to the client structure for later use. */ attach_conf(cptr, c_conf); if (!irc_in_addr_valid(&c_conf->address.addr)) memcpy(&c_conf->address.addr, &cli_ip(cptr), sizeof(c_conf->address.addr)); Debug((DEBUG_DNS, "sv_cl: access ok: %s[%s]", cli_name(cptr), cli_sockhost(cptr))); return 0; }
/** Report memory usage statistics to a client. * @param cptr Client to send data to. * @param sd StatDesc that generated the stats request (ignored). * @param param Extra parameter from user (ignored). */ void count_memory(struct Client *cptr, const struct StatDesc *sd, char *param) { struct Client *acptr; struct Invite *inv; struct SLink *link; struct Ban *ban; struct Channel *chptr; struct ConfItem *aconf; const struct ConnectionClass* cltmp; struct Membership* member; int acc = 0, /* accounts */ c = 0, /* clients */ cn = 0, /* connections */ ch = 0, /* channels */ lcc = 0, /* local client conf links */ chb = 0, /* channel bans */ wwu = 0, /* whowas users */ cl = 0, /* classes */ co = 0, /* conf lines */ listeners = 0, /* listeners */ memberships = 0; /* channel memberships */ int usi = 0, /* users invited */ aw = 0, /* aways set */ wwa = 0, /* whowas aways */ gl = 0, /* glines */ ju = 0; /* jupes */ size_t chm = 0, /* memory used by channels */ chbm = 0, /* memory used by channel bans */ cm = 0, /* memory used by clients */ cnm = 0, /* memory used by connections */ us = 0, /* user structs */ usm = 0, /* memory used by user structs */ awm = 0, /* memory used by aways */ wwam = 0, /* whowas away memory used */ wwm = 0, /* whowas array memory used */ wt = 0, /* watch entrys */ wtm = 0, /* memory used by watchs */ #if defined(DDB) dbs = 0, /* keys of database */ dbm = 0, /* memory used by DDB */ #endif glm = 0, /* memory used by glines */ jum = 0, /* memory used by jupes */ com = 0, /* memory used by conf lines */ dbufs_allocated = 0, /* memory used by dbufs */ dbufs_used = 0, /* memory used by dbufs */ msg_allocated = 0, /* memory used by struct Msg */ msgbuf_allocated = 0, /* memory used by struct MsgBuf */ listenersm = 0, /* memory used by listetners */ rm = 0, /* res memory used */ totcl = 0, totch = 0, totww = 0, tot = 0; count_whowas_memory(&wwu, &wwm, &wwa, &wwam); wwm += sizeof(struct Whowas) * feature_uint(FEAT_NICKNAMEHISTORYLENGTH); wwm += sizeof(struct Whowas *) * WW_MAX; for (acptr = GlobalClientList; acptr; acptr = cli_next(acptr)) { c++; if (MyConnect(acptr)) { cn++; for (link = cli_confs(acptr); link; link = link->next) lcc++; } if (cli_user(acptr)) { for (inv = cli_user(acptr)->invited; inv; inv = inv->next_user) usi++; for (member = cli_user(acptr)->channel; member; member = member->next_channel) ++memberships; if (cli_user(acptr)->away) { aw++; awm += (strlen(cli_user(acptr)->away) + 1); } } #if defined(UNDERNET) if (IsAccount(acptr)) acc++; #endif } cm = c * sizeof(struct Client); cnm = cn * sizeof(struct Connection); user_count_memory(&us, &usm); for (chptr = GlobalChannelList; chptr; chptr = chptr->next) { ch++; chm += (strlen(chptr->chname) + sizeof(struct Channel)); for (ban = chptr->banlist; ban; ban = ban->next) { chb++; chbm += strlen(ban->who) + strlen(ban->banstr) + 2 + sizeof(*ban); } } for (aconf = GlobalConfList; aconf; aconf = aconf->next) { co++; com += aconf->host ? strlen(aconf->host) + 1 : 0; com += aconf->passwd ? strlen(aconf->passwd) + 1 : 0; com += aconf->name ? strlen(aconf->name) + 1 : 0; com += sizeof(struct ConfItem); } for (cltmp = get_class_list(); cltmp; cltmp = cltmp->next) cl++; #if defined(USE_SSL) send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":Clients %d(%zu) Connections %d(%zu) SSL %d", c, cm, cn, cnm, ssl_count()); #else send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":Clients %d(%zu) Connections %d(%zu)", c, cm, cn, cnm); #endif send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":Users %zu(%zu) Accounts %d(%zu) Invites %d(%zu)", us, usm, acc, acc * (ACCOUNTLEN + 1), usi, usi * sizeof(struct Invite)); send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":User channels %d(%zu) Aways %d(%zu)", memberships, memberships * sizeof(struct Membership), aw, awm); totcl = cm + cnm + us * sizeof(struct User) + memberships * sizeof(struct Membership) + awm; totcl += lcc * sizeof(struct SLink) + usi * sizeof(struct SLink); send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":Conflines %d(%zu) Attached %d(%zu) Classes %d(%zu)", co, com, lcc, lcc * sizeof(struct SLink), cl, cl * sizeof(struct ConnectionClass)); send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":Channels %d(%zu) Bans %d(%zu)", ch, chm, chb, chbm); send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":Channel Members %d(%zu)", memberships, memberships * sizeof(struct Membership)); totch = chm + chbm; send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":Whowas Users %d(%zu) Away %d(%zu) Array %u(%zu)", wwu, wwu * sizeof(struct User), wwa, wwam, feature_uint(FEAT_NICKNAMEHISTORYLENGTH), wwm); totww = wwu * sizeof(struct User) + wwam + wwm; watch_count_memory(&wt, &wtm); send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":Watchs %d(%zu)", wt, wtm); motd_memory_count(cptr); gl = gline_memory_count(&glm); ju = jupe_memory_count(&jum); send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":Glines %d(%zu) Jupes %d(%zu)", gl, glm, ju, jum); send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":Hash: client %d(%zu), chan is the same", HASHSIZE, sizeof(void *) * HASHSIZE); count_listener_memory(&listeners, &listenersm); send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":Listeners allocated %d(%zu)", listeners, listenersm); #if defined(DDB) ddb_count_memory(&dbs, &dbm); send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":DDB keys allocated %d(%zu)", dbs, dbm); #endif /* * NOTE: this count will be accurate only for the exact instant that this * message is being sent, so the count is affected by the dbufs that * are being used to send this message out. If this is not desired, move * the dbuf_count_memory call to a place before we start sending messages * and cache DBufAllocCount and DBufUsedCount in variables until they * are sent. */ dbuf_count_memory(&dbufs_allocated, &dbufs_used); send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":DBufs allocated %u(%zu) used %u(%zu)", DBufAllocCount, dbufs_allocated, DBufUsedCount, dbufs_used); /* The DBuf caveats now count for this, but this routine now sends * replies all on its own. */ msgq_count_memory(cptr, &msg_allocated, &msgbuf_allocated); rm = cres_mem(cptr); tot = totww + totch + totcl + com + cl * sizeof(struct ConnectionClass) + dbufs_allocated + msg_allocated + msgbuf_allocated + rm; tot += sizeof(void *) * HASHSIZE * 3; #if defined(MDEBUG) send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":Allocations: %zu(%zu)", fda_get_block_count(), fda_get_byte_count()); #endif send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":Total: ww %zu ch %zu cl %zu co %zu db %zu ms %zu mb %zu", totww, totch, totcl, com, dbufs_allocated, msg_allocated, msgbuf_allocated); }
int ms_mode(struct Client *cptr, struct Client *sptr, int parc, char *parv[]) { struct Channel *chptr = 0; struct ModeBuf mbuf; struct Membership *member; if (parc < 3) return need_more_params(sptr, "MODE"); if (IsLocalChannel(parv[1])) return 0; if (!(chptr = FindChannel(parv[1]))) { struct Client *acptr; acptr = FindUser(parv[1]); if (!acptr) { return 0; } else if (sptr != acptr) { sendwallto_group_butone(&me, WALL_WALLOPS, 0, "MODE for User %s from %s!%s", parv[1], cli_name(cptr), cli_name(sptr)); return 0; } return set_user_mode(cptr, sptr, parc, parv, ALLOWMODES_ANY); } ClrFlag(sptr, FLAG_TS8); if (IsServer(sptr)) { if (find_conf_byhost(cli_confs(cptr), cli_name(sptr), CONF_UWORLD)) modebuf_init(&mbuf, sptr, cptr, chptr, (MODEBUF_DEST_CHANNEL | /* Send mode to clients */ MODEBUF_DEST_SERVER | /* Send mode to servers */ MODEBUF_DEST_HACK4)); /* Send a HACK(4) message */ else if (!feature_bool(FEAT_OPLEVELS)) modebuf_init(&mbuf, sptr, cptr, chptr, (MODEBUF_DEST_CHANNEL | /* Send mode to clients */ MODEBUF_DEST_SERVER | /* Send mode to servers */ MODEBUF_DEST_HACK3)); /* Send a HACK(3) message */ else /* Servers need to be able to op people who join using the Apass * or upass, as well as people joining a zannel, therefore we do * not generate HACK3 when oplevels are on. */ modebuf_init(&mbuf, sptr, cptr, chptr, (MODEBUF_DEST_CHANNEL | /* Send mode to clients */ MODEBUF_DEST_SERVER)); /* Send mode to servers */ mode_parse(&mbuf, cptr, sptr, chptr, parc - 2, parv + 2, (MODE_PARSE_SET | /* Set the mode */ MODE_PARSE_STRICT | /* Interpret it strictly */ MODE_PARSE_FORCE), /* And force it to be accepted */ NULL); } else { if (!(member = find_member_link(chptr, sptr)) || !IsChanOp(member)) { modebuf_init(&mbuf, sptr, cptr, chptr, (MODEBUF_DEST_SERVER | /* Send mode to server */ MODEBUF_DEST_HACK2 | /* Send a HACK(2) message */ MODEBUF_DEST_DEOP | /* Deop the source */ MODEBUF_DEST_BOUNCE)); /* And bounce the MODE */ mode_parse(&mbuf, cptr, sptr, chptr, parc - 2, parv + 2, (MODE_PARSE_STRICT | /* Interpret it strictly */ MODE_PARSE_BOUNCE), /* And bounce the MODE */ member); } else { modebuf_init(&mbuf, sptr, cptr, chptr, (MODEBUF_DEST_CHANNEL | /* Send mode to clients */ MODEBUF_DEST_SERVER)); /* Send mode to servers */ mode_parse(&mbuf, cptr, sptr, chptr, parc - 2, parv + 2, (MODE_PARSE_SET | /* Set the mode */ MODE_PARSE_STRICT | /* Interpret it strictly */ MODE_PARSE_FORCE), /* And force it to be accepted */ member); } } return modebuf_flush(&mbuf); }
/** 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)); }
/** 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_butone(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_butone(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_butone(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; }
/** 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; }
/* * ms_gline - server message handler * * parv[0] = Sender prefix * parv[1] = Target: server numeric * parv[2] = (+|-)<G-line mask> * parv[3] = G-line lifetime * * From Uworld: * * parv[4] = Comment * * From somewhere else: * * parv[4] = Last modification time * parv[5] = Comment * */ int ms_gline(struct Client *cptr, struct Client *sptr, int parc, char *parv[]) { struct Client *acptr = 0; struct Gline *agline; unsigned int flags = 0; time_t expire_off, lastmod = 0; char *mask = parv[2], *target = parv[1], *reason = "No reason"; if (*mask == '!') { mask++; flags |= GLINE_OPERFORCE; /* assume oper had WIDE_GLINE */ } if ((parc == 3 && *mask == '-') || parc == 5) { if (!find_conf_byhost(cli_confs(cptr), cli_name(sptr), CONF_UWORLD)) return need_more_params(sptr, "GLINE"); flags |= GLINE_FORCE; } else if (parc > 5) lastmod = atoi(parv[4]); else return need_more_params(sptr, "GLINE"); if (parc > 4) reason = parv[parc - 1]; if (IsServer(sptr)) flags |= GLINE_FORCE; if (!(target[0] == '*' && target[1] == '\0')) { if (!( (acptr = FindNServer(target)) || (acptr = SeekServer(target)) ) ) return 0; /* no such server */ if (!IsMe(acptr)) { /* manually propagate */ if (!lastmod) sendcmdto_one(sptr, CMD_GLINE, acptr, (parc == 3) ? "%s %s" : "%s %s %s :%s", target, mask, parv[3], reason); else sendcmdto_one(sptr, CMD_GLINE, acptr, "%s %s%s %s %s :%s", target, flags & GLINE_OPERFORCE ? "!" : "", mask, parv[3], parv[4], reason); return 0; } /* For asuka we don't want glines sent this way treated as local * flags |= GLINE_LOCAL; */ } if (*mask == '-') mask++; else if (*mask == '+') { flags |= GLINE_ACTIVE; mask++; } else flags |= GLINE_ACTIVE; expire_off = parc < 5 ? 0 : atoi(parv[3]); agline = gline_find(mask, GLINE_ANY | GLINE_EXACT); if (agline) { if (GlineIsLocal(agline) && !(flags & GLINE_LOCAL)) /* global over local */ gline_free(agline); else if (!lastmod && ((flags & GLINE_ACTIVE) == GlineIsRemActive(agline))) return gline_propagate(cptr, sptr, agline); else if (!lastmod || GlineLastMod(agline) < lastmod) { /* new mod */ if (flags & GLINE_ACTIVE) return gline_activate(cptr, sptr, agline, lastmod, flags); else return gline_deactivate(cptr, sptr, agline, lastmod, flags); } else if (GlineLastMod(agline) == lastmod || IsBurstOrBurstAck(cptr)) return 0; else return gline_resend(cptr, agline); /* other server desynched WRT gline */ } else if (parc == 3 && !(flags & GLINE_ACTIVE)) { /* U-lined server removing a G-line we don't have; propagate the removal * anyway. */ if (!(flags & GLINE_LOCAL)) sendcmdto_serv_butone(sptr, CMD_GLINE, cptr, "* -%s", mask); return 0; } else if (parc < 5) return need_more_params(sptr, "GLINE"); return gline_add(cptr, sptr, mask, reason, expire_off, lastmod, flags); }
/** 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; }