source: irc_commands.c @ b57fed0

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

CAP LS

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