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