source: irc_commands.c @ 7cd2e8a

Last change on this file since 7cd2e8a was 6b90431, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-06-16T08:31:40Z

More correct handling of channel names (according to RFC 1459). Pretty
much any 8-bit character is allowed in there - while nicknames are very
restricted.

  • Property mode set to 100644
File size: 17.5 KB
Line 
1  /********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
4  * Copyright 2002-2010 Wilmer van der Gaast and others                *
5  \********************************************************************/
6
7/* IRC commands                                                         */
8
9/*
10  This program is free software; you can redistribute it and/or modify
11  it under the terms of the GNU General Public License as published by
12  the Free Software Foundation; either version 2 of the License, or
13  (at your option) any later version.
14
15  This program is distributed in the hope that it will be useful,
16  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  GNU General Public License for more details.
19
20  You should have received a copy of the GNU General Public License with
21  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
22  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
23  Suite 330, Boston, MA  02111-1307  USA
24*/
25
26#define BITLBEE_CORE
27#include "bitlbee.h"
28#include "ipc.h"
29#include "chat.h"
30
31static void irc_cmd_pass( irc_t *irc, char **cmd )
32{
33        if( irc->status & USTATUS_LOGGED_IN )
34        {
35                char *send_cmd[] = { "identify", cmd[1], NULL };
36               
37                /* We're already logged in, this client seems to send the PASS
38                   command last. (Possibly it won't send it at all if it turns
39                   out we don't require it, which will break this feature.)
40                   Try to identify using the given password. */
41                return root_command( irc, send_cmd );
42        }
43        /* Handling in pre-logged-in state, first see if this server is
44           password-protected: */
45        else if( global.conf->auth_pass &&
46            ( strncmp( global.conf->auth_pass, "md5:", 4 ) == 0 ?
47                md5_verify_password( cmd[1], global.conf->auth_pass + 4 ) == 0 :
48                strcmp( cmd[1], global.conf->auth_pass ) == 0 ) )
49        {
50                irc->status |= USTATUS_AUTHORIZED;
51                irc_check_login( irc );
52        }
53        else if( global.conf->auth_pass )
54        {
55                irc_send_num( irc, 464, ":Incorrect password" );
56        }
57        else
58        {
59                /* Remember the password and try to identify after USER/NICK. */
60                irc_setpass( irc, cmd[1] );
61                irc_check_login( irc );
62        }
63}
64
65static void irc_cmd_user( irc_t *irc, char **cmd )
66{
67        irc->user->user = g_strdup( cmd[1] );
68        irc->user->fullname = g_strdup( cmd[4] );
69       
70        irc_check_login( irc );
71}
72
73static void irc_cmd_nick( irc_t *irc, char **cmd )
74{
75        if( irc_user_by_name( irc, cmd[1] ) )
76        {
77                irc_send_num( irc, 433, ":This nick is already in use" );
78        }
79        else if( !nick_ok( cmd[1] ) )
80        {
81                /* [SH] Invalid characters. */
82                irc_send_num( irc, 432, ":This nick contains invalid characters" );
83        }
84        else if( irc->user->nick )
85        {
86                if( irc->status & USTATUS_IDENTIFIED )
87                {
88                        irc_setpass( irc, NULL );
89                        irc->status &= ~USTATUS_IDENTIFIED;
90                        irc_umode_set( irc, "-R", 1 );
91                        irc_usermsg( irc, "Changing nicks resets your identify status. "
92                                     "Re-identify or register a new account if you want "
93                                     "your configuration to be saved. See \x02help "
94                                     "nick_changes\x02." );
95                }
96               
97                irc_user_set_nick( irc->user, cmd[1] );
98        }
99        else
100        {
101                irc->user->nick = g_strdup( cmd[1] );
102               
103                irc_check_login( irc );
104        }
105}
106
107static void irc_cmd_quit( irc_t *irc, char **cmd )
108{
109        if( cmd[1] && *cmd[1] )
110                irc_abort( irc, 0, "Quit: %s", cmd[1] );
111        else
112                irc_abort( irc, 0, "Leaving..." );
113}
114
115static void irc_cmd_ping( irc_t *irc, char **cmd )
116{
117        irc_write( irc, ":%s PONG %s :%s", irc->root->host,
118                   irc->root->host, cmd[1]?cmd[1]:irc->root->host );
119}
120
121static void irc_cmd_pong( irc_t *irc, char **cmd )
122{
123        /* We could check the value we get back from the user, but in
124           fact we don't care, we're just happy s/he's still alive. */
125        irc->last_pong = gettime();
126        irc->pinging = 0;
127}
128
129static void irc_cmd_join( irc_t *irc, char **cmd )
130{
131        irc_channel_t *ic;
132       
133        if( ( ic = irc_channel_by_name( irc, cmd[1] ) ) == NULL )
134                ic = irc_channel_new( irc, cmd[1] );
135       
136        if( ic == NULL )
137        {
138                irc_send_num( irc, 479, "%s :Invalid channel name", cmd[1] );
139                return;
140        }
141       
142        if( ic->flags & IRC_CHANNEL_JOINED )
143                return; /* Dude, you're already there...
144                           RFC doesn't have any reply for that though? */
145       
146        if( ic->f->join && !ic->f->join( ic ) )
147                /* The story is: FALSE either means the handler showed an error
148                   message, or is doing some work before the join should be
149                   confirmed. (In the latter case, the caller should take care
150                   of that confirmation.)
151                   TRUE means all's good, let the user join the channel right away. */
152                return;
153       
154        irc_channel_add_user( ic, irc->user );
155}
156
157static void irc_cmd_names( irc_t *irc, char **cmd )
158{
159        irc_channel_t *ic;
160       
161        if( cmd[1] && ( ic = irc_channel_by_name( irc, cmd[1] ) ) )
162                irc_send_names( ic );
163        /* With no args, we should show /names of all chans. Make the code
164           below work well if necessary.
165        else
166        {
167                GSList *l;
168               
169                for( l = irc->channels; l; l = l->next )
170                        irc_send_names( l->data );
171        }
172        */
173}
174
175static void irc_cmd_part( irc_t *irc, char **cmd )
176{
177        irc_channel_t *ic;
178       
179        if( ( ic = irc_channel_by_name( irc, cmd[1] ) ) == NULL )
180        {
181                irc_send_num( irc, 403, "%s :No such channel", cmd[1] );
182        }
183        else if( irc_channel_del_user( ic, irc->user, FALSE, cmd[2] ) )
184        {
185                if( ic->f->part )
186                        ic->f->part( ic, NULL );
187        }
188        else
189        {
190                irc_send_num( irc, 442, "%s :You're not on that channel", cmd[1] );
191        }
192}
193
194static void irc_cmd_whois( irc_t *irc, char **cmd )
195{
196        char *nick = cmd[1];
197        irc_user_t *iu = irc_user_by_name( irc, nick );
198       
199        if( iu )
200                irc_send_whois( iu );
201        else
202                irc_send_num( irc, 401, "%s :Nick does not exist", nick );
203}
204
205static void irc_cmd_whowas( irc_t *irc, char **cmd )
206{
207        /* For some reason irssi tries a whowas when whois fails. We can
208           ignore this, but then the user never gets a "user not found"
209           message from irssi which is a bit annoying. So just respond
210           with not-found and irssi users will get better error messages */
211       
212        irc_send_num( irc, 406, "%s :Nick does not exist", cmd[1] );
213        irc_send_num( irc, 369, "%s :End of WHOWAS", cmd[1] );
214}
215
216static void irc_cmd_motd( irc_t *irc, char **cmd )
217{
218        irc_send_motd( irc );
219}
220
221static void irc_cmd_mode( irc_t *irc, char **cmd )
222{
223        if( irc_channel_name_ok( cmd[1] ) )
224        {
225                irc_channel_t *ic;
226               
227                if( ( ic = irc_channel_by_name( irc, cmd[1] ) ) == NULL )
228                        irc_send_num( irc, 403, "%s :No such channel", cmd[1] );
229                else if( cmd[2] )
230                {
231                        if( *cmd[2] == '+' || *cmd[2] == '-' )
232                                irc_send_num( irc, 477, "%s :Can't change channel modes", cmd[1] );
233                        else if( *cmd[2] == 'b' )
234                                irc_send_num( irc, 368, "%s :No bans possible", cmd[1] );
235                }
236                else
237                        irc_send_num( irc, 324, "%s +%s", cmd[1], ic->mode );
238        }
239        else
240        {
241                if( nick_cmp( cmd[1], irc->user->nick ) == 0 )
242                {
243                        if( cmd[2] )
244                                irc_umode_set( irc, cmd[2], 0 );
245                        else
246                                irc_send_num( irc, 221, "+%s", irc->umode );
247                }
248                else
249                        irc_send_num( irc, 502, ":Don't touch their modes" );
250        }
251}
252
253static void irc_cmd_who( irc_t *irc, char **cmd )
254{
255        char *channel = cmd[1];
256        irc_channel_t *ic;
257       
258        if( !channel || *channel == '0' || *channel == '*' || !*channel )
259                irc_send_who( irc, irc->users, "**" );
260        else if( ( ic = irc_channel_by_name( irc, channel ) ) )
261                irc_send_who( irc, ic->users, channel );
262        else
263                irc_send_num( irc, 403, "%s :No such channel", channel );
264}
265
266static void irc_cmd_privmsg( irc_t *irc, char **cmd )
267{
268        irc_channel_t *ic;
269        irc_user_t *iu;
270       
271        if( !cmd[2] ) 
272        {
273                irc_send_num( irc, 412, ":No text to send" );
274                return;
275        }
276       
277        /* Don't treat CTCP actions as real CTCPs, just convert them right now. */
278        if( g_strncasecmp( cmd[2], "\001ACTION", 7 ) == 0 )
279        {
280                cmd[2] += 4;
281                strcpy( cmd[2], "/me" );
282                if( cmd[2][strlen(cmd[2])-1] == '\001' )
283                        cmd[2][strlen(cmd[2])-1] = '\0';
284        }
285       
286        if( irc_channel_name_ok( cmd[1] ) &&
287            ( ic = irc_channel_by_name( irc, cmd[1] ) ) )
288        {
289                if( ic->f->privmsg )
290                        ic->f->privmsg( ic, cmd[2] );
291        }
292        else if( ( iu = irc_user_by_name( irc, cmd[1] ) ) )
293        {
294                if( cmd[2][0] == '\001' )
295                {
296                        char **ctcp;
297                       
298                        if( iu->f->ctcp == NULL )
299                                return;
300                        if( cmd[2][strlen(cmd[2])-1] == '\001' )
301                                cmd[2][strlen(cmd[2])-1] = '\0';
302                       
303                        ctcp = split_command_parts( cmd[2] + 1 );
304                        iu->f->ctcp( iu, ctcp );
305                }
306                else if( iu->f->privmsg )
307                {
308                        iu->flags |= IRC_USER_PRIVATE;
309                        iu->f->privmsg( iu, cmd[2] );
310                }
311        }
312        else
313        {
314                irc_send_num( irc, 401, "%s :No such nick/channel", cmd[1] );
315        }
316}
317
318static void irc_cmd_nickserv( irc_t *irc, char **cmd )
319{
320        /* [SH] This aliases the NickServ command to PRIVMSG root */
321        /* [TV] This aliases the NS command to PRIVMSG root as well */
322        root_command( irc, cmd + 1 );
323}
324
325
326
327static void irc_cmd_oper( irc_t *irc, char **cmd )
328{
329        if( global.conf->oper_pass &&
330            ( strncmp( global.conf->oper_pass, "md5:", 4 ) == 0 ?
331                md5_verify_password( cmd[2], global.conf->oper_pass + 4 ) == 0 :
332                strcmp( cmd[2], global.conf->oper_pass ) == 0 ) )
333        {
334                irc_umode_set( irc, "+o", 1 );
335                irc_send_num( irc, 381, ":Password accepted" );
336        }
337        else
338        {
339                irc_send_num( irc, 432, ":Incorrect password" );
340        }
341}
342
343static void irc_cmd_invite( irc_t *irc, char **cmd )
344{
345        irc_channel_t *ic;
346        irc_user_t *iu;
347       
348        if( ( iu = irc_user_by_name( irc, cmd[1] ) ) == NULL )
349        {
350                irc_send_num( irc, 401, "%s :No such nick", cmd[1] );
351                return;
352        }
353        else if( ( ic = irc_channel_by_name( irc, cmd[2] ) ) == NULL )
354        {
355                irc_send_num( irc, 403, "%s :No such channel", cmd[2] );
356                return;
357        }
358       
359        if( !ic->f->invite )
360                irc_send_num( irc, 482, "%s :Can't invite people here", cmd[2] );
361        else if( ic->f->invite( ic, iu ) )
362                irc_send_num( irc, 341, "%s %s", iu->nick, ic->name );
363}
364
365static void irc_cmd_userhost( irc_t *irc, char **cmd )
366{
367        int i;
368       
369        /* [TV] Usable USERHOST-implementation according to
370                RFC1459. Without this, mIRC shows an error
371                while connecting, and the used way of rejecting
372                breaks standards.
373        */
374       
375        for( i = 1; cmd[i]; i ++ )
376        {
377                irc_user_t *iu = irc_user_by_name( irc, cmd[i] );
378               
379                if( iu )
380                        irc_send_num( irc, 302, ":%s=%c%s@%s", iu->nick,
381                                      irc_user_get_away( iu ) ? '-' : '+',
382                                      iu->user, iu->host );
383        }
384}
385
386static void irc_cmd_ison( irc_t *irc, char **cmd )
387{
388        char buff[IRC_MAX_LINE];
389        int lenleft, i;
390       
391        buff[0] = '\0';
392       
393        /* [SH] Leave room for : and \0 */
394        lenleft = IRC_MAX_LINE - 2;
395       
396        for( i = 1; cmd[i]; i ++ )
397        {
398                char *this, *next;
399               
400                this = cmd[i];
401                while( *this )
402                {
403                        irc_user_t *iu;
404                       
405                        if( ( next = strchr( this, ' ' ) ) )
406                                *next = 0;
407                       
408                        if( ( iu = irc_user_by_name( irc, this ) ) &&
409                            iu->bu && iu->bu->flags & BEE_USER_ONLINE )
410                        {
411                                lenleft -= strlen( iu->nick ) + 1;
412                               
413                                if( lenleft < 0 )
414                                        break;
415                               
416                                strcat( buff, iu->nick );
417                                strcat( buff, " " );
418                        }
419                       
420                        if( next )
421                        {
422                                *next = ' ';
423                                this = next + 1;
424                        }
425                        else
426                        {
427                                break;
428                        }   
429                }
430               
431                /* *sigh* */
432                if( lenleft < 0 )
433                        break;
434        }
435       
436        if( strlen( buff ) > 0 )
437                buff[strlen(buff)-1] = '\0';
438       
439        irc_send_num( irc, 303, ":%s", buff );
440}
441
442static void irc_cmd_watch( irc_t *irc, char **cmd )
443{
444        int i;
445       
446        /* Obviously we could also mark a user structure as being
447           watched, but what if the WATCH command is sent right
448           after connecting? The user won't exist yet then... */
449        for( i = 1; cmd[i]; i ++ )
450        {
451                char *nick;
452                irc_user_t *iu;
453               
454                if( !cmd[i][0] || !cmd[i][1] )
455                        break;
456               
457                nick = g_strdup( cmd[i] + 1 );
458                nick_lc( nick );
459               
460                iu = irc_user_by_name( irc, nick );
461               
462                if( cmd[i][0] == '+' )
463                {
464                        if( !g_hash_table_lookup( irc->watches, nick ) )
465                                g_hash_table_insert( irc->watches, nick, nick );
466                       
467                        if( iu && iu->bu && iu->bu->flags & BEE_USER_ONLINE )
468                                irc_send_num( irc, 604, "%s %s %s %d :%s", iu->nick, iu->user,
469                                              iu->host, (int) time( NULL ), "is online" );
470                        else
471                                irc_send_num( irc, 605, "%s %s %s %d :%s", nick, "*", "*",
472                                              (int) time( NULL ), "is offline" );
473                }
474                else if( cmd[i][0] == '-' )
475                {
476                        gpointer okey, ovalue;
477                       
478                        if( g_hash_table_lookup_extended( irc->watches, nick, &okey, &ovalue ) )
479                        {
480                                g_hash_table_remove( irc->watches, okey );
481                                g_free( okey );
482                               
483                                irc_send_num( irc, 602, "%s %s %s %d :%s", nick, "*", "*", 0, "Stopped watching" );
484                        }
485                }
486        }
487}
488
489static void irc_cmd_topic( irc_t *irc, char **cmd )
490{
491        irc_channel_t *ic = irc_channel_by_name( irc, cmd[1] );
492        const char *new = cmd[2];
493       
494        if( ic == NULL )
495        {
496                irc_send_num( irc, 403, "%s :No such channel", cmd[1] );
497        }
498        else if( new )
499        {
500                if( ic->f->topic == NULL )
501                        irc_send_num( irc, 482, "%s :Can't change this channel's topic", ic->name );
502                else if( ic->f->topic( ic, new ) )
503                        irc_send_topic( ic, TRUE );
504        }
505        else
506        {
507                irc_send_topic( ic, FALSE );
508        }
509}
510
511static void irc_cmd_away( irc_t *irc, char **cmd )
512{
513        if( cmd[1] && *cmd[1] )
514        {
515                char away[strlen(cmd[1])+1];
516                int i, j;
517               
518                /* Copy away string, but skip control chars. Mainly because
519                   Jabber really doesn't like them. */
520                for( i = j = 0; cmd[1][i]; i ++ )
521                        if( ( away[j] = cmd[1][i] ) >= ' ' )
522                                j ++;
523                away[j] = '\0';
524               
525                irc_send_num( irc, 306, ":You're now away: %s", away );
526                set_setstr( &irc->b->set, "away", away );
527        }
528        else
529        {
530                irc_send_num( irc, 305, ":Welcome back" );
531                set_setstr( &irc->b->set, "away", NULL );
532        }
533}
534
535static void irc_cmd_version( irc_t *irc, char **cmd )
536{
537        irc_send_num( irc, 351, "bitlbee-%s. %s :%s/%s ",
538                      BITLBEE_VERSION, irc->root->host, ARCH, CPU );
539}
540
541static void irc_cmd_completions( irc_t *irc, char **cmd )
542{
543        help_t *h;
544        set_t *s;
545        int i;
546       
547        irc_send_msg_raw( irc->root, "NOTICE", irc->user->nick, "COMPLETIONS OK" );
548       
549        for( i = 0; commands[i].command; i ++ )
550                irc_send_msg_f( irc->root, "NOTICE", irc->user->nick, "COMPLETIONS %s", commands[i].command );
551       
552        for( h = global.help; h; h = h->next )
553                irc_send_msg_f( irc->root, "NOTICE", irc->user->nick, "COMPLETIONS help %s", h->title );
554       
555        for( s = irc->b->set; s; s = s->next )
556                irc_send_msg_f( irc->root, "NOTICE", irc->user->nick, "COMPLETIONS set %s", s->key );
557       
558        irc_send_msg_raw( irc->root, "NOTICE", irc->user->nick, "COMPLETIONS END" );
559}
560
561static void irc_cmd_rehash( irc_t *irc, char **cmd )
562{
563        if( global.conf->runmode == RUNMODE_INETD )
564                ipc_master_cmd_rehash( NULL, NULL );
565        else
566                ipc_to_master( cmd );
567       
568        irc_send_num( irc, 382, "%s :Rehashing", global.conf_file );
569}
570
571static const command_t irc_commands[] = {
572        { "pass",        1, irc_cmd_pass,        0 },
573        { "user",        4, irc_cmd_user,        IRC_CMD_PRE_LOGIN },
574        { "nick",        1, irc_cmd_nick,        0 },
575        { "quit",        0, irc_cmd_quit,        0 },
576        { "ping",        0, irc_cmd_ping,        0 },
577        { "pong",        0, irc_cmd_pong,        IRC_CMD_LOGGED_IN },
578        { "join",        1, irc_cmd_join,        IRC_CMD_LOGGED_IN },
579        { "names",       1, irc_cmd_names,       IRC_CMD_LOGGED_IN },
580        { "part",        1, irc_cmd_part,        IRC_CMD_LOGGED_IN },
581        { "whois",       1, irc_cmd_whois,       IRC_CMD_LOGGED_IN },
582        { "whowas",      1, irc_cmd_whowas,      IRC_CMD_LOGGED_IN },
583        { "motd",        0, irc_cmd_motd,        IRC_CMD_LOGGED_IN },
584        { "mode",        1, irc_cmd_mode,        IRC_CMD_LOGGED_IN },
585        { "who",         0, irc_cmd_who,         IRC_CMD_LOGGED_IN },
586        { "privmsg",     1, irc_cmd_privmsg,     IRC_CMD_LOGGED_IN },
587        { "nickserv",    1, irc_cmd_nickserv,    IRC_CMD_LOGGED_IN },
588        { "ns",          1, irc_cmd_nickserv,    IRC_CMD_LOGGED_IN },
589        { "away",        0, irc_cmd_away,        IRC_CMD_LOGGED_IN },
590        { "version",     0, irc_cmd_version,     IRC_CMD_LOGGED_IN },
591        { "completions", 0, irc_cmd_completions, IRC_CMD_LOGGED_IN },
592        { "userhost",    1, irc_cmd_userhost,    IRC_CMD_LOGGED_IN },
593        { "ison",        1, irc_cmd_ison,        IRC_CMD_LOGGED_IN },
594        { "watch",       1, irc_cmd_watch,       IRC_CMD_LOGGED_IN },
595        { "invite",      2, irc_cmd_invite,      IRC_CMD_LOGGED_IN },
596#if 0
597        { "notice",      1, irc_cmd_privmsg,     IRC_CMD_LOGGED_IN },
598#endif
599        { "topic",       1, irc_cmd_topic,       IRC_CMD_LOGGED_IN },
600        { "oper",        2, irc_cmd_oper,        IRC_CMD_LOGGED_IN },
601        { "die",         0, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
602        { "deaf",        0, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
603        { "wallops",     1, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
604        { "wall",        1, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
605        { "rehash",      0, irc_cmd_rehash,      IRC_CMD_OPER_ONLY },
606        { "restart",     0, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
607        { "kill",        2, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
608        { NULL }
609};
610
611void irc_exec( irc_t *irc, char *cmd[] )
612{       
613        int i, n_arg;
614       
615        if( !cmd[0] )
616                return;
617       
618        for( i = 0; irc_commands[i].command; i++ )
619                if( g_strcasecmp( irc_commands[i].command, cmd[0] ) == 0 )
620                {
621                        /* There should be no typo in the next line: */
622                        for( n_arg = 0; cmd[n_arg]; n_arg ++ ); n_arg --;
623                       
624                        if( irc_commands[i].flags & IRC_CMD_PRE_LOGIN && irc->status & USTATUS_LOGGED_IN )
625                        {
626                                irc_send_num( irc, 462, ":Only allowed before logging in" );
627                        }
628                        else if( irc_commands[i].flags & IRC_CMD_LOGGED_IN && !( irc->status & USTATUS_LOGGED_IN ) )
629                        {
630                                irc_send_num( irc, 451, ":Register first" );
631                        }
632                        else if( irc_commands[i].flags & IRC_CMD_OPER_ONLY && !strchr( irc->umode, 'o' ) )
633                        {
634                                irc_send_num( irc, 481, ":Permission denied - You're not an IRC operator" );
635                        }
636                        else if( n_arg < irc_commands[i].required_parameters )
637                        {
638                                irc_send_num( irc, 461, "%s :Need more parameters", cmd[0] );
639                        }
640                        else if( irc_commands[i].flags & IRC_CMD_TO_MASTER )
641                        {
642                                /* IPC doesn't make sense in inetd mode,
643                                    but the function will catch that. */
644                                ipc_to_master( cmd );
645                        }
646                        else
647                        {
648                                irc_commands[i].execute( irc, cmd );
649                        }
650                       
651                        return;
652                }
653       
654        if( irc->status >= USTATUS_LOGGED_IN )
655                irc_send_num( irc, 421, "%s :Unknown command", cmd[0] );
656}
Note: See TracBrowser for help on using the repository browser.