]> arthur.barton.de Git - ngircd-alex.git/blob - src/ngircd/irc-server.c
Refactor join_send_topic() into IRC_Send_Channel_Info() and use it for JOIN and NJOIN...
[ngircd-alex.git] / src / ngircd / irc-server.c
1 /*
2  * ngIRCd -- The Next Generation IRC Daemon
3  * Copyright (c)2001-2022 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  * IRC commands for server links
17  */
18
19 #include <assert.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <strings.h>
24
25 #include "conn-func.h"
26 #include "conn-zip.h"
27 #include "conf.h"
28 #include "channel.h"
29 #include "log.h"
30 #include "messages.h"
31 #include "parse.h"
32 #include "numeric.h"
33 #include "ngircd.h"
34 #include "irc.h"
35 #include "irc-channel.h"
36 #include "irc-info.h"
37 #include "irc-write.h"
38 #include "op.h"
39
40 #include "irc-server.h"
41
42 /**
43  * Handler for the IRC "SERVER" command.
44  *
45  * @param Client The client from which this command has been received.
46  * @param Req Request structure with prefix and all parameters.
47  * @return CONNECTED or DISCONNECTED.
48  */
49 GLOBAL bool
50 IRC_SERVER( CLIENT *Client, REQUEST *Req )
51 {
52         char str[100];
53         CLIENT *from, *c;
54         int i;
55
56         assert( Client != NULL );
57         assert( Req != NULL );
58
59         /* Return an error if this is not a local client */
60         if (Client_Conn(Client) <= NONE)
61                 return IRC_WriteErrClient(Client, ERR_UNKNOWNCOMMAND_MSG,
62                                           Client_ID(Client), Req->command);
63
64         if (Client_Type(Client) == CLIENT_GOTPASS ||
65             Client_Type(Client) == CLIENT_GOTPASS_2813) {
66                 /* We got a PASS command from the peer, and now a SERVER
67                  * command: the peer tries to register itself as a server. */
68                 LogDebug("Connection %d: got SERVER command (new server link) ...",
69                         Client_Conn(Client));
70
71                 if (Req->argc != 2 && Req->argc != 3)
72                         return IRC_WriteErrClient(Client, ERR_NEEDMOREPARAMS_MSG,
73                                                   Client_ID(Client),
74                                                   Req->command);
75
76                 /* Get configuration index of new remote server ... */
77                 for (i = 0; i < MAX_SERVERS; i++)
78                         if (strcasecmp(Req->argv[0], Conf_Server[i].name) == 0)
79                                 break;
80
81                 /* Make sure the remote server is configured here */
82                 if (i >= MAX_SERVERS) {
83                         Log(LOG_ERR,
84                             "Connection %d: Server \"%s\" not configured here!",
85                             Client_Conn(Client), Req->argv[0]);
86                         Conn_Close(Client_Conn(Client), NULL,
87                                    "Server not configured here", true);
88                         return DISCONNECTED;
89                 }
90
91                 /* Check server password */
92                 if (strcmp(Conn_Password(Client_Conn(Client)),
93                     Conf_Server[i].pwd_in) != 0) {
94                         Log(LOG_ERR,
95                             "Connection %d: Got bad password from server \"%s\"!",
96                             Client_Conn(Client), Req->argv[0]);
97                         Conn_Close(Client_Conn(Client), NULL,
98                                    "Bad password", true);
99                         return DISCONNECTED;
100                 }
101
102                 /* Is there a registered server with this ID? */
103                 if (!Client_CheckID(Client, Req->argv[0]))
104                         return DISCONNECTED;
105
106                 /* Mark this connection as belonging to an configured server */
107                 if (!Conf_SetServer(i, Client_Conn(Client)))
108                         return DISCONNECTED;
109
110                 Client_SetID( Client, Req->argv[0] );
111                 Client_SetHops( Client, 1 );
112                 Client_SetInfo( Client, Req->argv[Req->argc - 1] );
113
114                 /* Is this server registering on our side, or are we connecting to
115                  * a remote server? */
116                 if (Client_Token(Client) != TOKEN_OUTBOUND) {
117                         /* Incoming connection, send user/pass */
118                         if (!IRC_WriteStrClient(Client, "PASS %s %s",
119                                                 Conf_Server[i].pwd_out,
120                                                 NGIRCd_ProtoID)
121                             || !IRC_WriteStrClient(Client, "SERVER %s 1 :%s",
122                                                    Conf_ServerName,
123                                                    Conf_ServerInfo)) {
124                                     Conn_Close(Client_Conn(Client),
125                                                "Unexpected server behavior!",
126                                                NULL, false);
127                                     return DISCONNECTED;
128                         }
129                         Client_SetIntroducer(Client, Client);
130                         Client_SetToken(Client, 1);
131                 } else {
132                         /* outgoing connect, we already sent a SERVER and PASS
133                          * command to the peer */
134                         Client_SetToken(Client, atoi(Req->argv[1]));
135                 }
136
137                 /* Check protocol level */
138                 if (Client_Type(Client) == CLIENT_GOTPASS) {
139                         /* We got a "simple" PASS command, so the peer is
140                          * using the protocol as defined in RFC 1459. */
141                         if (! (Conn_Options(Client_Conn(Client)) & CONN_RFC1459))
142                                 Log(LOG_INFO,
143                                     "Switching connection %d (\"%s\") to RFC 1459 compatibility mode.",
144                                     Client_Conn(Client), Client_ID(Client));
145                         Conn_SetOption(Client_Conn(Client), CONN_RFC1459);
146                 }
147
148                 Client_SetType(Client, CLIENT_UNKNOWNSERVER);
149
150 #ifdef ZLIB
151                 if (Client_HasFlag(Client, 'Z')
152                     && !Zip_InitConn(Client_Conn(Client))) {
153                         Conn_Close(Client_Conn(Client),
154                                    "Can't initialize compression (zlib)!",
155                                    NULL, false );
156                         return DISCONNECTED;
157                 }
158 #endif
159
160 #ifdef IRCPLUS
161                 if (Client_HasFlag(Client, 'H')) {
162                         LogDebug("Peer supports IRC+ extended server handshake ...");
163                         if (!IRC_Send_ISUPPORT(Client))
164                                 return DISCONNECTED;
165                         return IRC_WriteStrClient(Client, RPL_ENDOFMOTD_MSG,
166                                                   Client_ID(Client));
167                 } else {
168 #endif
169                         if (Conf_MaxNickLength != CLIENT_NICK_LEN_DEFAULT)
170                                 Log(LOG_CRIT,
171                                     "Attention: this server uses a non-standard nick length, but the peer doesn't support the IRC+ extended server handshake!");
172 #ifdef IRCPLUS
173                 }
174 #endif
175
176                 return IRC_Num_ENDOFMOTD(Client, Req);
177         }
178         else if( Client_Type( Client ) == CLIENT_SERVER )
179         {
180                 /* New server is being introduced to the network */
181
182                 if (Req->argc != 4)
183                         return IRC_WriteErrClient(Client, ERR_NEEDMOREPARAMS_MSG,
184                                                   Client_ID(Client), Req->command);
185
186                 /* check for existing server with same ID */
187                 if (!Client_CheckID(Client, Req->argv[0]))
188                         return DISCONNECTED;
189
190                 if (!Req->prefix) {
191                         /* We definitely need a prefix here! */
192                         Log(LOG_ALERT, "Got SERVER command without prefix! (on connection %d)",
193                             Client_Conn(Client));
194                         Conn_Close(Client_Conn(Client), NULL,
195                                    "SERVER command without prefix", true);
196                         return DISCONNECTED;
197                 }
198
199                 from = Client_Search( Req->prefix );
200                 if (! from) {
201                         /* Uh, Server, that introduced the new server is unknown?! */
202                         Log(LOG_ALERT,
203                             "Unknown ID in prefix of SERVER: \"%s\"! (on connection %d)",
204                             Req->prefix, Client_Conn(Client));
205                         Conn_Close(Client_Conn(Client), NULL,
206                                    "Unknown ID in prefix of SERVER", true);
207                         return DISCONNECTED;
208                 }
209
210                 c = Client_NewRemoteServer(Client, Req->argv[0], from,
211                                            atoi(Req->argv[1]), atoi(Req->argv[2]),
212                                            Req->argv[3], true);
213                 if (!c) {
214                         Log(LOG_ALERT,
215                             "Can't create client structure for server! (on connection %d)",
216                             Client_Conn(Client));
217                         Conn_Close(Client_Conn(Client), NULL,
218                                    "Can't allocate client structure for remote server",
219                                    true);
220                         return DISCONNECTED;
221                 }
222
223                 if (Client_Hops(c) > 1 && Req->prefix[0])
224                         snprintf(str, sizeof(str), "connected to %s, ",
225                                  Client_ID(from));
226                 else
227                         strcpy(str, "");
228                 Log(LOG_NOTICE|LOG_snotice,
229                     "Server \"%s\" registered (via %s, %s%d hop%s).",
230                     Client_ID(c), Client_ID(Client), str, Client_Hops(c),
231                     Client_Hops(c) > 1 ? "s": "" );
232
233                 /* notify other servers */
234                 IRC_WriteStrServersPrefix(Client, from, "SERVER %s %d %d :%s",
235                                           Client_ID(c), Client_Hops(c) + 1,
236                                           Client_MyToken(c), Client_Info(c));
237
238                 return CONNECTED;
239         } else
240                 return IRC_WriteErrClient(Client, ERR_NEEDMOREPARAMS_MSG,
241                                           Client_ID(Client), Req->command);
242 } /* IRC_SERVER */
243
244 /*
245  * Handler for the IRC "NJOIN" command.
246  *
247  * @param Client The client from which this command has been received.
248  * @param Req Request structure with prefix and all parameters.
249  * @return CONNECTED or DISCONNECTED.
250  */
251 GLOBAL bool
252 IRC_NJOIN( CLIENT *Client, REQUEST *Req )
253 {
254         char nick_in[COMMAND_LEN], nick_out[COMMAND_LEN], *channame, *ptr, modes[8];
255         bool is_owner, is_chanadmin, is_op, is_halfop, is_voiced;
256         CHANNEL *chan;
257         CLIENT *c;
258
259         assert(Client != NULL);
260         assert(Req != NULL);
261
262         strlcpy(nick_in, Req->argv[1], sizeof(nick_in));
263         strcpy(nick_out, "");
264
265         channame = Req->argv[0];
266
267         ptr = strtok(nick_in, ",");
268         while (ptr) {
269                 is_owner = is_chanadmin = is_op = is_halfop = is_voiced = false;
270
271                 /* cut off prefixes */
272                 while ((*ptr == '~') || (*ptr == '&') || (*ptr == '@') ||
273                        (*ptr == '%') || (*ptr == '+')) {
274                         if (*ptr == '~')
275                                 is_owner = true;
276                         if (*ptr == '&')
277                                 is_chanadmin = true;
278                         if (*ptr == '@')
279                                 is_op = true;
280                         if (*ptr == '%')
281                                 is_halfop = true;
282                         if (*ptr == '+')
283                                 is_voiced = true;
284                         ptr++;
285                 }
286
287                 c = Client_Search(ptr);
288                 if (!c) {
289                         /* Client not found? */
290                         Log(LOG_ERR,
291                             "Got NJOIN for unknown nick \"%s\" for channel \"%s\"!",
292                             ptr, channame);
293                         goto skip_njoin;
294                 }
295
296                 if (!Channel_Join(c, channame)) {
297                         /* Failed to join channel. Ooops!? */
298                         Log(LOG_ALERT,
299                             "Failed to join client \"%s\" to channel \"%s\" (NJOIN): killing it!",
300                             ptr, channame);
301                         IRC_KillClient(NULL, NULL, ptr, "Internal NJOIN error!");
302                         LogDebug("... done.");
303                         goto skip_njoin;
304                 }
305
306                 chan = Channel_Search(channame);
307                 assert(chan != NULL);
308
309                 if (is_owner)
310                         Channel_UserModeAdd(chan, c, 'q');
311                 if (is_chanadmin)
312                         Channel_UserModeAdd(chan, c, 'a');
313                 if (is_op)
314                         Channel_UserModeAdd(chan, c, 'o');
315                 if (is_halfop)
316                         Channel_UserModeAdd(chan, c, 'h');
317                 if (is_voiced)
318                         Channel_UserModeAdd(chan, c, 'v');
319
320                 /* Announce client to the channel */
321                 IRC_WriteStrChannelPrefix(Client, chan, c, false,
322                                           "JOIN :%s", channame);
323
324                 /* If the client is connected to this server, it was remotely
325                  * joined to the channel by another server/service: So send
326                  * TOPIC and NAMES messages like on a regular JOIN command! */
327                 if(Client_Conn(c) != NONE)
328                         IRC_Send_Channel_Info(c, chan);
329
330                 /* Announce "channel user modes" to the channel, if any */
331                 strlcpy(modes, Channel_UserModes(chan, c), sizeof(modes));
332                 if (modes[0])
333                         IRC_WriteStrChannelPrefix(Client, chan, Client, false,
334                                                   "MODE %s +%s %s", channame,
335                                                   modes, Client_ID(c));
336
337                 /* Build nick list for forwarding command */
338                 if (nick_out[0] != '\0')
339                         strlcat(nick_out, ",", sizeof(nick_out));
340                 if (is_owner)
341                         strlcat(nick_out, "~", sizeof(nick_out));
342                 if (is_chanadmin)
343                         strlcat(nick_out, "&", sizeof(nick_out));
344                 if (is_op)
345                         strlcat(nick_out, "@", sizeof(nick_out));
346                 if (is_halfop)
347                         strlcat(nick_out, "%", sizeof(nick_out));
348                 if (is_voiced)
349                         strlcat(nick_out, "+", sizeof(nick_out));
350                 strlcat(nick_out, ptr, sizeof(nick_out));
351
352               skip_njoin:
353                 /* Get next nick, if any ... */
354                 ptr = strtok(NULL, ",");
355         }
356
357         /* forward to other servers */
358         if (nick_out[0] != '\0')
359                 IRC_WriteStrServersPrefix(Client, Client_ThisServer(),
360                                           "NJOIN %s :%s", Req->argv[0], nick_out);
361
362         return CONNECTED;
363 } /* IRC_NJOIN */
364
365 /**
366  * Handler for the IRC "SQUIT" command.
367  *
368  * @param Client The client from which this command has been received.
369  * @param Req Request structure with prefix and all parameters.
370  * @return CONNECTED or DISCONNECTED.
371  */
372 GLOBAL bool
373 IRC_SQUIT(CLIENT * Client, REQUEST * Req)
374 {
375         char msg[COMMAND_LEN], logmsg[COMMAND_LEN];
376         CLIENT *from, *target;
377         CONN_ID con, client_con;
378         int loglevel;
379
380         assert(Client != NULL);
381         assert(Req != NULL);
382
383         if (Client_Type(Client) != CLIENT_SERVER
384             && !Client_HasMode(Client, 'o'))
385                 return Op_NoPrivileges(Client, Req);
386
387         if (Client_Type(Client) == CLIENT_SERVER && Req->prefix) {
388                 from = Client_Search(Req->prefix);
389                 if (Client_Type(from) != CLIENT_SERVER
390                     && !Op_Check(Client, Req))
391                         return Op_NoPrivileges(Client, Req);
392         } else
393                 from = Client;
394         if (!from)
395                 return IRC_WriteErrClient(Client, ERR_NOSUCHNICK_MSG,
396                                           Client_ID(Client), Req->prefix);
397
398         if (Client_Type(Client) == CLIENT_USER)
399                 loglevel = LOG_NOTICE | LOG_snotice;
400         else
401                 loglevel = LOG_DEBUG;
402         Log(loglevel, "Got SQUIT from %s for \"%s\": \"%s\" ...",
403             Client_ID(from), Req->argv[0], Req->argv[1]);
404
405         target = Client_Search(Req->argv[0]);
406         if (Client_Type(Client) != CLIENT_SERVER &&
407             target == Client_ThisServer())
408                 return Op_NoPrivileges(Client, Req);
409         if (!target) {
410                 /* The server is (already) unknown */
411                 Log(LOG_WARNING,
412                     "Got SQUIT from %s for unknown server \"%s\"!?",
413                     Client_ID(Client), Req->argv[0]);
414                 return CONNECTED;
415         }
416
417         client_con = Client_Conn(Client);
418         con = Client_Conn(target);
419
420         if (Req->argv[1][0])
421                 if (Client_NextHop(from) != Client || con > NONE)
422                         snprintf(msg, sizeof(msg), "\"%s\" (SQUIT from %s)",
423                                  Req->argv[1], Client_ID(from));
424                 else
425                         strlcpy(msg, Req->argv[1], sizeof(msg));
426         else
427                 snprintf(msg, sizeof(msg), "Got SQUIT from %s",
428                          Client_ID(from));
429
430         if (con > NONE) {
431                 /* We are directly connected to the target server, so we
432                  * have to tear down the connection and to inform all the
433                  * other remaining servers in the network */
434                 IRC_SendWallops(Client_ThisServer(), Client_ThisServer(),
435                                 "Received SQUIT %s from %s: %s",
436                                 Req->argv[0], Client_ID(from),
437                                 Req->argv[1][0] ? Req->argv[1] : "-");
438                 Conn_Close(con, NULL, msg, true);
439                 if (con == client_con)
440                         return DISCONNECTED;
441         } else {
442                 /* This server is not directly connected, so the SQUIT must
443                  * be forwarded ... */
444                 if (Client_Type(from) != CLIENT_SERVER) {
445                         /* The origin is not an IRC server, so don't evaluate
446                          * this SQUIT but simply forward it */
447                         IRC_WriteStrClientPrefix(Client_NextHop(target),
448                             from, "SQUIT %s :%s", Req->argv[0], Req->argv[1]);
449                 } else {
450                         /* SQUIT has been generated by another server, so
451                          * remove the target server from the network! */
452                         logmsg[0] = '\0';
453                         if (!strchr(msg, '('))
454                                 snprintf(logmsg, sizeof(logmsg),
455                                          "\"%s\" (SQUIT from %s)", Req->argv[1],
456                                          Client_ID(from));
457                         Client_Destroy(target, logmsg[0] ? logmsg : msg,
458                                        msg, false);
459                 }
460         }
461         return CONNECTED;
462 } /* IRC_SQUIT */
463
464 /* -eof- */