]> jfr.im git - solanum.git/blobdiff - src/channel.c
Fix some warnings when using -Wformat-security on Alpine.
[solanum.git] / src / channel.c
index 992b1edd26c50442b6bb6fb88c6121a8f3f325ae..2c4b41f73e9f2d24cfaba5b3f32778125a983e91 100644 (file)
@@ -41,6 +41,7 @@
 #include "s_conf.h"            /* ConfigFileEntry, ConfigChannel */
 #include "s_newconf.h"
 #include "logger.h"
+#include "ipv4_from_ipv6.h"
 
 struct config_channel_entry ConfigChannel;
 rb_dlink_list global_channel_list;
@@ -62,6 +63,8 @@ static struct ChCapCombo chcap_combos[NCHCAP_COMBOS];
 static void free_topic(struct Channel *chptr);
 
 static int h_can_join;
+static int h_can_send;
+int h_get_channel_access;
 
 /* init_channels()
  *
@@ -78,6 +81,8 @@ init_channels(void)
        member_heap = rb_bh_create(sizeof(struct membership), MEMBER_HEAP_SIZE, "member_heap");
 
        h_can_join = register_hook("can_join");
+       h_can_send = register_hook("can_send");
+       h_get_channel_access = register_hook("get_channel_access");
 }
 
 /*
@@ -101,12 +106,13 @@ free_channel(struct Channel *chptr)
 }
 
 struct Ban *
-allocate_ban(const char *banstr, const char *who)
+allocate_ban(const char *banstr, const char *who, const char *forward)
 {
        struct Ban *bptr;
        bptr = rb_bh_alloc(ban_heap);
        bptr->banstr = rb_strdup(banstr);
        bptr->who = rb_strdup(who);
+       bptr->forward = forward ? rb_strdup(forward) : NULL;
 
        return (bptr);
 }
@@ -116,9 +122,31 @@ free_ban(struct Ban *bptr)
 {
        rb_free(bptr->banstr);
        rb_free(bptr->who);
+       rb_free(bptr->forward);
        rb_bh_free(ban_heap, bptr);
 }
 
+/*
+ * send_channel_join()
+ *
+ * input        - channel to join, client joining.
+ * output       - none
+ * side effects - none
+ */
+void
+send_channel_join(struct Channel *chptr, struct Client *client_p)
+{
+       if (!IsClient(client_p))
+               return;
+
+       sendto_channel_local_with_capability(ALL_MEMBERS, NOCAPS, CLICAP_EXTENDED_JOIN, chptr, ":%s!%s@%s JOIN %s",
+                                            client_p->name, client_p->username, client_p->host, chptr->chname);
+
+       sendto_channel_local_with_capability(ALL_MEMBERS, CLICAP_EXTENDED_JOIN, NOCAPS, chptr, ":%s!%s@%s JOIN %s %s :%s",
+                                            client_p->name, client_p->username, client_p->host, chptr->chname,
+                                            EmptyString(client_p->user->suser) ? "*" : client_p->user->suser,
+                                            client_p->info);
+}
 
 /* find_channel_membership()
  *
@@ -488,21 +516,26 @@ del_invite(struct Channel *chptr, struct Client *who)
        rb_dlinkFindDestroy(chptr, &who->user->invited);
 }
 
-/* is_banned()
+/* is_banned_list()
  *
- * input       - channel to check bans for, user to check bans against
- *                optional prebuilt buffers
+ * input       - channel to check bans for, ban list (banlist or quietlist),
+ *                user to check bans against, optional prebuilt buffers,
+ *                optional forward channel pointer
  * output      - 1 if banned, else 0
  * side effects -
  */
