]>
jfr.im git - irc/quakenet/newserv.git/blob - channel/channelhandlers.c
7 #include "../server/server.h"
8 #include "../nick/nick.h"
9 #include "../lib/irc_string.h"
10 #include "../irc/irc_config.h"
11 #include "../parser/parser.h"
12 #include "../irc/irc.h"
13 #include "../lib/base64.h"
14 #include "../lib/strlfunc.h"
16 int handleburstmsg(void *source
, int cargc
, char **cargv
) {
24 int waslimit
,waskeyed
;
26 unsigned long currentmode
;
29 /* (we don't see the first 2 params in cargc) */
30 /* AK B #+lod+ 1017561154 +tnk eits ATJWu:o,AiW1a,Ag3lV,AiWnl,AE6oI :%*!@D577A90D.kabel.telenet.be */
33 Error("channel",ERR_WARNING
,"Burst message with only %d parameters",cargc
);
37 timestamp
=strtol(cargv
[1],NULL
,10);
39 if ((cp
=findchannel(cargv
[0]))==NULL
) {
40 /* We don't have this channel already */
41 cp
=createchannel(cargv
[0]);
42 cp
->timestamp
=timestamp
;
46 if (timestamp
<cp
->timestamp
) {
47 /* The incoming timestamp is older. Erase all our current channel modes, and the topic. */
48 cp
->timestamp
=timestamp
;
49 freesstring(cp
->topic
);
57 /* Remove all +v, +o we currently have */
58 for(i
=0;i
<cp
->users
->hashsize
;i
++) {
59 if (cp
->users
->content
[i
]!=nouser
) {
60 cp
->users
->content
[i
]&=CU_NUMERICMASK
;
63 } else if (timestamp
>cp
->timestamp
) {
64 /* The incoming timestamp is greater. Ignore any incoming modes they may happen to set */
69 /* OK, dealt with the name and timestamp.
70 * Loop over the remaining args */
71 for (arg
=2;arg
<cargc
;arg
++) {
72 if (cargv
[arg
][0]=='+') {
75 /* We ignore the modes, but we need to see if they include +l or +k
76 * so that we can ignore their corresponding values */
77 for (charp
=cargv
[arg
];*charp
;charp
++) {
78 if (*charp
=='k' || *charp
=='l') {
83 /* Clear off the limit and key flags before calling setflags so we can see if the burst tried to set them */
84 /* If the burst doesn't set them, we restore them afterwards */
85 waslimit
=IsLimit(cp
); ClearLimit(cp
);
86 waskeyed
=IsKey(cp
); ClearKey(cp
);
87 /* We can then use the flag function for these modes */
88 setflags(&(cp
->flags
),CHANMODE_ALL
,cargv
[arg
],cmodeflags
,REJECT_NONE
);
89 /* Pick up the limit and key, if they were set. Note that the limit comes first */
90 if (IsLimit(cp
)) { /* A limit was SET by the burst */
92 /* Ran out of args -- damn ircd is spewing out crap again */
93 Error("channel",ERR_WARNING
,"Burst +l with no argument");
94 break; /* "break" being the operative word */
96 newlimit
=strtol(cargv
[arg
],NULL
,10);
98 if (cp
->limit
>0 && waslimit
) {
99 /* We had a limit before -- we now have the lowest one of the two */
100 if (newlimit
<cp
->limit
) {
104 /* No limit before -- we just have the new one */
107 } else if (waslimit
) {
108 SetLimit(cp
); /* We had a limit before, but the burst didn't set one. Restore flag. */
111 if (IsKey(cp
)) { /* A key was SET by the burst */
113 /* Ran out of args -- oopsie! */
114 Error("channel",ERR_WARNING
,"Burst +k with no argument");
118 /* We had a key before -- alphabetically first wins */
119 if (ircd_strcmp(cargv
[arg
],cp
->key
->content
)<0) {
120 /* Replace our key */
121 freesstring(cp
->key
);
122 cp
->key
=getsstring(cargv
[arg
],KEYLEN
);
125 /* No key before -- just the new one */
126 cp
->key
=getsstring(cargv
[arg
],KEYLEN
);
128 } else if (waskeyed
) {
129 SetKey(cp
); /* We had a key before, but the burst didn't set one. Restore flag. */
132 } else if (cargv
[arg
][0]=='%') {
133 /* We have one or more bans here */
134 nextnum
=cargv
[arg
]+1;
136 /* Split off the next ban */
137 for (charp
=nextnum
;*charp
;charp
++) {
148 /* List of numerics */
149 nextnum
=charp
=cargv
[arg
];
151 while (*nextnum
!='\0') {
152 /* Step over the next numeric */
163 } else if (*charp
==':') {
168 for (;*charp
;charp
++) {
170 currentmode
|=CUMODE_VOICE
;
171 } else if (*charp
=='o') {
172 currentmode
|=CUMODE_OP
;
173 } else if (*charp
==',') {
178 /* If we're ignore incoming modes, zap it to zero again */
183 /* OK. At this point charp points to either '\0' if we're at the end,
184 * or the start of the next numeric otherwise. nextnum points at a valid numeric
185 * we need to add, and currentmode reflects the correct mode */
186 addnicktochannel(cp
,(numerictolong(nextnum
,5)|currentmode
));
191 if (cp
->users
->totalusers
==0) {
192 /* Oh dear, the channel is now empty. Perhaps one of those
193 * charming empty burst messages you get sometimes.. */
195 /* I really don't think this can happen, can it..? */
196 /* Only send the LOSTCHANNEL if the channel existed before */
197 triggerhook(HOOK_CHANNEL_LOSTCHANNEL
,cp
);
201 /* If this is a new channel, we do the NEWCHANNEL hook also */
203 triggerhook(HOOK_CHANNEL_NEWCHANNEL
,cp
);
205 /* Just one hook to say "something happened to this channel" */
206 triggerhook(HOOK_CHANNEL_BURST
,cp
);
212 int handlejoinmsg(void *source
, int cargc
, char **cargv
) {
226 /* We must have received a timestamp too */
227 timestamp
=strtol(cargv
[1],NULL
,10);
230 /* Find out who we are talking about here */
231 np
=getnickbynumericstr(source
);
233 Error("channel",ERR_WARNING
,"Channel join from non existent user %s",(char *)source
);
237 nextchan
=pos
=cargv
[0];
238 while (*nextchan
!='\0') {
239 /* Find the next chan position */
240 for (;*pos
!='\0' && *pos
!=',';pos
++)
248 /* OK, pos now points at either null or the next chan
249 * and nextchan now points at the channel name we want to parse next */
251 if(nextchan
[0]=='0' && nextchan
[1]=='\0') {
252 /* Leave all channels
253 * We do this as if they were leaving the network,
254 * then vape their channels array and start again */
255 ch
=(channel
**)(np
->channels
->content
);
256 for(i
=0;i
<np
->channels
->cursi
;i
++) {
261 triggerhook(HOOK_CHANNEL_PART
,harg
);
262 delnickfromchannel(ch
[i
],np
->numeric
,0);
264 array_free(np
->channels
);
265 array_init(np
->channels
,sizeof(channel
*));
267 /* It's an actual channel join */
269 if ((cp
=findchannel(nextchan
))==NULL
) {
270 /* User joined non-existent channel - create it. Note that createchannel automatically
271 * puts the right magic timestamp in for us */
272 Error("channel",ERR_DEBUG
,"User %s joined non existent channel %s.",np
->nick
,nextchan
);
273 cp
=createchannel(nextchan
);
276 if (cp
->timestamp
==MAGIC_REMOTE_JOIN_TS
&& timestamp
) {
277 /* No valid timestamp on the chan and we received one -- set */
278 cp
->timestamp
=timestamp
;
280 /* OK, this is slightly inefficient since we turn the nick * into a numeric,
281 * and addnicktochannel then converts it back again. BUT it's fewer lines of code :) */
282 if (addnicktochannel(cp
,np
->numeric
)) {
283 /* The user wasn't added */
288 chanindex
*cip
=cp
->index
;
290 /* If we just created a channel, flag it */
292 triggerhook(HOOK_CHANNEL_NEWCHANNEL
,cp
);
295 /* Don't send HOOK_CHANNEL_JOIN if the channel doesn't exist any
296 * more (can happen if something destroys it in response to
297 * HOOK_CHANNEL_NEWCHANNEL) */
298 if (cp
== cip
->channel
) {
302 triggerhook(HOOK_CHANNEL_JOIN
,harg
);
311 int handlecreatemsg(void *source
, int cargc
, char **cargv
) {
323 timestamp
=strtol(cargv
[1],NULL
,10);
325 /* Find out who we are talking about here */
326 np
=getnickbynumericstr(source
);
328 Error("channel",ERR_WARNING
,"Channel create from non existent user %s",(char *)source
);
332 nextchan
=pos
=cargv
[0];
333 while (*nextchan
!='\0') {
334 /* Find the next chan position */
335 for (;*pos
!='\0' && *pos
!=',';pos
++)
343 /* OK, pos now points at either null or the next chan
344 * and nextchan now points at the channel name we want to parse next */
346 /* It's a channel create */
347 if ((cp
=findchannel(nextchan
))==NULL
) {
348 /* This is the expected case -- the channel didn't exist before */
349 cp
=createchannel(nextchan
);
350 cp
->timestamp
=timestamp
;
353 Error("channel",ERR_DEBUG
,"Received CREATE for already existing channel %s",cp
->index
->name
->content
);
354 if (cp
->timestamp
==MAGIC_REMOTE_JOIN_TS
&& timestamp
) {
355 /* No valid timestamp on the chan and we received one -- set */
356 cp
->timestamp
=timestamp
;
360 /* Add the user to the channel, preopped */
361 if (addnicktochannel(cp
,(np
->numeric
)|CUMODE_OP
)) {
366 chanindex
*cip
= cp
->index
;
368 /* Flag the channel as new if necessary */
370 triggerhook(HOOK_CHANNEL_NEWCHANNEL
,cp
);
373 /* If HOOK_CHANNEL_NEWCHANNEL has caused the channel to be deleted,
374 * don't trigger the CREATE hook. */
375 if (cip
->channel
== cp
) {
379 triggerhook(HOOK_CHANNEL_CREATE
,harg
);
388 int handlepartmsg(void *source
, int cargc
, char **cargv
) {
399 Error("channel",ERR_WARNING
,"PART with too many parameters (%d)",cargc
);
403 harg
[2]=(void *)cargv
[1];
408 /* Find out who we are talking about here */
409 np
=getnickbynumericstr(source
);
411 Error("channel",ERR_WARNING
,"PART from non existent numeric %s",(char *)source
);
417 nextchan
=pos
=cargv
[0];
418 while (*nextchan
!='\0') {
419 /* Find the next chan position */
420 for (;*pos
!='\0' && *pos
!=',';pos
++)
428 /* OK, pos now points at either null or the next chan
429 * and nextchan now points at the channel name we want to parse next */
431 if ((cp
=findchannel(nextchan
))==NULL
) {
432 /* Erm, parting a channel that's not there?? */
433 Error("channel",ERR_WARNING
,"Nick %s left non-existent channel %s",np
->nick
,nextchan
);
435 /* Trigger hook *FIRST* */
437 triggerhook(HOOK_CHANNEL_PART
,harg
);
439 delnickfromchannel(cp
,np
->numeric
,1);
447 int handlekickmsg(void *source
, int cargc
, char **cargv
) {
456 /* Find out who we are talking about here */
457 if ((np
=getnickbynumericstr(cargv
[1]))==NULL
) {
458 Error("channel",ERR_DEBUG
,"Non-existant numeric %s kicked from channel %s",(char *)source
,cargv
[0]);
462 /* And who did the kicking */
463 if (((char *)source
)[2]=='\0') {
464 /* 'Twas a server.. */
466 } else if ((kicker
=getnickbynumericstr((char *)source
))==NULL
) {
467 /* It looks strange, but we let the kick go through anyway */
468 Error("channel",ERR_DEBUG
,"Kick from non-existant nick %s",(char *)source
);
471 /* And find out which channel */
472 if ((cp
=findchannel(cargv
[0]))==NULL
) {
473 /* OK, not a channel that actually exists then.. */
474 Error("channel",ERR_DEBUG
,"Nick %s kicked from non-existent channel %s",np
->nick
,cargv
[0]);
476 /* Before we do anything else, we have to acknowledge the kick to the network */
477 if (homeserver(np
->numeric
)==mylongnum
) {
478 irc_send("%s L %s",longtonumeric(np
->numeric
,5),cp
->index
->name
->content
);
481 /* Trigger hook *FIRST* */
486 triggerhook(HOOK_CHANNEL_KICK
,harg
);
488 /* We let delnickfromchannel() worry about whether this nick is actually on this channel */
489 delnickfromchannel(cp
,np
->numeric
,1);
495 int handletopicmsg(void *source
, int cargc
, char **cargv
) {
499 time_t topictime
=0, timestamp
=0;
506 topictime
=strtol(cargv
[cargc
-2], NULL
, 10);
509 timestamp
=strtol(cargv
[cargc
-3], NULL
, 10);
511 /* The following check removed because servers can set topics.. */
513 if ((np
=getnickbynumericstr((char *)source
))==NULL
) {
514 /* We should check the sender exists, but we still change the topic even if it doesn't */
515 Error("channel",ERR_WARNING
,"Topic change by non-existent user %s",(char *)source
);
519 /* Grab channel pointer */
520 if ((cp
=findchannel(cargv
[0]))==NULL
) {
521 /* We're not going to create a channel for the sake of a topic.. */
524 if (timestamp
&& (cp
->timestamp
< timestamp
)) {
525 /* Ignore topic change for younger channel
526 * (note that topic change for OLDER channel should be impossible!) */
529 if (topictime
&& (cp
->topictime
> topictime
)) {
530 /* Ignore topic change with older topic */
533 if (cp
->topic
!=NULL
) {
534 freesstring(cp
->topic
);
536 if (cargv
[cargc
-1][0]=='\0') {
540 cp
->topic
=getsstring(cargv
[cargc
-1],TOPICLEN
);
541 cp
->topictime
=topictime
?topictime
:getnettime();
546 triggerhook(HOOK_CHANNEL_TOPIC
,harg
);
553 * Note that this function is also used for processing OPMODE messages.
554 * There is no checking on the source etc. anyway, so this should be OK.
555 * (we are trusting ircd not to feed us bogus modes)
558 int handlemodemsg(void *source
, int cargc
, char **cargv
) {
573 if (cargv
[0][0]!='#' && cargv
[0][0]!='+') {
574 /* Not a channel, ignore */
578 if ((cp
=findchannel(cargv
[0]))==NULL
) {
579 /* No channel, abort */
580 Error("channel",ERR_WARNING
,"Mode change on non-existent channel %s",cargv
[0]);
584 if (((char *)source
)[2]=='\0') {
585 /* Server mode change, treat as divine intervention */
587 } else if ((np
=getnickbynumericstr((char *)source
))==NULL
) {
588 /* No sender, continue but moan */
589 Error("channel",ERR_WARNING
,"Mode change by non-existent user %s on channel %s",(char *)source
,cp
->index
->name
->content
);
592 /* Set up the hook data */
596 /* Process the mode string one character at a time */
597 /* Maybe I'll write this more intelligently one day if I can comprehend the ircu code that does this */
598 for (modestr
=cargv
[1];*modestr
;modestr
++) {
600 /* Set whether we are adding or removing modes */
610 /* Simple modes: just set or clear based on value of dir */
613 if (dir
) { SetNoExtMsg(cp
); } else { ClearNoExtMsg(cp
); }
614 changes
|= MODECHANGE_MODES
;
618 if (dir
) { SetTopicLimit(cp
); } else { ClearTopicLimit(cp
); }
619 changes
|= MODECHANGE_MODES
;
623 if (dir
) { SetSecret(cp
); ClearPrivate(cp
); } else { ClearSecret(cp
); }
624 changes
|= MODECHANGE_MODES
;
628 if (dir
) { SetPrivate(cp
); ClearSecret(cp
); } else { ClearPrivate(cp
); }
629 changes
|= MODECHANGE_MODES
;
633 if (dir
) { SetInviteOnly(cp
); } else { ClearInviteOnly(cp
); }
634 changes
|= MODECHANGE_MODES
;
638 if (dir
) { SetModerated(cp
); } else { ClearModerated(cp
); }
639 changes
|= MODECHANGE_MODES
;
643 if (dir
) { SetNoColour(cp
); } else { ClearNoColour(cp
); }
644 changes
|= MODECHANGE_MODES
;
648 if (dir
) { SetNoCTCP(cp
); } else { ClearNoCTCP(cp
); }
649 changes
|= MODECHANGE_MODES
;
653 if (dir
) { SetRegOnly(cp
); } else { ClearRegOnly(cp
); }
654 changes
|= MODECHANGE_MODES
;
658 if (dir
) { SetDelJoins(cp
); } else { ClearDelJoins(cp
); }
659 changes
|= MODECHANGE_MODES
;
663 if (dir
) { SetNoQuitMsg(cp
); } else { ClearNoQuitMsg(cp
); }
664 changes
|= MODECHANGE_MODES
;
668 if (dir
) { SetNoNotice(cp
); } else { ClearNoNotice(cp
); }
669 changes
|= MODECHANGE_MODES
;
673 if (dir
) { SetModNoAuth(cp
); } else { ClearModNoAuth(cp
); }
674 changes
|= MODECHANGE_MODES
;
678 if (dir
) { SetSingleTarg(cp
); } else { ClearSingleTarg(cp
); }
679 changes
|= MODECHANGE_MODES
;
682 /* Parameter modes: advance parameter and possibly read it in */
686 /* +l uses a parameter, but -l does not.
687 * If there is no parameter, don't set the mode.
688 * I guess we should moan too in that case, but
689 * they might be even nastier to us if we do ;) */
691 cp
->limit
=strtol(cargv
[arg
++],NULL
,10);
698 changes
|= MODECHANGE_MODES
;
703 /* +k uses a parameter in both directions */
705 freesstring(cp
->key
); /* It's probably NULL, but be safe */
706 cp
->key
=getsstring(cargv
[arg
++],KEYLEN
);
710 freesstring(cp
->key
);
713 arg
++; /* Eat the arg without looking at it, even if it's not there */
715 changes
|= MODECHANGE_MODES
;
723 if((lp
=getnumerichandlefromchanhash(cp
->users
,numerictolong(cargv
[arg
++],5)))==NULL
) {
724 /* They're not on the channel; MODE crossed with part/kill/kick/blah */
725 Error("channel",ERR_DEBUG
,"Mode change for user %s not on channel %s",cargv
[arg
-1],cp
->index
->name
->content
);
727 if ((target
=getnickbynumeric(*lp
))==NULL
) {
728 /* This really is a fuckup, we found them on the channel but there isn't a user with that numeric */
729 /* This means there's a serious bug in the nick/channel tracking code */
730 Error("channel",ERR_ERROR
,"Mode change for user %s on channel %s who doesn't exist",cargv
[arg
-1],cp
->index
->name
->content
);
731 } else { /* Do the mode change whilst admiring the beautiful code layout */
733 if (*modestr
=='o') { if (dir
) { *lp
|= CUMODE_OP
; hooknum
=HOOK_CHANNEL_OPPED
; } else
734 { *lp
&= ~CUMODE_OP
; hooknum
=HOOK_CHANNEL_DEOPPED
; } }
735 else { if (dir
) { *lp
|= CUMODE_VOICE
; hooknum
=HOOK_CHANNEL_VOICED
; } else
736 { *lp
&= ~CUMODE_VOICE
; hooknum
=HOOK_CHANNEL_DEVOICED
; } }
737 triggerhook(hooknum
,harg
);
741 changes
|= MODECHANGE_USERS
;
747 setban(cp
,cargv
[arg
++]);
748 triggerhook(HOOK_CHANNEL_BANSET
,harg
);
750 clearban(cp
,cargv
[arg
++],0);
751 triggerhook(HOOK_CHANNEL_BANCLEAR
,harg
);
754 changes
|= MODECHANGE_BANS
;
758 Error("channel",ERR_DEBUG
,"Unknown mode char '%c' %s on %s",*modestr
,dir
?"set":"cleared",cp
->index
->name
->content
);
763 harg
[2]=(void *)((long)changes
);
764 triggerhook(HOOK_CHANNEL_MODECHANGE
,(void *)harg
);
769 * Deal with 2.10.11ism: CLEARMODE
771 * [hAAA CM #twilightzone ovpsmikbl
774 int handleclearmodemsg(void *source
, int cargc
, char **cargv
) {
779 unsigned long usermask
=0;
787 if ((cp
=findchannel(cargv
[0]))==NULL
) {
788 /* No channel, abort */
789 Error("channel",ERR_WARNING
,"Mode change on non-existent channel %s",cargv
[0]);
793 if (((char *)source
)[2]=='\0') {
794 /* Server mode change? (I don't think servers are allowed to do CLEARMODE) */
796 } else if ((np
=getnickbynumericstr((char *)source
))==NULL
) {
797 /* No sender, continue but moan */
798 Error("channel",ERR_WARNING
,"Mode change by non-existent user %s on channel %s",(char *)source
,cp
->index
->name
->content
);
804 for (mcp
=cargv
[1];*mcp
;mcp
++) {
807 usermask
|= CUMODE_OP
;
808 changes
|= MODECHANGE_USERS
;
812 usermask
|= CUMODE_VOICE
;
813 changes
|= MODECHANGE_USERS
;
818 changes
|= MODECHANGE_MODES
;
823 changes
|= MODECHANGE_MODES
;
828 changes
|= MODECHANGE_MODES
;
833 changes
|= MODECHANGE_MODES
;
838 changes
|= MODECHANGE_MODES
;
843 changes
|= MODECHANGE_MODES
;
848 changes
|= MODECHANGE_MODES
;
853 changes
|= MODECHANGE_MODES
;
858 changes
|= MODECHANGE_MODES
;
863 changes
|= MODECHANGE_MODES
;
868 changes
|= MODECHANGE_MODES
;
873 changes
|= MODECHANGE_MODES
;
878 changes
|= MODECHANGE_MODES
;
883 changes
|= MODECHANGE_MODES
;
888 changes
|= MODECHANGE_BANS
;
892 /* This is all safe even if there is no key atm */
893 freesstring(cp
->key
);
896 changes
|= MODECHANGE_MODES
;
902 changes
|= MODECHANGE_MODES
;
908 /* We have to strip something off each user */
909 for (i
=0;i
<cp
->users
->hashsize
;i
++) {
910 if (cp
->users
->content
[i
]!=nouser
&& (cp
->users
->content
[i
] & usermask
)) {
911 /* This user exists and has at least one of the modes we're clearing */
912 if ((target
=getnickbynumeric(cp
->users
->content
[i
]))==NULL
) {
913 /* This really is a fuckup, we found them on the channel but there isn't a user with that numeric */
914 /* This means there's a serious bug in the nick/channel tracking code */
915 Error("channel",ERR_ERROR
,"CLEARMODE failed: user on channel who doesn't exist?");
918 /* Yes, these are deliberate three way bitwise ANDs.. */
919 if (cp
->users
->content
[i
] & usermask
& CUMODE_OP
)
920 triggerhook(HOOK_CHANNEL_DEOPPED
, harg
);
921 if (cp
->users
->content
[i
] & usermask
& CUMODE_VOICE
)
922 triggerhook(HOOK_CHANNEL_DEVOICED
, harg
);
923 cp
->users
->content
[i
] &= ~usermask
;
929 harg
[2]=(void *)((long)changes
);
930 triggerhook(HOOK_CHANNEL_MODECHANGE
, harg
);
934 void handlewhoischannels(int hooknum
, void *arg
) {
941 void **args
= (void **)arg
;
942 nick
*sender
= (nick
*)args
[0], *target
= (nick
*)args
[1];
944 if(IsService(target
) || IsHideChan(target
))
947 chans
= (channel
**)(target
->channels
->content
);
952 /* Not handling delayed joins. */
953 for(i
=target
->channels
->cursi
-1;i
>=0;i
--) {
954 /* Secret / Private channels: only show if the sender is on the channel as well */
955 if(IsSecret(chans
[i
]) || IsPrivate(chans
[i
])) {
956 if (!getnumerichandlefromchanhash(chans
[i
]->users
, sender
->numeric
))
960 name
= chans
[i
]->index
->name
;
961 if (bufpos
+ name
->length
> 508) { /* why 508? - need room for -@#channame\0 + 1 slack */
962 irc_send("%s", buffer
);
967 if(buffer
[0] == '\0')
968 bufpos
=snprintf(buffer
, sizeof(buffer
), ":%s 319 %s %s :", myserver
->content
, sender
->nick
, target
->nick
);
970 num
= getnumerichandlefromchanhash(chans
[i
]->users
, target
->numeric
);
972 /* Adding these flags might make the string "unsafe" (without terminating \0). */
973 /* sprintf'ing the channel name afterwards is guaranteed to fix it though */
975 buffer
[bufpos
++]='-';
976 if (*num
& CUMODE_OP
)
977 buffer
[bufpos
++]='@';
978 else if (*num
& CUMODE_VOICE
)
979 buffer
[bufpos
++]='+';
981 bufpos
+= sprintf(buffer
+bufpos
, "%s ",name
->content
);
984 if (buffer
[0] != '\0')
985 irc_send("%s", buffer
);