source: irc_commands.c @ b95932e

Last change on this file since b95932e was b95932e, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-03-27T03:39:23Z

Added WHOIS command.

  • Property mode set to 100644
File size: 17.4 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->nick )
76        {
77                irc_send_num( irc, 438, ":The hand of the deity is upon thee, thy nick may not change" );
78        }
79        else if( irc_user_find( irc, cmd[1] ) )
80        {
81                irc_send_num( irc, 433, ":This nick is already in use" );
82        }
83        else if( !nick_ok( cmd[1] ) )
84        {
85                /* [SH] Invalid characters. */
86                irc_send_num( irc, 432, ":This nick contains invalid characters" );
87        }
88        else
89        {
90                irc->user->nick = g_strdup( cmd[1] );
91               
92                irc_check_login( irc );
93        }
94}
95
96static void irc_cmd_quit( irc_t *irc, char **cmd )
97{
98        if( cmd[1] && *cmd[1] )
99                irc_abort( irc, 0, "Quit: %s", cmd[1] );
100        else
101                irc_abort( irc, 0, "Leaving..." );
102}
103
104static void irc_cmd_ping( irc_t *irc, char **cmd )
105{
106        irc_write( irc, ":%s PONG %s :%s", irc->root->host,
107                   irc->root->host, cmd[1]?cmd[1]:irc->root->host );
108}
109
110static void irc_cmd_join( irc_t *irc, char **cmd )
111{
112        irc_channel_t *ic;
113       
114        if( ( ic = irc_channel_by_name( irc, cmd[1] ) ) == NULL )
115                ic = irc_channel_new( irc, cmd[1] );
116       
117        if( ic == NULL )
118                irc_send_num( irc, 479, "%s :Invalid channel name", cmd[1] );
119       
120        if( ic->flags & IRC_CHANNEL_JOINED )
121                return; /* Dude, you're already there...
122                           RFC doesn't have any reply for that though? */
123       
124        irc_channel_add_user( ic, irc->user );
125}
126
127static void irc_cmd_names( irc_t *irc, char **cmd )
128{
129        irc_channel_t *ic;
130       
131        if( cmd[1] && ( ic = irc_channel_by_name( irc, cmd[1] ) ) )
132                irc_send_names( ic );
133        /* With no args, we should show /names of all chans. Make the code
134           below work well if necessary.
135        else
136        {
137                GSList *l;
138               
139                for( l = irc->channels; l; l = l->next )
140                        irc_send_names( l->data );
141        }
142        */
143}
144
145static void irc_cmd_part( irc_t *irc, char **cmd )
146{
147        irc_channel_t *ic;
148       
149        if( ( ic = irc_channel_by_name( irc, cmd[1] ) ) == NULL )
150        {
151                irc_send_num( irc, 403, "%s :No such channel", cmd[1] );
152        }
153        else if( !irc_channel_del_user( ic, irc->user ) )
154        {
155                irc_send_num( irc, 442, "%s :You're not on that channel", cmd[1] );
156        }
157}
158
159static void irc_cmd_whois( irc_t *irc, char **cmd )
160{
161        char *nick = cmd[1];
162        irc_user_t *iu = irc_user_find( irc, nick );
163       
164        if( iu )
165                irc_send_whois( iu );
166        else
167                irc_send_num( irc, 401, "%s :Nick does not exist", nick );
168}
169
170static void irc_cmd_whowas( irc_t *irc, char **cmd )
171{
172        /* For some reason irssi tries a whowas when whois fails. We can
173           ignore this, but then the user never gets a "user not found"
174           message from irssi which is a bit annoying. So just respond
175           with not-found and irssi users will get better error messages */
176       
177        irc_send_num( irc, 406, "%s :Nick does not exist", cmd[1] );
178        irc_send_num( irc, 369, "%s :End of WHOWAS", cmd[1] );
179}
180
181#if 0
182//#if 0
183static void irc_cmd_oper( irc_t *irc, char **cmd )
184{
185        if( global.conf->oper_pass &&
186            ( strncmp( global.conf->oper_pass, "md5:", 4 ) == 0 ?
187                md5_verify_password( cmd[2], global.conf->oper_pass + 4 ) == 0 :
188                strcmp( cmd[2], global.conf->oper_pass ) == 0 ) )
189        {
190                irc_umode_set( irc, "+o", 1 );
191                irc_send_num( irc, 381, ":Password accepted" );
192        }
193        else
194        {
195                irc_send_num( irc, 432, ":Incorrect password" );
196        }
197}
198
199static void irc_cmd_mode( irc_t *irc, char **cmd )
200{
201        if( strchr( CTYPES, *cmd[1] ) )
202        {
203                if( cmd[2] )
204                {
205                        if( *cmd[2] == '+' || *cmd[2] == '-' )
206                                irc_send_num( irc, 477, "%s :Can't change channel modes", cmd[1] );
207                        else if( *cmd[2] == 'b' )
208                                irc_send_num( irc, 368, "%s :No bans possible", cmd[1] );
209                }
210                else
211                        irc_send_num( irc, 324, "%s +%s", cmd[1], CMODE );
212        }
213        else
214        {
215                if( nick_cmp( cmd[1], irc->nick ) == 0 )
216                {
217                        if( cmd[2] )
218                                irc_umode_set( irc, cmd[2], 0 );
219                        else
220                                irc_send_num( irc, 221, "+%s", irc->umode );
221                }
222                else
223                        irc_send_num( irc, 502, ":Don't touch their modes" );
224        }
225}
226
227static void irc_cmd_invite( irc_t *irc, char **cmd )
228{
229        char *nick = cmd[1], *channel = cmd[2];
230        struct groupchat *c = irc_chat_by_channel( irc, channel );
231        user_t *u = user_find( irc, nick );
232       
233        if( u && c && ( u->ic == c->ic ) )
234                if( c->ic && c->ic->acc->prpl->chat_invite )
235                {
236                        c->ic->acc->prpl->chat_invite( c, u->handle, NULL );
237                        irc_send_num( irc, 341, "%s %s", nick, channel );
238                        return;
239                }
240       
241        irc_send_num( irc, 482, "%s :Invite impossible; User/Channel non-existent or incompatible", channel );
242}
243
244static void irc_cmd_privmsg( irc_t *irc, char **cmd )
245{
246        if ( !cmd[2] )
247        {
248                irc_send_num( irc, 412, ":No text to send" );
249        }
250        else if ( irc->nick && g_strcasecmp( cmd[1], irc->nick ) == 0 )
251        {
252                irc_write( irc, ":%s!%s@%s %s %s :%s", irc->nick, irc->user, irc->host, cmd[0], cmd[1], cmd[2] );
253        }
254        else
255        {
256                if( g_strcasecmp( cmd[1], irc->channel ) == 0 )
257                {
258                        unsigned int i;
259                        char *t = set_getstr( &irc->set, "default_target" );
260                       
261                        if( g_strcasecmp( t, "last" ) == 0 && irc->last_target )
262                                cmd[1] = irc->last_target;
263                        else if( g_strcasecmp( t, "root" ) == 0 )
264                                cmd[1] = irc->mynick;
265                       
266                        for( i = 0; i < strlen( cmd[2] ); i ++ )
267                        {
268                                if( cmd[2][i] == ' ' ) break;
269                                if( cmd[2][i] == ':' || cmd[2][i] == ',' )
270                                {
271                                        cmd[1] = cmd[2];
272                                        cmd[2] += i;
273                                        *cmd[2] = 0;
274                                        while( *(++cmd[2]) == ' ' );
275                                        break;
276                                }
277                        }
278                       
279                        irc->is_private = 0;
280                       
281                        if( cmd[1] != irc->last_target )
282                        {
283                                g_free( irc->last_target );
284                                irc->last_target = g_strdup( cmd[1] );
285                        }
286                }
287                else
288                {
289                        irc->is_private = 1;
290                }
291                irc_send( irc, cmd[1], cmd[2], ( g_strcasecmp( cmd[0], "NOTICE" ) == 0 ) ? OPT_AWAY : 0 );
292        }
293}
294
295static void irc_cmd_who( irc_t *irc, char **cmd )
296{
297        char *channel = cmd[1];
298        user_t *u = irc->users;
299        struct groupchat *c;
300        GList *l;
301       
302        if( !channel || *channel == '0' || *channel == '*' || !*channel )
303                while( u )
304                {
305                        irc_send_num( irc, 352, "%s %s %s %s %s %c :0 %s", u->online ? irc->channel : "*", u->user, u->host, irc->myhost, u->nick, u->online ? ( u->away ? 'G' : 'H' ) : 'G', u->realname );
306                        u = u->next;
307                }
308        else if( g_strcasecmp( channel, irc->channel ) == 0 )
309                while( u )
310                {
311                        if( u->online )
312                                irc_send_num( irc, 352, "%s %s %s %s %s %c :0 %s", channel, u->user, u->host, irc->myhost, u->nick, u->away ? 'G' : 'H', u->realname );
313                        u = u->next;
314                }
315        else if( ( c = irc_chat_by_channel( irc, channel ) ) )
316                for( l = c->in_room; l; l = l->next )
317                {
318                        if( ( u = user_findhandle( c->ic, l->data ) ) )
319                                irc_send_num( irc, 352, "%s %s %s %s %s %c :0 %s", channel, u->user, u->host, irc->myhost, u->nick, u->away ? 'G' : 'H', u->realname );
320                }
321        else if( ( u = user_find( irc, channel ) ) )
322                irc_send_num( irc, 352, "%s %s %s %s %s %c :0 %s", channel, u->user, u->host, irc->myhost, u->nick, u->online ? ( u->away ? 'G' : 'H' ) : 'G', u->realname );
323       
324        irc_send_num( irc, 315, "%s :End of /WHO list", channel?channel:"**" );
325}
326
327static void irc_cmd_userhost( irc_t *irc, char **cmd )
328{
329        user_t *u;
330        int i;
331       
332        /* [TV] Usable USERHOST-implementation according to
333                RFC1459. Without this, mIRC shows an error
334                while connecting, and the used way of rejecting
335                breaks standards.
336        */
337       
338        for( i = 1; cmd[i]; i ++ )
339                if( ( u = user_find( irc, cmd[i] ) ) )
340                {
341                        if( u->online && u->away )
342                                irc_send_num( irc, 302, ":%s=-%s@%s", u->nick, u->user, u->host );
343                        else
344                                irc_send_num( irc, 302, ":%s=+%s@%s", u->nick, u->user, u->host );
345                }
346}
347
348static void irc_cmd_ison( irc_t *irc, char **cmd )
349{
350        user_t *u;
351        char buff[IRC_MAX_LINE];
352        int lenleft, i;
353       
354        buff[0] = '\0';
355       
356        /* [SH] Leave room for : and \0 */
357        lenleft = IRC_MAX_LINE - 2;
358       
359        for( i = 1; cmd[i]; i ++ )
360        {
361                char *this, *next;
362               
363                this = cmd[i];
364                while( *this )
365                {
366                        if( ( next = strchr( this, ' ' ) ) )
367                                *next = 0;
368                       
369                        if( ( u = user_find( irc, this ) ) && u->online )
370                        {
371                                lenleft -= strlen( u->nick ) + 1;
372                               
373                                if( lenleft < 0 )
374                                        break;
375                               
376                                strcat( buff, u->nick );
377                                strcat( buff, " " );
378                        }
379                       
380                        if( next )
381                        {
382                                *next = ' ';
383                                this = next + 1;
384                        }
385                        else
386                        {
387                                break;
388                        }   
389                }
390               
391                /* *sigh* */
392                if( lenleft < 0 )
393                        break;
394        }
395       
396        if( strlen( buff ) > 0 )
397                buff[strlen(buff)-1] = '\0';
398       
399        irc_send_num( irc, 303, ":%s", buff );
400}
401
402static void irc_cmd_watch( irc_t *irc, char **cmd )
403{
404        int i;
405       
406        /* Obviously we could also mark a user structure as being
407           watched, but what if the WATCH command is sent right
408           after connecting? The user won't exist yet then... */
409        for( i = 1; cmd[i]; i ++ )
410        {
411                char *nick;
412                user_t *u;
413               
414                if( !cmd[i][0] || !cmd[i][1] )
415                        break;
416               
417                nick = g_strdup( cmd[i] + 1 );
418                nick_lc( nick );
419               
420                u = user_find( irc, nick );
421               
422                if( cmd[i][0] == '+' )
423                {
424                        if( !g_hash_table_lookup( irc->watches, nick ) )
425                                g_hash_table_insert( irc->watches, nick, nick );
426                       
427                        if( u && u->online )
428                                irc_send_num( irc, 604, "%s %s %s %d :%s", u->nick, u->user, u->host, (int) time( NULL ), "is online" );
429                        else
430                                irc_send_num( irc, 605, "%s %s %s %d :%s", nick, "*", "*", (int) time( NULL ), "is offline" );
431                }
432                else if( cmd[i][0] == '-' )
433                {
434                        gpointer okey, ovalue;
435                       
436                        if( g_hash_table_lookup_extended( irc->watches, nick, &okey, &ovalue ) )
437                        {
438                                g_hash_table_remove( irc->watches, okey );
439                                g_free( okey );
440                               
441                                irc_send_num( irc, 602, "%s %s %s %d :%s", nick, "*", "*", 0, "Stopped watching" );
442                        }
443                }
444        }
445}
446
447static void irc_cmd_topic( irc_t *irc, char **cmd )
448{
449        char *channel = cmd[1];
450        char *topic = cmd[2];
451       
452        if( topic )
453        {
454                /* Send the topic */
455                struct groupchat *c = irc_chat_by_channel( irc, channel );
456                if( c && c->ic && c->ic->acc->prpl->chat_topic )
457                        c->ic->acc->prpl->chat_topic( c, topic );
458        }
459        else
460        {
461                /* Get the topic */
462                irc_topic( irc, channel );
463        }
464}
465
466static void irc_cmd_away( irc_t *irc, char **cmd )
467{
468        user_t *u = user_find( irc, irc->nick );
469        char *away = cmd[1];
470       
471        if( !u ) return;
472       
473        if( away && *away )
474        {
475                int i, j;
476               
477                /* Copy away string, but skip control chars. Mainly because
478                   Jabber really doesn't like them. */
479                u->away = g_malloc( strlen( away ) + 1 );
480                for( i = j = 0; away[i]; i ++ )
481                        if( ( u->away[j] = away[i] ) >= ' ' )
482                                j ++;
483                u->away[j] = 0;
484               
485                irc_send_num( irc, 306, ":You're now away: %s", u->away );
486                /* irc_umode_set( irc, irc->myhost, "+a" ); */
487        }
488        else
489        {
490                if( u->away ) g_free( u->away );
491                u->away = NULL;
492                /* irc_umode_set( irc, irc->myhost, "-a" ); */
493                irc_send_num( irc, 305, ":Welcome back" );
494        }
495       
496        set_setstr( &irc->set, "away", u->away );
497}
498
499static void irc_cmd_nickserv( irc_t *irc, char **cmd )
500{
501        /* [SH] This aliases the NickServ command to PRIVMSG root */
502        /* [TV] This aliases the NS command to PRIVMSG root as well */
503        root_command( irc, cmd + 1 );
504}
505
506static void irc_cmd_motd( irc_t *irc, char **cmd )
507{
508        irc_motd( irc );
509}
510
511static void irc_cmd_pong( irc_t *irc, char **cmd )
512{
513        /* We could check the value we get back from the user, but in
514           fact we don't care, we're just happy he's still alive. */
515        irc->last_pong = gettime();
516        irc->pinging = 0;
517}
518
519static void irc_cmd_version( irc_t *irc, char **cmd )
520{
521        irc_send_num( irc, 351, "bitlbee-%s. %s :%s/%s ", BITLBEE_VERSION, irc->myhost, ARCH, CPU );
522}
523
524static void irc_cmd_completions( irc_t *irc, char **cmd )
525{
526        user_t *u = user_find( irc, irc->mynick );
527        help_t *h;
528        set_t *s;
529        int i;
530       
531        irc_privmsg( irc, u, "NOTICE", irc->nick, "COMPLETIONS ", "OK" );
532       
533        for( i = 0; commands[i].command; i ++ )
534                irc_privmsg( irc, u, "NOTICE", irc->nick, "COMPLETIONS ", commands[i].command );
535       
536        for( h = global.help; h; h = h->next )
537                irc_privmsg( irc, u, "NOTICE", irc->nick, "COMPLETIONS help ", h->title );
538       
539        for( s = irc->set; s; s = s->next )
540                irc_privmsg( irc, u, "NOTICE", irc->nick, "COMPLETIONS set ", s->key );
541       
542        irc_privmsg( irc, u, "NOTICE", irc->nick, "COMPLETIONS ", "END" );
543}
544
545static void irc_cmd_rehash( irc_t *irc, char **cmd )
546{
547        if( global.conf->runmode == RUNMODE_INETD )
548                ipc_master_cmd_rehash( NULL, NULL );
549        else
550                ipc_to_master( cmd );
551       
552        irc_send_num( irc, 382, "%s :Rehashing", global.conf_file );
553}
554#endif
555
556static const command_t irc_commands[] = {
557        { "pass",        1, irc_cmd_pass,        0 },
558        { "user",        4, irc_cmd_user,        IRC_CMD_PRE_LOGIN },
559        { "nick",        1, irc_cmd_nick,        0 },
560        { "quit",        0, irc_cmd_quit,        0 },
561        { "ping",        0, irc_cmd_ping,        0 },
562        { "join",        1, irc_cmd_join,        IRC_CMD_LOGGED_IN },
563        { "names",       1, irc_cmd_names,       IRC_CMD_LOGGED_IN },
564        { "part",        1, irc_cmd_part,        IRC_CMD_LOGGED_IN },
565        { "whois",       1, irc_cmd_whois,       IRC_CMD_LOGGED_IN },
566        { "whowas",      1, irc_cmd_whowas,      IRC_CMD_LOGGED_IN },
567#if 0
568        { "oper",        2, irc_cmd_oper,        IRC_CMD_LOGGED_IN },
569        { "mode",        1, irc_cmd_mode,        IRC_CMD_LOGGED_IN },
570        { "invite",      2, irc_cmd_invite,      IRC_CMD_LOGGED_IN },
571        { "privmsg",     1, irc_cmd_privmsg,     IRC_CMD_LOGGED_IN },
572        { "notice",      1, irc_cmd_privmsg,     IRC_CMD_LOGGED_IN },
573        { "who",         0, irc_cmd_who,         IRC_CMD_LOGGED_IN },
574        { "userhost",    1, irc_cmd_userhost,    IRC_CMD_LOGGED_IN },
575        { "ison",        1, irc_cmd_ison,        IRC_CMD_LOGGED_IN },
576        { "watch",       1, irc_cmd_watch,       IRC_CMD_LOGGED_IN },
577        { "topic",       1, irc_cmd_topic,       IRC_CMD_LOGGED_IN },
578        { "away",        0, irc_cmd_away,        IRC_CMD_LOGGED_IN },
579        { "nickserv",    1, irc_cmd_nickserv,    IRC_CMD_LOGGED_IN },
580        { "ns",          1, irc_cmd_nickserv,    IRC_CMD_LOGGED_IN },
581        { "motd",        0, irc_cmd_motd,        IRC_CMD_LOGGED_IN },
582        { "pong",        0, irc_cmd_pong,        IRC_CMD_LOGGED_IN },
583        { "version",     0, irc_cmd_version,     IRC_CMD_LOGGED_IN },
584        { "completions", 0, irc_cmd_completions, IRC_CMD_LOGGED_IN },
585        { "die",         0, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
586        { "deaf",        0, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
587        { "wallops",     1, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
588        { "wall",        1, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
589        { "rehash",      0, irc_cmd_rehash,      IRC_CMD_OPER_ONLY },
590        { "restart",     0, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
591        { "kill",        2, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
592#endif
593        { NULL }
594};
595
596void irc_exec( irc_t *irc, char *cmd[] )
597{       
598        int i, n_arg;
599       
600        if( !cmd[0] )
601                return;
602       
603        for( i = 0; irc_commands[i].command; i++ )
604                if( g_strcasecmp( irc_commands[i].command, cmd[0] ) == 0 )
605                {
606                        /* There should be no typo in the next line: */
607                        for( n_arg = 0; cmd[n_arg]; n_arg ++ ); n_arg --;
608                       
609                        if( irc_commands[i].flags & IRC_CMD_PRE_LOGIN && irc->status & USTATUS_LOGGED_IN )
610                        {
611                                irc_send_num( irc, 462, ":Only allowed before logging in" );
612                        }
613                        else if( irc_commands[i].flags & IRC_CMD_LOGGED_IN && !( irc->status & USTATUS_LOGGED_IN ) )
614                        {
615                                irc_send_num( irc, 451, ":Register first" );
616                        }
617                        else if( irc_commands[i].flags & IRC_CMD_OPER_ONLY && !strchr( irc->umode, 'o' ) )
618                        {
619                                irc_send_num( irc, 481, ":Permission denied - You're not an IRC operator" );
620                        }
621                        else if( n_arg < irc_commands[i].required_parameters )
622                        {
623                                irc_send_num( irc, 461, "%s :Need more parameters", cmd[0] );
624                        }
625                        else if( irc_commands[i].flags & IRC_CMD_TO_MASTER )
626                        {
627                                /* IPC doesn't make sense in inetd mode,
628                                    but the function will catch that. */
629                                ipc_to_master( cmd );
630                        }
631                        else
632                        {
633                                irc_commands[i].execute( irc, cmd );
634                        }
635                       
636                        return;
637                }
638       
639        if( irc->status >= USTATUS_LOGGED_IN )
640                irc_send_num( irc, 421, "%s :Unknown command", cmd[0] );
641}
Note: See TracBrowser for help on using the repository browser.