-int
-is_banned(struct Channel *chptr, struct Client *who, struct membership *msptr,
-         const char *s, const char *s2)
+static int
+is_banned_list(struct Channel *chptr, rb_dlink_list *list,
+              struct Client *who, struct membership *msptr,
+              const char *s, const char *s2, const char **forward)
 {
        char src_host[NICKLEN + USERLEN + HOSTLEN + 6];
        char src_iphost[NICKLEN + USERLEN + HOSTLEN + 6];
        char src_althost[NICKLEN + USERLEN + HOSTLEN + 6];
+       char src_ip4host[NICKLEN + USERLEN + HOSTLEN + 6];
        char *s3 = NULL;
+       char *s4 = NULL;
+       struct sockaddr_in ip4;
        rb_dlink_node *ptr;
        struct Ban *actualBan = NULL;
        struct Ban *actualExcept = NULL;
@@ -535,15 +568,31 @@ is_banned(struct Channel *chptr, struct Client *who, struct membership *msptr,
                        s3 = src_althost;
                }
        }
+#ifdef RB_IPV6
+       if(who->localClient->ip.ss_family == AF_INET6 &&
+                       ipv4_from_ipv6((const struct sockaddr_in6 *)&who->localClient->ip, &ip4))
+       {
+               rb_sprintf(src_ip4host, "%s!%s@", who->name, who->username);
+               s4 = src_ip4host + strlen(src_ip4host);
+               rb_inet_ntop_sock((struct sockaddr *)&ip4,
+                               s4, src_ip4host + sizeof src_ip4host - s4);
+               s4 = src_ip4host;
+       }
+#endif
 
-       RB_DLINK_FOREACH(ptr, chptr->banlist.head)
+       RB_DLINK_FOREACH(ptr, list->head)
        {
                actualBan = ptr->data;
                if(match(actualBan->banstr, s) ||
                   match(actualBan->banstr, s2) ||
                   match_cidr(actualBan->banstr, s2) ||
                   match_extban(actualBan->banstr, who, chptr, CHFL_BAN) ||
-                  (s3 != NULL && match(actualBan->banstr, s3)))
+                  (s3 != NULL && match(actualBan->banstr, s3))
+#ifdef RB_IPV6
+                  ||
+                  (s4 != NULL && (match(actualBan->banstr, s4) || match_cidr(actualBan->banstr, s4)))
+#endif
+                  )
                        break;
                else
                        actualBan = NULL;
@@ -591,9 +640,27 @@ is_banned(struct Channel *chptr, struct Client *who, struct membership *msptr,
                }
        }
 
+       if (actualBan && actualBan->forward && forward)
+               *forward = actualBan->forward;
+
        return ((actualBan ? CHFL_BAN : 0));
 }
 
