]> arthur.barton.de Git - ngircd-alex.git/blob - src/ngircd/irc-server.c
Don't abort startup when setgid/setuid() fails with EINVAL
[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                 from = Client_Search( Req->prefix );
190                 if (! from) {
191                         /* Uh, Server, that introduced the new server is unknown?! */
192                         Log(LOG_ALERT,
193                             "Unknown ID in prefix of SERVER: \"%s\"! (on connection %d)",
194                             Req->prefix, Client_Conn(Client));
195                         Conn_Close(Client_Conn(Client), NULL,
196                                    "Unknown ID in prefix of SERVER", true);
197                         return DISCONNECTED;
198                 }
199
200                 c = Client_NewRemoteServer(Client, Req->argv[0], from,
201                                            atoi(Req->argv[1]), atoi(Req->argv[2]),
202                                            Req->argv[3], true);
203                 if (!c) {
204                         Log(LOG_ALERT,
205                             "Can't create client structure for server! (on connection %d)",
206                             Client_Conn(Client));
207                         Conn_Close(Client_Conn(Client), NULL,
208                                    "Can't allocate client structure for remote server",
209                                    true);
210                         return DISCONNECTED;
211                 }
212
213                 if (Client_Hops(c) > 1 && Req->prefix[0])
214                         snprintf(str, sizeof(str), "connected to %s, ",
215                                  Client_ID(from));
216                 else
217                         strcpy(str, "");
218                 Log(LOG_NOTICE|LOG_snotice,
219                     "Server \"%s\" registered (via %s, %s%d hop%s).",
220                     Client_ID(c), Client_ID(Client), str, Client_Hops(c),
221                     Client_Hops(c) > 1 ? "s": "" );
222
223                 /* notify other servers */
224                 IRC_WriteStrServersPrefix(Client, from, "SERVER %s %d %d :%s",
225                                           Client_ID(c), Client_Hops(c) + 1,
226                                           Client_MyToken(c), Client_Info(c));
227
228                 return CONNECTED;
229         } else
230                 return IRC_WriteErrClient(Client, ERR_NEEDMOREPARAMS_MSG,
231                                           Client_ID(Client), Req->command);
232 } /* IRC_SERVER */
233
234 /*
235  * Handler for the IRC "NJOIN" command.
236  *
237  * @param Client The client from which this command has been received.
238  * @param Req Request structure with prefix and all parameters.
239  * @return CONNECTED or DISCONNECTED.
240  */
241 GLOBAL bool
242 IRC_NJOIN( CLIENT *Client, REQUEST *Req )
243 {
244         char nick_in[COMMAND_LEN], nick_out[COMMAND_LEN], *channame, *ptr, modes[8];
245         bool is_owner, is_chanadmin, is_op, is_halfop, is_voiced;
246         CHANNEL *chan;
247         CLIENT *c;
248
249         assert(Client != NULL);
250         assert(Req != NULL);
251
252         strlcpy(nick_in, Req->argv[1], sizeof(nick_in));
253         strcpy(nick_out, "");
254
255         channame = Req->argv[0];
256
257         ptr = strtok(nick_in, ",");
258         while (ptr) {
259                 is_owner = is_chanadmin = is_op = is_halfop = is_voiced = false;
260
261                 /* cut off prefixes */
262                 while ((*ptr == '~') || (*ptr == '&') || (*ptr == '@') ||
263                        (*ptr == '%') || (*ptr == '+')) {
264                         if (*ptr == '~')
265                                 is_owner = true;
266                         if (*ptr == '&')
267                                 is_chanadmin = true;
268                         if (*ptr == '@')
269                                 is_op = true;
270                         if (*ptr == '%')
271                                 is_halfop = true;
272                         if (*ptr == '+')
273                                 is_voiced = true;
274                         ptr++;
275                 }
276
277                 c = Client_Search(ptr);
278                 if (!c) {
279                         /* Client not found? */
280                         Log(LOG_ERR,
281                             "Got NJOIN for unknown nick \"%s\" for channel \"%s\"!",
282                             ptr, channame);
283                         goto skip_njoin;
284                 }
285
286                 if (!Channel_Join(c, channame)) {
287                         /* Failed to join channel. Ooops!? */
288                         Log(LOG_ALERT,
289                             "Failed to join client \"%s\" to channel \"%s\" (NJOIN): killing it!",
290                             ptr, channame);
291                         IRC_KillClient(NULL, NULL, ptr, "Internal NJOIN error!");
292                         Log(LOG_DEBUG, "... done.");
293                         goto skip_njoin;
294                 }
295
296                 chan = Channel_Search(channame);
297                 assert(chan != NULL);
298
299                 if (is_owner)
300                         Channel_UserModeAdd(chan, c, 'q');
301                 if (is_chanadmin)
302                         Channel_UserModeAdd(chan, c, 'a');
303                 if (is_op)
304                         Channel_UserModeAdd(chan, c, 'o');
305                 if (is_halfop)
306                         Channel_UserModeAdd(chan, c, 'h');
307                 if (is_voiced)
308                         Channel_UserModeAdd(chan, c, 'v');
309
310                 /* Announce client to the channel */
311                 IRC_WriteStrChannelPrefix(Client, chan, c, false,
312                                           "JOIN :%s", channame);
313
314                 /* Announce "channel user modes" to the channel, if any */
315                 strlcpy(modes, Channel_UserModes(chan, c), sizeof(modes));
316                 if (modes[0])
317                         IRC_WriteStrChannelPrefix(Client, chan, Client, false,
318                                                   "MODE %s +%s %s", channame,
319                                                   modes, Client_ID(c));
320
321                 /* Build nick list for forwarding command */
322                 if (nick_out[0] != '\0')
323                         strlcat(nick_out, ",", sizeof(nick_out));
324                 if (is_owner)
325                         strlcat(nick_out, "~", sizeof(nick_out));
326                 if (is_chanadmin)
327                         strlcat(nick_out, "&", sizeof(nick_out));
328                 if (is_op)
329                         strlcat(nick_out, "@", sizeof(nick_out));
330                 if (is_halfop)
331                         strlcat(nick_out, "%", sizeof(nick_out));
332                 if (is_voiced)
333                         strlcat(nick_out, "+", sizeof(nick_out));
334                 strlcat(nick_out, ptr, sizeof(nick_out));
335
336               skip_njoin:
337                 /* Get next nick, if any ... */
338                 ptr = strtok(NULL, ",");
339         }
340
341         /* forward to other servers */
342         if (nick_out[0] != '\0')
343                 IRC_WriteStrServersPrefix(Client, Client_ThisServer(),
344                                           "NJOIN %s :%s", Req->argv[0], nick_out);
345
346         return CONNECTED;
347 } /* IRC_NJOIN */
348
349 /**
350  * Handler for the IRC "SQUIT" command.
351  *
352  * @param Client The client from which this command has been received.
353  * @param Req Request structure with prefix and all parameters.
354  * @return CONNECTED or DISCONNECTED.
355  */
356 GLOBAL bool
357 IRC_SQUIT(CLIENT * Client, REQUEST * Req)
358 {
359         char msg[COMMAND_LEN], logmsg[COMMAND_LEN];
360         CLIENT *from, *target;
361         CONN_ID con;
362         int loglevel;
363
364         assert(Client != NULL);
365         assert(Req != NULL);
366
367         if (Client_Type(Client) != CLIENT_SERVER
368             && !Client_HasMode(Client, 'o'))
369                 return Op_NoPrivileges(Client, Req);
370
371         if (Client_Type(Client) == CLIENT_SERVER && Req->prefix) {
372                 from = Client_Search(Req->prefix);
373                 if (Client_Type(from) != CLIENT_SERVER
374                     && !Op_Check(Client, Req))
375                         return Op_NoPrivileges(Client, Req);
376         } else
377                 from = Client;
378         if (!from)
379                 return IRC_WriteErrClient(Client, ERR_NOSUCHNICK_MSG,
380                                           Client_ID(Client), Req->prefix);
381
382         if (Client_Type(Client) == CLIENT_USER)
383                 loglevel = LOG_NOTICE | LOG_snotice;
384         else
385                 loglevel = LOG_DEBUG;
386         Log(loglevel, "Got SQUIT from %s for \"%s\": \"%s\" ...",
387             Client_ID(from), Req->argv[0], Req->argv[1]);
388
389         target = Client_Search(Req->argv[0]);
390         if (Client_Type(Client) != CLIENT_SERVER &&
391             target == Client_ThisServer())
392                 return Op_NoPrivileges(Client, Req);
393         if (!target) {
394                 /* The server is (already) unknown */
395                 Log(LOG_WARNING,
396                     "Got SQUIT from %s for unknown server \"%s\"!?",
397                     Client_ID(Client), Req->argv[0]);
398                 return CONNECTED;
399         }
400
401         con = Client_Conn(target);
402
403         if (Req->argv[1][0])
404                 if (Client_NextHop(from) != Client || con > NONE)
405                         snprintf(msg, sizeof(msg), "\"%s\" (SQUIT from %s)",
406                                  Req->argv[1], Client_ID(from));
407                 else
408                         strlcpy(msg, Req->argv[1], sizeof(msg));
409         else
410                 snprintf(msg, sizeof(msg), "Got SQUIT from %s",
411                          Client_ID(from));
412
413         if (con > NONE) {
414                 /* We are directly connected to the target server, so we
415                  * have to tear down the connection and to inform all the
416                  * other remaining servers in the network */
417                 IRC_SendWallops(Client_ThisServer(), Client_ThisServer(),
418                                 "Received SQUIT %s from %s: %s",
419                                 Req->argv[0], Client_ID(from),
420                                 Req->argv[1][0] ? Req->argv[1] : "-");
421                 Conn_Close(con, NULL, msg, true);
422                 if (con == Client_Conn(Client))
423                         return DISCONNECTED;
424         } else {
425                 /* This server is not directly connected, so the SQUIT must
426                  * be forwarded ... */
427                 if (Client_Type(from) != CLIENT_SERVER) {
428                         /* The origin is not an IRC server, so don't evaluate
429                          * this SQUIT but simply forward it */
430                         IRC_WriteStrClientPrefix(Client_NextHop(target),
431                             from, "SQUIT %s :%s", Req->argv[0], Req->argv[1]);
432                 } else {
433                         /* SQUIT has been generated by another server, so
434                          * remove the target server from the network! */
435                         logmsg[0] = '\0';
436                         if (!strchr(msg, '('))
437                                 snprintf(logmsg, sizeof(logmsg),
438                                          "\"%s\" (SQUIT from %s)", Req->argv[1],
439                                          Client_ID(from));
440                         Client_Destroy(target, logmsg[0] ? logmsg : msg,
441                                        msg, false);
442                 }
443         }
444         return CONNECTED;
445 } /* IRC_SQUIT */
446
447 /* -eof- */