/* * ms_links - server message handler * * parv[0] = sender prefix * parv[1] = servername mask * * or * * parv[0] = sender prefix * parv[1] = server to query * parv[2] = servername mask */ int ms_links(struct Client* cptr, struct Client* sptr, int parc, char* parv[]) { char *mask; struct Client *acptr; if (parc > 2) { if (hunt_server_cmd(sptr, CMD_LINKS, cptr, 1, "%C :%s", 1, parc, parv) != HUNTED_ISME) return 0; mask = parv[2]; } else mask = parc < 2 ? 0 : parv[1]; for (acptr = GlobalClientList, collapse(mask); acptr; acptr = cli_next(acptr)) { if (!IsServer(acptr) && !IsMe(acptr)) continue; if (!BadPtr(mask) && match(mask, cli_name(acptr))) continue; send_reply(sptr, RPL_LINKS, cli_name(acptr), cli_name(cli_serv(acptr)->up), cli_hopcount(acptr), cli_serv(acptr)->prot, ((cli_info(acptr))[0] ? cli_info(acptr) : "(Unknown Location)")); } send_reply(sptr, RPL_ENDOFLINKS, BadPtr(mask) ? "*" : mask); return 0; }
static void stats_servers_verbose(struct Client* sptr, struct StatDesc* sd, int stat, char* param) { struct Client *acptr; /* lowercase 'v' is for human-readable, * uppercase 'V' is for machine-readable */ if (stat == 'v') send_reply(sptr, SND_EXPLICIT | RPL_STATSVERBOSE, "%-20s %-20s Flags Hops Numeric Lag RTT Up Down " "Clients/Max Proto %-10s :Info", "Servername", "Uplink", "LinkTS"); for (acptr = GlobalClientList; acptr; acptr = cli_next(acptr)) { if (!IsServer(acptr) && !IsMe(acptr)) continue; if (param && match(param, cli_name(acptr))) /* narrow search */ continue; send_reply(sptr, SND_EXPLICIT | RPL_STATSVERBOSE, stat == 'v' ? "%-20s %-20s %c%c%c%c %4i %s %-4i %5i %4i %4i %4i %5i %5i " "P%-2i %Tu :%s" : "%s %s %c%c%c%c %i %s %i %i %i %i %i %i %i P%i %Tu :%s", cli_name(acptr), cli_name(cli_serv(acptr)->up), IsBurst(acptr) ? 'B' : '-', IsBurstAck(acptr) ? 'A' : '-', IsHub(acptr) ? 'H' : '-', IsService(acptr) ? 'S' : '-', cli_hopcount(acptr), NumServ(acptr), base64toint(cli_yxx(acptr)), cli_serv(acptr)->lag, cli_serv(acptr)->asll_rtt, cli_serv(acptr)->asll_to, cli_serv(acptr)->asll_from, cli_serv(acptr)->clients, cli_serv(acptr)->nn_mask, cli_serv(acptr)->prot, cli_serv(acptr)->timestamp, cli_info(acptr)); } }
/* * m_links - generic message handler * * parv[0] = sender prefix * parv[1] = servername mask * * or * * parv[0] = sender prefix * parv[1] = server to query * parv[2] = servername mask */ int m_links(struct Client* cptr, struct Client* sptr, int parc, char* parv[]) { char *mask; struct Client *acptr; if (feature_bool(FEAT_HIS_LINKS) && !IsAnOper(sptr)) { send_reply(sptr, RPL_ENDOFLINKS, parc < 2 ? "*" : parv[1]); sendcmdto_one(&me, CMD_NOTICE, sptr, "%C :%s %s", sptr, "/LINKS has been disabled, from CFV-165. Visit ", feature_str(FEAT_HIS_URLSERVERS)); return 0; } if (parc > 2) { if (hunt_server_cmd(sptr, CMD_LINKS, cptr, 1, "%C :%s", 1, parc, parv) != HUNTED_ISME) return 0; mask = parv[2]; } else mask = parc < 2 ? 0 : parv[1]; for (acptr = GlobalClientList, collapse(mask); acptr; acptr = cli_next(acptr)) { if (!IsServer(acptr) && !IsMe(acptr)) continue; if (!BadPtr(mask) && match(mask, cli_name(acptr))) continue; send_reply(sptr, RPL_LINKS, cli_name(acptr), cli_name(cli_serv(acptr)->up), cli_hopcount(acptr), cli_serv(acptr)->prot, ((cli_info(acptr))[0] ? cli_info(acptr) : "(Unknown Location)")); } send_reply(sptr, RPL_ENDOFLINKS, BadPtr(mask) ? "*" : mask); return 0; }
/** 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; }
/* * The function that actually prints out the WHO reply for a client found */ void do_who(struct Client* sptr, struct Client* acptr, struct Channel* repchan, int fields, char* qrt) { char *p1; struct Channel *chptr = repchan; static char buf1[512]; /* NOTE: with current fields list and sizes this _cannot_ overrun, and also the message finally sent shouldn't ever be truncated */ p1 = buf1; buf1[1] = '\0'; /* If we don't have a channel and we need one... try to find it, unless the listing is for a channel service, we already know that there are no common channels, thus use PubChannel and not SeeChannel */ if (!chptr && (!fields || (fields & (WHO_FIELD_CHA | WHO_FIELD_FLA))) && !IsChannelService(acptr)) { struct Membership* chan; for (chan = cli_user(acptr)->channel; chan && !chptr; chan = chan->next_channel) if (PubChannel(chan->channel) && (acptr == sptr || !IsZombie(chan))) chptr = chan->channel; } /* Place the fields one by one in the buffer and send it note that fields == NULL means "default query" */ if (fields & WHO_FIELD_QTY) /* Query type */ { *(p1++) = ' '; if (BadPtr(qrt)) *(p1++) = '0'; else while ((*qrt) && (*(p1++) = *(qrt++))); } if (!fields || (fields & WHO_FIELD_CHA)) { char *p2; *(p1++) = ' '; if ((p2 = (chptr ? chptr->chname : NULL))) while ((*p2) && (*(p1++) = *(p2++))); else *(p1++) = '*'; } if (!fields || (fields & WHO_FIELD_UID)) { char *p2 = cli_user(acptr)->username; *(p1++) = ' '; while ((*p2) && (*(p1++) = *(p2++))); } if (fields & WHO_FIELD_NIP) { const char* p2 = IsHiddenHost(acptr) && (!IsViewip(sptr) && acptr != sptr) ? feature_str(FEAT_HIDDEN_IP) : ircd_ntoa((const char*) &(cli_ip(acptr))); *(p1++) = ' '; while ((*p2) && (*(p1++) = *(p2++))); } if (!fields || (fields & WHO_FIELD_HOS)) { char *p2 = (IsViewip(sptr) || acptr == sptr) ? get_realhost(acptr) : get_virtualhost(acptr); *(p1++) = ' '; while ((*p2) && (*(p1++) = *(p2++))); } if (!fields || (fields & WHO_FIELD_SER)) { char *p2; int haspriv = IsAnOper(sptr) || es_representante(sptr); if (IsMe(cli_user(acptr)->server)) { if ((sptr != acptr) && feature_bool(FEAT_HIS_WHO_SERVERNAME) && !haspriv) p2 = (char *)feature_str(FEAT_HIS_SERVERNAME); else p2 = cli_name(&me); } else { if (IsHiddenserv(cli_user(acptr)->server) && !haspriv) p2 = (char *)feature_str(FEAT_HIS_SERVERNAME); else p2 = cli_name(cli_user(acptr)->server); } *(p1++) = ' '; while ((*p2) && (*(p1++) = *(p2++))); } if (!fields || (fields & WHO_FIELD_NIC)) { char *p2 = cli_name(acptr); *(p1++) = ' '; while ((*p2) && (*(p1++) = *(p2++))); } if (!fields || (fields & WHO_FIELD_FLA)) { *(p1++) = ' '; if (cli_user(acptr)->away) *(p1++) = 'G'; else *(p1++) = 'H'; if ((IsAnOper(acptr) || es_representante(acptr) || IsPreoper(acptr))/* && (HasPriv(acptr, PRIV_DISPLAY) || HasPriv(sptr, PRIV_SEE_OPERS))*/) *(p1++) = '*'; if (fields) { /* If you specified flags then we assume you know how to parse * multiple channel status flags, as this is currently the only * way to know if someone has @'s *and* is +'d. */ if (chptr && is_chan_op(acptr, chptr)) *(p1++) = '@'; if (chptr && has_voice(acptr, chptr)) *(p1++) = '+'; if (chptr && is_chan_halfop(acptr, chptr)) *(p1++) = '%'; if (chptr && is_chan_owner(acptr, chptr)) *(p1++) = '.'; if (chptr && is_zombie(acptr, chptr)) *(p1++) = '!'; } else { if (chptr && is_chan_op(acptr, chptr)) *(p1++) = '@'; else if (chptr && has_voice(acptr, chptr)) *(p1++) = '+'; else if (chptr && is_zombie(acptr, chptr)) *(p1++) = '!'; else if (chptr && is_chan_halfop(acptr, chptr)) *(p1++) = '%'; else if (chptr && is_chan_owner(acptr, chptr)) *(p1++) = '.'; } if (IsDeaf(acptr)) *(p1++) = 'd'; if (IsAnOper(sptr)) { if (IsInvisible(acptr)) *(p1++) = 'i'; if (SendWallops(acptr)) *(p1++) = 'w'; if (SendDebug(acptr)) *(p1++) = 'g'; } if (HasHiddenHost(acptr)) *(p1++) = 'x'; if (IsAnOper(acptr)) *(p1++) = 'o'; if (IsPreoper(acptr)) *(p1++) = 'p'; if (IsHelpOp(acptr)) *(p1++) = 'h'; if (IsCoadmin(acptr)) *(p1++) = 'a'; if (IsAdmin(acptr)) *(p1++) = 'A'; if (IsDevel(acptr)) *(p1++) = 'D'; if (IsViewip(acptr)) *(p1++) = 'X'; } if (!fields || (fields & WHO_FIELD_DIS)) { *p1++ = ' '; if (!fields) *p1++ = ':'; /* Place colon here for default reply */ if (feature_bool(FEAT_HIS_WHO_HOPCOUNT) && !IsAnOper(sptr)) *p1++ = (sptr == acptr) ? '0' : '3'; else /* three digit hopcount maximum */ p1 += ircd_snprintf(0, p1, 3, "%d", cli_hopcount(acptr)); } if (fields & WHO_FIELD_IDL) { *p1++ = ' '; if (MyUser(acptr)) { p1 += ircd_snprintf(0, p1, 11, "%d", CurrentTime - cli_user(acptr)->last); } else { *p1++ = '0'; } } if (!fields || (fields & WHO_FIELD_REN)) { char *p2 = cli_info(acptr); *p1++ = ' '; if (fields) *p1++ = ':'; /* Place colon here for special reply */ while ((*p2) && (*(p1++) = *(p2++))); } if (fields & WHO_FIELD_ACC) { char *p2 = cli_user(acptr)->account; *(p1++) = ' '; while ((*p2) && (*(p1++) = *(p2++))); } /* The first char will always be an useless blank and we need to terminate buf1 */ *p1 = '\0'; p1 = buf1; send_reply(sptr, fields ? RPL_WHOSPCRPL : RPL_WHOREPLY, ++p1); }