/* chanset.c -- handles: low-level channel and chanset manipulation channel pointers to the userrec cache check expired channel stuff dprintf'ized, 5feb96 multi-channel, 6feb96 */ /* This file is part of the eggdrop source code copyright (c) 1997 Robey Pointer and is distributed according to the GNU general public license. For full details, read the top of 'main.c' or the file called COPYING that was distributed with this code. */ #if HAVE_CONFIG_H #include #endif #include #include #include #include #include "eggdrop.h" #include "users.h" #include "chan.h" #include "proto.h" /* data for each channel */ struct chanset_t *chanset=NULL; extern int cx_line; extern char cx_file[]; extern char botname[]; extern char newbotname[]; extern int serv; extern int ban_time; /* memory expected to be used by this module */ int expmem_chan() { int tot=0; banlist *b; struct chanset_t *chan=chanset; while (chan!=NULL) { tot+=sizeof(struct chanset_t); tot+=strlen(chan->channel.key)+1; tot+=(sizeof(struct memstruct)*(chan->channel.members+1)); b=chan->channel.ban; while (b!=NULL) { tot+=strlen(b->ban)+1; if (b->ban[0]) tot+=strlen(b->who)+1; tot+=sizeof(struct banstruct); b=b->next; } chan=chan->next; } return tot; } /* find a chanset by channel name */ struct chanset_t *findchan(name) char *name; { struct chanset_t *chan=chanset; while (chan!=NULL) { if (strcasecmp(chan->name,name)==0) return chan; chan=chan->next; } return NULL; } /* get pointer to new chanset */ struct chanset_t *newchanset() { struct chanset_t *c; c=(struct chanset_t *)nmalloc(sizeof(struct chanset_t)); return c; } /* add a chanset pointer to the list */ void addchanset(chan) struct chanset_t *chan; { struct chanset_t *c=chanset,*old=NULL; chan->next=NULL; while (c!=NULL) { old=c; c=c->next; } if (old!=NULL) old->next=chan; else chanset=chan; } /* destroy a chanset in the list */ /* does NOT free up memory associated with channel data inside the chanset! */ int killchanset(name) char *name; { struct chanset_t *c=chanset,*old=NULL; while (c!=NULL) { if (strcasecmp(c->name,name)==0) { if (old!=NULL) old->next=c->next; else chanset=c->next; nfree(c); return 1; } old=c; c=c->next; } return 0; } /* get channels list */ void getchanlist(s,maxlen) char *s; int maxlen; { struct chanset_t *chan=chanset; s[0]=0; while (chan!=NULL) { if (strlen(s)+strlen(chan->name)+1 > maxlen) return; if (s[0]) strcat(s," "); strcat(s,chan->name); chan=chan->next; } } /* set the key */ void set_key(chan,k) struct chanset_t *chan; char *k; { context; nfree(chan->channel.key); if (k==NULL) { chan->channel.key=(char *)nmalloc(1); chan->channel.key[0]=0; return; } chan->channel.key=(char *)nmalloc(strlen(k)+1); strcpy(chan->channel.key,k); } /* is this channel +s/+p? */ int channel_hidden(chan) struct chanset_t *chan; { return (chan->channel.mode&(CHANPRIV|CHANSEC)); } /* is this channel +t? */ int channel_optopic(chan) struct chanset_t *chan; { return (chan->channel.mode&CHANTOPIC); } int hand_on_chan(chan,handle) struct chanset_t *chan; char *handle; { char s[UHOSTLEN],h[10]; memberlist *m=chan->channel.member; while (m->nick[0]) { sprintf(s,"%s!%s",m->nick,m->userhost); get_handle_by_host(h,s); if (strcasecmp(h,handle)==0) return 1; m=m->next; } return 0; } /* initialize out the channel record */ void init_channel(chan) struct chanset_t *chan; { context; chan->channel.maxmembers=(-1); chan->channel.mode=0; chan->channel.members=0; chan->channel.key=(char *)nmalloc(1); chan->channel.key[0]=0; chan->channel.ban=(banlist *)nmalloc(sizeof(banlist)); chan->channel.ban->ban=(char *)nmalloc(1); chan->channel.ban->ban[0]=0; chan->channel.ban->who=NULL; chan->channel.ban->next=NULL; chan->channel.member=(memberlist *)nmalloc(sizeof(memberlist)); chan->channel.member->nick[0]=0; chan->channel.member->next=NULL; } /* clear out channel data from memory */ void clear_channel(chan,reset) struct chanset_t *chan; int reset; { memberlist *m,*m1; banlist *b,*b1; context; nfree(chan->channel.key); m=chan->channel.member; while (m!=NULL) { m1=m->next; nfree(m); m=m1; } b=chan->channel.ban; while (b!=NULL) { b1=b->next; if (b->ban[0]) nfree(b->who); nfree(b->ban); nfree(b); b=b1; } if (reset) init_channel(chan); context; } /* reset all the channels, as if we just left a server or something */ void clear_channels() { struct chanset_t *chan; chan=chanset; while (chan!=NULL) { clear_channel(chan,1); chan->stat&=~(CHANPEND|CHANACTIVE); chan=chan->next; } } /* shortcut for get_user_by_host -- might have user record in one */ /* of the channel caches */ struct userrec *check_chanlist(host) char *host; { char nick[NICKLEN],uhost[UHOSTLEN],s1[NICKLEN]; memberlist *m; struct chanset_t *chan; chan=chanset; strncpy(uhost,host,UHOSTLEN); uhost[UHOSTLEN-1]=0; strncpy(s1,host,NICKLEN); s1[NICKLEN-1]=0; splitnick(nick,s1); while (chan!=NULL) { m=chan->channel.member; while (m->nick[0]) { if ((strcasecmp(uhost,m->userhost)==0) && (strcasecmp(nick,m->nick)==0)) return m->user; m=m->next; } chan=chan->next; } return NULL; } /* shortcut for get_user_by_handle -- might have user record in channels */ struct userrec *check_chanlist_hand(hand) char *hand; { struct chanset_t *chan=chanset; memberlist *m; while (chan!=NULL) { m=chan->channel.member; while (m->nick[0]) { if (m->user != NULL) if (strcasecmp(m->user->handle,hand)==0) return m->user; m=m->next; } chan=chan->next; } return NULL; } /* clear the user pointers in the chanlists */ /* (necessary when a hostmask is added/removed or a user is added) */ void clear_chanlist() { memberlist *m; struct chanset_t *chan=chanset; while (chan!=NULL) { m=chan->channel.member; while (m->nick[0]) { m->user=NULL; m=m->next; } chan=chan->next; } } /* if this user@host is in a channel, set it (it was null) */ void set_chanlist(host,rec) char *host; struct userrec *rec; { char nick[NICKLEN],uhost[UHOSTLEN]; memberlist *m; struct chanset_t *chan=chanset; strcpy(uhost,host); splitnick(nick,uhost); while (chan!=NULL) { m=chan->channel.member; while (m->nick[0]) { if ((strcasecmp(uhost,m->userhost)==0) && (strcasecmp(nick,m->nick)==0)) m->user=rec; m=m->next; } chan=chan->next; } } int defined_channel(name) char *name; { struct chanset_t *chan; chan=findchan(name); if (chan==NULL) return 0; return 1; } int active_channel(name) char *name; { struct chanset_t *chan; chan=findchan(name); if (chan==NULL) return 0; if (chan->stat&CHANACTIVE) return 1; return 0; } /* returns a pointer to a new channel member structure */ memberlist *newmember(chan) struct chanset_t *chan; { memberlist *x; x=chan->channel.member; while (x->nick[0]) x=x->next; x->next=(memberlist *)nmalloc(sizeof(memberlist)); x->next->next=NULL; x->next->nick[0]=0; x->next->split=0L; x->next->last=0L; chan->channel.members++; return x; } /* adds a ban to the list */ void newban(chan,s,who) struct chanset_t *chan; char *s; char *who; { banlist *b; context; b=chan->channel.ban; while ((b->ban[0]) && (strcasecmp(b->ban,s)!=0)) b=b->next; if (b->ban[0]) return; /* already existent ban */ b->next=(banlist *)nmalloc(sizeof(banlist)); b->next->next=NULL; b->next->ban=(char *)nmalloc(1); b->next->ban[0]=0; nfree(b->ban); b->ban=(char *)nmalloc(strlen(s)+1); strcpy(b->ban,s); b->who=(char *)nmalloc(strlen(who)+1); strcpy(b->who,who); b->timer=time(NULL); } /* removes a nick from the channel member list (returns 1 if successful) */ int killmember(chan,nick) struct chanset_t *chan; char *nick; { memberlist *x,*old; x=chan->channel.member; old=NULL; while ((x->nick[0]) && (strcasecmp(x->nick,nick)!=0)) { old=x; x=x->next; } if ((x->nick[0]==0) && (!(chan->channel.mode & CHANPEND))) { putlog(LOG_MISC,"*","(!) killmember(%s) -> nonexistent",nick); return 0; } if (old==NULL) chan->channel.member=x->next; else old->next=x->next; nfree(x); chan->channel.members--; return 1; } /* removes a ban from the list */ int killban(chan,s) struct chanset_t *chan; char *s; { banlist *b,*old; context; b=chan->channel.ban; old=NULL; while ((b->ban[0]) && (strcasecmp(b->ban,s)!=0)) { old=b; b=b->next; } if (b->ban[0]==0) return 0; if (old==NULL) chan->channel.ban=b->next; else old->next=b->next; nfree(b->ban); nfree(b->who); nfree(b); return 1; } /* returns memberfields if the nick is in the member list */ memberlist *ismember(chan,nick) struct chanset_t *chan; char *nick; { memberlist *x; x=chan->channel.member; while ((x->nick[0]) && (strcasecmp(x->nick,nick)!=0)) x=x->next; if (x->nick[0]==0) return NULL; return x; } /* boolean form for other modules to use */ int ischanmember(chname,nick) char *nick,*chname; { struct chanset_t *chan; chan=findchan(chname); if (chan==NULL) return 0; return (ismember(chan,nick) != NULL); } /* am i a chanop? */ int me_op(chan) struct chanset_t *chan; { memberlist *mx=NULL; if (newbotname[0]) mx=ismember(chan,newbotname); if (mx==NULL) mx=ismember(chan,botname); if (mx==NULL) return 0; if (mx->flags&CHANOP) return 1; else return 0; } /* are there any ops on the channel? */ int any_ops(chan) struct chanset_t *chan; { memberlist *x=chan->channel.member; while ((x->nick[0]) && (!(x->flags&CHANOP))) x=x->next; if (x->nick[0]==0) return 0; return 1; } /* returns true if this is one of the channel bans */ int isbanned(chan,user) struct chanset_t *chan; char *user; { banlist *b; context; b=chan->channel.ban; while ((b->ban[0]) && (strcasecmp(b->ban,user)!=0)) b=b->next; if (b->ban[0]==0) return 0; return 1; } void getchanhost(chname,nick,host) char *chname,*nick,*host; { struct chanset_t *chan; memberlist *m; host[0]=0; chan=findchan(chname); if (chan==NULL) return; m=ismember(chan,nick); if (m==NULL) return; strcpy(host,m->userhost); } int is_split(chname,nick) char *chname,*nick; { memberlist *m; struct chanset_t *chan; chan=findchan(chname); if (chan==NULL) return 0; m=ismember(chan,nick); if (m==NULL) return 0; return (int)(m->split); } /* called only if dynamic-bans is on */ void check_expired_chanbans() { banlist *b; time_t now=time(NULL); struct chanset_t *chan; context; chan=chanset; while (chan!=NULL) { if ((chan->stat&CHAN_DYNAMICBANS) && (me_op(chan))) { b=chan->channel.ban; while (b->ban[0]) { if (now - b->timer > 60*ban_time) { putlog(LOG_MODES,chan->name,"(%s) Channel ban on %s expired.", chan->name,b->ban); add_mode(chan,'-','b',b->ban); b->timer=time(NULL); /* reset timer to avoid repititive deban */ } b=b->next; } } chan=chan->next; } context; } /* kick anyone off the channel who matches a ban */ void kick_match_ban(chan,ban) struct chanset_t *chan; char *ban; { memberlist *m; char s[UHOSTLEN]; context; if (!(chan->stat&CHAN_ENFORCEBANS)) return; m=chan->channel.member; while (m->nick[0]) { sprintf(s,"%s!%s",m->nick,m->userhost); if ((wild_match(ban,s)) && (strcasecmp(m->nick,botname)!=0)) mprintf(serv,"KICK %s %s :banned\n",chan->name,m->nick); m=m->next; } } /* kick everyone on the channel with matching hostmask that joined since the time stamp */ void kick_match_since(chan,mask,timestamp) struct chanset_t *chan; char *mask; time_t timestamp; { char s[UHOSTLEN]; memberlist *m=chan->channel.member; context; while (m->nick[0]) { sprintf(s,"%s!%s",m->nick,m->userhost); if ((wild_match(mask,s)) && (m->joined >= timestamp)) tprintf(serv,"KICK %s %s :lemmingbot\n",chan->name,m->nick); m=m->next; } } /* resets the bans on the channel */ void resetbans(chan) struct chanset_t *chan; { banlist *b=chan->channel.ban; context; if (!me_op(chan)) return; /* can't do it */ /* remove bans we didn't put there */ while (b->ban[0]) { if ((!equals_ban(b->ban)) && (!u_equals_ban(chan->bans,b->ban))) add_mode(chan,'-','b',b->ban); b=b->next; } context; /* make sure the intended bans are still there */ recheck_bans(chan); context; } /* remove any bogus bans */ void kill_bogus_bans(chan) struct chanset_t *chan; { banlist *b=chan->channel.ban; int bogus,i; context; if (!me_op(chan)) return; while (b->ban[0]) { bogus=0; for (i=0; iban); i++) if ((b->ban[i]<32) || (b->ban[i]>126)) bogus=1; if (bogus) add_mode(chan,'-','b',b->ban); b=b->next; } } /* check that this person has ops on all AUTOOP channels */ void recheck_ops(nick,hand) char *nick,*hand; { struct chanset_t *chan=chanset; int atr; atr=get_attr_handle(hand); while (chan!=NULL) { if ((atr & USER_OP) && (chan->stat & CHAN_OPONJOIN)) add_mode(chan,'+','o',nick); chan=chan->next; } } /* things to do when i just became a chanop: */ void recheck_channel(chan) struct chanset_t *chan; { memberlist *m; char s[UHOSTLEN],hand[10]; int atr; /* okay, sort through who needs to be deopped. */ context; m=chan->channel.member; while (m->nick[0]) { sprintf(s,"%s!%s",m->nick,m->userhost); atr=get_attr_host(s); get_handle_by_host(hand,s); /* ignore myself */ if ((newbotname[0]) && (strcasecmp(m->nick,newbotname)==0)) { /* skip */ } else if ((!newbotname[0]) && (strcasecmp(m->nick,botname)==0)) { /* skip */ } else { if ((m->flags&CHANOP) && ((atr & USER_DEOP) || ((chan->stat&CHAN_BITCH) && (!(atr & USER_OP))))) add_mode(chan,'-','o',m->nick); if ((!(m->flags&CHANOP)) && (atr & USER_OP) && (chan->stat&CHAN_OPONJOIN)) add_mode(chan,'+','o',m->nick); if ((chan->stat&CHAN_ENFORCEBANS) && ((match_ban(s)) || (u_match_ban(chan->bans,s)))) refresh_ban_kick(chan,s,m->nick); /* ^ will use the ban comment */ else if (atr & USER_KICK) { get_handle_comment(hand,s); if (!s[0]) tprintf(serv,"KICK %s %s :...and thank you for playing.\n", chan->name,m->nick); else tprintf(serv,"KICK %s %s :%s\n",chan->name,m->nick,s); } } m=m->next; } recheck_bans(chan); context; recheck_chanmode(chan); context; } /* recheck ALL channels */ void recheck_channels() { struct chanset_t *chan; chan=chanset; while (chan!=NULL) { recheck_channel(chan); chan=chan->next; } } void newly_chanop(chan) struct chanset_t *chan; { recheck_channel(chan); /* find out the bans now */ tprintf(serv,"MODE %s +b\n",chan->name); } /* is user x a chanop? */ int member_op(chname,x) char *chname,*x; { memberlist *mx; struct chanset_t *chan; chan=findchan(chname); if (chan==NULL) return 0; mx=ismember(chan,x); if (mx==NULL) return 0; if (mx->flags&CHANOP) return 1; else return 0; } /* is user x a voice? (+v) */ int member_voice(chname,x) char *chname,*x; { memberlist *mx; struct chanset_t *chan; chan=findchan(chname); if (chan==NULL) return 0; mx=ismember(chan,x); if (mx==NULL) return 0; if (mx->flags&CHANVOICE) return 1; else return 0; } /* reset the channel information */ void reset_chan_info(chan) struct chanset_t *chan; { clear_channel(chan,1); chan->stat|=CHANPEND; chan->stat&=~CHANACTIVE; tprintf(serv,"WHO %s\n",chan->name); tprintf(serv,"MODE %s\n",chan->name); if (me_op(chan)) newly_chanop(chan); } /* if i'm the only person on the channel, and i'm not op'd, might as well leave and rejoin */ /* if i'm NOT the only person on the channel, but i'm still not op'd, demand ops */ void check_lonely_channel(chan) struct chanset_t *chan; { memberlist *m; char s[UHOSTLEN]; int i=0; static int whined=0; context; if (chan->stat&CHANPEND) return; m=chan->channel.member; if ((chan->stat&CHANACTIVE) && (!me_op(chan))) { /* count non-split channel members */ while (m->nick[0]) { if (m->split == 0L) i++; m=m->next; } if (i==1) { tprintf(serv,"PART %s\n",chan->name); tprintf(serv,"JOIN %s %s\n",chan->name,chan->key_prot); putlog(LOG_MISC,"*","Trying to cycle %s to regain ops.",chan->name); whined=0; } else if (any_ops(chan)) { whined=0; if (chan->need_op[0]) do_tcl("need-op",chan->need_op); } else { /* other people here, but none are ops */ /* are there other bots? make them LEAVE. */ int ok=1; if (!whined) { putlog(LOG_MISC,"*","%s is active but has no ops :(",chan->name); whined=1; } m=chan->channel.member; while (m->nick[0]) { sprintf(s,"%s!%s",m->nick,m->userhost); if ((strcasecmp(m->nick,botname)!=0) && (!(get_attr_host(s) & USER_BOT))) ok=0; m=m->next; } if (ok) { /* ALL bots! make them LEAVE!!! */ m=chan->channel.member; while (m->nick[0]) { mprintf(serv,"PRIVMSG %s :go %s\n",m->nick,chan->name); m=m->next; } } else { /* some humans on channel, but still op-less */ if (chan->need_op[0]) do_tcl("need-op",chan->need_op); } } } } void check_lonely_channels() { struct chanset_t *chan; chan=chanset; while (chan!=NULL) { check_lonely_channel(chan); chan=chan->next; } } /* check for expired netsplit people */ void check_expired_splits() { memberlist *m,*n; time_t now; char s[UHOSTLEN],hand[10]; struct chanset_t *chan; context; now=time(NULL); chan=chanset; while (chan!=NULL) { m=chan->channel.member; while (m->nick[0]) { if (m->split) { n=m->next; if (!(chan->stat&CHANACTIVE)) killmember(chan,m->nick); else if (now-(m->split) > WAIT_SPLIT) { sprintf(s,"%s!%s",m->nick,m->userhost); get_handle_by_host(hand,s); check_tcl_sign(m->nick,m->userhost,hand,chan->name, "lost in the netsplit"); context; putlog(LOG_JOIN,chan->name,"%s (%s) got lost in the net-split.", m->nick,m->userhost); killmember(chan,m->nick); } m=n; } else m=m->next; } chan=chan->next; } } void check_for_split() { /* called once a minute... but if we're the only one on the channel, we only wanna send out "lusers" once every 5 mins */ static count=4; int ok=0; struct chanset_t *chan; context; chan=chanset; while (chan!=NULL) { if ((chan->stat&CHANACTIVE) && (chan->channel.members==1)) ok=1; chan=chan->next; } if (!ok) return; count++; if (count>=5) { mprintf(serv,"LUSERS\n"); count=0; } } /* called once a minute to kick idlers */ void check_idle_kick() { memberlist *m; time_t now; char s[UHOSTLEN]; int atr; struct chanset_t *chan; context; now=time(NULL); chan=chanset; while (chan!=NULL) { if ((chan->stat&CHANACTIVE) && (chan->idle_kick)) { m=chan->channel.member; while (m->nick[0]) { if ((now-(m->last) >= chan->idle_kick*60) && (strcmp(m->nick,botname)!=0) && (strcmp(m->nick,newbotname)!=0)) { sprintf(s,"%s!%s",m->nick,m->userhost); atr=get_attr_host(s); if (!(atr & (USER_MASTER|USER_BOT|USER_FRIEND|USER_OP))) mprintf(serv,"KICK %s %s :idle %d min\n",chan->name,m->nick, chan->idle_kick); } m=m->next; } } chan=chan->next; } } void update_idle(chname,nick) char *chname,*nick; { memberlist *m; struct chanset_t *chan; chan=findchan(chname); if (chan==NULL) return; m=ismember(chan,nick); if (m==NULL) return; m->last=time(NULL); } /* write channel's local banlist to a file */ int write_chanbans(f) FILE *f; { struct chanset_t *chan; struct eggqueue *q; chan=chanset; while (chan!=NULL) { if (fprintf(f,"::%s bans\n",chan->name)==EOF) return 0; q=chan->bans->host; while (q!=NULL) { if (strcmp(q->item,"none")!=0) if (fprintf(f,"- %s\n",q->item)==EOF) return 0; q=q->next; } chan=chan->next; } return 1; } /* channel ban loaded from user file */ void restore_chanban(chname,host) char *chname,*host; { struct chanset_t *chan; chan=findchan(chname); if (chan==NULL) { putlog(LOG_MISC,"*","* Trying to load ban to nonexistent channel '%s'!", chname); return; } addhost_by_handle2(chan->bans,"null",host); }