+/* is_banned()
+ *
+ * input       - channel to check bans for, user to check bans against
+ *                optional prebuilt buffers, optional forward channel pointer
+ * output      - 1 if banned, else 0
+ * side effects -
+ */
+int
+is_banned(struct Channel *chptr, struct Client *who, struct membership *msptr,
+         const char *s, const char *s2, const char **forward)
+{
+       return is_banned_list(chptr, &chptr->banlist, who, msptr, s, s2,
+                       forward);
+}
+
 /* is_quieted()
  *
  * input       - channel to check bans for, user to check bans against
@@ -605,109 +672,19 @@ int
 is_quieted(struct Channel *chptr, struct Client *who, struct membership *msptr,
           const char *s, const char *s2)
 {
-       char src_host[NICKLEN + USERLEN + HOSTLEN + 6];
-       char src_iphost[NICKLEN + USERLEN + HOSTLEN + 6];
-       char src_althost[NICKLEN + USERLEN + HOSTLEN + 6];
-       char *s3 = NULL;
-       rb_dlink_node *ptr;
-       struct Ban *actualBan = NULL;
-       struct Ban *actualExcept = NULL;
-
-       if(!MyClient(who))
-               return 0;
-
-       /* if the buffers havent been built, do it here */
-       if(s == NULL)
-       {
-               rb_sprintf(src_host, "%s!%s@%s", who->name, who->username, who->host);
-               rb_sprintf(src_iphost, "%s!%s@%s", who->name, who->username, who->sockhost);
-
-               s = src_host;
-               s2 = src_iphost;
-       }
-       if(who->localClient->mangledhost != NULL)
-       {
-               /* if host mangling mode enabled, also check their real host */
-               if(!strcmp(who->host, who->localClient->mangledhost))
-               {
-                       rb_sprintf(src_althost, "%s!%s@%s", who->name, who->username, who->orighost);
-                       s3 = src_althost;
-               }
-               /* if host mangling mode not enabled and no other spoof,
-                * also check the mangled form of their host */
-               else if (!IsDynSpoof(who))
-               {
-                       rb_sprintf(src_althost, "%s!%s@%s", who->name, who->username, who->localClient->mangledhost);
-                       s3 = src_althost;
-               }
-       }
-
-       RB_DLINK_FOREACH(ptr, chptr->quietlist.head)
-       {
-               actualBan = ptr->data;
-               if(match(actualBan->banstr, s) ||
-                  match(actualBan->banstr, s2) ||
-                  match_cidr(actualBan->banstr, s2) ||
-                  match_extban(actualBan->banstr, who, chptr, CHFL_QUIET) ||
-                  (s3 != NULL && match(actualBan->banstr, s3)))
-                       break;
-               else
-                       actualBan = NULL;
-       }
-
-       if((actualBan != NULL) && ConfigChannel.use_except)
-       {
-               RB_DLINK_FOREACH(ptr, chptr->exceptlist.head)
-               {
-                       actualExcept = ptr->data;
-
-                       /* theyre exempted.. */
-                       if(match(actualExcept->banstr, s) ||
-                          match(actualExcept->banstr, s2) ||
-                          match_cidr(actualExcept->banstr, s2) ||
-                          match_extban(actualExcept->banstr, who, chptr, CHFL_EXCEPTION) ||
-                          (s3 != NULL && match(actualExcept->banstr, s3)))
-                       {
-                               /* cache the fact theyre not banned */
-                               if(msptr != NULL)
-                               {
-                                       msptr->bants = chptr->bants;
-                                       msptr->flags &= ~CHFL_BANNED;
-                               }
-
-                               return CHFL_EXCEPTION;
-                       }
-               }
-       }
-
-       /* cache the banned/not banned status */
-       if(msptr != NULL)
-       {
-               msptr->bants = chptr->bants;
-
-               if(actualBan != NULL)
-               {
-                       msptr->flags |= CHFL_BANNED;
-                       return CHFL_BAN;
-               }
-               else
-               {
-                       msptr->flags &= ~CHFL_BANNED;
-                       return 0;
-               }
-       }
-
-       return ((actualBan ? CHFL_BAN : 0));
+       return is_banned_list(chptr, &chptr->quietlist, who, msptr, s, s2,
+                       NULL);
 }
 
 /* can_join()
  *
  * input       - client to check, channel to check for, key
- * output      - reason for not being able to join, else 0
+ * output      - reason for not being able to join, else 0, channel name to forward to
  * side effects -
+ * caveats      - this function should only be called on a local user.
  */
 int
-can_join(struct Client *source_p, struct Channel *chptr, char *key)
+can_join(struct Client *source_p, struct Channel *chptr, const char *key, const char **forward)
 {
        rb_dlink_node *invite = NULL;
        rb_dlink_node *ptr;
@@ -721,6 +698,10 @@ can_join(struct Client *source_p, struct Channel *chptr, char *key)
 
        s_assert(source_p->localClient != NULL);
 
+       moduledata.client = source_p;
+       moduledata.chptr = chptr;
+       moduledata.approved = 0;
+
        rb_sprintf(src_host, "%s!%s@%s", source_p->name, source_p->username, source_p->host);
        rb_sprintf(src_iphost, "%s!%s@%s", source_p->name, source_p->username, source_p->sockhost);
        if(source_p->localClient->mangledhost != NULL)
@@ -740,8 +721,21 @@ can_join(struct Client *source_p, struct Channel *chptr, char *key)
                }
        }
 
