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