2 ctrlproxy: A modular IRC proxy
3 (c) 2002-2007 Jelmer Vernooij <jelmer@nl.linux.org>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 3 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
24 #include <netinet/in.h>
26 #include <sys/types.h>
27 #include <sys/socket.h>
30 #include "internals.h"
33 static gboolean process_to_client(struct irc_client *c, const struct irc_line *l)
35 log_client_line(c, l, FALSE);
39 static void handle_offline_command(struct irc_client *c, const struct irc_line *l, const char *offline_reason)
41 if (!g_strcasecmp(l->args[0], "PRIVMSG") || !g_strcasecmp(l->args[0], "NOTICE")) {
42 client_send_response(c, ERR_NOSUCHNICK, l->args[1], offline_reason, NULL);
43 } else if (!g_strcasecmp(l->args[0], "JOIN")) {
44 /* Make network->internal_state join channel */
45 client_send_response(c, ERR_NOSUCHCHANNEL, l->args[1], offline_reason, NULL);
46 } else if (!g_strcasecmp(l->args[0], "PART")) {
47 /* Make network->internal_state part channel */
48 client_send_response(c, ERR_NOTONCHANNEL, l->args[1], offline_reason, NULL);
50 client_send_response(c, ERR_UNKNOWNCOMMAND, l->args[0], offline_reason, NULL);
55 * Process incoming lines from a client.
57 * @param c Client to talk to
58 * @param l Line received
59 * @return Whether the line was processed correctly
61 static gboolean process_from_client(struct irc_client *c, const struct irc_line *_l)
63 struct irc_line ol, *l;
70 log_client_line(c, l, TRUE);
72 l->origin = g_strdup(c->state->me.hostmask);
74 if (!run_client_filter(c, l, TO_SERVER)) {
79 g_assert(l->args[0] != NULL);
81 if (!g_strcasecmp(l->args[0], "QUIT")) {
82 client_disconnect(c, "Client exiting");
85 } else if (!g_strcasecmp(l->args[0], "PING")) {
86 client_send_args(c, "PONG", c->network->name, l->args[1], NULL);
87 } else if (!g_strcasecmp(l->args[0], "PONG")) {
89 client_send_response(c, ERR_NEEDMOREPARAMS, l->args[0],
90 "Not enough parameters", NULL);
94 c->last_pong = time(NULL);
95 } else if (!g_strcasecmp(l->args[0], "USER") ||
96 !g_strcasecmp(l->args[0], "PASS")) {
97 client_send_response(c, ERR_ALREADYREGISTERED,
98 "Please register only once per session", NULL);
99 } else if (!g_strcasecmp(l->args[0], "CTRLPROXY")) {
100 admin_process_command(c, l, 1);
101 } else if (c->network->global->config->admin_user != NULL &&
102 !g_strcasecmp(l->args[0], "PRIVMSG") &&
103 !g_strcasecmp(l->args[1], c->network->global->config->admin_user)) {
104 admin_process_command(c, l, 2);
105 } else if (!g_strcasecmp(l->args[0], "PRIVMSG") && l->argc > 2 &&
106 l->args[2][0] == '\001' &&
107 g_strncasecmp(l->args[2], "\001ACTION", 7) != 0) {
108 ctcp_client_request_record(c, l);
110 /* send off to server */
111 network_forward_line(c->network, c, l, TRUE);
112 } else if (!g_strcasecmp(l->args[0], "NOTICE") && l->argc > 2 &&
113 l->args[2][0] == '\001') {
114 network_forward_line(c->network, c, l, TRUE);
115 } else if (!g_strcasecmp(l->args[0], "")) {
116 } else if (c->network->connection.state == NETWORK_CONNECTION_STATE_MOTD_RECVD) {
117 struct network_config *nc = c->network->private_data;
119 if (nc->disable_cache || !client_try_cache(c, c->network->external_state, l, &c->network->global->config->cache)) {
120 /* Perhaps check for validity of input here ? It could save us some bandwidth
121 * to the server, though unlikely to occur often */
122 network_forward_line(c->network, c, l, FALSE);
124 } else if (c->network->connection.state == NETWORK_CONNECTION_STATE_NOT_CONNECTED) {
126 if (c->network->connection.data.tcp.last_disconnect_reason == NULL)
127 msg = g_strdup("Currently not connected to server...");
129 msg = g_strdup_printf("Currently not connected to server... (%s)",
130 c->network->connection.data.tcp.last_disconnect_reason);
132 handle_offline_command(c, l, msg);
142 * Send welcome information to a client, optionally disconnecting
143 * the client if it isn't welcome.
145 * @param client Client to talk to.
146 * @return whether the client was accepted or refused
148 static gboolean welcome_client(struct irc_client *client)
150 char *features, *tmp;
151 struct network_config *nc;
155 if (client->network == NULL) {
156 client_disconnect(client,
157 "Please select a network first, or specify one in your configuration file");
161 client->network->clients = g_list_append(client->network->clients, client);
162 client_send_response(client, RPL_WELCOME, "Welcome to the ctrlproxy", NULL);
163 tmp = g_strdup_printf("Host %s is running ctrlproxy", client->default_origin);
164 client_send_response(client, RPL_YOURHOST, tmp, NULL);
166 client_send_response(client, RPL_CREATED,
167 "Ctrlproxy (c) 2002-2008 Jelmer Vernooij <jelmer@samba.org>", NULL);
168 client_send_response(client, RPL_MYINFO,
169 client->network->name,
171 (client->network->external_state != NULL && client->network->info->supported_user_modes)?client->network->info->supported_user_modes:ALLMODES,
172 (client->network->external_state != NULL && client->network->info->supported_channel_modes)?client->network->info->supported_channel_modes:ALLMODES,
175 features = network_generate_feature_string(client->network);
177 client_send_response(client, RPL_BOUNCE, features,
178 "are supported on this server", NULL);
182 if (client->network->external_state != NULL) {
183 client_send_luserchannels(client, g_list_length(client->network->external_state->channels));
186 tmp = g_strdup_printf("I have %d clients", g_list_length(client->network->clients));
187 client_send_response(client, RPL_LUSERME, tmp, NULL);
190 if (!client->network->global->config->motd_file) {
191 client_send_motd(client, NULL);
192 } else if (!strcmp(client->network->global->config->motd_file, "")) {
193 client_send_motd(client, NULL);
195 char **lines = get_motd_lines(client->network->global->config->motd_file);
196 client_send_motd(client, lines);
200 g_assert(client->state != NULL);
201 g_assert(client->network != NULL);
203 if (client->network->external_state != NULL) {
204 if (g_strcasecmp(client->state->me.nick, client->network->external_state->me.nick) != 0) {
205 /* Tell the client our his/her real nick */
206 client_send_args_ex(client, client->state->me.hostmask, "NICK",
207 client->network->external_state->me.nick, NULL);
209 /* Try to get the nick the client specified */
210 nc = client->network->private_data;
211 if (!nc->ignore_first_nick) {
212 network_send_args(client->network, "NICK",
213 client->login_details->nick, NULL);
218 if (!new_client_hook_execute(client)) {
219 client_disconnect(client, "Refused by client connect hook");
223 client_replicate(client);
227 static void client_free_private(struct irc_client *c)
229 irc_network_unref(c->network);
232 struct lose_client_hook_data {
234 lose_client_hook hook;
238 GList *lose_client_hooks = NULL;
241 void add_lose_client_hook(const char *name, lose_client_hook h, void *userdata)
243 struct lose_client_hook_data *d;
245 d = g_malloc(sizeof(struct lose_client_hook_data));
246 d->name = g_strdup(name);
248 d->userdata = userdata;
249 lose_client_hooks = g_list_append(lose_client_hooks, d);
252 void del_lose_client_hook(const char *name)
255 for (l = lose_client_hooks; l; l = l->next)
257 struct lose_client_hook_data *d = (struct lose_client_hook_data *)l->data;
258 if (!strcmp(d->name, name)) {
260 lose_client_hooks = g_list_remove(lose_client_hooks, d);
267 static void handle_client_disconnect(struct irc_client *c)
271 for (l = lose_client_hooks; l; l = l->next)
273 struct lose_client_hook_data *d = (struct lose_client_hook_data *)l->data;
274 d->hook(c, d->userdata);
277 if (c->network != NULL)
278 c->network->clients = g_list_remove(c->network->clients, c);
281 static void client_connect_command(struct irc_client *client, const char *hostname, guint16 port)
283 extern struct global *my_global;
285 struct irc_network *network;
287 network = network_ref(find_network_by_hostname(my_global,
288 hostname, port, my_global->config->create_implicit,
289 client->login_details));
291 if (network == NULL) {
292 client_log(LOG_ERROR, client,
293 "Unable to connect to network with name %s",
295 client->network = NULL;
299 if (network->connection.state == NETWORK_CONNECTION_STATE_NOT_CONNECTED) {
300 client_send_args(client, "NOTICE",
301 client_get_default_target(client),
302 "Connecting to network", NULL);
303 connect_network(network);
306 client->network = network;
309 void log_client(enum log_level, const struct irc_client *, const char *data);
310 static struct irc_client_callbacks default_callbacks = {
311 .process_from_client = process_from_client,
312 .process_to_client = process_to_client,
313 .log_fn = log_client,
314 .disconnect = handle_client_disconnect,
315 .free_private_data = client_free_private,
316 .welcome = welcome_client,
317 .on_connect = client_connect_command,
320 struct irc_client *client_init_iochannel(struct irc_network *n, GIOChannel *c, const char *desc)
322 struct irc_transport *transport;
323 struct irc_client *client;
324 g_io_channel_set_flags(c, G_IO_FLAG_NONBLOCK, NULL);
325 g_io_channel_set_close_on_unref(c, TRUE);
326 transport = irc_transport_new_iochannel(c);
328 client = client_init(n, transport, desc);
333 struct irc_client *client_init(struct irc_network *n, struct irc_transport *transport, const char *desc)
335 struct irc_client *client;
337 client = irc_client_new(transport, n?n->name:get_my_hostname(), desc, &default_callbacks);
338 client->authenticated = FALSE;
340 client->network = network_ref(n);
342 if (n != NULL && n->global != NULL)
343 client_set_charset(client, n->global->config->client_charset);
345 client->exit_on_close = FALSE;
350 struct irc_line *irc_line_replace_hostmask(const struct irc_line *l,
351 const struct irc_network_info *info,
352 const struct network_nick *old,
353 const struct network_nick *new)
355 struct irc_line *ret;
357 if (irccmp(info, old->hostmask, new->hostmask) == 0)
358 return NULL; /* No need to replace anything */
360 /* Replace lines "faked" to be from the user itself */
361 if (l->origin != NULL && line_from_nick(info, l, old->nick)) {
364 ret->origin = g_strdup(new->hostmask);
367 switch (irc_line_respcode(l)) {
369 if (strstr(l->args[2], old->hostname)) {
371 gchar **users = g_strsplit(g_strstrip(l->args[2]), " ", 0);
372 for (i = 0; users[i]; i++) {
373 gchar** tmp302 = g_strsplit(users[i], "=", 2);
374 if (g_strv_length(tmp302) > 1) {
375 /* FIXME: strip *'s from the end of tmp302[0] */
376 if (!irccmp(info, tmp302[0], old->nick)) {
378 users[i] = g_strdup_printf("%s=%c%s@%s",
379 tmp302[0], tmp302[1][0],
380 new->username, new->hostname);
386 g_free(ret->args[2]);
387 ret->args[2] = g_strjoinv(" ", users);
394 if (!irccmp(info, l->args[6], old->nick)) {
396 g_free(ret->args[4]);
397 ret->args[4] = g_strdup(new->hostname);
398 g_free(ret->args[3]);
399 ret->args[3] = g_strdup(new->username);
407 if (!irccmp(info, l->args[2], old->nick)) {
409 g_free(ret->args[4]);
410 ret->args[4] = g_strdup(new->hostname);
411 g_free(ret->args[3]);
412 ret->args[3] = g_strdup(new->username);
421 * Forward a server line to a client.
422 * This will optionally make sure the local hostmask is sent.
424 static gboolean client_forward_from_server(struct irc_client *c, const struct irc_line *l)
429 /* Make sure the client only sees its only hostmask */
430 if (c->network->external_state != NULL) {
431 nl = irc_line_replace_hostmask(l,
433 &c->network->external_state->me,
441 ret = client_send_line(c, l, NULL);
447 * Send a line to a list of clients.
449 * @param clients List of clients to send to
450 * @param l Line to send
451 * @param exception Client to which nothing should be sent. Can be NULL.
453 void clients_send(GList *clients, const struct irc_line *l,
454 const struct irc_client *exception)
458 for (g = clients; g; g = g->next) {
459 struct irc_client *c = (struct irc_client *)g->data;
463 if (run_client_filter(c, l, FROM_SERVER)) {
464 client_forward_from_server(c, l);
469 void clients_send_args_ex(GList *clients, const char *hostmask, ...)
474 va_start(ap, hostmask);
475 l = virc_parse_line(hostmask, ap);
478 clients_send(clients, l, NULL);
480 free_line(l); l = NULL;