-       if((is_banned(chptr, source_p, NULL, src_host, src_iphost)) == CHFL_BAN)
-               return (ERR_BANNEDFROMCHAN);
+       if((is_banned(chptr, source_p, NULL, src_host, src_iphost, forward)) == CHFL_BAN)
+       {
+               moduledata.approved = ERR_BANNEDFROMCHAN;
+               goto finish_join_check;
+       }
+
+       if(*chptr->mode.key && (EmptyString(key) || irccmp(chptr->mode.key, key)))
+       {
+               moduledata.approved = ERR_BADCHANNELKEY;
+               goto finish_join_check;
+       }
+
+       /* All checks from this point on will forward... */
+       if(forward)
+               *forward = chptr->mode.forward;
 
        if(chptr->mode.mode & MODE_INVITEONLY)
        {
@@ -753,7 +747,7 @@ can_join(struct Client *source_p, struct Channel *chptr, char *key)
                if(invite == NULL)
                {
                        if(!ConfigChannel.use_invex)
-                               return (ERR_INVITEONLYCHAN);
+                               moduledata.approved = ERR_INVITEONLYCHAN;
                        RB_DLINK_FOREACH(ptr, chptr->invexlist.head)
                        {
                                invex = ptr->data;
@@ -765,13 +759,10 @@ can_join(struct Client *source_p, struct Channel *chptr, char *key)
                                        break;
                        }
                        if(ptr == NULL)
-                               return (ERR_INVITEONLYCHAN);
+                               moduledata.approved = ERR_INVITEONLYCHAN;
                }
        }
 
-       if(*chptr->mode.key && (EmptyString(key) || irccmp(chptr->mode.key, key)))
-               return (ERR_BADCHANNELKEY);
-
        if(chptr->mode.limit &&
           rb_dlink_list_length(&chptr->members) >= (unsigned long) chptr->mode.limit)
                i = ERR_CHANNELISFULL;
@@ -795,13 +786,10 @@ can_join(struct Client *source_p, struct Channel *chptr, char *key)
                                break;
                }
                if (invite == NULL)
-                       return i;
+                       moduledata.approved = i;
        }
 
-       moduledata.client = source_p;
-       moduledata.chptr = chptr;
-       moduledata.approved = 0;
-
+finish_join_check:
        call_hook(h_can_join, &moduledata);
 
        return moduledata.approved;
