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,
51 struct client *c, struct query *);
52 static int handle_topic(const struct line *, const struct network *n,
53 struct client *c, struct query *);
55 static struct query queries[] = {
56 /* Commands that get a one-client reply:
57 * WHOIS [<server>] <nickmask>[,<nickmask>[,...]] */
59 { RPL_WHOISUSER, RPL_WHOISCHANNELS, RPL_AWAY,
60 RPL_WHOISIDLE, RPL_WHOISCHANNELS, RPL_WHOISIP,
61 RPL_WHOISSERVER, RPL_WHOISOPERATOR, RPL_WHOISACTUALLY,
62 RPL_WHOISACCOUNT, RPL_WHOISIDENTIFIED, 0 },
63 { RPL_ENDOFWHOIS, 0 },
64 { ERR_NOSUCHSERVER, ERR_NONICKNAMEGIVEN, ERR_NOSUCHNICK, 0 },
68 /* WHO [<name> [<o>]] */
70 { RPL_WHOREPLY, RPL_WHOSPCRPL, 0 },
72 { ERR_NOSUCHSERVER, 0 },
76 /* NAMES [<channel>{,<channel>}]*/
79 { RPL_ENDOFNAMES, 0 },
80 { ERR_TOOMANYMATCHES, ERR_NOSUCHSERVER, 0 },
84 /* LIST [<channel>{,<channel>} [<server>]*/
86 { RPL_LIST, RPL_LISTSTART, 0 },
88 { ERR_TOOMANYMATCHES, ERR_NOSUCHSERVER, 0 },
92 /* TOPIC <channel> [<topic>]*/
95 { RPL_NOTOPIC, RPL_TOPIC, 0 },
96 { ERR_NOTONCHANNEL, ERR_NEEDMOREPARAMS, ERR_CHANOPPRIVSNEEDED,
101 /* WHOWAS <nickname> [<count> [<server>]]*/
103 { RPL_WHOWASUSER, RPL_WHOISSERVER, RPL_WHOWAS_TIME, 0 },
104 { RPL_ENDOFWHOWAS, 0 },
105 { ERR_NONICKNAMEGIVEN, ERR_WASNOSUCHNICK, 0 },
109 /* STATS [<query> [<server>]]*/
111 { RPL_STATSCLINE, RPL_STATSILINE, RPL_STATSQLINE,
112 RPL_STATSLINKINFO, RPL_STATSCOMMANDS, RPL_STATSHLINE, RPL_STATSNLINE,
113 RPL_STATSKLINE, RPL_STATSLLINE, RPL_STATSUPTIME, RPL_STATSOLINE,
116 { RPL_TRYAGAIN, RPL_ENDOFSTATS, 0 },
117 { ERR_NOSUCHSERVER, 0 },
121 /* VERSION [<server>]*/
125 { ERR_NOSUCHSERVER, 0 },
129 /* LINKS [[<remote server>] <server mask>]*/
132 { RPL_ENDOFLINKS, 0 },
133 { ERR_NOSUCHSERVER, 0 },
141 { ERR_NOSUCHSERVER, 0 },
145 /* TRACE [<server>]*/
147 { RPL_TRACELINK, RPL_TRACECONNECTING,
148 RPL_TRACEUNKNOWN, RPL_TRACEUSER, RPL_TRACECLASS,
149 RPL_TRACEHANDSHAKE, RPL_TRACEOPERATOR,
150 RPL_TRACESERVER, RPL_TRACENEWTYPE, 0 },
152 { ERR_NOSUCHSERVER, 0 },
156 /* SUMMON <user> [<server>]*/
159 { RPL_SUMMONING, 0 },
160 { ERR_NORECIPIENT, ERR_FILEERROR, ERR_NOLOGIN, ERR_NOSUCHSERVER, 0 },
164 /* USERS [<server>]*/
166 { RPL_USERSSTART, RPL_USERS, RPL_NOUSERS, 0 },
167 { RPL_ENDOFUSERS, 0 },
168 { ERR_NOSUCHSERVER, ERR_FILEERROR, ERR_USERSDISABLED, 0 },
172 /* USERHOST <nickname>{ <nickname>}{ ...}*/
176 { ERR_NEEDMOREPARAMS, 0 },
180 /* ISON <nickname>{ <nickname>}{ ...} */
184 { ERR_NEEDMOREPARAMS, 0 },
188 /* JOIN <channel>{,<channel>} [<key>{,<key>}] */
192 { ERR_NEEDMOREPARAMS, ERR_BANNEDFROMCHAN,
193 ERR_INVITEONLYCHAN, ERR_BADCHANNELKEY,
194 ERR_CHANNELISFULL, ERR_BADCHANMASK,
195 ERR_FORWARDING, ERR_NOSUCHCHANNEL,
196 ERR_TOOMANYCHANNELS, 0 },
200 /* PART <channel> *( "," <channel> ) [ <Part Message> ] */
204 { ERR_NEEDMOREPARAMS, ERR_NOSUCHCHANNEL, ERR_NOTONCHANNEL, 0 },
208 /* NICK <nickname> */
212 { ERR_NONICKNAMEGIVEN, ERR_ERRONEUSNICKNAME, ERR_NICKNAMEINUSE,
213 ERR_UNAVAILRESOURCE, ERR_RESTRICTED, ERR_NICKCOLLISION,
214 ERR_NICKTOOFAST, 0 },
218 /* USER <username> <hostname> <servername> <realname> */
222 { ERR_NEEDMOREPARAMS, ERR_ALREADYREGISTERED, 0 },
226 /* QUIT [<quit message>] */
234 /* OPER <name> <password> */
237 { RPL_YOUREOPER, 0 },
238 { ERR_NEEDMOREPARAMS, ERR_NOOPERHOST, ERR_PASSWDMISMATCH, 0 },
242 /* MODE <nick> <mode> */
244 { /* Replies to channel mode queries */
245 RPL_BANLIST, RPL_EXCEPTLIST, RPL_INVITELIST,
248 /* Replies to user mode queries */
251 /* Replies to channel mode queries */
252 RPL_CHANNELMODEIS, RPL_ENDOFBANLIST, RPL_ENDOFEXCEPTLIST,
253 RPL_ENDOFINVITELIST, RPL_UNIQOPIS,
260 /* Replies to user mode queries */
261 ERR_UMODEUNKNOWNFLAG, ERR_USERSDONTMATCH,
263 /* Replies to channel mode queries */
264 ERR_USERNOTINCHANNEL, ERR_KEYSET, ERR_CHANOPPRIVSNEEDED,
265 ERR_UNKNOWNMODE, ERR_NOCHANMODES, ERR_NOSUCHCHANNEL,
266 ERR_ILLEGALCHANNELNAME,
272 /* SERVICE <nick> <reserved> <distribution> <type> <reserved> <info> */
275 { RPL_YOURESERVICE, RPL_YOURHOST, RPL_MYINFO, 0 },
276 { ERR_ALREADYREGISTERED, ERR_NEEDMOREPARAMS, ERR_ERRONEUSNICKNAME, 0 },
280 /* SQUIT <server> <comment> */
284 { ERR_NOPRIVILEGES, ERR_NEEDMOREPARAMS, ERR_NOSUCHSERVER, 0 },
288 /* INVITE <nick> <channel> */
291 { RPL_INVITING, RPL_AWAY, 0 },
292 { ERR_NEEDMOREPARAMS, ERR_NOTONCHANNEL, ERR_NOSUCHNICK,
293 ERR_CHANOPPRIVSNEEDED, ERR_USERONCHANNEL, 0 },
297 /* KICK <channel> * ( "," <channel> ) <user> *( "," <user> ) [<comment>] */
301 { ERR_NEEDMOREPARAMS, ERR_BADCHANMASK, ERR_USERNOTINCHANNEL,
302 ERR_NOSUCHCHANNEL, ERR_CHANOPPRIVSNEEDED, ERR_NOTONCHANNEL, 0 },
306 /* PRIVMSG <msgtarget> <text> */
310 { ERR_NORECIPIENT, ERR_NOTEXTTOSEND, ERR_CANNOTSENDTOCHAN,
311 ERR_NOTOPLEVEL, ERR_TOOMANYTARGETS, ERR_WILDTOPLEVEL,
312 ERR_NOSUCHNICK, ERR_NOSUCHCHANNEL, ERR_BLOCKING_NOTID, 0 },
316 /* MOTD [<target>] */
318 { RPL_MOTDSTART, RPL_MOTD, 0 },
319 { RPL_ENDOFMOTD, 0 },
324 /* LUSERS [ <mask> [ <target> ] ] */
326 { RPL_LUSERCLIENT, RPL_LUSEROP, RPL_LUSERUNKNOWN, RPL_LUSERCHANNELS,
327 RPL_LUSERME, RPL_STATSTLINE, 0 },
329 { ERR_NOSUCHSERVER, 0 },
333 /* CONNECT <target> <port> [ <remote server> ] */
337 { ERR_NOSUCHSERVER, ERR_NEEDMOREPARAMS, ERR_NOPRIVILEGES, 0 },
341 /* ADMIN [ <target> ] */
343 { RPL_ADMINME, RPL_ADMINLOC2, RPL_ADMINLOC1,
346 { ERR_NOSUCHSERVER, 0 },
350 /* INFO [ <target> ] */
353 { RPL_ENDOFINFO, 0 },
354 { ERR_NOSUCHSERVER, 0 },
358 /* SERVLIST [ <mask> [ <type> ] ] */
361 { RPL_SERVLISTEND, 0 },
366 /* SQUERY <servicename> <text> */
367 /* Same responses as for PRIVMSG */
371 { ERR_NORECIPIENT, ERR_NOTEXTTOSEND, ERR_CANNOTSENDTOCHAN,
372 ERR_NOTOPLEVEL, ERR_TOOMANYTARGETS, ERR_WILDTOPLEVEL,
377 /* KILL <nick> <comment> */
381 { ERR_NOPRIVILEGES, ERR_NEEDMOREPARAMS, ERR_NOSUCHNICK, ERR_CANTKILLSERVER, 0 },
385 /* AWAY [ <text> ] */
388 { RPL_UNAWAY, RPL_NOWAWAY, 0 },
396 { RPL_REHASHING, 0 },
397 { ERR_NOPRIVILEGES, 0 },
405 { ERR_NOPRIVILEGES, 0 },
413 { ERR_NOPRIVILEGES, 0 },
429 { ERR_NEEDMOREPARAMS, 0 },
437 { ERR_NOORIGIN, ERR_NOSUCHSERVER, 0 },
453 { ERR_NEEDMOREPARAMS, ERR_ALREADYAUTHENTICATED, ERR_ALREADYREGISTERED,
454 ERR_AUTHENTICATIONFAILED, ERR_AUTHENTICATIONSUSPENDED,
455 ERR_BADCOMMAND, ERR_UNKNOWNPACKAGE, 0 },
462 static struct query unknown_query = {
466 { ERR_UNKNOWNCOMMAND, 0 },
470 static gboolean handle_465(struct network *n, struct line *l)
472 log_network(LOG_ERROR, n, "Banned from server: %s", l->args[1]);
476 static gboolean handle_451(struct network *n, struct line *l)
478 log_network(LOG_ERROR, n, "Not registered error, this is probably a bug...");
482 static gboolean handle_462(struct network *n, struct line *l)
484 log_network(LOG_ERROR, n, "Double registration error, this is probably a bug...");
488 static gboolean handle_463(struct network *n, struct line *l)
490 log_network(LOG_ERROR, n, "Host not privileged to connect");
494 static gboolean handle_464(struct network *n, struct line *l)
496 log_network(LOG_ERROR, n, "Password mismatch");
500 /* List of responses that should be sent to all clients */
501 static int response_all[] = { RPL_NOWAWAY, RPL_UNAWAY,
502 ERR_NO_OP_SPLIT, RPL_HIDINGHOST,
503 ERR_NEEDREGGEDNICK, RPL_UMODEIS,
504 RPL_LUSERCLIENT, RPL_LUSEROP, RPL_LUSERUNKNOWN, RPL_LUSERCHANNELS,
505 RPL_LUSERME, ERR_NO_OP_SPLIT, RPL_LOCALUSERS, RPL_GLOBALUSERS,
506 RPL_NAMREPLY, RPL_ENDOFNAMES, RPL_TOPIC, RPL_TOPICWHOTIME,
507 RPL_CREATIONTIME, 0 };
508 static int response_none[] = { ERR_NOMOTD, RPL_MOTDSTART, RPL_MOTD,
512 gboolean (*handler) (struct network *n, struct line *);
513 } response_handler[] = {
514 { ERR_PASSWDMISMATCH, handle_464 },
515 { ERR_ALREADYREGISTERED, handle_462 },
516 { ERR_NOPERMFORHOST, handle_463 },
517 { ERR_NOTREGISTERED, handle_451 },
518 { ERR_YOUREBANNEDCREEP, handle_465 },
523 * Check whether reply r is part of a specified list
525 static int is_reply(const int *replies, int r)
528 g_assert(replies != NULL);
530 for(i = 0; i < 20 && replies[i]; i++) {
531 if(replies[i] == r) return 1;
536 static struct query *find_query(char *name)
539 for(i = 0; queries[i].name; i++) {
540 if(!g_strcasecmp(queries[i].name, name)) return &queries[i];
547 * Redirect a response received from the server.
549 * @return TRUE if the message was redirected to zero or more clients,
550 * FALSE if it was sent to all clients.
552 gboolean redirect_response(struct network *network, struct line *l)
554 struct query_stack *s, *p = NULL;
555 const struct client *c = NULL;
560 g_assert(l->args[0]);
562 n = atoi(l->args[0]);
564 /* Find a request that this response is a reply to */
565 for (s = stack; s; s = s->next) {
566 if(s->network == network &&
567 (is_reply(s->query->replies, n) ||
568 is_reply(s->query->errors, n) ||
569 is_reply(s->query->end_replies, n))) {
571 /* Send to client that queried, if that client still exists */
572 if (s->client != NULL && verify_client(s->network, s->client)) {
574 client_send_line(s->client, l);
577 if (!is_reply(s->query->replies, n)) {
578 /* Remove from stack */
579 if (p == NULL)stack = s->next;
580 else p->next = s->next;
589 /* See if this is a response that should be sent to all clients */
590 for (i = 0; response_all[i]; i++) {
591 if (response_all[i] == n) {
592 clients_send(network->clients, l, c);
597 /* See if this is a response that shouldn't be sent to clients at all */
598 for (i = 0; response_none[i]; i++) {
599 if (response_none[i] == n) {
604 /* Handle response using custom function */
605 for (i = 0; response_handler[i].handler; i++) {
606 if (response_handler[i].response == n) {
607 return response_handler[i].handler(network, l);
612 log_network(LOG_WARNING, network, "Unable to redirect response %s", l->args[0]);
613 clients_send(network->clients, l, NULL);
619 void redirect_clear(const struct network *net)
621 struct query_stack *q, *p = NULL, *n;
627 if (q->network != net) {
633 /* Remove from stack */
634 if (p == NULL)stack = q->next;
635 else p->next = q->next;
642 void redirect_record(const struct network *n, struct client *c,
643 const struct line *l)
649 g_assert(l->args[0]);
651 q = find_query(l->args[0]);
654 log_client(LOG_WARNING, c, "Unknown command from client: %s",
657 log_network(LOG_WARNING, n, "Sending unknown command '%s'",
664 /* Push it up the stack! */
665 q->handle(l, n, c, q);
668 static int handle_default(const struct line *l, const struct network *n,
669 struct client *c, struct query *q)
671 struct query_stack *s = g_new(struct query_stack, 1);
677 s->time = time(NULL);
684 static int handle_topic(const struct line *l, const struct network *n, struct client *c, struct query *q)
686 if (l->args[2] != NULL)
688 return handle_default(l,n,c,q);