2 ctrlproxy: A modular IRC proxy
3 Send numerics to the right places
4 (c) 2002-2005 Jelmer Vernooij <jelmer@nl.linux.org>
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 #include "ctrlproxy.h"
25 /* TODO: Clean up stack occasionally */
28 const struct query *query;
29 const struct network *network;
30 struct client *client;
32 struct query_stack *next;
35 static struct query_stack *stack = NULL;
38 * IRC Query done by a client
45 /* Should add this query to the stack. return TRUE if this has
46 * been done successfully, FALSE otherwise */
47 int (*handle) (const struct line *, const struct network *n, struct client *c, struct query *);
50 static int handle_default(const struct line *, const struct network *n, struct client *c, struct query *);
51 static int handle_topic(const struct line *, const struct network *n, struct client *c, struct query *);
53 static struct query queries[] = {
54 /* Commands that get a one-client reply:
55 * WHOIS [<server>] <nickmask>[,<nickmask>[,...]] */
57 { RPL_WHOISUSER, RPL_WHOISCHANNELS, RPL_AWAY,
58 RPL_WHOISIDLE, RPL_WHOISCHANNELS,
59 RPL_WHOISSERVER, RPL_WHOISOPERATOR, RPL_WHOISACTUALLY,
60 RPL_WHOISIDENTIFIED, 0 },
61 { RPL_ENDOFWHOIS, 0 },
62 { ERR_NOSUCHSERVER, ERR_NONICKNAMEGIVEN, ERR_NOSUCHNICK, 0 },
66 /* WHO [<name> [<o>]] */
70 { ERR_NOSUCHSERVER, 0 },
74 /* NAMES [<channel>{,<channel>}]*/
77 { RPL_ENDOFNAMES, 0 },
78 { ERR_TOOMANYMATCHES, ERR_NOSUCHSERVER, 0 },
82 /* LIST [<channel>{,<channel>} [<server>]*/
84 { RPL_LIST, RPL_LISTSTART, 0 },
86 { ERR_TOOMANYMATCHES, ERR_NOSUCHSERVER, 0 },
90 /* TOPIC <channel> [<topic>]*/
93 { RPL_NOTOPIC, RPL_TOPIC, 0 },
94 { ERR_NOTONCHANNEL, ERR_NEEDMOREPARAMS, ERR_CHANOPPRIVSNEEDED,
99 /* WHOWAS <nickname> [<count> [<server>]]*/
101 { RPL_WHOWASUSER, RPL_WHOISSERVER, RPL_WHOWAS_TIME, 0 },
102 { RPL_ENDOFWHOWAS, 0 },
103 { ERR_NONICKNAMEGIVEN, ERR_WASNOSUCHNICK, 0 },
107 /* STATS [<query> [<server>]]*/
109 { RPL_STATSCLINE, RPL_STATSILINE, RPL_STATSQLINE,
110 RPL_STATSLINKINFO, RPL_STATSCOMMANDS, RPL_STATSHLINE, RPL_STATSNLINE,
111 RPL_STATSKLINE, RPL_STATSLLINE, RPL_STATSUPTIME, RPL_STATSOLINE,
113 { RPL_ENDOFSTATS, 0 },
114 { ERR_NOSUCHSERVER, 0 },
118 /* VERSION [<server>]*/
122 { ERR_NOSUCHSERVER, 0 },
126 /* LINKS [[<remote server>] <server mask>]*/
129 { RPL_ENDOFLINKS, 0 },
130 { ERR_NOSUCHSERVER, 0 },
138 { ERR_NOSUCHSERVER, 0 },
142 /* TRACE [<server>]*/
144 { RPL_TRACELINK, RPL_TRACECONNECTING,
145 RPL_TRACEUNKNOWN, RPL_TRACEUSER, RPL_TRACECLASS,
146 RPL_TRACEHANDSHAKE, RPL_TRACEOPERATOR,
147 RPL_TRACESERVER, RPL_TRACENEWTYPE, 0 },
149 { ERR_NOSUCHSERVER, 0 },
153 /* SUMMON <user> [<server>]*/
156 { RPL_SUMMONING, 0 },
157 { ERR_NORECIPIENT, ERR_FILEERROR, ERR_NOLOGIN, ERR_NOSUCHSERVER, 0 },
161 /* USERS [<server>]*/
163 { RPL_USERSSTART, RPL_USERS, RPL_NOUSERS, 0 },
164 { RPL_ENDOFUSERS, 0 },
165 { ERR_NOSUCHSERVER, ERR_FILEERROR, ERR_USERSDISABLED, 0 },
169 /* USERHOST <nickname>{ <nickname>}{ ...}*/
173 { ERR_NEEDMOREPARAMS, 0 },
177 /* ISON <nickname>{ <nickname>}{ ...} */
181 { ERR_NEEDMOREPARAMS, 0 },
185 /* JOIN <channel>{,<channel>} [<key>{,<key>}] */
187 { RPL_TOPIC, RPL_TOPICWHOTIME, RPL_CREATIONTIME, 0 },
189 { ERR_NEEDMOREPARAMS, ERR_BANNEDFROMCHAN,
190 ERR_INVITEONLYCHAN, ERR_BADCHANNELKEY,
191 ERR_CHANNELISFULL, ERR_BADCHANMASK,
192 ERR_NOSUCHCHANNEL, ERR_TOOMANYCHANNELS, 0 },
196 /* PART <channel> *( "," <channel> ) [ <Part Message> ] */
200 { ERR_NEEDMOREPARAMS, ERR_NOSUCHCHANNEL, ERR_NOTONCHANNEL, 0 },
204 /* NICK <nickname> */
208 { ERR_NONICKNAMEGIVEN, ERR_ERRONEUSNICKNAME, ERR_NICKNAMEINUSE,
209 ERR_UNAVAILRESOURCE, ERR_RESTRICTED, ERR_NICKCOLLISION,
210 ERR_NICKTOOFAST, 0 },
214 /* USER <username> <hostname> <servername> <realname> */
218 { ERR_NEEDMOREPARAMS, ERR_ALREADYREGISTERED, 0 },
222 /* QUIT [<quit message>] */
230 /* OPER <name> <password> */
233 { RPL_YOUREOPER, 0 },
234 { ERR_NEEDMOREPARAMS, ERR_NOOPERHOST, ERR_PASSWDMISMATCH, 0 },
238 /* MODE <nick> <mode> */
240 { /* Replies to channel mode queries */
241 RPL_BANLIST, RPL_EXCEPTLIST, RPL_INVITELIST,
244 /* Replies to user mode queries */
247 /* Replies to channel mode queries */
248 RPL_CHANNELMODEIS, RPL_ENDOFBANLIST, RPL_ENDOFEXCEPTLIST,
249 RPL_ENDOFINVITELIST, RPL_UNIQOPIS,
256 /* Replies to user mode queries */
257 ERR_UMODEUNKNOWNFLAG, ERR_USERSDONTMATCH,
259 /* Replies to channel mode queries */
260 ERR_USERNOTINCHANNEL, ERR_KEYSET, ERR_CHANOPPRIVSNEEDED,
261 ERR_UNKNOWNMODE, ERR_NOCHANMODES,
267 /* SERVICE <nick> <reserved> <distribution> <type> <reserved> <info> */
270 { RPL_YOURESERVICE, RPL_YOURHOST, RPL_MYINFO, 0 },
271 { ERR_ALREADYREGISTERED, ERR_NEEDMOREPARAMS, ERR_ERRONEUSNICKNAME, 0 },
275 /* SQUIT <server> <comment> */
279 { ERR_NOPRIVILEGES, ERR_NEEDMOREPARAMS, ERR_NOSUCHSERVER, 0 },
283 /* INVITE <nick> <channel> */
286 { RPL_INVITING, RPL_AWAY, 0 },
287 { ERR_NEEDMOREPARAMS, ERR_NOTONCHANNEL, ERR_NOSUCHNICK,
288 ERR_CHANOPPRIVSNEEDED, ERR_USERONCHANNEL, 0 },
292 /* KICK <channel> * ( "," <channel> ) <user> *( "," <user> ) [<comment>] */
296 { ERR_NEEDMOREPARAMS, ERR_BADCHANMASK, ERR_USERNOTINCHANNEL,
297 ERR_NOSUCHCHANNEL, ERR_CHANOPPRIVSNEEDED, ERR_NOTONCHANNEL, 0 },
301 /* PRIVMSG <msgtarget> <text> */
305 { ERR_NORECIPIENT, ERR_NOTEXTTOSEND, ERR_CANNOTSENDTOCHAN,
306 ERR_NOTOPLEVEL, ERR_TOOMANYTARGETS, ERR_WILDTOPLEVEL,
307 ERR_NOSUCHNICK, ERR_NOSUCHCHANNEL, ERR_BLOCKING_NOTID, 0 },
311 /* MOTD [<target>] */
313 { RPL_MOTDSTART, RPL_MOTD, 0 },
314 { RPL_ENDOFMOTD, 0 },
319 /* LUSERS [ <mask> [ <target> ] ] */
321 { RPL_LUSERCLIENT, RPL_LUSEROP, RPL_LUSERUNKNOWN, RPL_LUSERCHANNELS,
324 { ERR_NOSUCHSERVER, 0 },
328 /* CONNECT <target> <port> [ <remote server> ] */
332 { ERR_NOSUCHSERVER, ERR_NEEDMOREPARAMS, ERR_NOPRIVILEGES, 0 },
336 /* ADMIN [ <target> ] */
338 { RPL_ADMINME, RPL_ADMINLOC2, RPL_ADMINLOC1,
341 { ERR_NOSUCHSERVER, 0 },
345 /* INFO [ <target> ] */
348 { RPL_ENDOFINFO, 0 },
349 { ERR_NOSUCHSERVER, 0 },
353 /* SERVLIST [ <mask> [ <type> ] ] */
356 { RPL_SERVLISTEND, 0 },
361 /* SQUERY <servicename> <text> */
362 /* Same responses as for PRIVMSG */
366 { ERR_NORECIPIENT, ERR_NOTEXTTOSEND, ERR_CANNOTSENDTOCHAN,
367 ERR_NOTOPLEVEL, ERR_TOOMANYTARGETS, ERR_WILDTOPLEVEL,
372 /* KILL <nick> <comment> */
376 { ERR_NOPRIVILEGES, ERR_NEEDMOREPARAMS, ERR_NOSUCHNICK, ERR_CANTKILLSERVER, 0 },
380 /* AWAY [ <text> ] */
383 { RPL_UNAWAY, RPL_NOWAWAY, 0 },
391 { RPL_REHASHING, 0 },
392 { ERR_NOPRIVILEGES, 0 },
400 { ERR_NOPRIVILEGES, 0 },
408 { ERR_NOPRIVILEGES, 0 },
424 { ERR_NEEDMOREPARAMS, 0 },
432 { ERR_NOORIGIN, ERR_NOSUCHSERVER, 0 },
448 { ERR_NEEDMOREPARAMS, ERR_ALREADYAUTHENTICATED, ERR_ALREADYREGISTERED,
449 ERR_AUTHENTICATIONFAILED, ERR_AUTHENTICATIONSUSPENDED,
450 ERR_BADCOMMAND, ERR_UNKNOWNPACKAGE, 0 },
457 static struct query unknown_query = {
461 { ERR_UNKNOWNCOMMAND, 0 },
465 static gboolean handle_465(struct network *n, struct line *l)
467 log_network(LOG_ERROR, n, "Banned from server: %s", l->args[1]);
471 static gboolean handle_451(struct network *n, struct line *l)
473 log_network(LOG_ERROR, n, "Not registered error, this is probably a bug...");
477 static gboolean handle_462(struct network *n, struct line *l)
479 log_network(LOG_ERROR, n, "Double registration error, this is probably a bug...");
483 static gboolean handle_463(struct network *n, struct line *l)
485 log_network(LOG_ERROR, n, "Host not privileged to connect");
489 static gboolean handle_464(struct network *n, struct line *l)
491 log_network(LOG_ERROR, n, "Password mismatch");
495 /* List of responses that should be sent to all clients */
496 static int response_all[] = { RPL_NOWAWAY, RPL_UNAWAY, RPL_NAMREPLY,
497 ERR_NO_OP_SPLIT, RPL_HIDINGHOST,
498 RPL_ENDOFNAMES, ERR_NEEDREGGEDNICK, RPL_UMODEIS,
499 RPL_LUSERCLIENT, RPL_LUSEROP, RPL_LUSERUNKNOWN, RPL_LUSERCHANNELS,
500 RPL_LUSERME, ERR_NO_OP_SPLIT, RPL_LOCALUSERS, RPL_GLOBALUSERS, 0 };
501 static int response_none[] = { ERR_NOMOTD, RPL_ENDOFMOTD, 0 };
504 gboolean (*handler) (struct network *n, struct line *);
505 } response_handler[] = {
506 { ERR_PASSWDMISMATCH, handle_464 },
507 { ERR_ALREADYREGISTERED, handle_462 },
508 { ERR_NOPERMFORHOST, handle_463 },
509 { ERR_NOTREGISTERED, handle_451 },
510 { ERR_YOUREBANNEDCREEP, handle_465 },
514 static int is_reply(const int *replies, int r)
519 for(i = 0; i < 20 && replies[i]; i++) {
520 if(replies[i] == r) return 1;
525 static struct query *find_query(char *name)
528 for(i = 0; queries[i].name; i++) {
529 if(!g_strcasecmp(queries[i].name, name)) return &queries[i];
536 * Redirect a response received from the server.
538 * @return TRUE if the message was redirected to zero or more clients,
539 * FALSE if it was sent to all clients.
541 gboolean redirect_response(struct network *network, struct line *l)
543 struct query_stack *s, *p = NULL;
544 const struct client *c = NULL;
549 g_assert(l->args[0]);
551 n = atoi(l->args[0]);
553 /* Find a request that this response is a reply to */
554 for (s = stack; s; s = s->next) {
555 if(s->network == network &&
556 (is_reply(s->query->replies, n) ||
557 is_reply(s->query->errors, n) ||
558 is_reply(s->query->end_replies, n))) {
560 /* Send to client that queried, if that client still exists */
561 if (s->client && verify_client(s->network, s->client)) {
563 client_send_line(s->client, l);
566 if(!is_reply(s->query->replies, n)) {
567 /* Remove from stack */
568 if(!p)stack = s->next;
569 else p->next = s->next;
578 /* See if this is a response that should be sent to all clients */
579 for (i = 0; response_all[i]; i++) {
580 if (response_all[i] == n) {
581 clients_send(network->clients, l, c);
586 /* See if this is a response that shouldn't be sent to clients at all */
587 for (i = 0; response_none[i]; i++) {
588 if (response_none[i] == n) {
593 /* Handle response using custom function */
594 for (i = 0; response_handler[i].handler; i++) {
595 if (response_handler[i].response == n) {
596 return response_handler[i].handler(network, l);
601 log_network(LOG_WARNING, network, "Unable to redirect response %s", l->args[0]);
602 clients_send(network->clients, l, NULL);
608 void redirect_clear(const struct network *net)
610 struct query_stack *q, *p = NULL, *n;
616 if (q->network != net) {
622 /* Remove from stack */
623 if(!p)stack = q->next;
624 else p->next = q->next;
631 void redirect_record(const struct network *n, struct client *c, const struct line *l)
637 g_assert(l->args[0]);
639 q = find_query(l->args[0]);
642 log_client(LOG_WARNING, c, "Unknown command from client: %s", l->args[0]);
644 log_network(LOG_WARNING, n, "Sending unknown command '%s'", l->args[0]);
650 /* Push it up the stack! */
651 q->handle(l, n, c, q);
654 static int handle_default(const struct line *l, const struct network *n, struct client *c, struct query *q)
656 struct query_stack *s = g_new(struct query_stack,1);
662 s->time = time(NULL);
669 static int handle_topic(const struct line *l, const struct network *n, struct client *c, struct query *q)
671 if (l->args[2] != NULL)
673 return handle_default(l,n,c,q);