/* * ptasks.C: Implementation of common protocol tasks. * Rights to this code are documented in doc/pod/license.pod. * * Copyright © 2005-2007 Atheme Project (http://www.atheme.org) */ static char const rcsid[] = "$Id: ptasks.C,v 1.8 2007/09/05 11:23:15 pippijn Exp $"; #include "atheme.h" #include #include #include #include #include #include "uplink.h" #include "pmodule.h" #include "privs.h" #include "connection.h" void handle_info (user_t *u) { unsigned int i; if (u == NULL) return; if (floodcheck (u, NULL)) return; for (i = 0; infotext[i]; i++) phandler->numeric_sts (me.name, 371, u->nick, ":%s", infotext[i]); phandler->numeric_sts (me.name, 374, u->nick, ":End of /INFO list"); } void handle_version (user_t *u) { if (u == NULL) return; if (floodcheck (u, NULL)) return; phandler->numeric_sts (me.name, 351, u->nick, ":" PACKAGE_NAME "-%s. %s %s%s%s%s%s%s%s%s%s%s [%s]", version, me.name, (match_mapping) ? "A" : "", log_debug_enabled ()? "d" : "", (me.auth) ? "e" : "", (config_options.flood_msgs) ? "F" : "", (config_options.leave_chans) ? "l" : "", (config_options.join_chans) ? "j" : "", (chansvs.changets) ? "t" : "", (!match_mapping) ? "R" : "", (config_options.raw) ? "r" : "", (runflags & RF_LIVE) ? "n" : "", ircd->ircdname); phandler->numeric_sts (me.name, 351, u->nick, ":Compile time: %s, build %s", creation, generation); } void handle_admin (user_t *u) { if (u == NULL) return; if (floodcheck (u, NULL)) return; phandler->numeric_sts (me.name, 256, u->nick, ":Administrative info about %s", me.name); phandler->numeric_sts (me.name, 257, u->nick, ":%s", me.adminname); phandler->numeric_sts (me.name, 258, u->nick, ":Ermyth IRC Services (" PACKAGE_NAME "-%s)", version); phandler->numeric_sts (me.name, 259, u->nick, ":<%s>", me.adminemail); } static void connection_stats_cb (char const * const line, void *privdata) { phandler->numeric_sts (me.name, 249, ((user_t *) privdata)->nick, "F :%s", line); } void handle_stats (user_t *u, char req) { kline_t *k; node_t *n; kline_vector::iterator klit = klnlist.begin (); kline_vector::iterator klet = klnlist.end (); uplink_t *uplink; soper_t *soper; int i, j; char fl[10]; if (floodcheck (u, NULL)) return; logcommand_user (NULL, u, CMDLOG_GET, "STATS %c", req); switch (req) { #if 0 // bye for now, dictionaries case 'B': if (!has_priv_user (u, PRIV_SERVER_AUSPEX)) break; dictionary_stats (dictionary_stats_cb, u); break; #endif case 'C': case 'c': if (!has_priv_user (u, PRIV_SERVER_AUSPEX)) break; LIST_FOREACH (n, uplinks.head) { uplink = (uplink_t *) n->data; phandler->numeric_sts (me.name, 213, u->nick, "C *@127.0.0.1 A %s %d uplink", uplink->name, uplink->port); } break; case 'E': case 'e': if (!has_priv_user (u, PRIV_SERVER_AUSPEX)) break; phandler->numeric_sts (me.name, 249, u->nick, "E :Last event to run: %s", last_event_ran); phandler->numeric_sts (me.name, 249, u->nick, "E :%-28s %s", "Operation", "Next Execution"); for (i = 0; i < MAX_EVENTS; i++) { if (event_table[i].active) phandler->numeric_sts (me.name, 249, u->nick, "E :%-28s %4d seconds (%d)", event_table[i].name, event_table[i].when - NOW, event_table[i].frequency); } break; case 'f': case 'F': if (!has_priv_user (u, PRIV_SERVER_AUSPEX)) break; connection_t::stats (connection_stats_cb, u); break; case 'H': case 'h': if (!has_priv_user (u, PRIV_SERVER_AUSPEX)) break; LIST_FOREACH (n, uplinks.head) { uplink = (uplink_t *) n->data; phandler->numeric_sts (me.name, 244, u->nick, "H * * %s", uplink->name); } break; case 'I': case 'i': phandler->numeric_sts (me.name, 215, u->nick, "I * * *@%s 0 nonopered", me.name); break; case 'K': case 'k': if (!has_priv_user (u, PRIV_AKILL)) break; while (klit != klet) { k = *klit; phandler->numeric_sts (me.name, 216, u->nick, "K %s * %s :%s", k->host, k->user, k->reason); ++klit; } break; case 'o': case 'O': if (!has_priv_user (u, PRIV_VIEWPRIVS)) break; LIST_FOREACH (n, soperlist.head) { soper = static_cast (n->data); j = 0; if (!(soper->flags & SOPER_CONF)) fl[j++] = 'D'; if (soper->operclass != NULL && soper->operclass->flags & OPERCLASS_NEEDOPER) fl[j++] = 'O'; if (j == 0) fl[j++] = '*'; fl[j] = '\0'; phandler->numeric_sts (me.name, 243, u->nick, "O *@* %s %s %s %s", fl, soper->myuser ? soper->myuser->name : soper->name, soper->operclass ? soper->operclass->name : soper->classname, "-1"); } break; case 'T': case 't': if (!has_priv_user (u, PRIV_SERVER_AUSPEX)) break; phandler->numeric_sts (me.name, 249, u->nick, "T :event %7d", system_state.event); phandler->numeric_sts (me.name, 249, u->nick, "T :node %7d", system_state.node); phandler->numeric_sts (me.name, 249, u->nick, "T :connection %7d", connection_t::count ()); phandler->numeric_sts (me.name, 249, u->nick, "T :operclass %7d", cnt.operclass); phandler->numeric_sts (me.name, 249, u->nick, "T :soper %7d", cnt.soper); phandler->numeric_sts (me.name, 249, u->nick, "T :tld %7d", cnt.tld); phandler->numeric_sts (me.name, 249, u->nick, "T :kline %7d", cnt.kline); phandler->numeric_sts (me.name, 249, u->nick, "T :server %7d", cnt.server); phandler->numeric_sts (me.name, 249, u->nick, "T :user %7d", cnt.user); phandler->numeric_sts (me.name, 249, u->nick, "T :chan %7d", cnt.chan); phandler->numeric_sts (me.name, 249, u->nick, "T :chanuser %7d", cnt.chanuser); phandler->numeric_sts (me.name, 249, u->nick, "T :myuser %7d", cnt.myuser); phandler->numeric_sts (me.name, 249, u->nick, "T :myuser_acc %7d", cnt.myuser_access); phandler->numeric_sts (me.name, 249, u->nick, "T :mynick %7d", cnt.mynick); phandler->numeric_sts (me.name, 249, u->nick, "T :mychan %7d", cnt.mychan); phandler->numeric_sts (me.name, 249, u->nick, "T :chanacs %7d", cnt.chanacs); phandler->numeric_sts (me.name, 249, u->nick, "T :bytes sent %7.2f%s", bytes (cnt.bout), sbytes (cnt.bout)); phandler->numeric_sts (me.name, 249, u->nick, "T :bytes recv %7.2f%s", bytes (cnt.bin), sbytes (cnt.bin)); break; case 'u': phandler->numeric_sts (me.name, 242, u->nick, ":Services Uptime: %s", timediff (NOW - me.start)); break; case 'V': case 'v': if (!has_priv_user (u, PRIV_SERVER_AUSPEX)) break; /* we received this command from the uplink, so, * hmm, it is not idle */ phandler->numeric_sts (me.name, 249, u->nick, "V :%s (AutoConn.!*@*) Idle: 0 SendQ: ? Connected: %s", curr_uplink->name, timediff (NOW - curr_uplink->conn->first_recv)); break; default: break; } phandler->numeric_sts (me.name, 219, u->nick, "%c :End of /STATS report", req); } void handle_whois (user_t *u, char *target) { user_t *t = user_find_named (target); if (u == NULL) return; if (floodcheck (u, NULL)) return; if (t != NULL) { phandler->numeric_sts (me.name, 311, u->nick, "%s %s %s * :%s", t->nick, t->user, t->vhost, t->gecos); /* channels purposely omitted */ phandler->numeric_sts (me.name, 312, u->nick, "%s %s :%s", t->nick, t->server->name, t->server->desc); if (t->flags & UF_AWAY) phandler->numeric_sts (me.name, 301, u->nick, "%s :Gone", t->nick); if (is_ircop (t)) phandler->numeric_sts (me.name, 313, u->nick, "%s :%s", t->nick, is_internal_client (t) ? "is a Network Service" : "is an IRC Operator"); if (t->myuser) phandler->numeric_sts (me.name, 330, u->nick, "%s %s :is logged in as", t->nick, t->myuser->name); } else phandler->numeric_sts (me.name, 401, u->nick, "%s :No such nick", target); phandler->numeric_sts (me.name, 318, u->nick, "%s :End of WHOIS", target); } static void single_trace (user_t *u, user_t *t) { if (is_ircop (t)) phandler->numeric_sts (me.name, 204, u->nick, "Oper service %s[%s@%s] (255.255.255.255) 0 0", t->nick, t->user, t->vhost); else phandler->numeric_sts (me.name, 205, u->nick, "User service %s[%s@%s] (255.255.255.255) 0 0", t->nick, t->user, t->vhost); } /* target -> object to trace * dest -> server to execute command on */ void handle_trace (user_t *u, char *target, char *dest) { user_t *t; node_t *n; int nusers; if (u == NULL) return; if (floodcheck (u, NULL)) return; if (!match (target, me.name) || !irccasecmp (target, ME)) { nusers = cnt.user; LIST_FOREACH (n, me.me->userlist.head) { t = static_cast (n->data); single_trace (u, t); nusers--; } if (has_priv_user (u, PRIV_SERVER_AUSPEX)) phandler->numeric_sts (me.name, 206, u->nick, "Serv uplink %dS %dC %s *!*@%s 0", cnt.server - 1, nusers, me.actual, me.name); target = me.name; } else { t = dest != NULL ? user_find_named (target) : user_find (target); if (t != NULL && t->server == me.me) { single_trace (u, t); target = t->nick; } } phandler->numeric_sts (me.name, 262, u->nick, "%s :End of TRACE", target); } void handle_motd (user_t *u) { FILE *f; char lbuf[BUFSIZE]; char nebuf[BUFSIZE]; char cebuf[BUFSIZE]; char ubuf[BUFSIZE]; char cbuf[BUFSIZE]; char nbuf[BUFSIZE]; if (u == NULL) return; if (floodcheck (u, NULL)) return; f = fopen (SYSCONFDIR "/" PACKAGE_NAME ".motd", "r"); if (!f) { phandler->numeric_sts (me.name, 422, u->nick, ":The MOTD file is unavailable."); return; } snprintf (nebuf, BUFSIZE, "%" PRItime, nicksvs.expiry / 86400); snprintf (cebuf, BUFSIZE, "%" PRItime, chansvs.expiry / 86400); snprintf (ubuf, BUFSIZE, "%d", cnt.myuser); snprintf (nbuf, BUFSIZE, "%d", nicksvs.no_nick_ownership ? 0 : cnt.mynick); snprintf (cbuf, BUFSIZE, "%d", cnt.mychan); phandler->numeric_sts (me.name, 375, u->nick, ":- %s Message of the Day -", me.name); while (fgets (lbuf, BUFSIZE, f)) { strip (lbuf); replace (lbuf, BUFSIZE, "&network&", me.netname); replace (lbuf, BUFSIZE, "&nickexpiry&", nebuf); replace (lbuf, BUFSIZE, "&chanexpiry&", cebuf); replace (lbuf, BUFSIZE, "&myusers&", ubuf); replace (lbuf, BUFSIZE, "&mynicks&", nbuf); replace (lbuf, BUFSIZE, "&mychans&", cbuf); phandler->numeric_sts (me.name, 372, u->nick, ":- %s", lbuf); } phandler->numeric_sts (me.name, 376, u->nick, ":End of the message of the day."); fclose (f); } void handle_away (user_t *u, char const * const message) { if (message == NULL || *message == '\0') { if (u->flags & UF_AWAY) { u->flags &= ~UF_AWAY; u->callback.away (u); } } else { if (!(u->flags & UF_AWAY)) { u->flags |= UF_AWAY; u->callback.away (u); } } } void handle_channel_message (sourceinfo_t *si, char *target, bool is_notice, char *message) { char *vec[3]; node_t *n; /* Call hook here */ channel_t *c = channel_find (target); /* No such channel, ignore... */ if (c == NULL) return; c->callback.message (c, si->su, message); vec[0] = target; vec[1] = message; vec[2] = NULL; LIST_FOREACH (n, c->members.head) { chanuser_t *cu = static_cast (n->data); if (!is_internal_client (cu->user)) continue; si->service = find_service (cu->user->nick); if (si->service == NULL) continue; if (si->service->chanmsg == false) continue; if (is_notice) si->service->notice_handler (si, 2, vec); else si->service->handler (si, 2, vec); } } void handle_message (sourceinfo_t *si, char *target, bool is_notice, char *message) { char *vec[3]; /* message from server, ignore */ if (si->su == NULL) return; /* if this is a channel, handle it differently. */ if (*target == '#') { handle_channel_message (si, target, is_notice, message); return; } si->service = find_service (target); if (si->service == NULL) { if (!is_notice && (isalnum (target[0]) || strchr ("[\\]^_`{|}~", target[0]))) { /* If it's not a notice and looks like a nick or * user@server, send back an error message */ if (strchr (target, '@') || !ircd->uses_uid || (!ircd->uses_p10 && !isdigit (target[0]))) phandler->numeric_sts (me.name, 401, si->su->nick, "%s :No such nick", target); else phandler->numeric_sts (me.name, 401, si->su->nick, "* :Target left IRC. Failed to deliver: [%.20s]", message); } return; } /* Run it through flood checks. Channel commands are checked * separately. */ if (si->service->me != NULL && floodcheck (si->su, si->service->me)) return; if (!is_notice && config_options.secure && irccasecmp (target, si->service->disp)) { notice (si->service->me->nick, si->su->nick, "For security reasons, \2/msg %s\2 has been disabled." " Use \2/%s%s \2 to send a command.", si->service->me->nick, ircd->uses_rcommand ? "" : "msg ", si->service->disp); return; } vec[0] = target; vec[1] = message; vec[2] = NULL; if (is_notice) si->service->notice_handler (si, 2, vec); else si->service->handler (si, 2, vec); } void handle_topic_from (sourceinfo_t *si, channel_t *c, char const * const setter, time_t ts, char const *topic) { bool approved = false; if (topic != NULL && topic[0] == '\0') topic = NULL; if (topic != NULL ? c->topic == NULL || strcmp (topic, c->topic) : c->topic != NULL) /* Only call the hook if the topic actually changed */ approved = c->callback.can_change_topic (c, si->su, si->s, setter, ts, topic); if (approved) { /* Allowed, process the change further */ handle_topic (c, setter, ts, topic); } else { /* Not allowed, change it back */ if (c->topic != NULL) phandler->topic_sts (c, c->topic_setter, c->topicts, ts, c->topic); else /* Ick, there was no topic */ phandler->topic_sts (c, chansvs.nick != NULL ? chansvs.nick : me.name, ts - 1, ts, ""); } } void handle_topic (channel_t *c, char const * const setter, time_t ts, char const * const topic) { char newsetter[HOSTLEN], *p; /* setter can be a nick, nick!user@host or server. * strip off !user@host part if it's there * (do we really want this?) */ strlcpy (newsetter, setter, sizeof newsetter); p = strchr (newsetter, '!'); if (p != NULL) *p = '\0'; /* drop identical topics from servers or chanserv, and ones with * identical topicts */ if (topic != NULL && c->topic != NULL && !strcmp (topic, c->topic) && (strchr (newsetter, '.') || (chansvs.nick && !irccasecmp (newsetter, chansvs.nick)) || ts == c->topicts)) return; if (c->topic != NULL) sfree (c->topic); if (c->topic_setter != NULL) sfree (c->topic_setter); if (topic != NULL && topic[0] != '\0') { c->topic = sstrdup (topic); c->topic_setter = sstrdup (newsetter); } else c->topic = c->topic_setter = NULL; c->topicts = ts; c->callback.topic (c); } void handle_kill (sourceinfo_t *si, char *victim, char const *reason) { char const * const source = get_oper_name (si); user_t *u; static time_t lastkill = 0; static unsigned int killcount = 0; u = user_find (victim); if (u == NULL) slog (LG_DEBUG, "handle_kill(): %s killed unknown user %s (%s)", source, victim, reason); else if (u->server == me.me) { slog (LG_INFO, "handle_kill(): %s killed service %s (%s)", source, u->nick, reason); if (lastkill != NOW && killcount < 5 + me.me->users) killcount = 0, lastkill = NOW; killcount++; if (killcount < 5 + me.me->users) reintroduce_user (u); else { slog (LG_ERROR, "handle_kill(): services kill fight (%s -> %s), shutting down", source, u->nick); wallops (_("Services kill fight (%s -> %s), shutting down!"), source, u->nick); snoop (_("ERROR: Services kill fight (%s -> %s), shutting down!"), source, u->nick); runflags |= RF_SHUTDOWN; } } else { slog (LG_DEBUG, "handle_kill(): %s killed user %s (%s)", source, u->nick, reason); user_delete (u); } } server_t * handle_server (sourceinfo_t *si, char const * const name, char const * const sid, int hops, char const * const desc) { server_t *s = NULL; if (si->s != NULL) { /* A server introducing another server */ s = server_add (name, hops, si->s->name, sid, desc); } else if (cnt.server == 1) { /* Our uplink introducing itself */ if (irccasecmp (name, curr_uplink->name)) slog (LG_ERROR, "handle_server(): uplink %s actually has name %s, continuing anyway", curr_uplink->name, name); s = server_add (name, hops, me.name, sid, desc); me.actual = s->name; me.recvsvr = true; } else slog (LG_ERROR, "handle_server(): unregistered/unknown server attempting to introduce another server %s", name); return s; } void handle_eob (server_t *s) { node_t *n; server_t *s2; if (s == NULL) return; if (s->flags & SF_EOB) return; slog (LG_NETWORK, "handle_eob(): end of burst from %s (%d users)", s->name, s->users); s->callback.eob (s); s->flags |= SF_EOB; /* convert P10 style EOB to ircnet/ratbox style */ LIST_FOREACH (n, s->children.head) { s2 = static_cast (n->data); if (s2->flags & SF_EOB2) handle_eob (s2); } /* Reseed RNG now we have a little more data to seed with */ if (s->uplink == me.me) init_gen_rand (gen_rand32 () ^ ((NOW << 20) + cnt.user + (cnt.chanuser << 12)) ^ (cnt.chan << 17) ^ ~cnt.bin); } /* Received a message from a user, check if they are flooding * Returns true if the message should be ignored. * u - user sending the message * t - target of the message (to be used in warning the user, may be NULL * to use the server name) */ int floodcheck (user_t *u, user_t *t) { char *from; static time_t last_ignore_notice = 0; if (t == NULL) from = me.name; else if (t->server == me.me) from = t->nick; else { slog (LG_ERROR, "BUG: tried to floodcheck message to non-service %s", t->nick); return 0; } /* Check if we match a services ignore */ if (svsignore_find(u)) { if (u->msgs == 0 && last_ignore_notice != NOW) { /* tell them once per session, don't flood */ u->msgs = 1; last_ignore_notice = NOW; notice (from, u->nick, _("You are on services ignore. You may not use any service.")); } return 1; } if (config_options.flood_msgs) { /* check if they're being ignored */ if (u->offenses > 10) { if ((NOW - u->lastmsg) > 30) { u->offenses -= 10; u->lastmsg = NOW; u->msgs = 0; } else return 1; } if ((NOW - u->lastmsg) > config_options.flood_time) { u->lastmsg = NOW; u->msgs = 0; } u->msgs++; if (u->msgs > config_options.flood_msgs) { /* they're flooding. */ /* perhaps allowed to? -- jilles */ if (has_priv_user (u, PRIV_FLOOD)) { u->msgs = 0; return 0; } if (!u->offenses) { /* ignore them the first time */ u->lastmsg = NOW; u->msgs = 0; u->offenses = 11; /* ok to use nick here, notice() will * change it to UID if necessary -- jilles */ notice (from, u->nick, "You have triggered services flood protection."); notice (from, u->nick, "This is your first offense. You will be ignored for 30 seconds."); snoop ("FLOOD: \2%s\2", u->nick); return 1; } if (u->offenses == 1) { /* ignore them the second time */ u->lastmsg = NOW; u->msgs = 0; u->offenses = 12; notice (from, u->nick, "You have triggered services flood protection."); notice (from, u->nick, "This is your last warning. You will be ignored for 30 seconds."); snoop ("FLOOD: \2%s\2", u->nick); return 1; } if (u->offenses == 2) { kline_t *k; /* kline them the third time */ k = kline_add ("*", u->host, "ten minute ban: flooding services", 600); k->setby = sstrdup (chansvs.nick); snoop ("FLOOD:KLINE: \2%s\2", u->nick); return 1; } } } return 0; } bool should_reg_umode (user_t *u) { mynick_t *mn; if (nicksvs.me == NULL || nicksvs.no_nick_ownership || u->myuser == NULL || u->myuser->flags & MU_WAITAUTH) return false; mn = mynick_find (u->nick); return mn != NULL && mn->owner == u->myuser; }