@@ -816,12 +804,16 @@ can_join(struct Client *source_p, struct Channel *chptr, char *key)
 int
 can_send(struct Channel *chptr, struct Client *source_p, struct membership *msptr)
 {
+       hook_data_channel_approval moduledata;
+
+       moduledata.approved = CAN_SEND_NONOP;
+
        if(IsServer(source_p) || IsService(source_p))
                return CAN_SEND_OPV;
 
        if(MyClient(source_p) && hash_find_resv(chptr->chname) &&
           !IsOper(source_p) && !IsExemptResv(source_p))
-               return CAN_SEND_NO;
+               moduledata.approved = CAN_SEND_NO;
 
        if(msptr == NULL)
        {
@@ -834,17 +826,16 @@ can_send(struct Channel *chptr, struct Client *source_p, struct membership *mspt
                         * theres no possibility of caching them --fl
                         */
                        if(chptr->mode.mode & MODE_NOPRIVMSGS || chptr->mode.mode & MODE_MODERATED)
-                               return CAN_SEND_NO;
+                               moduledata.approved = CAN_SEND_NO;
                        else
-                               return CAN_SEND_NONOP;
+                               moduledata.approved = CAN_SEND_NONOP;
+
+                       return moduledata.approved;
                }
        }
 
-       if(is_chanop_voiced(msptr))
-               return CAN_SEND_OPV;
-
        if(chptr->mode.mode & MODE_MODERATED)
-               return CAN_SEND_NO;
+               moduledata.approved = CAN_SEND_NO;
 
        if(MyClient(source_p))
        {
@@ -852,14 +843,80 @@ can_send(struct Channel *chptr, struct Client *source_p, struct membership *mspt
                if(msptr->bants == chptr->bants)
                {
                        if(can_send_banned(msptr))
-                               return CAN_SEND_NO;
+                               moduledata.approved = CAN_SEND_NO;
                }
-               else if(is_banned(chptr, source_p, msptr, NULL, NULL) == CHFL_BAN
+               else if(is_banned(chptr, source_p, msptr, NULL, NULL, NULL) == CHFL_BAN
                        || is_quieted(chptr, source_p, msptr, NULL, NULL) == CHFL_BAN)
-                       return CAN_SEND_NO;
+                       moduledata.approved = CAN_SEND_NO;
+       }
+
+       if(is_chanop_voiced(msptr))
+               moduledata.approved = CAN_SEND_OPV;
+
+       moduledata.client = source_p;
+       moduledata.chptr = msptr->chptr;
+       moduledata.msptr = msptr;
+       moduledata.target = NULL;
+
+       call_hook(h_can_send, &moduledata);
+
+       return moduledata.approved;
+}
+
+/*
+ * flood_attack_channel
+ * inputs       - flag 0 if PRIVMSG 1 if NOTICE. RFC
+ *                says NOTICE must not auto reply
+ *              - pointer to source Client 
+ *             - pointer to target channel
+ * output      - 1 if target is under flood attack
+ * side effects        - check for flood attack on target chptr
+ */
+int
+flood_attack_channel(int p_or_n, struct Client *source_p, struct Channel *chptr, char *chname)
+{
+       int delta;
+
+       if(GlobalSetOptions.floodcount && MyClient(source_p))
+       {
+               if((chptr->first_received_message_time + 1) < rb_current_time())
+               {
+                       delta = rb_current_time() - chptr->first_received_message_time;
+                       chptr->received_number_of_privmsgs -= delta;
+                       chptr->first_received_message_time = rb_current_time();
+                       if(chptr->received_number_of_privmsgs <= 0)
+                       {
+                               chptr->received_number_of_privmsgs = 0;
+                               chptr->flood_noticed = 0;
+                       }
+               }
+
+               if((chptr->received_number_of_privmsgs >= GlobalSetOptions.floodcount)
+                  || chptr->flood_noticed)
+               {
+                       if(chptr->flood_noticed == 0)
+                       {
+                               sendto_realops_snomask(SNO_BOTS, *chptr->chname == '&' ? L_ALL : L_NETWIDE,
+                                                    "Possible Flooder %s[%s@%s] on %s target: %s",
+                                                    source_p->name, source_p->username,
+                                                    source_p->orighost,
+                                                    source_p->servptr->name, chptr->chname);
+                               chptr->flood_noticed = 1;
+
+                               /* Add a bit of penalty */
+                               chptr->received_number_of_privmsgs += 2;
+                       }
+                       if(MyClient(source_p) && (p_or_n != 1))
+                               sendto_one(source_p,
+                                          ":%s NOTICE %s :*** Message to %s throttled due to flooding",
+                                          me.name, source_p->name, chptr->chname);
+                       return 1;
+               }
+               else
+                       chptr->received_number_of_privmsgs++;
        }
 
-       return CAN_SEND_NONOP;
+       return 0;
 }
 
 /* find_bannickchange_channel()
@@ -893,7 +950,7 @@ find_bannickchange_channel(struct Client *client_p)
                        if (can_send_banned(msptr))
                                return chptr;
                }
-               else if (is_banned(chptr, client_p, msptr, src_host, src_iphost) == CHFL_BAN
+               else if (is_banned(chptr, client_p, msptr, src_host, src_iphost, NULL) == CHFL_BAN
                        || is_quieted(chptr, client_p, msptr, src_host, src_iphost) == CHFL_BAN)
                        return chptr;
        }
@@ -1125,7 +1182,8 @@ channel_modes(struct Channel *chptr, struct Client *client_p)
                                           chptr->mode.join_time);
        }
 
-       if(*chptr->mode.forward && (ConfigChannel.use_forward || !IsClient(client_p)))
+       if(*chptr->mode.forward &&
+                       (ConfigChannel.use_forward || !IsClient(client_p)))
        {
                *mbuf++ = 'f';