Fix unreg_{notice,privmsg}_func().
[srvx.git] / src / proto-bahamut.c
index f10f0f2cf282a210579ef66db54e9c9bc06e2d16..0e8309feb358939feabbe2859a953a7b5a47d817 100644 (file)
@@ -1,11 +1,12 @@
 /* proto-bahamut.c - IRC protocol output
- * Copyright 2000-2004 srvx Development Team
+ * Copyright 2000-2006 srvx Development Team
  *
- * This program is free software; you can redistribute it and/or modify
+ * This file is part of srvx.
+ *
+ * srvx is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.  Important limitations are
- * listed in the COPYING file that accompanies this software.
+ * (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -13,7 +14,8 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program; if not, email srvx-maintainers@srvx.net.
+ * along with srvx; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
  */
 
 #include "proto-common.c"
@@ -110,16 +112,19 @@ is_valid_nick(const char *nick) {
 }
 
 struct userNode *
-AddUser(struct server* uplink, const char *nick, const char *ident, const char *hostname, const char *modes, const char *userinfo, time_t timestamp, struct in_addr realip, const char *stamp) {
+AddUser(struct server* uplink, const char *nick, const char *ident, const char *hostname, const char *modes, const char *userinfo, time_t timestamp, irc_in_addr_t realip, const char *stamp) {
     struct userNode *uNode, *oldUser;
-    unsigned int nn;
+    unsigned int nn, dummy;
 
     if (!uplink) {
         log_module(MAIN_LOG, LOG_WARNING, "AddUser(%p, %s, ...): server does not exist!", uplink, nick);
         return NULL;
     }
 
-    if (!is_valid_nick(nick)) {
+    dummy = modes && modes[0] == '*';
+    if (dummy) {
+        ++modes;
+    } else if (!is_valid_nick(nick)) {
         log_module(MAIN_LOG, LOG_WARNING, "AddUser(%p, %s, ...): invalid nickname detected.", uplink, nick);
         return NULL;
     }
@@ -160,6 +165,7 @@ AddUser(struct server* uplink, const char *nick, const char *ident, const char *
     }
 
     mod_usermode(uNode, modes);
+    if (dummy) uNode->modes |= FLAGS_DUMMY;
     if (stamp) call_account_func(uNode, stamp);
     if (IsLocal(uNode)) irc_user(uNode);
     for (nn=0; nn<nuf_used; nn++) {
@@ -169,29 +175,22 @@ AddUser(struct server* uplink, const char *nick, const char *ident, const char *
 }
 
 struct userNode *
-AddService(const char *nick, const char *desc) {
+AddLocalUser(const char *nick, const char *ident, const char *hostname, const char *desc, const char *modes)
+{
     time_t timestamp = now;
     struct userNode *old_user = GetUserH(nick);
-    struct in_addr ipaddr = { INADDR_LOOPBACK };
-    if (old_user) {
-        if (IsLocal(old_user))
-            return old_user;
-        timestamp = old_user->timestamp - 1;
-    }
-    return AddUser(self, nick, nick, self->name, "+oikr", desc, timestamp, ipaddr, 0);
-}
+    static const irc_in_addr_t ipaddr;
 
-struct userNode *
-AddClone(const char *nick, const char *ident, const char *hostname, const char *desc) {
-    time_t timestamp = now;
-    struct userNode *old_user = GetUserH(nick);
-    struct in_addr ipaddr = { INADDR_LOOPBACK };
+    if (!modes)
+        modes = "+oikr";
     if (old_user) {
         if (IsLocal(old_user))
             return old_user;
         timestamp = old_user->timestamp - 1;
     }
-    return AddUser(self, nick, ident, hostname, "+ir", desc, timestamp, ipaddr, 0);
+    if (!hostname)
+        hostname = self->name;
+    return AddUser(self, nick, ident, hostname, modes, desc, timestamp, ipaddr, 0);
 }
 
 void
@@ -206,7 +205,7 @@ DelUser(struct userNode* user, struct userNode *killer, int announce, const char
     unsigned int nn;
 
     for (nn=user->channels.used; nn>0;) {
-        DelChannelUser(user, user->channels.list[--nn]->channel, false, 0);
+        DelChannelUser(user, user->channels.list[--nn]->channel, NULL, false);
     }
     for (nn=duf_used; nn>0; ) duf_list[--nn](user, killer, why);
     user->uplink->clients--;
@@ -221,6 +220,7 @@ DelUser(struct userNode* user, struct userNode *killer, int announce, const char
             irc_kill(killer, user, why);
         }
     }
+    dict_remove(service_msginfo_dict, user->nick);
     modeList_clean(&user->channels);
     user->dead = 1;
     if (dead_users.size) {
@@ -243,18 +243,18 @@ void
 irc_user(struct userNode *user) {
     char modes[32];
     int modelen = 0;
+    if (!user || user->nick[0] != ' ') return;
     if (IsOper(user)) modes[modelen++] = 'o';
     if (IsInvisible(user)) modes[modelen++] = 'i';
     if (IsWallOp(user)) modes[modelen++] = 'w';
     if (IsService(user)) modes[modelen++] = 'k';
-    if (IsServNotice(user)) modes[modelen++] = 's';
     if (IsDeaf(user)) modes[modelen++] = 'd';
     if (IsReggedNick(user)) modes[modelen++] = 'r';
     if (IsGlobal(user)) modes[modelen++] = 'g';
     modes[modelen] = 0;
     putsock("NICK %s %d "FMT_TIME_T" +%s %s %s %s %d %u :%s",
             user->nick, user->uplink->hops+2, user->timestamp, modes,
-            user->ident, user->hostname, user->uplink->name, 0, ntohl(user->ip.s_addr), user->info);
+            user->ident, user->hostname, user->uplink->name, 0, ntohl(user->ip.in6_32[3]), user->info);
 }
 
 void
@@ -267,6 +267,12 @@ irc_account(struct userNode *user, const char *stamp)
     }
 }
 
+void
+irc_fakehost(UNUSED_ARG(struct userNode *user), UNUSED_ARG(const char *host))
+{
+    /* not supported in bahamut */
+}
+
 void
 irc_regnick(struct userNode *user)
 {
@@ -305,11 +311,6 @@ irc_svinfo() {
     putsock("SVINFO 3 3 0 :"FMT_TIME_T, now);
 }
 
-void
-irc_burst() {
-    putsock("BURST");
-}
-
 void
 irc_introduce(const char *passwd) {
     extern time_t burst_begin;
@@ -357,14 +358,49 @@ irc_squit(struct server *srv, const char *message, const char *service_message)
     }
 }
 
+static int
+deliver_to_dummy(struct userNode *source, struct userNode *dest, const char *message, int type)
+{
+    struct service_message_info *smi;
+
+    if (!dest || !IsDummy(dest) || !IsLocal(dest))
+        return 0;
+    smi = dict_find(service_msginfo_dict, dest->nick, NULL);
+    switch (type) {
+    default:
+        if (smi && smi->on_privmsg)
+        {
+            smi->on_privmsg(source, dest, message, 0);
+            return 1;
+        }
+        break;
+    case 1:
+        if (smi && smi->on_notice)
+        {
+            smi->on_notice(source, dest, message, 0);
+            return 1;
+        }
+        break;
+    }
+    return 0;
+}
+
 void
 irc_privmsg(struct userNode *from, const char *to, const char *message) {
-    putsock(":%s PRIVMSG %s :%s", from->nick, to, message);
+    if (!deliver_to_dummy(from, GetUserH(to), message, 1))
+        putsock(":%s PRIVMSG %s :%s", from->nick, to, message);
 }
 
 void
 irc_notice(struct userNode *from, const char *to, const char *message) {
-    putsock(":%s NOTICE %s :%s", from->nick, to, message);
+    if (!deliver_to_dummy(from, GetUserH(to), message, 0))
+        putsock(":%s NOTICE %s :%s", from->nick, to, message);
+}
+
+void
+irc_notice_user(struct userNode *from, struct userNode *to, const char *message) {
+    if (!deliver_to_dummy(from, to, message, 0))
+        putsock(":%s NOTICE %s :%s", from->nick, to->nick, message);
 }
 
 void
@@ -580,6 +616,39 @@ static CMD_FUNC(cmd_privmsg) {
     return 1;
 }
 
+static CMD_FUNC(cmd_whois) {
+    struct userNode *from;
+    struct userNode *who;
+
+    if (argc < 3)
+        return 0;
+    if (!(from = GetUserH(origin))) {
+        log_module(MAIN_LOG, LOG_ERROR, "Could not find WHOIS origin user %s", origin);
+        return 0;
+    }
+    if(!(who = GetUserH(argv[2]))) {
+        irc_numeric(from, ERR_NOSUCHNICK, "%s@%s :No such nick", argv[2], self->name);
+        return 1;
+    }
+    if (IsHiddenHost(who) && !IsOper(from)) {
+        /* Just stay quiet. */
+        return 1;
+    }
+    irc_numeric(from, RPL_WHOISUSER, "%s %s %s * :%s", who->nick, who->ident, who->hostname, who->info);
+#ifdef WITH_PROTOCOL_P10
+    if (his_servername && his_servercomment)
+      irc_numeric(from, RPL_WHOISSERVER, "%s %s :%s", who->nick, his_servername, his_servercomment);
+    else
+#endif
+    irc_numeric(from, RPL_WHOISSERVER, "%s %s :%s", who->nick, who->uplink->name, who->uplink->description);
+
+    if (IsOper(who)) {
+        irc_numeric(from, RPL_WHOISOPERATOR, "%s :is a megalomaniacal power hungry tyrant", who->nick);
+    }
+    irc_numeric(from, RPL_ENDOFWHOIS, "%s :End of /WHOIS list", who->nick);
+    return 1;
+}
+
 static CMD_FUNC(cmd_capab) {
     static const struct {
         const char *name;
@@ -693,7 +762,6 @@ static CMD_FUNC(cmd_server) {
         AddServer(GetServerH(origin), argv[1], atoi(argv[2]), 0, now, 0, argv[3]);
     } else {
         self->uplink = AddServer(self, argv[1], atoi(argv[2]), 0, now, 0, argv[3]);
-        send_burst();
     }
     return 1;
 }
@@ -702,6 +770,7 @@ static CMD_FUNC(cmd_svinfo) {
     if (argc < 5) return 0;
     if ((atoi(argv[1]) < 3) || (atoi(argv[2]) > 3)) return 0;
     /* TODO: something with the timestamp we get from the other guy */
+    send_burst();
     return 1;
 }
 
@@ -737,12 +806,13 @@ static CMD_FUNC(cmd_nick) {
         /* new nick from a server */
         char id[8];
         unsigned long stamp;
-        struct in_addr ip;
+        irc_in_addr_t ip;
 
         if (argc < 10) return 0;
         stamp = strtoul(argv[8], NULL, 0);
         if (stamp) inttobase64(id, stamp, IDLEN);
-        ip.s_addr = (argc > 10) ? atoi(argv[9]) : 0;
+        if (argc > 10)
+            ip.in6_32[3] = htonl(atoi(argv[9]));
         un = AddUser(GetServerH(argv[7]), argv[1], argv[5], argv[6], argv[4], argv[argc-1], atoi(argv[3]), ip, (stamp ? id : 0));
     }
     return 1;
@@ -848,12 +918,6 @@ static CMD_FUNC(cmd_topic) {
     return 1;
 }
 
-static CMD_FUNC(cmd_part) {
-    if (argc < 2) return 0;
-    parse_foreach(argv[1], part_helper, NULL, NULL, NULL, GetUserH(origin));
-    return 1;
-}
-
 static CMD_FUNC(cmd_away) {
     struct userNode *un;
 
@@ -1109,6 +1173,18 @@ reg_privmsg_func(struct userNode *user, privmsg_func_t handler) {
     info->on_privmsg = handler;
 }
 
+void
+unreg_privmsg_func(struct userNode *user) {
+    struct service_message_info *info;
+    info = dict_find(service_msginfo_dict, user->nick, NULL);
+    if (info) {
+        info->on_privmsg = NULL;
+        if (info->on_notice == NULL) {
+            dict_remove(service_msginfo_dict, user->nick);
+        }
+    }
+}
+
 void
 reg_notice_func(struct userNode *user, privmsg_func_t handler) {
     struct service_message_info *info = dict_find(service_msginfo_dict, user->nick, NULL);
@@ -1119,6 +1195,18 @@ reg_notice_func(struct userNode *user, privmsg_func_t handler) {
     info->on_notice = handler;
 }
 
+void
+unreg_notice_func(struct userNode *user) {
+    struct service_message_info *info;
+    info = dict_find(service_msginfo_dict, user->nick, NULL);
+    if (info) {
+        info->on_notice = NULL;
+        if (info->on_privmsg == NULL) {
+            dict_remove(service_msginfo_dict, user->nick);
+        }
+    }
+}
+
 void
 reg_oper_func(oper_func_t handler)
 {
@@ -1164,24 +1252,21 @@ void mod_usermode(struct userNode *user, const char *mode_change) {
                userList_remove(&curr_opers, user);
            }
            break;
-       case 'O': do_user_mode(FLAGS_LOCOP); break;
        case 'i': do_user_mode(FLAGS_INVISIBLE);
            if (add) invis_clients++; else invis_clients--;
            break;
        case 'w': do_user_mode(FLAGS_WALLOP); break;
-       case 's': do_user_mode(FLAGS_SERVNOTICE); break;
        case 'd': do_user_mode(FLAGS_DEAF); break;
        case 'r': do_user_mode(FLAGS_REGNICK); break;
        case 'k': do_user_mode(FLAGS_SERVICE); break;
        case 'g': do_user_mode(FLAGS_GLOBAL); break;
-       case 'h': do_user_mode(FLAGS_HELPER); break;
        }
 #undef do_user_mode
     }
 }
 
 struct mod_chanmode *
-mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, unsigned int flags)
+mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, unsigned int flags, short base_oplevel)
 {
     struct mod_chanmode *change;
     unsigned int ii, in_arg, ch_arg, add;
@@ -1211,6 +1296,14 @@ mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, un
         case 'p': do_chan_mode(MODE_PRIVATE); break;
         case 's': do_chan_mode(MODE_SECRET); break;
         case 't': do_chan_mode(MODE_TOPICLIMIT); break;
+       case 'r':
+           if (!(flags & MCP_REGISTERED)) {
+            do_chan_mode(MODE_REGISTERED);
+           } else {
+            mod_chanmode_free(change);
+            return NULL;
+           }
+           break;
 #undef do_chan_mode
         case 'l':
             if (add) {
@@ -1245,7 +1338,7 @@ mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, un
             change->args[ch_arg].mode = MODE_BAN;
             if (!add)
                 change->args[ch_arg].mode |= MODE_REMOVE;
-            change->args[ch_arg++].hostmask = modes[in_arg++];
+            change->args[ch_arg++].u.hostmask = modes[in_arg++];
             break;
         case 'o': case 'v':
         {
@@ -1258,17 +1351,31 @@ mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, un
             if (!add)
                 change->args[ch_arg].mode |= MODE_REMOVE;
             victim = GetUserH(modes[in_arg++]);
-            if ((change->args[ch_arg].member = GetUserMode(channel, victim)))
+            if (!victim)
+                continue;
+            if ((change->args[ch_arg].u.member = GetUserMode(channel, victim)))
                 ch_arg++;
             break;
         }
+        default:
+            if (!(flags & MCP_FROM_SERVER))
+                goto error;
+            break;
         }
     }
-    change->argc = argc; /* in case any turned out to be ignored */
+    change->argc = ch_arg; /* in case any turned out to be ignored */
+    if (change->modes_set & MODE_SECRET) {
+        change->modes_set &= ~(MODE_PRIVATE);
+        change->modes_clear |= MODE_PRIVATE;
+    } else if (change->modes_set & MODE_PRIVATE) {
+        change->modes_set &= ~(MODE_SECRET);
+        change->modes_clear |= MODE_SECRET;
+    }
     return change;
   error:
     mod_chanmode_free(change);
     return NULL;
+    (void)base_oplevel;
 }
 
 struct chanmode_buffer {
@@ -1307,6 +1414,7 @@ mod_chanmode_announce(struct userNode *who, struct chanNode *channel, struct mod
     char int_buff[32];
     unsigned int arg;
 
+    assert(change->argc <= change->alloc_argc);
     memset(&chbuf, 0, sizeof(chbuf));
     chbuf.channel = channel;
     chbuf.actor = who;
@@ -1327,6 +1435,7 @@ mod_chanmode_announce(struct userNode *who, struct chanNode *channel, struct mod
         DO_MODE_CHAR(DELAYJOINS, 'D');
         DO_MODE_CHAR(REGONLY, 'R');
         DO_MODE_CHAR(NOCOLORS, 'c');
+        DO_MODE_CHAR(REGISTERED, 'r');
 #undef DO_MODE_CHAR
         if (change->modes_clear & channel->modes & MODE_KEY)
             mod_chanmode_append(&chbuf, 'k', channel->key);
@@ -1336,13 +1445,13 @@ mod_chanmode_announce(struct userNode *who, struct chanNode *channel, struct mod
             continue;
         switch (change->args[arg].mode & ~MODE_REMOVE) {
         case MODE_BAN:
-            mod_chanmode_append(&chbuf, 'b', change->args[arg].hostmask);
+            mod_chanmode_append(&chbuf, 'b', change->args[arg].u.hostmask);
             break;
         default:
             if (change->args[arg].mode & MODE_CHANOP)
-                mod_chanmode_append(&chbuf, 'o', change->args[arg].member->user->nick);
+                mod_chanmode_append(&chbuf, 'o', change->args[arg].u.member->user->nick);
             if (change->args[arg].mode & MODE_VOICE)
-                mod_chanmode_append(&chbuf, 'v', change->args[arg].member->user->nick);
+                mod_chanmode_append(&chbuf, 'v', change->args[arg].u.member->user->nick);
             break;
         }
     }
@@ -1361,6 +1470,7 @@ mod_chanmode_announce(struct userNode *who, struct chanNode *channel, struct mod
         DO_MODE_CHAR(DELAYJOINS, 'D');
         DO_MODE_CHAR(REGONLY, 'R');
         DO_MODE_CHAR(NOCOLORS, 'c');
+        DO_MODE_CHAR(REGISTERED, 'r');
 #undef DO_MODE_CHAR
         if (change->modes_set & MODE_KEY)
             mod_chanmode_append(&chbuf, 'k', change->new_key);
@@ -1375,13 +1485,13 @@ mod_chanmode_announce(struct userNode *who, struct chanNode *channel, struct mod
             continue;
         switch (change->args[arg].mode) {
         case MODE_BAN:
-            mod_chanmode_append(&chbuf, 'b', change->args[arg].hostmask);
+            mod_chanmode_append(&chbuf, 'b', change->args[arg].u.hostmask);
             break;
         default:
             if (change->args[arg].mode & MODE_CHANOP)
-                mod_chanmode_append(&chbuf, 'o', change->args[arg].member->user->nick);
+                mod_chanmode_append(&chbuf, 'o', change->args[arg].u.member->user->nick);
             if (change->args[arg].mode & MODE_VOICE)
-                mod_chanmode_append(&chbuf, 'v', change->args[arg].member->user->nick);
+                mod_chanmode_append(&chbuf, 'v', change->args[arg].u.member->user->nick);
             break;
         }
     }
@@ -1399,6 +1509,7 @@ char *
 mod_chanmode_format(struct mod_chanmode *change, char *outbuff)
 {
     unsigned int used = 0;
+    assert(change->argc <= change->alloc_argc);
     if (change->modes_clear) {
         outbuff[used++] = '-';
 #define DO_MODE_CHAR(BIT, CHAR) if (change->modes_clear & MODE_##BIT) outbuff[used++] = CHAR
@@ -1411,8 +1522,9 @@ mod_chanmode_format(struct mod_chanmode *change, char *outbuff)
         DO_MODE_CHAR(LIMIT, 'l');
         DO_MODE_CHAR(KEY, 'k');
         DO_MODE_CHAR(DELAYJOINS, 'D');
-        DO_MODE_CHAR(REGONLY, '$');
+        DO_MODE_CHAR(REGONLY, 'R');
         DO_MODE_CHAR(NOCOLORS, 'c');
+        DO_MODE_CHAR(REGISTERED, 'r');
 #undef DO_MODE_CHAR
     }
     if (change->modes_set) {
@@ -1427,6 +1539,7 @@ mod_chanmode_format(struct mod_chanmode *change, char *outbuff)
         DO_MODE_CHAR(DELAYJOINS, 'D');
         DO_MODE_CHAR(REGONLY, 'R');
         DO_MODE_CHAR(NOCOLORS, 'c');
+        DO_MODE_CHAR(REGISTERED, 'r');
 #undef DO_MODE_CHAR
         switch (change->modes_set & (MODE_KEY|MODE_LIMIT)) {
         case MODE_KEY|MODE_LIMIT: