source: irc_commands.c @ d797fb4

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

CAP LIST

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