source: irc_commands.c @ dc96e6e

Last change on this file since dc96e6e was dc96e6e, checked in by dequis <dx@…>, at 2015-09-11T02:31:10Z

CAP REQ

  • Property mode set to 100644
File size: 24.0 KB
Line 
1/********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
4  * Copyright 2002-2012 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., 51 Franklin St.,
23  Fifth Floor, Boston, MA  02110-1301  USA
24*/
25
26#define BITLBEE_CORE
27#include "bitlbee.h"
28#include "help.h"
29#include "ipc.h"
30
31typedef guint32 cap_flag;     /* 32 bits ought to be enough for anybody */
32
33typedef struct _cap_info {
34        char *name;
35        cap_flag flag;
36} cap_info_t;
37
38#define CAP_FOO (1 << 0)
39#define CAP_BAR (1 << 1)
40
41static const cap_info_t supported_caps[] = {
42        {"foo", CAP_FOO},
43        {"bar", CAP_BAR},
44        {NULL},
45};
46
47static cap_flag cap_flag_from_string(char *cap_name) {
48        int i;
49
50        if (!cap_name && !cap_name[0]) {
51                return 0;
52        }
53
54        if (cap_name[0] == '-') {
55                cap_name++;
56        }
57
58        for (i = 0; supported_caps[i].name; i++) {
59                if (strcmp(supported_caps[i].name, cap_name) == 0) {
60                        return supported_caps[i].flag;
61                }
62        }
63        return 0;
64}
65
66static gboolean irc_cmd_cap_req(irc_t *irc, char *caps)
67{
68        int i;
69        char *lower = NULL;
70        char **split = NULL;
71        cap_flag new_caps = irc->caps;
72
73        if (!caps || !caps[0]) {
74                return FALSE;
75        }
76
77        lower = g_ascii_strdown(caps, -1);
78        split = g_strsplit(lower, " ", -1);
79        g_free(lower);
80
81        for (i = 0; split[i]; i++) {
82                gboolean remove;
83                cap_flag flag;
84
85                if (!split[i][0]) {
86                        continue;   /* skip empty items (consecutive spaces) */
87                }
88
89                remove = (split[i][0] == '-');
90                flag = cap_flag_from_string(split[i]);
91               
92                if (!flag || (remove && !(irc->caps & flag))) {
93                        /* unsupported cap, or removing something that isn't there */
94                        g_strfreev(split);
95                        return FALSE;
96                }
97
98                if (remove) {
99                        new_caps &= ~flag;
100                } else {
101                        new_caps |= flag;
102                }
103        }
104
105        /* if we got here, set the new caps and ack */
106        irc->caps = new_caps;
107
108        g_strfreev(split);
109        return TRUE;
110}
111
112static void irc_cmd_cap(irc_t *irc, char **cmd)
113{
114        if (!(irc->status & USTATUS_LOGGED_IN)) {
115                /* Put registration on hold until CAP END */
116                irc->status |= USTATUS_CAP_PENDING;
117        }
118
119        if (g_strcasecmp(cmd[1], "LS") == 0) {
120                /* gboolean irc302 = (g_strcmp0(cmd[2], "302") == 0); */
121                //char *ls = g_strjoinv(" ", (char **) supported_caps);
122
123                irc_send_cap(irc, "LS", "foo bar");
124
125                //g_free(ls);
126
127        } else if (g_strcasecmp(cmd[1], "LIST") == 0) {
128                irc_send_cap(irc, "LIST", "");
129
130        } else if (g_strcasecmp(cmd[1], "REQ") == 0) {
131                gboolean ack = irc_cmd_cap_req(irc, cmd[2]);
132
133                irc_send_cap(irc, ack ? "ACK" : "NAK", cmd[2] ? : "");
134
135        } else if (g_strcasecmp(cmd[1], "END") == 0) {
136                irc->status &= ~USTATUS_CAP_PENDING;
137                irc_check_login(irc);
138
139        } else {
140                irc_send_num(irc, 410, "%s :Invalid CAP command", cmd[1]);
141        }
142
143}
144
145static void irc_cmd_pass(irc_t *irc, char **cmd)
146{
147        if (irc->status & USTATUS_LOGGED_IN) {
148                char *send_cmd[] = { "identify", cmd[1], NULL };
149
150                /* We're already logged in, this client seems to send the PASS
151                   command last. (Possibly it won't send it at all if it turns
152                   out we don't require it, which will break this feature.)
153                   Try to identify using the given password. */
154                root_command(irc, send_cmd);
155                return;
156        }
157        /* Handling in pre-logged-in state, first see if this server is
158           password-protected: */
159        else if (global.conf->auth_pass &&
160                 (strncmp(global.conf->auth_pass, "md5:", 4) == 0 ?
161                  md5_verify_password(cmd[1], global.conf->auth_pass + 4) == 0 :
162                  strcmp(cmd[1], global.conf->auth_pass) == 0)) {
163                irc->status |= USTATUS_AUTHORIZED;
164                irc_check_login(irc);
165        } else if (global.conf->auth_pass) {
166                irc_send_num(irc, 464, ":Incorrect password");
167        } else {
168                /* Remember the password and try to identify after USER/NICK. */
169                irc_setpass(irc, cmd[1]);
170                irc_check_login(irc);
171        }
172}
173
174static void irc_cmd_user(irc_t *irc, char **cmd)
175{
176        irc->user->user = g_strdup(cmd[1]);
177        irc->user->fullname = g_strdup(cmd[4]);
178
179        irc_check_login(irc);
180}
181
182static void irc_cmd_nick(irc_t *irc, char **cmd)
183{
184        irc_user_t *iu;
185
186        if ((iu = irc_user_by_name(irc, cmd[1])) && iu != irc->user) {
187                irc_send_num(irc, 433, "%s :This nick is already in use", cmd[1]);
188        } else if (!nick_ok(NULL, cmd[1])) {
189                /* [SH] Invalid characters. */
190                irc_send_num(irc, 432, "%s :This nick contains invalid characters", cmd[1]);
191        } else if (irc->status & USTATUS_LOGGED_IN) {
192                /* WATCH OUT: iu from the first if reused here to check if the
193                   new nickname is the same (other than case, possibly). If it
194                   is, no need to reset identify-status. */
195                if ((irc->status & USTATUS_IDENTIFIED) && iu != irc->user) {
196                        irc_setpass(irc, NULL);
197                        irc->status &= ~USTATUS_IDENTIFIED;
198                        irc_umode_set(irc, "-R", 1);
199                        irc_rootmsg(irc, "Changing nicks resets your identify status. "
200                                    "Re-identify or register a new account if you want "
201                                    "your configuration to be saved. See \x02help "
202                                    "nick_changes\x02.");
203                }
204
205                if (strcmp(cmd[1], irc->user->nick) != 0) {
206                        irc_user_set_nick(irc->user, cmd[1]);
207                }
208        } else {
209                g_free(irc->user->nick);
210                irc->user->nick = g_strdup(cmd[1]);
211
212                irc_check_login(irc);
213        }
214}
215
216static void irc_cmd_quit(irc_t *irc, char **cmd)
217{
218        if (cmd[1] && *cmd[1]) {
219                irc_abort(irc, 0, "Quit: %s", cmd[1]);
220        } else {
221                irc_abort(irc, 0, "Leaving...");
222        }
223}
224
225static void irc_cmd_ping(irc_t *irc, char **cmd)
226{
227        irc_write(irc, ":%s PONG %s :%s", irc->root->host,
228                  irc->root->host, cmd[1] ? cmd[1] : irc->root->host);
229}
230
231static void irc_cmd_pong(irc_t *irc, char **cmd)
232{
233        /* We could check the value we get back from the user, but in
234           fact we don't care, we're just happy s/he's still alive. */
235        irc->last_pong = gettime();
236        irc->pinging = 0;
237}
238
239static void irc_cmd_join(irc_t *irc, char **cmd)
240{
241        char *comma, *s = cmd[1];
242
243        while (s) {
244                irc_channel_t *ic;
245
246                if ((comma = strchr(s, ','))) {
247                        *comma = '\0';
248                }
249
250                if ((ic = irc_channel_by_name(irc, s)) == NULL &&
251                    (ic = irc_channel_new(irc, s))) {
252                        if (strcmp(set_getstr(&ic->set, "type"), "control") != 0) {
253                                /* Autoconfiguration is for control channels only ATM. */
254                        } else if (bee_group_by_name(ic->irc->b, ic->name + 1, FALSE)) {
255                                set_setstr(&ic->set, "group", ic->name + 1);
256                                set_setstr(&ic->set, "fill_by", "group");
257                        } else if (set_setstr(&ic->set, "protocol", ic->name + 1)) {
258                                set_setstr(&ic->set, "fill_by", "protocol");
259                        } else if (set_setstr(&ic->set, "account", ic->name + 1)) {
260                                set_setstr(&ic->set, "fill_by", "account");
261                        } else {
262                                /* The set commands above will run this already,
263                                   but if we didn't hit any, we have to fill the
264                                   channel with the default population. */
265                                bee_irc_channel_update(ic->irc, ic, NULL);
266                        }
267                } else if (ic == NULL) {
268                        irc_send_num(irc, 479, "%s :Invalid channel name", s);
269                        goto next;
270                }
271
272                if (ic->flags & IRC_CHANNEL_JOINED) {
273                        /* Dude, you're already there...
274                           RFC doesn't have any reply for that though? */
275                        goto next;
276                }
277
278                if (ic->f->join && !ic->f->join(ic)) {
279                        /* The story is: FALSE either means the handler
280                           showed an error message, or is doing some work
281                           before the join should be confirmed. (In the
282                           latter case, the caller should take care of that
283                           confirmation.) TRUE means all's good, let the
284                           user join the channel right away. */
285                        goto next;
286                }
287
288                irc_channel_add_user(ic, irc->user);
289
290next:
291                if (comma) {
292                        s = comma + 1;
293                        *comma = ',';
294                } else {
295                        break;
296                }
297        }
298}
299
300static void irc_cmd_names(irc_t *irc, char **cmd)
301{
302        irc_channel_t *ic;
303
304        if (cmd[1] && (ic = irc_channel_by_name(irc, cmd[1]))) {
305                irc_send_names(ic);
306        }
307        /* With no args, we should show /names of all chans. Make the code
308           below work well if necessary.
309        else
310        {
311                GSList *l;
312
313                for( l = irc->channels; l; l = l->next )
314                        irc_send_names( l->data );
315        }
316        */
317}
318
319static void irc_cmd_part(irc_t *irc, char **cmd)
320{
321        irc_channel_t *ic;
322
323        if ((ic = irc_channel_by_name(irc, cmd[1])) == NULL) {
324                irc_send_num(irc, 403, "%s :No such channel", cmd[1]);
325        } else if (irc_channel_del_user(ic, irc->user, IRC_CDU_PART, cmd[2])) {
326                if (ic->f->part) {
327                        ic->f->part(ic, NULL);
328                }
329        } else {
330                irc_send_num(irc, 442, "%s :You're not on that channel", cmd[1]);
331        }
332}
333
334static void irc_cmd_whois(irc_t *irc, char **cmd)
335{
336        char *nick = cmd[1];
337        irc_user_t *iu = irc_user_by_name(irc, nick);
338
339        if (iu) {
340                irc_send_whois(iu);
341        } else {
342                irc_send_num(irc, 401, "%s :Nick does not exist", nick);
343        }
344}
345
346static void irc_cmd_whowas(irc_t *irc, char **cmd)
347{
348        /* For some reason irssi tries a whowas when whois fails. We can
349           ignore this, but then the user never gets a "user not found"
350           message from irssi which is a bit annoying. So just respond
351           with not-found and irssi users will get better error messages */
352
353        irc_send_num(irc, 406, "%s :Nick does not exist", cmd[1]);
354        irc_send_num(irc, 369, "%s :End of WHOWAS", cmd[1]);
355}
356
357static void irc_cmd_motd(irc_t *irc, char **cmd)
358{
359        irc_send_motd(irc);
360}
361
362static void irc_cmd_mode(irc_t *irc, char **cmd)
363{
364        if (irc_channel_name_ok(cmd[1])) {
365                irc_channel_t *ic;
366
367                if ((ic = irc_channel_by_name(irc, cmd[1])) == NULL) {
368                        irc_send_num(irc, 403, "%s :No such channel", cmd[1]);
369                } else if (cmd[2]) {
370                        if (*cmd[2] == '+' || *cmd[2] == '-') {
371                                irc_send_num(irc, 477, "%s :Can't change channel modes", cmd[1]);
372                        } else if (*cmd[2] == 'b') {
373                                irc_send_num(irc, 368, "%s :No bans possible", cmd[1]);
374                        }
375                } else {
376                        irc_send_num(irc, 324, "%s +%s", cmd[1], ic->mode);
377                }
378        } else {
379                if (nick_cmp(NULL, cmd[1], irc->user->nick) == 0) {
380                        if (cmd[2]) {
381                                irc_umode_set(irc, cmd[2], 0);
382                        } else {
383                                irc_send_num(irc, 221, "+%s", irc->umode);
384                        }
385                } else {
386                        irc_send_num(irc, 502, ":Don't touch their modes");
387                }
388        }
389}
390
391static void irc_cmd_who(irc_t *irc, char **cmd)
392{
393        char *channel = cmd[1];
394        irc_channel_t *ic;
395        irc_user_t *iu;
396
397        if (!channel || *channel == '0' || *channel == '*' || !*channel) {
398                irc_send_who(irc, irc->users, "**");
399        } else if ((ic = irc_channel_by_name(irc, channel))) {
400                irc_send_who(irc, ic->users, channel);
401        } else if ((iu = irc_user_by_name(irc, channel))) {
402                /* Tiny hack! */
403                GSList *l = g_slist_append(NULL, iu);
404                irc_send_who(irc, l, channel);
405                g_slist_free(l);
406        } else {
407                irc_send_num(irc, 403, "%s :No such channel", channel);
408        }
409}
410
411static void irc_cmd_privmsg(irc_t *irc, char **cmd)
412{
413        irc_channel_t *ic;
414        irc_user_t *iu;
415
416        if (!cmd[2]) {
417                irc_send_num(irc, 412, ":No text to send");
418                return;
419        }
420
421        /* Don't treat CTCP actions as real CTCPs, just convert them right now. */
422        if (g_strncasecmp(cmd[2], "\001ACTION", 7) == 0) {
423                cmd[2] += 4;
424                memcpy(cmd[2], "/me", 3);
425                if (cmd[2][strlen(cmd[2]) - 1] == '\001') {
426                        cmd[2][strlen(cmd[2]) - 1] = '\0';
427                }
428        }
429
430        if (irc_channel_name_ok(cmd[1]) &&
431            (ic = irc_channel_by_name(irc, cmd[1]))) {
432                if (cmd[2][0] == '\001') {
433                        /* CTCPs to channels? Nah. Maybe later. */
434                } else if (ic->f->privmsg) {
435                        ic->f->privmsg(ic, cmd[2]);
436                }
437        } else if ((iu = irc_user_by_name(irc, cmd[1]))) {
438                if (cmd[2][0] == '\001') {
439                        char **ctcp;
440
441                        if (iu->f->ctcp == NULL) {
442                                return;
443                        }
444                        if (cmd[2][strlen(cmd[2]) - 1] == '\001') {
445                                cmd[2][strlen(cmd[2]) - 1] = '\0';
446                        }
447
448                        ctcp = split_command_parts(cmd[2] + 1, 0);
449                        iu->f->ctcp(iu, ctcp);
450                } else if (iu->f->privmsg) {
451                        iu->last_channel = NULL;
452                        iu->f->privmsg(iu, cmd[2]);
453                }
454        } else {
455                irc_send_num(irc, 401, "%s :No such nick/channel", cmd[1]);
456        }
457}
458
459static void irc_cmd_notice(irc_t *irc, char **cmd)
460{
461        irc_user_t *iu;
462
463        if (!cmd[2]) {
464                irc_send_num(irc, 412, ":No text to send");
465                return;
466        }
467
468        /* At least for now just echo. IIRC some IRC clients use self-notices
469           for lag checks, so try to support that. */
470        if (nick_cmp(NULL, cmd[1], irc->user->nick) == 0) {
471                irc_send_msg(irc->user, "NOTICE", irc->user->nick, cmd[2], NULL);
472        } else if ((iu = irc_user_by_name(irc, cmd[1]))) {
473                iu->f->privmsg(iu, cmd[2]);
474        }
475}
476
477static void irc_cmd_nickserv(irc_t *irc, char **cmd)
478{
479        /* [SH] This aliases the NickServ command to PRIVMSG root */
480        /* [TV] This aliases the NS command to PRIVMSG root as well */
481        root_command(irc, cmd + 1);
482}
483
484static void irc_cmd_oper_hack(irc_t *irc, char **cmd);
485
486static void irc_cmd_oper(irc_t *irc, char **cmd)
487{
488        /* Very non-standard evil but useful/secure hack, see below. */
489        if (irc->status & OPER_HACK_ANY) {
490                return irc_cmd_oper_hack(irc, cmd);
491        }
492
493        if (global.conf->oper_pass &&
494            (strncmp(global.conf->oper_pass, "md5:", 4) == 0 ?
495             md5_verify_password(cmd[2], global.conf->oper_pass + 4) == 0 :
496             strcmp(cmd[2], global.conf->oper_pass) == 0)) {
497                irc_umode_set(irc, "+o", 1);
498                irc_send_num(irc, 381, ":Password accepted");
499        } else {
500                irc_send_num(irc, 491, ":Incorrect password");
501        }
502}
503
504static void irc_cmd_oper_hack(irc_t *irc, char **cmd)
505{
506        char *password = g_strjoinv(" ", cmd + 2);
507
508        /* /OPER can now also be used to enter IM/identify passwords without
509           echoing. It's a hack but the extra password security is worth it. */
510        if (irc->status & OPER_HACK_ACCOUNT_PASSWORD) {
511                account_t *a;
512
513                for (a = irc->b->accounts; a; a = a->next) {
514                        if (strcmp(a->pass, PASSWORD_PENDING) == 0) {
515                                set_setstr(&a->set, "password", password);
516                                irc_rootmsg(irc, "Password added to IM account "
517                                            "%s", a->tag);
518                                /* The IRC client may expect this. 491 suggests the OPER
519                                   password was wrong, so the client won't expect a +o.
520                                   It may however repeat the password prompt. We'll see. */
521                                irc_send_num(irc, 491, ":Password added to IM account "
522                                             "%s", a->tag);
523                        }
524                }
525        } else if (irc->status & OPER_HACK_IDENTIFY) {
526                char *send_cmd[] = { "identify", password, NULL, NULL };
527                irc->status &= ~OPER_HACK_IDENTIFY;
528                if (irc->status & OPER_HACK_IDENTIFY_NOLOAD) {
529                        send_cmd[1] = "-noload";
530                        send_cmd[2] = password;
531                } else if (irc->status & OPER_HACK_IDENTIFY_FORCE) {
532                        send_cmd[1] = "-force";
533                        send_cmd[2] = password;
534                }
535                irc_send_num(irc, 491, ":Trying to identify");
536                root_command(irc, send_cmd);
537        } else if (irc->status & OPER_HACK_REGISTER) {
538                char *send_cmd[] = { "register", password, NULL };
539                irc_send_num(irc, 491, ":Trying to identify");
540                root_command(irc, send_cmd);
541        }
542
543        irc->status &= ~OPER_HACK_ANY;
544        g_free(password);
545}
546
547static void irc_cmd_invite(irc_t *irc, char **cmd)
548{
549        irc_channel_t *ic;
550        irc_user_t *iu;
551
552        if ((iu = irc_user_by_name(irc, cmd[1])) == NULL) {
553                irc_send_num(irc, 401, "%s :No such nick", cmd[1]);
554                return;
555        } else if ((ic = irc_channel_by_name(irc, cmd[2])) == NULL) {
556                irc_send_num(irc, 403, "%s :No such channel", cmd[2]);
557                return;
558        }
559
560        if (!ic->f->invite) {
561                irc_send_num(irc, 482, "%s :Can't invite people here", cmd[2]);
562        } else if (ic->f->invite(ic, iu)) {
563                irc_send_num(irc, 341, "%s %s", iu->nick, ic->name);
564        }
565}
566
567static void irc_cmd_kick(irc_t *irc, char **cmd)
568{
569        irc_channel_t *ic;
570        irc_user_t *iu;
571
572        if ((iu = irc_user_by_name(irc, cmd[2])) == NULL) {
573                irc_send_num(irc, 401, "%s :No such nick", cmd[2]);
574                return;
575        } else if ((ic = irc_channel_by_name(irc, cmd[1])) == NULL) {
576                irc_send_num(irc, 403, "%s :No such channel", cmd[1]);
577                return;
578        } else if (!ic->f->kick) {
579                irc_send_num(irc, 482, "%s :Can't kick people here", cmd[1]);
580                return;
581        }
582
583        ic->f->kick(ic, iu, cmd[3] ? cmd[3] : NULL);
584}
585
586static void irc_cmd_userhost(irc_t *irc, char **cmd)
587{
588        int i;
589
590        /* [TV] Usable USERHOST-implementation according to
591                RFC1459. Without this, mIRC shows an error
592                while connecting, and the used way of rejecting
593                breaks standards.
594        */
595
596        for (i = 1; cmd[i]; i++) {
597                irc_user_t *iu = irc_user_by_name(irc, cmd[i]);
598
599                if (iu) {
600                        irc_send_num(irc, 302, ":%s=%c%s@%s", iu->nick,
601                                     irc_user_get_away(iu) ? '-' : '+',
602                                     iu->user, iu->host);
603                }
604        }
605}
606
607static void irc_cmd_ison(irc_t *irc, char **cmd)
608{
609        char buff[IRC_MAX_LINE];
610        int lenleft, i;
611
612        buff[0] = '\0';
613
614        /* [SH] Leave room for : and \0 */
615        lenleft = IRC_MAX_LINE - 2;
616
617        for (i = 1; cmd[i]; i++) {
618                char *this, *next;
619
620                this = cmd[i];
621                while (*this) {
622                        irc_user_t *iu;
623
624                        if ((next = strchr(this, ' '))) {
625                                *next = 0;
626                        }
627
628                        if ((iu = irc_user_by_name(irc, this)) &&
629                            iu->bu && iu->bu->flags & BEE_USER_ONLINE) {
630                                lenleft -= strlen(iu->nick) + 1;
631
632                                if (lenleft < 0) {
633                                        break;
634                                }
635
636                                strcat(buff, iu->nick);
637                                strcat(buff, " ");
638                        }
639
640                        if (next) {
641                                *next = ' ';
642                                this = next + 1;
643                        } else {
644                                break;
645                        }
646                }
647
648                /* *sigh* */
649                if (lenleft < 0) {
650                        break;
651                }
652        }
653
654        if (strlen(buff) > 0) {
655                buff[strlen(buff) - 1] = '\0';
656        }
657
658        irc_send_num(irc, 303, ":%s", buff);
659}
660
661static void irc_cmd_watch(irc_t *irc, char **cmd)
662{
663        int i;
664
665        /* Obviously we could also mark a user structure as being
666           watched, but what if the WATCH command is sent right
667           after connecting? The user won't exist yet then... */
668        for (i = 1; cmd[i]; i++) {
669                char *nick;
670                irc_user_t *iu;
671
672                if (!cmd[i][0] || !cmd[i][1]) {
673                        break;
674                }
675
676                nick = g_strdup(cmd[i] + 1);
677                nick_lc(irc, nick);
678
679                iu = irc_user_by_name(irc, nick);
680
681                if (cmd[i][0] == '+') {
682                        if (!g_hash_table_lookup(irc->watches, nick)) {
683                                g_hash_table_insert(irc->watches, nick, nick);
684                        }
685
686                        if (iu && iu->bu && iu->bu->flags & BEE_USER_ONLINE) {
687                                irc_send_num(irc, 604, "%s %s %s %d :%s", iu->nick, iu->user,
688                                             iu->host, (int) time(NULL), "is online");
689                        } else {
690                                irc_send_num(irc, 605, "%s %s %s %d :%s", nick, "*", "*",
691                                             (int) time(NULL), "is offline");
692                        }
693                } else if (cmd[i][0] == '-') {
694                        gpointer okey, ovalue;
695
696                        if (g_hash_table_lookup_extended(irc->watches, nick, &okey, &ovalue)) {
697                                g_hash_table_remove(irc->watches, okey);
698                                g_free(okey);
699
700                                irc_send_num(irc, 602, "%s %s %s %d :%s", nick, "*", "*", 0, "Stopped watching");
701                        }
702                }
703        }
704}
705
706static void irc_cmd_topic(irc_t *irc, char **cmd)
707{
708        irc_channel_t *ic = irc_channel_by_name(irc, cmd[1]);
709        const char *new = cmd[2];
710
711        if (ic == NULL) {
712                irc_send_num(irc, 403, "%s :No such channel", cmd[1]);
713        } else if (new) {
714                if (ic->f->topic == NULL) {
715                        irc_send_num(irc, 482, "%s :Can't change this channel's topic", ic->name);
716                } else if (ic->f->topic(ic, new)) {
717                        irc_send_topic(ic, TRUE);
718                }
719        } else {
720                irc_send_topic(ic, FALSE);
721        }
722}
723
724static void irc_cmd_away(irc_t *irc, char **cmd)
725{
726        if (cmd[1] && *cmd[1]) {
727                char away[strlen(cmd[1]) + 1];
728                int i, j;
729
730                /* Copy away string, but skip control chars. Mainly because
731                   Jabber really doesn't like them. */
732                for (i = j = 0; cmd[1][i]; i++) {
733                        if ((unsigned char) (away[j] = cmd[1][i]) >= ' ') {
734                                j++;
735                        }
736                }
737                away[j] = '\0';
738
739                irc_send_num(irc, 306, ":You're now away: %s", away);
740                set_setstr(&irc->b->set, "away", away);
741        } else {
742                irc_send_num(irc, 305, ":Welcome back");
743                set_setstr(&irc->b->set, "away", NULL);
744        }
745}
746
747static void irc_cmd_list(irc_t *irc, char **cmd)
748{
749        GSList *l;
750
751        for (l = irc->channels; l; l = l->next) {
752                irc_channel_t *ic = l->data;
753
754                irc_send_num(irc, 322, "%s %d :%s",
755                             ic->name, g_slist_length(ic->users), ic->topic ? : "");
756        }
757        irc_send_num(irc, 323, ":%s", "End of /LIST");
758}
759
760static void irc_cmd_version(irc_t *irc, char **cmd)
761{
762        irc_send_num(irc, 351, "%s-%s. %s :%s/%s ",
763                     PACKAGE, BITLBEE_VERSION, irc->root->host, ARCH, CPU);
764}
765
766static void irc_cmd_completions(irc_t *irc, char **cmd)
767{
768        help_t *h;
769        set_t *s;
770        int i;
771
772        irc_send_msg_raw(irc->root, "NOTICE", irc->user->nick, "COMPLETIONS OK");
773
774        for (i = 0; root_commands[i].command; i++) {
775                irc_send_msg_f(irc->root, "NOTICE", irc->user->nick, "COMPLETIONS %s", root_commands[i].command);
776        }
777
778        for (h = global.help; h; h = h->next) {
779                irc_send_msg_f(irc->root, "NOTICE", irc->user->nick, "COMPLETIONS help %s", h->title);
780        }
781
782        for (s = irc->b->set; s; s = s->next) {
783                irc_send_msg_f(irc->root, "NOTICE", irc->user->nick, "COMPLETIONS set %s", s->key);
784        }
785
786        irc_send_msg_raw(irc->root, "NOTICE", irc->user->nick, "COMPLETIONS END");
787}
788
789static void irc_cmd_rehash(irc_t *irc, char **cmd)
790{
791        if (global.conf->runmode == RUNMODE_INETD) {
792                ipc_master_cmd_rehash(NULL, NULL);
793        } else {
794                ipc_to_master(cmd);
795        }
796
797        irc_send_num(irc, 382, "%s :Rehashing", global.conf_file);
798}
799
800static const command_t irc_commands[] = {
801        { "cap",         1, irc_cmd_cap,         0 },
802        { "pass",        1, irc_cmd_pass,        0 },
803        { "user",        4, irc_cmd_user,        IRC_CMD_PRE_LOGIN },
804        { "nick",        1, irc_cmd_nick,        0 },
805        { "quit",        0, irc_cmd_quit,        0 },
806        { "ping",        0, irc_cmd_ping,        0 },
807        { "pong",        0, irc_cmd_pong,        IRC_CMD_LOGGED_IN },
808        { "join",        1, irc_cmd_join,        IRC_CMD_LOGGED_IN },
809        { "names",       1, irc_cmd_names,       IRC_CMD_LOGGED_IN },
810        { "part",        1, irc_cmd_part,        IRC_CMD_LOGGED_IN },
811        { "whois",       1, irc_cmd_whois,       IRC_CMD_LOGGED_IN },
812        { "whowas",      1, irc_cmd_whowas,      IRC_CMD_LOGGED_IN },
813        { "motd",        0, irc_cmd_motd,        IRC_CMD_LOGGED_IN },
814        { "mode",        1, irc_cmd_mode,        IRC_CMD_LOGGED_IN },
815        { "who",         0, irc_cmd_who,         IRC_CMD_LOGGED_IN },
816        { "privmsg",     1, irc_cmd_privmsg,     IRC_CMD_LOGGED_IN },
817        { "notice",      1, irc_cmd_notice,      IRC_CMD_LOGGED_IN },
818        { "nickserv",    1, irc_cmd_nickserv,    IRC_CMD_LOGGED_IN },
819        { "ns",          1, irc_cmd_nickserv,    IRC_CMD_LOGGED_IN },
820        { "away",        0, irc_cmd_away,        IRC_CMD_LOGGED_IN },
821        { "version",     0, irc_cmd_version,     IRC_CMD_LOGGED_IN },
822        { "completions", 0, irc_cmd_completions, IRC_CMD_LOGGED_IN },
823        { "userhost",    1, irc_cmd_userhost,    IRC_CMD_LOGGED_IN },
824        { "ison",        1, irc_cmd_ison,        IRC_CMD_LOGGED_IN },
825        { "watch",       1, irc_cmd_watch,       IRC_CMD_LOGGED_IN },
826        { "invite",      2, irc_cmd_invite,      IRC_CMD_LOGGED_IN },
827        { "kick",        2, irc_cmd_kick,        IRC_CMD_LOGGED_IN },
828        { "topic",       1, irc_cmd_topic,       IRC_CMD_LOGGED_IN },
829        { "oper",        2, irc_cmd_oper,        IRC_CMD_LOGGED_IN },
830        { "list",        0, irc_cmd_list,        IRC_CMD_LOGGED_IN },
831        { "die",         0, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
832        { "deaf",        0, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
833        { "wallops",     1, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
834        { "wall",        1, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
835        { "rehash",      0, irc_cmd_rehash,      IRC_CMD_OPER_ONLY },
836        { "restart",     0, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
837        { "kill",        2, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
838        { NULL }
839};
840
841void irc_exec(irc_t *irc, char *cmd[])
842{
843        int i, n_arg;
844
845        if (!cmd[0]) {
846                return;
847        }
848
849        for (i = 0; irc_commands[i].command; i++) {
850                if (g_strcasecmp(irc_commands[i].command, cmd[0]) == 0) {
851                        /* There should be no typo in the next line: */
852                        for (n_arg = 0; cmd[n_arg]; n_arg++) {
853                                ;
854                        }
855                        n_arg--;
856
857                        if (irc_commands[i].flags & IRC_CMD_PRE_LOGIN && irc->status & USTATUS_LOGGED_IN) {
858                                irc_send_num(irc, 462, ":Only allowed before logging in");
859                        } else if (irc_commands[i].flags & IRC_CMD_LOGGED_IN && !(irc->status & USTATUS_LOGGED_IN)) {
860                                irc_send_num(irc, 451, ":Register first");
861                        } else if (irc_commands[i].flags & IRC_CMD_OPER_ONLY && !strchr(irc->umode, 'o')) {
862                                irc_send_num(irc, 481, ":Permission denied - You're not an IRC operator");
863                        } else if (n_arg < irc_commands[i].required_parameters) {
864                                irc_send_num(irc, 461, "%s :Need more parameters", cmd[0]);
865                        } else if (irc_commands[i].flags & IRC_CMD_TO_MASTER) {
866                                /* IPC doesn't make sense in inetd mode,
867                                    but the function will catch that. */
868                                ipc_to_master(cmd);
869                        } else {
870                                irc_commands[i].execute(irc, cmd);
871                        }
872
873                        return;
874                }
875        }
876
877        if (irc->status & USTATUS_LOGGED_IN) {
878                irc_send_num(irc, 421, "%s :Unknown command", cmd[0]);
879        }
880}
Note: See TracBrowser for help on using the repository browser.