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