source: irc_commands.c @ 57119e8

Last change on this file since 57119e8 was 57119e8, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-04-11T22:26:45Z

Actually bomb out if channel creation fails at /join time.

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