feef1a64ee31ba6cf389aafc70eff3bbf06ab831
[jelmer/ctrlproxy.git] / src / repl.c
1 /*
2         ctrlproxy: A modular IRC proxy
3         (c) 2002-2005 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 2 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 #include "internals.h"
21 #include "irc.h"
22 #include "repl.h"
23
24 void string2mode(char *modes, char ar[255])
25 {
26         memset(ar, 0, sizeof(ar));
27
28         if (modes == NULL)
29                 return;
30
31         g_assert(modes[0] == '+');
32         modes++;
33         for (; *modes; modes++) {
34                 ar[(int)(*modes)] = 1;
35         }
36 }
37
38 char *mode2string(char modes[255])
39 {
40         char ret[256];
41         unsigned char i;
42         int pos = 0;
43         ret[0] = '\0';
44         for(i = 0; i < 255; i++) {
45                 if (modes[i]) { ret[pos] = (char)i; pos++; }
46         }
47         ret[pos] = '\0';
48
49         if (strlen(ret) == 0) {
50                 return NULL;
51         } else {
52                 return g_strdup_printf("+%s", ret);
53         }
54 }
55
56 static void client_send_channel_state(struct client *c, 
57                                                                           struct channel_state *ch)
58 {
59         struct line *l = NULL;
60         GList *nl;
61
62         g_assert(c != NULL);
63         g_assert(ch != NULL);
64         g_assert(c->network != NULL);
65         g_assert(c->network->state != NULL);
66         g_assert(ch->name != NULL);
67
68         client_send_args_ex(c, client_get_own_hostmask(c), "JOIN", ch->name, 
69                                                 NULL);
70
71         if (ch->topic != NULL) {
72                 client_send_response(c, RPL_TOPIC, ch->name, ch->topic, NULL);
73         }
74         if (ch->topic_set_time != 0 && ch->topic_set_by != NULL) {
75                 char *tmp = g_strdup_printf("%lu", ch->topic_set_time);
76                 client_send_response(c, RPL_TOPICWHOTIME, ch->name, ch->topic_set_by, 
77                                                          tmp, NULL);
78                 g_free(tmp);
79         }
80
81         for (nl = ch->nicks; nl; nl = nl->next) {
82                 char mode[2] = { ch->mode, 0 };
83                 char *arg;
84                 struct channel_nick *n = (struct channel_nick *)nl->data;
85
86                 if (n->mode != '\0' && n->mode != ' ') {
87                         arg = g_strdup_printf("%c%s", n->mode, n->global_nick->nick);
88                 } else  { 
89                         arg = g_strdup(n->global_nick->nick);
90                 }
91
92                 if (l == NULL || !line_add_arg(l, arg)) {
93                         char *tmp;
94                         if (l != NULL) {
95                                 client_send_line(c, l);
96                                 free_line(l);
97                         }
98
99                         l = irc_parse_line_args(client_get_default_origin(c), "353",
100                                                                         client_get_default_target(c), mode, 
101                                                                         ch->name, NULL);
102                         l->has_endcolon = WITHOUT_COLON;
103                         tmp = g_strdup_printf(":%s", arg);
104                         g_assert(line_add_arg(l, tmp));
105                         g_free(tmp);
106                 }
107
108                 g_free(arg);
109         }
110
111         if (l != NULL) {
112                 client_send_line(c, l);
113                 free_line(l);
114         }
115
116         client_send_response(c, RPL_ENDOFNAMES, ch->name, "End of /NAMES list", 
117                                                  NULL);
118 }
119
120 gboolean client_send_channel_state_diff(struct client *client, 
121                                                                                 struct channel_state *old_state,
122                                                                                 struct channel_state *new_state)
123 {
124         GList *gl;
125
126         /* Send PART for each user that is only in old_state */
127         for (gl = old_state->nicks; gl; gl = gl->next) {
128                 struct channel_nick *on = gl->data;
129                 struct channel_nick *nn;
130                 
131                 nn = find_channel_nick_hostmask(new_state, on->global_nick->hostmask);
132                 if (nn == NULL)
133                         client_send_args_ex(client, on->global_nick->hostmask, 
134                                                                 "PART", on->global_nick->nick, NULL);
135                 else
136                         client_send_args_ex(client, on->global_nick->hostmask,
137                                                                 "NICK", nn->global_nick->nick, NULL);
138         }
139
140         /* Send JOIN for each user that is only in new_state */
141         for (gl = new_state->nicks; gl; gl = gl->next) {
142                 struct channel_nick *nn = gl->data;
143                 struct channel_nick *on;
144                 
145                 on = find_channel_nick(old_state, nn->global_nick->nick);
146                 if (on == NULL)
147                         client_send_args_ex(client, nn->global_nick->hostmask, "JOIN", 
148                                                                 on->channel->name, NULL);
149         }
150
151         /* Send TOPIC if the topic is different */
152         if (strcmp(old_state->topic, new_state->topic) != 0) 
153                 client_send_args_ex(client, new_state->topic_set_by, "TOPIC", new_state->topic, NULL);
154
155         /* Send MODE if the mode changed */
156         if (memcmp(old_state->modes, new_state->modes, 
157                            sizeof(old_state->modes)) != 0) {
158                 char *mode = mode2string(new_state->modes);
159                 client_send_args(client, "MODE", new_state->name, mode, NULL);
160                 g_free(mode);
161         }
162
163         return TRUE;
164 }
165
166
167
168 /**
169  * Send the diff between the current state to change it to some other state.
170  * @param c Client to send to
171  * @param state State to send
172  * @return Whether the state was sent correctly
173  */
174 gboolean client_send_state_diff(struct client *client, struct network_state *new_state)
175 {
176         struct network_state *old_state = client->network->state;
177         GList *gl;
178
179         /* Call client_send_channel_state_diff() for each channel that exists 
180          * in both states*/
181         /* Send PART for each channel that is only in old_state */
182         for (gl = old_state->channels; gl; gl = gl->next) {
183                 struct channel_state *os = gl->data;
184                 struct channel_state *ns;
185
186                 ns = find_channel(new_state, os->name);
187
188                 if (ns != NULL)
189                         client_send_channel_state_diff(client, os, ns);
190                 else
191                         client_send_args_ex(client, client_get_own_hostmask(client), 
192                                                                 "PART", os->name, NULL);
193         }
194
195         /* Call client_send_channel_state() for each channel that is only 
196          * in new_state */
197         for (gl = new_state->channels; gl; gl = gl->next) {
198                 struct channel_state *ns = gl->data;
199                 struct channel_state *os;
200
201                 os = find_channel(old_state, ns->name);
202                 if (os == NULL)
203                         client_send_channel_state(client, ns);
204         }
205
206         return TRUE;
207 }
208
209 /**
210  * Send a particular state to a client.
211  *
212  * @param c Client to send to
213  * @param state State to send
214  */
215 gboolean client_send_state(struct client *c, struct network_state *state)
216 {
217         GList *cl;
218         struct channel_state *ch;
219         char *mode;
220
221         if (strcmp(state->me.nick, c->nick) != 0) {
222                 client_send_args_ex(c, c->hostmask, "NICK", state->me.nick, NULL);
223         }
224
225         g_assert(c != NULL);
226         g_assert(state != NULL);
227
228     log_client(LOG_TRACE, c, "Sending state (%d channels)", 
229                            g_list_length(state->channels));
230
231         for (cl = state->channels; cl; cl = cl->next) {
232                 ch = (struct channel_state *)cl->data;
233
234                 client_send_channel_state(c, ch);
235         }
236
237         mode = mode2string(state->me.modes);
238         if (mode != NULL) 
239                 client_send_args_ex(c, state->me.nick, "MODE", mode, NULL);
240         g_free(mode);
241
242         return TRUE;
243 }
244
245 static GList *backends = NULL;
246
247 void register_replication_backend(const struct replication_backend *backend)
248 {
249         backends = g_list_append(backends, g_memdup(backend, sizeof(*backend)));
250 }
251
252 /**
253  * Replicate the current state and backlog to the client.
254  *
255  * @param client Client to send data to.
256  */
257 void client_replicate(struct client *client)
258 {
259         void (*fn) (struct client *);
260         const char *bn = client->network->global->config->replication;
261         GList *gl;
262         
263         if (bn == NULL)
264                 bn = "none";
265
266         fn = NULL;
267
268         for (gl = backends; gl; gl = gl->next) {
269                 struct replication_backend *backend = gl->data;
270                 if (!strcmp(backend->name, bn))
271                         fn = backend->replication_fn;
272         }
273
274         if (fn == NULL) {
275                 log_client(LOG_WARNING, client, 
276                                    "Unable to find replication backend '%s'", bn);
277
278                 if (client->network->state)
279                         client_send_state(client, client->network->state);
280
281                 return;
282         } 
283
284         fn(client);
285 }