]> arthur.barton.de Git - ngircd-alex.git/blob - src/ngircd/numeric.c
5217e14ed4902161f5a65ed7b3b828069994edc7
[ngircd-alex.git] / src / ngircd / numeric.c
1 /*
2  * ngIRCd -- The Next Generation IRC Daemon
3  * Copyright (c)2001-2014 Alexander Barton (alex@barton.de) and Contributors.
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  * Please read the file COPYING, README and AUTHORS for more information.
10  */
11
12 #include "portab.h"
13
14 /**
15  * @file
16  * Handlers for IRC numerics sent to the server
17  */
18
19 #include <assert.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <time.h>
24
25 #include "conn-func.h"
26 #include "conf.h"
27 #include "channel.h"
28 #include "class.h"
29 #include "irc-write.h"
30 #include "lists.h"
31 #include "log.h"
32 #include "parse.h"
33
34 /**
35  * Announce a channel and its users in the network.
36  */
37 static bool
38 Announce_Channel(CLIENT *Client, CHANNEL *Chan)
39 {
40         CL2CHAN *cl2chan;
41         CLIENT *cl;
42         char str[COMMAND_LEN], *ptr;
43         bool njoin, xop;
44
45         /* Check features of remote server */
46         njoin = Conn_Options(Client_Conn(Client)) & CONN_RFC1459 ? false : true;
47         xop = Client_HasFlag(Client, 'X') ? true : false;
48
49         /* Get all the members of this channel */
50         cl2chan = Channel_FirstMember(Chan);
51         snprintf(str, sizeof(str), "NJOIN %s :", Channel_Name(Chan));
52         while (cl2chan) {
53                 cl = Channel_GetClient(cl2chan);
54                 assert(cl != NULL);
55
56                 if (njoin) {
57                         /* RFC 2813: send NJOIN with nicknames and modes
58                          * (if user is channel operator or has voice) */
59                         if (str[strlen(str) - 1] != ':')
60                                 strlcat(str, ",", sizeof(str));
61
62                         /* Prepare user prefix (ChanOp, voiced, ...) */
63                         if (xop && Channel_UserHasMode(Chan, cl, 'q'))
64                                 strlcat(str, "~", sizeof(str));
65                         if (xop && Channel_UserHasMode(Chan, cl, 'a'))
66                                 strlcat(str, "&", sizeof(str));
67                         if (Channel_UserHasMode(Chan, cl, 'o'))
68                                 strlcat(str, "@", sizeof(str));
69                         if (xop && Channel_UserHasMode(Chan, cl, 'h'))
70                                 strlcat(str, "%", sizeof(str));
71                         if (Channel_UserHasMode(Chan, cl, 'v'))
72                                 strlcat(str, "+", sizeof(str));
73
74                         strlcat(str, Client_ID(cl), sizeof(str));
75
76                         /* Send the data if the buffer is "full" */
77                         if (strlen(str) > (sizeof(str) - CLIENT_NICK_LEN - 8)) {
78                                 if (!IRC_WriteStrClient(Client, "%s", str))
79                                         return DISCONNECTED;
80                                 snprintf(str, sizeof(str), "NJOIN %s :",
81                                          Channel_Name(Chan));
82                         }
83                 } else {
84                         /* RFC 1459: no NJOIN, send JOIN and MODE */
85                         if (!IRC_WriteStrClientPrefix(Client, cl, "JOIN %s",
86                                                 Channel_Name(Chan)))
87                                 return DISCONNECTED;
88                         ptr = Channel_UserModes(Chan, cl);
89                         while (*ptr) {
90                                 if (!IRC_WriteStrClientPrefix(Client, cl,
91                                                    "MODE %s +%c %s",
92                                                    Channel_Name(Chan), ptr[0],
93                                                    Client_ID(cl)))
94                                         return DISCONNECTED;
95                                 ptr++;
96                         }
97                 }
98
99                 cl2chan = Channel_NextMember(Chan, cl2chan);
100         }
101
102         /* Data left in the buffer? */
103         if (str[strlen(str) - 1] != ':') {
104                 /* Yes, send it ... */
105                 if (!IRC_WriteStrClient(Client, "%s", str))
106                         return DISCONNECTED;
107         }
108
109         return CONNECTED;
110 } /* Announce_Channel */
111
112 /**
113  * Announce new server in the network
114  * @param Client New server
115  * @param Server Existing server in the network
116  */
117 static bool
118 Announce_Server(CLIENT * Client, CLIENT * Server)
119 {
120         CLIENT *c;
121
122         if (Client_Conn(Server) > NONE) {
123                 /* Announce the new server to the one already registered
124                  * which is directly connected to the local server */
125                 if (!IRC_WriteStrClient
126                     (Server, "SERVER %s %d %d :%s", Client_ID(Client),
127                      Client_Hops(Client) + 1, Client_MyToken(Client),
128                      Client_Info(Client)))
129                         return DISCONNECTED;
130         }
131
132         if (Client_Hops(Server) == 1)
133                 c = Client_ThisServer();
134         else
135                 c = Client_TopServer(Server);
136
137         /* Inform new server about the one already registered in the network */
138         return IRC_WriteStrClientPrefix(Client, c, "SERVER %s %d %d :%s",
139                 Client_ID(Server), Client_Hops(Server) + 1,
140                 Client_MyToken(Server), Client_Info(Server));
141 } /* Announce_Server */
142
143 #ifdef IRCPLUS
144
145 /**
146  * Synchronize invite, ban, G- and K-Line lists between servers.
147  *
148  * @param Client New server.
149  * @return CONNECTED or DISCONNECTED.
150  */
151 static bool
152 Synchronize_Lists(CLIENT * Client)
153 {
154         CHANNEL *c;
155         struct list_head *head;
156         struct list_elem *elem;
157
158         assert(Client != NULL);
159
160         /* g-lines */
161         head = Class_GetList(CLASS_GLINE);
162         elem = Lists_GetFirst(head);
163         while (elem) {
164                 if (!IRC_WriteStrClient(Client, "GLINE %s %ld :%s",
165                                         Lists_GetMask(elem),
166                                         Lists_GetValidity(elem) - time(NULL),
167                                         Lists_GetReason(elem)))
168                         return DISCONNECTED;
169                 elem = Lists_GetNext(elem);
170         }
171
172         c = Channel_First();
173         while (c) {
174                 /* ban list */
175                 head = Channel_GetListBans(c);
176                 elem = Lists_GetFirst(head);
177                 while (elem) {
178                         if (!IRC_WriteStrClient(Client, "MODE %s +b %s",
179                                                 Channel_Name(c),
180                                                 Lists_GetMask(elem))) {
181                                 return DISCONNECTED;
182                         }
183                         elem = Lists_GetNext(elem);
184                 }
185
186                 /* invite list */
187                 head = Channel_GetListInvites(c);
188                 elem = Lists_GetFirst(head);
189                 while (elem) {
190                         if (!IRC_WriteStrClient(Client, "MODE %s +I %s",
191                                                 Channel_Name(c),
192                                                 Lists_GetMask(elem))) {
193                                 return DISCONNECTED;
194                         }
195                         elem = Lists_GetNext(elem);
196                 }
197
198                 c = Channel_Next(c);
199         }
200         return CONNECTED;
201 }
202
203 /**
204  * Send CHANINFO commands to a new server (inform it about existing channels).
205  * @param Client New server
206  * @param Chan Channel
207  */
208 static bool
209 Send_CHANINFO(CLIENT * Client, CHANNEL * Chan)
210 {
211         char *modes, *topic;
212         bool has_k, has_l;
213
214 #ifdef DEBUG
215         Log(LOG_DEBUG, "Sending CHANINFO commands ...");
216 #endif
217
218         modes = Channel_Modes(Chan);
219         topic = Channel_Topic(Chan);
220
221         if (!*modes && !*topic)
222                 return CONNECTED;
223
224         has_k = Channel_HasMode(Chan, 'k');
225         has_l = Channel_HasMode(Chan, 'l');
226
227         /* send CHANINFO */
228         if (!has_k && !has_l) {
229                 if (!*topic) {
230                         /* "CHANINFO <chan> +<modes>" */
231                         return IRC_WriteStrClient(Client, "CHANINFO %s +%s",
232                                                   Channel_Name(Chan), modes);
233                 }
234                 /* "CHANINFO <chan> +<modes> :<topic>" */
235                 return IRC_WriteStrClient(Client, "CHANINFO %s +%s :%s",
236                                           Channel_Name(Chan), modes, topic);
237         }
238         /* "CHANINFO <chan> +<modes> <key> <limit> :<topic>" */
239         return IRC_WriteStrClient(Client, "CHANINFO %s +%s %s %lu :%s",
240                                   Channel_Name(Chan), modes,
241                                   has_k ? Channel_Key(Chan) : "*",
242                                   has_l ? Channel_MaxUsers(Chan) : 0, topic);
243 } /* Send_CHANINFO */
244
245 #endif /* IRCPLUS */
246
247 /**
248  * Handle ENDOFMOTD (376) numeric and login remote server.
249  * The peer is either an IRC server (no IRC+ protocol), or we got the
250  * ENDOFMOTD numeric from an IRC+ server. We have to register the new server.
251  */
252 GLOBAL bool
253 IRC_Num_ENDOFMOTD(CLIENT * Client, UNUSED REQUEST * Req)
254 {
255         int max_hops, i;
256         CLIENT *c;
257         CHANNEL *chan;
258
259         Client_SetType(Client, CLIENT_SERVER);
260
261         Log(LOG_NOTICE | LOG_snotice,
262             "Server \"%s\" registered (connection %d, 1 hop - direct link).",
263             Client_ID(Client), Client_Conn(Client));
264
265         /* Get highest hop count */
266         max_hops = 0;
267         c = Client_First();
268         while (c) {
269                 if (Client_Hops(c) > max_hops)
270                         max_hops = Client_Hops(c);
271                 c = Client_Next(c);
272         }
273
274         /* Inform the new server about all other servers, and announce the
275          * new server to all the already registered ones. Important: we have
276          * to do this "in order" and can't introduce servers of which the
277          * "toplevel server" isn't known already. */
278         for (i = 0; i < (max_hops + 1); i++) {
279                 for (c = Client_First(); c != NULL; c = Client_Next(c)) {
280                         if (Client_Type(c) != CLIENT_SERVER)
281                                 continue;       /* not a server */
282                         if (Client_Hops(c) != i)
283                                 continue;       /* not actual "nesting level" */
284                         if (c == Client || c == Client_ThisServer())
285                                 continue;       /* that's us or the peer! */
286
287                         if (!Announce_Server(Client, c))
288                                 return DISCONNECTED;
289                 }
290         }
291
292         /* Announce all the users to the new server */
293         c = Client_First();
294         while (c) {
295                 if (Client_Type(c) == CLIENT_USER ||
296                     Client_Type(c) == CLIENT_SERVICE) {
297                         if (!Client_Announce(Client, Client_ThisServer(), c))
298                                 return DISCONNECTED;
299                 }
300                 c = Client_Next(c);
301         }
302
303         /* Announce all channels to the new server */
304         chan = Channel_First();
305         while (chan) {
306                 if (Channel_IsLocal(chan)) {
307                         chan = Channel_Next(chan);
308                         continue;
309                 }
310 #ifdef IRCPLUS
311                 /* Send CHANINFO if the peer supports it */
312                 if (Client_HasFlag(Client, 'C')) {
313                         if (!Send_CHANINFO(Client, chan))
314                                 return DISCONNECTED;
315                 }
316 #endif
317
318                 if (!Announce_Channel(Client, chan))
319                         return DISCONNECTED;
320
321                 /* Get next channel ... */
322                 chan = Channel_Next(chan);
323         }
324
325 #ifdef IRCPLUS
326         if (Client_HasFlag(Client, 'L')) {
327                 LogDebug("Synchronizing INVITE- and BAN-lists ...");
328                 if (!Synchronize_Lists(Client))
329                         return DISCONNECTED;
330         }
331 #endif
332
333         if (!IRC_WriteStrClient(Client, "PING :%s",
334             Client_ID(Client_ThisServer())))
335                 return DISCONNECTED;
336
337         return CONNECTED;
338 } /* IRC_Num_ENDOFMOTD */
339
340 /**
341  * Handle ISUPPORT (005) numeric.
342  */
343 GLOBAL bool
344 IRC_Num_ISUPPORT(CLIENT * Client, REQUEST * Req)
345 {
346         int i;
347         char *key, *value;
348
349         for (i = 1; i < Req->argc - 1; i++) {
350                 key = Req->argv[i];
351                 value = strchr(key, '=');
352                 if (value)
353                         *value++ = '\0';
354                 else
355                         value = "";
356
357                 if (strcmp("NICKLEN", key) == 0) {
358                         if ((unsigned int)atol(value) == Conf_MaxNickLength - 1)
359                                 continue;
360
361                         /* Nickname length settings are different! */
362                         Log(LOG_ERR,
363                             "Peer uses incompatible nickname length (%d/%d)! Disconnecting ...",
364                             Conf_MaxNickLength - 1, atoi(value));
365                         Conn_Close(Client_Conn(Client),
366                                    "Incompatible nickname length",
367                                    NULL, false);
368                         return DISCONNECTED;
369                 }
370         }
371
372         return CONNECTED;
373 } /* IRC_Num_ISUPPORT */
374
375 /* -eof- */