98ae56fd1f3b6f69adda2b6854be82b90d540525
[jelmer/ctrlproxy.git] / src / client.c
1 /*
2         ctrlproxy: A modular IRC proxy
3         (c) 2002-2007 Jelmer Vernooij <jelmer@nl.linux.org>
4
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.
9
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.
14
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.
18 */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include <netinet/in.h>
25 #include <errno.h>
26 #include <sys/types.h>
27 #include <sys/socket.h>
28 #include <netdb.h>
29
30 #include "internals.h"
31 #include "irc.h"
32
33 static gboolean process_to_client(struct irc_client *c, const struct irc_line *l)
34 {
35         log_client_line(c, l, FALSE);
36         return TRUE;
37 }
38
39 static void handle_offline_command(struct irc_client *c, const struct irc_line *l, const char *offline_reason)
40 {
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);
49         } else {
50                 client_send_response(c, ERR_UNKNOWNCOMMAND, l->args[0], offline_reason, NULL);
51         }
52 }
53
54 /**
55  * Process incoming lines from a client.
56  *
57  * @param c Client to talk to
58  * @param l Line received
59  * @return Whether the line was processed correctly
60  */
61 static gboolean process_from_client(struct irc_client *c, const struct irc_line *_l)
62 {
63         struct irc_line ol, *l;
64         g_assert(c != NULL);
65         g_assert(_l != NULL);
66
67         ol = *_l;
68         l = &ol;
69
70         log_client_line(c, l, TRUE);
71
72         l->origin = g_strdup(c->state->me.hostmask);
73
74         if (!run_client_filter(c, l, TO_SERVER)) {
75                 g_free(l->origin);
76                 return TRUE;
77         }
78
79         g_assert(l->args[0] != NULL);
80
81         if (!g_strcasecmp(l->args[0], "QUIT")) {
82                 client_disconnect(c, "Client exiting");
83                 g_free(l->origin);
84                 return FALSE;
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")) {
88                 if (l->argc < 2) {
89                         client_send_response(c, ERR_NEEDMOREPARAMS, l->args[0], 
90                                                                  "Not enough parameters", NULL);
91                         g_free(l->origin);
92                         return TRUE;
93                 }
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);
109
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;
118
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);
123                 }
124         } else if (c->network->connection.state == NETWORK_CONNECTION_STATE_NOT_CONNECTED) {
125                 char *msg;
126                 if (c->network->connection.data.tcp.last_disconnect_reason == NULL)
127                         msg = g_strdup("Currently not connected to server...");
128                 else
129                         msg = g_strdup_printf("Currently not connected to server... (%s)",
130                                         c->network->connection.data.tcp.last_disconnect_reason);
131
132                 handle_offline_command(c, l, msg);
133
134                 g_free(msg);
135         }
136
137         g_free(l->origin);
138         return TRUE;
139 }
140
141 /**
142  * Send welcome information to a client, optionally disconnecting 
143  * the client if it isn't welcome.
144  *
145  * @param client Client to talk to.
146  * @return whether the client was accepted or refused
147  */
148 static gboolean welcome_client(struct irc_client *client)
149 {
150         char *features, *tmp;
151         struct network_config *nc;
152
153         g_assert(client);
154
155         if (client->network == NULL) {
156                 client_disconnect(client, 
157                           "Please select a network first, or specify one in your configuration file");
158                 return FALSE;
159         }
160
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); 
165         g_free(tmp);
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, 
170                  ctrlproxy_version(), 
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,
173                  NULL);
174
175         features = network_generate_feature_string(client->network);
176
177         client_send_response(client, RPL_BOUNCE, features, 
178                                                  "are supported on this server", NULL);
179
180         g_free(features);
181
182         if (client->network->external_state != NULL) {
183                 client_send_luserchannels(client, g_list_length(client->network->external_state->channels));
184         }
185
186         tmp = g_strdup_printf("I have %d clients", g_list_length(client->network->clients));
187         client_send_response(client, RPL_LUSERME, tmp, NULL);
188         g_free(tmp);
189
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);
194         } else {
195                 char **lines = get_motd_lines(client->network->global->config->motd_file);
196                 client_send_motd(client, lines);
197                 g_strfreev(lines);
198         }
199
200         g_assert(client->state != NULL);
201         g_assert(client->network != NULL);
202
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); 
208
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);
214                         }
215                 }
216         }
217
218         if (!new_client_hook_execute(client)) {
219                 client_disconnect(client, "Refused by client connect hook");
220                 return FALSE;
221         }
222
223         client_replicate(client);
224         return TRUE;
225 }
226
227 static void client_free_private(struct irc_client *c)
228 {
229         irc_network_unref(c->network);
230 }
231
232 struct lose_client_hook_data {
233         char *name;
234         lose_client_hook hook;
235         void *userdata;
236 };
237
238 GList *lose_client_hooks = NULL;
239
240
241 void add_lose_client_hook(const char *name, lose_client_hook h, void *userdata)
242 {
243         struct lose_client_hook_data *d;
244         
245         d = g_malloc(sizeof(struct lose_client_hook_data));
246         d->name = g_strdup(name);
247         d->hook = h;
248         d->userdata = userdata;
249         lose_client_hooks = g_list_append(lose_client_hooks, d);
250 }
251
252 void del_lose_client_hook(const char *name)
253 {
254         GList *l;
255         for (l = lose_client_hooks; l; l = l->next)
256         {
257                 struct lose_client_hook_data *d = (struct lose_client_hook_data *)l->data;
258                 if (!strcmp(d->name, name)) {
259                         g_free(d->name);
260                         lose_client_hooks = g_list_remove(lose_client_hooks, d);
261                         g_free(d);
262                         return;
263                 }
264         }
265 }
266
267 static void handle_client_disconnect(struct irc_client *c)
268 {
269         GList *l;
270         
271         for (l = lose_client_hooks; l; l = l->next)
272         {
273                 struct lose_client_hook_data *d = (struct lose_client_hook_data *)l->data;
274                 d->hook(c, d->userdata);
275         }
276
277         if (c->network != NULL)
278                 c->network->clients = g_list_remove(c->network->clients, c);
279 }
280
281 static void client_connect_command(struct irc_client *client, const char *hostname, guint16 port)
282 {
283         extern struct global *my_global;
284
285         struct irc_network *network;
286
287         network = network_ref(find_network_by_hostname(my_global, 
288                                                                                                    hostname, port, my_global->config->create_implicit, 
289                                                                                                    client->login_details));
290
291         if (network == NULL) {
292                 client_log(LOG_ERROR, client, 
293                         "Unable to connect to network with name %s", 
294                         hostname);
295                 client->network = NULL;
296                 return;
297         }
298
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);
304         }
305
306         client->network = network;
307 }
308
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,
318 };
319
320 struct irc_client *client_init_iochannel(struct irc_network *n, GIOChannel *c, const char *desc)
321 {
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);
327
328         client = client_init(n, transport, desc);
329
330         return client;
331 }
332
333 struct irc_client *client_init(struct irc_network *n, struct irc_transport *transport, const char *desc)
334 {
335         struct irc_client *client;
336
337         client = irc_client_new(transport, n?n->name:get_my_hostname(), desc, &default_callbacks);
338         client->authenticated = FALSE;
339
340         client->network = network_ref(n);
341
342         if (n != NULL && n->global != NULL)
343                 client_set_charset(client, n->global->config->client_charset);
344
345         client->exit_on_close = FALSE;
346         
347         return client;
348 }
349
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)
354 {
355         struct irc_line *ret;
356
357         if (irccmp(info, old->hostmask, new->hostmask) == 0)
358                 return NULL; /* No need to replace anything */
359
360         /* Replace lines "faked" to be from the user itself */
361         if (l->origin != NULL && line_from_nick(info, l, old->nick)) {
362                 ret = linedup(l);
363                 g_free(ret->origin);
364                 ret->origin = g_strdup(new->hostmask);
365                 return ret;
366         }
367         switch (irc_line_respcode(l)) {
368                 case RPL_USERHOST:
369                 if (strstr(l->args[2], old->hostname)) {
370                         int i;
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)) {
377                                                 g_free(users[i]);
378                                                 users[i] = g_strdup_printf("%s=%c%s@%s", 
379                                                                 tmp302[0], tmp302[1][0], 
380                                                                 new->username, new->hostname);
381                                         }
382                                 }
383                                 g_strfreev(tmp302);
384                         }
385                         ret = linedup(l);
386                         g_free(ret->args[2]);
387                         ret->args[2] = g_strjoinv(" ", users);
388                         g_strfreev(users);
389                         return ret;
390                 }
391                 break;
392                 case RPL_WHOREPLY:
393                 if (l->argc >= 6) {
394                         if (!irccmp(info, l->args[6], old->nick)) {
395                                 ret = linedup(l);
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);
400                                 return ret;
401                         }
402                 }
403                 break;
404                 case RPL_WHOISUSER:
405                 case RPL_WHOWASUSER:
406                 if (l->argc >= 4) {
407                         if (!irccmp(info, l->args[2], old->nick)) {
408                                 ret = linedup(l);
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);
413                                 return ret;
414                         }
415                 }
416         }
417         return NULL;
418 }
419
420 /**
421  * Forward a server line to a client.
422  * This will optionally make sure the local hostmask is sent.
423  */
424 static gboolean client_forward_from_server(struct irc_client *c, const struct irc_line *l)
425 {
426         struct irc_line *nl;
427         gboolean ret;
428
429         /* Make sure the client only sees its only hostmask */
430         if (c->network->external_state != NULL) {
431                 nl = irc_line_replace_hostmask(l, 
432                                                                   c->network->info, 
433                                                                   &c->network->external_state->me, 
434                                                                   &c->state->me);
435                 if (nl != NULL)
436                         l = nl;
437         } else {
438                 nl = NULL;
439         }
440
441         ret = client_send_line(c, l, NULL);
442         free_line(nl);
443         return ret;
444 }
445
446 /**
447  * Send a line to a list of clients.
448  *
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.
452  */
453 void clients_send(GList *clients, const struct irc_line *l, 
454                                   const struct irc_client *exception) 
455 {
456         GList *g;
457
458         for (g = clients; g; g = g->next) {
459                 struct irc_client *c = (struct irc_client *)g->data;
460                 if (c == exception)
461                         continue;
462
463                 if (run_client_filter(c, l, FROM_SERVER)) { 
464                         client_forward_from_server(c, l);
465                 }
466         }
467 }
468
469 void clients_send_args_ex(GList *clients, const char *hostmask, ...)
470 {
471         struct irc_line *l;
472         va_list ap;
473
474         va_start(ap, hostmask);
475         l = virc_parse_line(hostmask, ap);
476         va_end(ap);
477
478         clients_send(clients, l, NULL);
479
480         free_line(l); l = NULL;
481 }