]> arthur.barton.de Git - ngircd-alex.git/blob - src/ngircd/irc-login.c
Re-add #include's for header files of the C file itself
[ngircd-alex.git] / src / ngircd / irc-login.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  * Login and logout
17  */
18
19 #include <assert.h>
20 #include <ctype.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <strings.h>
24 #include <time.h>
25
26 #include "conn-func.h"
27 #include "conf.h"
28 #include "channel.h"
29 #include "log.h"
30 #include "login.h"
31 #include "messages.h"
32 #include "parse.h"
33 #include "irc.h"
34 #include "irc-macros.h"
35 #include "irc-write.h"
36
37 #include "irc-login.h"
38
39 static void Change_Nick PARAMS((CLIENT * Origin, CLIENT * Target, char *NewNick,
40                                 bool InformClient));
41
42 /**
43  * Handler for the IRC "PASS" 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_PASS( CLIENT *Client, REQUEST *Req )
51 {
52         char *type, *orig_flags;
53         int protohigh, protolow;
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_UNKNOWN && Req->argc == 1) {
64                 /* Not yet registered "unknown" connection, PASS with one
65                  * argument: either a regular client, service, or server
66                  * using the old RFC 1459 section 4.1.1 syntax. */
67                 LogDebug("Connection %d: got PASS command (RFC 1459) ...",
68                          Client_Conn(Client));
69         } else if ((Client_Type(Client) == CLIENT_UNKNOWN ||
70                     Client_Type(Client) == CLIENT_UNKNOWNSERVER) &&
71                    (Req->argc == 3 || Req->argc == 4)) {
72                 /* Not yet registered "unknown" connection or outgoing server
73                  * link, PASS with three or four argument: server using the
74                  * RFC 2813 section 4.1.1 syntax. */
75                 LogDebug("Connection %d: got PASS command (RFC 2813, new server link) ...",
76                          Client_Conn(Client));
77         } else if (Client_Type(Client) == CLIENT_UNKNOWN ||
78                    Client_Type(Client) == CLIENT_UNKNOWNSERVER) {
79                 /* Unregistered connection, but wrong number of arguments: */
80                 return IRC_WriteErrClient(Client, ERR_NEEDMOREPARAMS_MSG,
81                                           Client_ID(Client), Req->command);
82         } else {
83                 /* Registered connection, PASS command is not allowed! */
84                 return IRC_WriteErrClient(Client, ERR_ALREADYREGISTRED_MSG,
85                                           Client_ID(Client));
86         }
87
88         Conn_SetPassword(Client_Conn(Client), Req->argv[0]);
89
90         /* Protocol version */
91         if (Req->argc >= 2 && strlen(Req->argv[1]) >= 4) {
92                 int c2, c4;
93
94                 c2 = Req->argv[1][2];
95                 c4 = Req->argv[1][4];
96
97                 Req->argv[1][4] = '\0';
98                 protolow = atoi(&Req->argv[1][2]);
99                 Req->argv[1][2] = '\0';
100                 protohigh = atoi(Req->argv[1]);
101
102                 Req->argv[1][2] = c2;
103                 Req->argv[1][4] = c4;
104
105                 Client_SetType(Client, CLIENT_GOTPASS_2813);
106         } else {
107                 protohigh = protolow = 0;
108                 Client_SetType(Client, CLIENT_GOTPASS);
109         }
110
111         /* Protocol type, see doc/Protocol.txt */
112         if (Req->argc >= 2 && strlen(Req->argv[1]) > 4)
113                 type = &Req->argv[1][4];
114         else
115                 type = NULL;
116
117         /* Protocol flags/options */
118         if (Req->argc >= 4)
119                 orig_flags = Req->argv[3];
120         else
121                 orig_flags = "";
122
123         /* Implementation, version and IRC+ flags */
124         if (Req->argc >= 3) {
125                 char *impl, *ptr, *serverver, *flags;
126
127                 impl = Req->argv[2];
128                 ptr = strchr(impl, '|');
129                 if (ptr)
130                         *ptr = '\0';
131
132                 if (type && strcmp(type, PROTOIRCPLUS) == 0) {
133                         /* The peer seems to be a server which supports the
134                          * IRC+ protocol (see doc/Protocol.txt). */
135                         serverver = ptr ? ptr + 1 : "?";
136                         flags = strchr(ptr ? serverver : impl, ':');
137                         if (flags) {
138                                 *flags = '\0';
139                                 flags++;
140                         } else
141                                 flags = "";
142                         Log(LOG_INFO,
143                             "Peer on connection %d announces itself as %s-%s using protocol %d.%d/IRC+ (flags: \"%s\").",
144                             Client_Conn(Client), impl, serverver,
145                             protohigh, protolow, flags);
146                 } else {
147                         /* The peer seems to be a server supporting the
148                          * "original" IRC protocol (RFC 2813). */
149                         if (strchr(orig_flags, 'Z'))
150                                 flags = "Z";
151                         else
152                                 flags = "";
153                         Log(LOG_INFO,
154                             "Peer on connection %d announces itself as \"%s\" using protocol %d.%d (flags: \"%s\").",
155                             Client_Conn(Client), impl,
156                             protohigh, protolow, flags);
157                 }
158                 Client_SetFlags(Client, flags);
159         }
160
161         return CONNECTED;
162 } /* IRC_PASS */
163
164 /**
165  * Handler for the IRC "NICK" command.
166  *
167  * @param Client The client from which this command has been received.
168  * @param Req Request structure with prefix and all parameters.
169  * @return CONNECTED or DISCONNECTED.
170  */
171 GLOBAL bool
172 IRC_NICK( CLIENT *Client, REQUEST *Req )
173 {
174         CLIENT *intr_c, *target, *c;
175         char *nick, *user, *hostname, *modes, *info;
176         int token, hops;
177
178         assert( Client != NULL );
179         assert( Req != NULL );
180
181         /* Some IRC clients, for example BitchX, send the NICK and USER
182          * commands in the wrong order ... */
183         if(Client_Type(Client) == CLIENT_UNKNOWN
184             || Client_Type(Client) == CLIENT_GOTPASS
185             || Client_Type(Client) == CLIENT_GOTNICK
186 #ifndef STRICT_RFC
187             || Client_Type(Client) == CLIENT_GOTUSER
188 #endif
189             || Client_Type(Client) == CLIENT_USER
190             || Client_Type(Client) == CLIENT_SERVICE
191             || (Client_Type(Client) == CLIENT_SERVER && Req->argc == 1))
192         {
193                 /* User registration or change of nickname */
194                 _IRC_ARGC_EQ_OR_RETURN_(Client, Req, 1)
195
196                 /* Search "target" client */
197                 if (Client_Type(Client) == CLIENT_SERVER) {
198                         target = Client_Search(Req->prefix);
199                         if (!target)
200                                 return IRC_WriteErrClient(Client,
201                                                           ERR_NOSUCHNICK_MSG,
202                                                           Client_ID(Client),
203                                                           Req->argv[0]);
204                 } else {
205                         /* Is this a restricted client? */
206                         if (Client_HasMode(Client, 'r'))
207                                 return IRC_WriteErrClient(Client,
208                                                           ERR_RESTRICTED_MSG,
209                                                           Client_ID(Client));
210                         target = Client;
211                 }
212
213 #ifndef STRICT_RFC
214                 /* If the clients tries to change to its own nickname we won't
215                  * do anything. This is how the original ircd behaves and some
216                  * clients (for example Snak) expect it to be like this.
217                  * But I doubt that this is "really the right thing" ... */
218                 if (strcmp(Client_ID(target), Req->argv[0]) == 0)
219                         return CONNECTED;
220 #endif
221
222                 /* Check that the new nickname is available. Special case:
223                  * the client only changes from/to upper to lower case. */
224                 if (strcasecmp(Client_ID(target), Req->argv[0]) != 0) {
225                         if (!Client_CheckNick(target, Req->argv[0]))
226                                 return CONNECTED;
227                 }
228
229                 if (Client_Type(target) != CLIENT_USER &&
230                     Client_Type(target) != CLIENT_SERVICE &&
231                     Client_Type(target) != CLIENT_SERVER) {
232                         /* New client */
233                         LogDebug("Connection %d: got valid NICK command ...",
234                              Client_Conn( Client ));
235
236                         /* Register new nickname of this client */
237                         Client_SetID( target, Req->argv[0] );
238
239 #ifndef STRICT_RFC
240                         if (Conf_AuthPing) {
241 #ifdef HAVE_ARC4RANDOM
242                                 Conn_SetAuthPing(Client_Conn(Client), arc4random());
243 #else
244                                 Conn_SetAuthPing(Client_Conn(Client), rand());
245 #endif
246                                 IRC_WriteStrClient(Client, "PING :%ld",
247                                         Conn_GetAuthPing(Client_Conn(Client)));
248                                 LogDebug("Connection %d: sent AUTH PING %ld ...",
249                                         Client_Conn(Client),
250                                         Conn_GetAuthPing(Client_Conn(Client)));
251                         }
252 #endif
253
254                         /* If we received a valid USER command already then
255                          * register the new client! */
256                         if( Client_Type( Client ) == CLIENT_GOTUSER )
257                                 return Login_User( Client );
258                         else
259                                 Client_SetType( Client, CLIENT_GOTNICK );
260                 } else {
261                         /* Nickname change */
262                         Change_Nick(Client, target, Req->argv[0],
263                                     Client_Type(Client) == CLIENT_USER ? true : false);
264                         IRC_SetPenalty(target, 2);
265                 }
266
267                 return CONNECTED;
268         } else if(Client_Type(Client) == CLIENT_SERVER ||
269                   Client_Type(Client) == CLIENT_SERVICE) {
270                 /* Server or service introduces new client */
271
272                 /* Bad number of parameters? */
273                 if (Req->argc != 2 && Req->argc != 7)
274                         return IRC_WriteErrClient(Client, ERR_NEEDMOREPARAMS_MSG,
275                                                   Client_ID(Client), Req->command);
276
277                 if (Req->argc >= 7) {
278                         /* RFC 2813 compatible syntax */
279                         nick = Req->argv[0];
280                         hops = atoi(Req->argv[1]);
281                         user = Req->argv[2];
282                         hostname = Req->argv[3];
283                         token = atoi(Req->argv[4]);
284                         modes = Req->argv[5] + 1;
285                         info = Req->argv[6];
286                 } else {
287                         /* RFC 1459 compatible syntax */
288                         nick = Req->argv[0];
289                         hops = 1;
290                         user = Req->argv[0];
291                         hostname = Client_ID(Client);
292                         token = atoi(Req->argv[1]);
293                         modes = "";
294                         info = Req->argv[0];
295                 }
296
297                 c = Client_Search(nick);
298                 if(c) {
299                         /*
300                          * the new nick is already present on this server:
301                          * the new and the old one have to be disconnected now.
302                          */
303                         Log(LOG_ERR,
304                             "Server %s introduces already registered nick \"%s\"!",
305                             Client_ID(Client), Req->argv[0]);
306                         return IRC_KillClient(Client, NULL, Req->argv[0],
307                                               "Nick collision");
308                 }
309
310                 /* Find the Server this client is connected to */
311                 intr_c = Client_GetFromToken(Client, token);
312                 if (!intr_c) {
313                         Log(LOG_ERR,
314                             "Server %s introduces nick \"%s\" on unknown server!?",
315                             Client_ID(Client), Req->argv[0]);
316                         return IRC_KillClient(Client, NULL, Req->argv[0],
317                                               "Unknown server");
318                 }
319
320                 c = Client_NewRemoteUser(intr_c, nick, hops, user, hostname,
321                                          token, modes, info, true);
322                 if (!c) {
323                         /* Out of memory, we need to disconnect client to keep
324                          * network state consistent! */
325                         Log(LOG_ALERT,
326                             "Can't create client structure! (on connection %d)",
327                             Client_Conn(Client));
328                         return IRC_KillClient(Client, NULL, Req->argv[0],
329                                               "Server error");
330                 }
331
332                 /* RFC 2813: client is now fully registered, inform all the
333                  * other servers about the new user.
334                  * RFC 1459: announce the new client only after receiving the
335                  * USER command, first we need more information! */
336                 if (Req->argc < 7) {
337                         LogDebug("Client \"%s\" is being registered (RFC 1459) ...",
338                                  Client_Mask(c));
339                         Client_SetType(c, CLIENT_GOTNICK);
340                 } else
341                         Client_Introduce(Client, c, CLIENT_USER);
342
343                 return CONNECTED;
344         }
345         else
346                 return IRC_WriteErrClient(Client, ERR_ALREADYREGISTRED_MSG,
347                                           Client_ID(Client));
348 } /* IRC_NICK */
349
350 /**
351  * Handler for the IRC "SVSNICK" command.
352  *
353  * @param Client The client from which this command has been received.
354  * @param Req Request structure with prefix and all parameters.
355  * @return CONNECTED or DISCONNECTED.
356  */
357 GLOBAL bool
358 IRC_SVSNICK(CLIENT *Client, REQUEST *Req)
359 {
360         CLIENT *from, *target;
361
362         assert(Client != NULL);
363         assert(Req != NULL);
364
365         /* Search the originator */
366         from = Client_Search(Req->prefix);
367         if (!from)
368                 from = Client;
369
370         /* Search the target */
371         target = Client_Search(Req->argv[0]);
372         if (!target || Client_Type(target) != CLIENT_USER)
373                 return IRC_WriteErrClient(Client, ERR_NOSUCHNICK_MSG,
374                                           Client_ID(Client), Req->argv[0]);
375
376         if (Client_Conn(target) <= NONE) {
377                 /* We have to forward the message to the server handling
378                  * this user; this is required to make sure all servers
379                  * in the network do follow the nick name change! */
380                 return IRC_WriteStrClientPrefix(Client_NextHop(target), from,
381                                                 "SVSNICK %s %s",
382                                                 Req->argv[0], Req->argv[1]);
383         }
384
385         /* Make sure that the new nickname is valid */
386         if (!Client_CheckNick(from, Req->argv[1]))
387                 return CONNECTED;
388
389         Change_Nick(from, target, Req->argv[1], true);
390         return CONNECTED;
391 }
392
393 /**
394  * Handler for the IRC "USER" command.
395  *
396  * @param Client The client from which this command has been received.
397  * @param Req Request structure with prefix and all parameters.
398  * @return CONNECTED or DISCONNECTED.
399  */
400 GLOBAL bool
401 IRC_USER(CLIENT * Client, REQUEST * Req)
402 {
403         CLIENT *c;
404         char *ptr;
405
406         assert(Client != NULL);
407         assert(Req != NULL);
408
409         if (Client_Type(Client) == CLIENT_GOTNICK ||
410 #ifndef STRICT_RFC
411             Client_Type(Client) == CLIENT_UNKNOWN ||
412 #endif
413             Client_Type(Client) == CLIENT_GOTPASS)
414         {
415                 /* New connection */
416                 _IRC_ARGC_EQ_OR_RETURN_(Client, Req, 4)
417
418                 /* User name: only alphanumeric characters and limited
419                    punctuation is allowed.*/
420                 ptr = Req->argv[0];
421                 while (*ptr) {
422                         if (!isalnum((int)*ptr) &&
423                             *ptr != '+' && *ptr != '-' && *ptr != '@' &&
424                             *ptr != '.' && *ptr != '_') {
425                                 Conn_Close(Client_Conn(Client), NULL,
426                                            "Invalid user name", true);
427                                 return DISCONNECTED;
428                         }
429                         ptr++;
430                 }
431
432                 /* Save the received username for authentication, and use
433                  * it up to the first '@' as default user name (like ircd2.11,
434                  * bahamut, ircd-seven, ...), prefixed with '~', if needed: */
435                 Client_SetOrigUser(Client, Req->argv[0]);
436                 ptr = strchr(Req->argv[0], '@');
437                 if (ptr)
438                         *ptr = '\0';
439 #ifdef IDENTAUTH
440                 ptr = Client_User(Client);
441                 if (!ptr || !*ptr || *ptr == '~')
442                         Client_SetUser(Client, Req->argv[0], false);
443 #else
444                 Client_SetUser(Client, Req->argv[0], false);
445 #endif
446
447                 /* "Real name" or user info text: Don't set it to the empty
448                  * string, the original ircd can't deal with such "real names"
449                  * (e. g. "USER user * * :") ... */
450                 if (*Req->argv[3])
451                         Client_SetInfo(Client, Req->argv[3]);
452                 else
453                         Client_SetInfo(Client, "-");
454
455                 LogDebug("Connection %d: got valid USER command ...",
456                     Client_Conn(Client));
457                 if (Client_Type(Client) == CLIENT_GOTNICK)
458                         return Login_User(Client);
459                 else
460                         Client_SetType(Client, CLIENT_GOTUSER);
461                 return CONNECTED;
462
463         } else if (Client_Type(Client) == CLIENT_SERVER ||
464                    Client_Type(Client) == CLIENT_SERVICE) {
465                 /* Server/service updating an user */
466                 _IRC_ARGC_EQ_OR_RETURN_(Client, Req, 4)
467
468                 c = Client_Search(Req->prefix);
469                 if (!c)
470                         return IRC_WriteErrClient(Client, ERR_NOSUCHNICK_MSG,
471                                                   Client_ID(Client),
472                                                   Req->prefix);
473
474                 Client_SetUser(c, Req->argv[0], true);
475                 Client_SetOrigUser(c, Req->argv[0]);
476                 Client_SetHostname(c, Req->argv[1]);
477                 Client_SetInfo(c, Req->argv[3]);
478
479                 LogDebug("Connection %d: got valid USER command for \"%s\".",
480                          Client_Conn(Client), Client_Mask(c));
481
482                 /* RFC 1459 style user registration?
483                  * Introduce client to network: */
484                 if (Client_Type(c) == CLIENT_GOTNICK)
485                         Client_Introduce(Client, c, CLIENT_USER);
486
487                 return CONNECTED;
488         } else if (Client_Type(Client) == CLIENT_USER) {
489                 /* Already registered connection */
490                 return IRC_WriteErrClient(Client, ERR_ALREADYREGISTRED_MSG,
491                                           Client_ID(Client));
492         } else {
493                 /* Unexpected/invalid connection state? */
494                 return IRC_WriteErrClient(Client, ERR_NOTREGISTERED_MSG,
495                                           Client_ID(Client));
496         }
497 } /* IRC_USER */
498
499 /**
500  * Handler for the IRC "SERVICE" command.
501  *
502  * At the moment ngIRCd doesn't support directly linked services, so this
503  * function returns ERR_ERRONEUSNICKNAME when the SERVICE command has not been
504  * received from a peer server.
505  *
506  * @param Client The client from which this command has been received.
507  * @param Req Request structure with prefix and all parameters.
508  * @return CONNECTED or DISCONNECTED.
509  */
510 GLOBAL bool
511 IRC_SERVICE(CLIENT *Client, REQUEST *Req)
512 {
513         CLIENT *c, *intr_c;
514         char *nick, *user, *host, *info, *modes, *ptr;
515         int token, hops;
516
517         assert(Client != NULL);
518         assert(Req != NULL);
519
520         if (Client_Type(Client) != CLIENT_GOTPASS &&
521             Client_Type(Client) != CLIENT_SERVER)
522                 return IRC_WriteErrClient(Client, ERR_ALREADYREGISTRED_MSG,
523                                           Client_ID(Client));
524
525         if (Client_Type(Client) != CLIENT_SERVER)
526                 return IRC_WriteErrClient(Client, ERR_ERRONEUSNICKNAME_MSG,
527                                   Client_ID(Client), Req->argv[0]);
528
529         nick = Req->argv[0];
530         user = NULL; host = NULL;
531         token = atoi(Req->argv[1]);
532         hops = atoi(Req->argv[4]);
533         info = Req->argv[5];
534
535         /* Validate service name ("nickname") */
536         c = Client_Search(nick);
537         if(c) {
538                 /* Nickname collision: disconnect (KILL) both clients! */
539                 Log(LOG_ERR,
540                     "Server %s introduces already registered service \"%s\"!",
541                     Client_ID(Client), nick);
542                 return IRC_KillClient(Client, NULL, nick, "Nick collision");
543         }
544
545         /* Get the server to which the service is connected */
546         intr_c = Client_GetFromToken(Client, token);
547         if (! intr_c) {
548                 Log(LOG_ERR,
549                     "Server %s introduces service \"%s\" on unknown server!?",
550                     Client_ID(Client), nick);
551                 return IRC_KillClient(Client, NULL, nick, "Unknown server");
552         }
553
554         /* Get user and host name */
555         ptr = strchr(nick, '@');
556         if (ptr) {
557                 *ptr = '\0';
558                 host = ++ptr;
559         }
560         if (!host)
561                 host = Client_Hostname(intr_c);
562         ptr = strchr(nick, '!');
563         if (ptr) {
564                 *ptr = '\0';
565                 user = ++ptr;
566         }
567         if (!user)
568                 user = nick;
569
570         /* According to RFC 2812/2813 parameter 4 <type> "is currently reserved
571          * for future usage"; but we use it to transfer the modes and check
572          * that the first character is a '+' sign and ignore it otherwise. */
573         modes = (Req->argv[3][0] == '+') ? ++Req->argv[3] : "";
574
575         c = Client_NewRemoteUser(intr_c, nick, hops, user, host,
576                                  token, modes, info, true);
577         if (! c) {
578                 /* Couldn't create client structure, so KILL the service to
579                  * keep network status consistent ... */
580                 Log(LOG_ALERT,
581                     "Can't create client structure! (on connection %d)",
582                     Client_Conn(Client));
583                 return IRC_KillClient(Client, NULL, nick, "Server error");
584         }
585
586         Client_Introduce(Client, c, CLIENT_SERVICE);
587         return CONNECTED;
588 } /* IRC_SERVICE */
589
590 /**
591  * Handler for the IRC "WEBIRC" command.
592  *
593  * @param Client The client from which this command has been received.
594  * @param Req Request structure with prefix and all parameters.
595  * @return CONNECTED or DISCONNECTED.
596  */
597 GLOBAL bool
598 IRC_WEBIRC(CLIENT *Client, REQUEST *Req)
599 {
600         if (!Conf_WebircPwd[0] || strcmp(Req->argv[0], Conf_WebircPwd) != 0)
601                 return IRC_WriteErrClient(Client, ERR_PASSWDMISMATCH_MSG,
602                                           Client_ID(Client));
603
604         LogDebug("Connection %d: got valid WEBIRC command: user=%s, host=%s, ip=%s",
605                  Client_Conn(Client), Req->argv[1], Req->argv[2], Req->argv[3]);
606
607         Client_SetUser(Client, Req->argv[1], true);
608         Client_SetOrigUser(Client, Req->argv[1]);
609         if (Conf_DNS)
610                 Client_SetHostname(Client, Req->argv[2]);
611         else
612                 Client_SetHostname(Client, Req->argv[3]);
613         Client_SetIPAText(Client, Req->argv[3]);
614
615         return CONNECTED;
616 } /* IRC_WEBIRC */
617
618 /**
619  * Handler for the IRC "QUIT" command.
620  *
621  * @param Client The client from which this command has been received.
622  * @param Req Request structure with prefix and all parameters.
623  * @return CONNECTED or DISCONNECTED.
624  */
625 GLOBAL bool
626 IRC_QUIT( CLIENT *Client, REQUEST *Req )
627 {
628         CLIENT *target;
629         char quitmsg[COMMAND_LEN];
630
631         assert(Client != NULL);
632         assert(Req != NULL);
633
634         if (Req->argc == 1)
635                 strlcpy(quitmsg, Req->argv[0], sizeof quitmsg);
636
637         if (Client_Type(Client) == CLIENT_SERVER) {
638                 /* Server */
639                 target = Client_Search(Req->prefix);
640                 if (!target) {
641                         Log(LOG_WARNING,
642                             "Got QUIT from %s for unknown client!?",
643                             Client_ID(Client));
644                         return CONNECTED;
645                 }
646
647                 if (target != Client) {
648                         Client_Destroy(target, "Got QUIT command",
649                                        Req->argc == 1 ? quitmsg : NULL, true);
650                         return CONNECTED;
651                 } else {
652                         Conn_Close(Client_Conn(Client), "Got QUIT command",
653                                    Req->argc == 1 ? quitmsg : NULL, true);
654                         return DISCONNECTED;
655                 }
656         } else {
657                 if (Req->argc == 1 && quitmsg[0] != '\"') {
658                         /* " " to avoid confusion */
659                         strlcpy(quitmsg, "\"", sizeof quitmsg);
660                         strlcat(quitmsg, Req->argv[0], sizeof quitmsg-1);
661                         strlcat(quitmsg, "\"", sizeof quitmsg );
662                 }
663
664                 /* User, Service, or not yet registered */
665                 Conn_Close(Client_Conn(Client), "Got QUIT command",
666                            Req->argc == 1 ? quitmsg : NULL, true);
667
668                 return DISCONNECTED;
669         }
670 } /* IRC_QUIT */
671
672 #ifndef STRICT_RFC
673
674 /**
675  * Handler for HTTP command, e.g. GET and POST
676  *
677  * We handle these commands here to avoid the quite long timeout when
678  * some user tries to access this IRC daemon using an web browser ...
679  *
680  * @param Client The client from which this command has been received.
681  * @param Req Request structure with prefix and all parameters.
682  * @return CONNECTED or DISCONNECTED.
683  */
684 GLOBAL bool
685 IRC_QUIT_HTTP( CLIENT *Client, REQUEST *Req )
686 {
687         Req->argc = 1;
688         Req->argv[0] = "Oops, HTTP request received? This is IRC!";
689         return IRC_QUIT(Client, Req);
690 } /* IRC_QUIT_HTTP */
691
692 #endif
693
694 /**
695  * Handler for the IRC "PING" command.
696  *
697  * @param Client The client from which this command has been received.
698  * @param Req Request structure with prefix and all parameters.
699  * @return CONNECTED or DISCONNECTED.
700  */
701 GLOBAL bool
702 IRC_PING(CLIENT *Client, REQUEST *Req)
703 {
704         CLIENT *target, *from;
705
706         assert(Client != NULL);
707         assert(Req != NULL);
708
709         if (Req->argc < 1)
710                 return IRC_WriteErrClient(Client, ERR_NOORIGIN_MSG,
711                                           Client_ID(Client));
712 #ifdef STRICT_RFC
713         /* Don't ignore additional arguments when in "strict" mode */
714         _IRC_ARGC_LE_OR_RETURN_(Client, Req, 2)
715 #endif
716
717         if (Req->argc > 1) {
718                 /* A target has been specified ... */
719                 target = Client_Search(Req->argv[1]);
720
721                 if (!target || Client_Type(target) != CLIENT_SERVER)
722                         return IRC_WriteErrClient(Client, ERR_NOSUCHSERVER_MSG,
723                                         Client_ID(Client), Req->argv[1]);
724
725                 if (target != Client_ThisServer()) {
726                         /* Ok, we have to forward the PING */
727                         if (Client_Type(Client) == CLIENT_SERVER)
728                                 from = Client_Search(Req->prefix);
729                         else
730                                 from = Client;
731                         if (!from)
732                                 return IRC_WriteErrClient(Client,
733                                                 ERR_NOSUCHSERVER_MSG,
734                                                 Client_ID(Client), Req->prefix);
735
736                         return IRC_WriteStrClientPrefix(target, from,
737                                         "PING %s :%s", Req->argv[0],
738                                         Req->argv[1] );
739                 }
740         }
741
742         if (Client_Type(Client) == CLIENT_SERVER) {
743                 if (Req->prefix)
744                         from = Client_Search(Req->prefix);
745                 else
746                         from = Client;
747         } else
748                 from = Client_ThisServer();
749         if (!from)
750                 return IRC_WriteErrClient(Client, ERR_NOSUCHSERVER_MSG,
751                                         Client_ID(Client), Req->prefix);
752
753         Log(LOG_DEBUG, "Connection %d: got PING, sending PONG ...",
754             Client_Conn(Client));
755
756 #ifdef STRICT_RFC
757         return IRC_WriteStrClient(Client, "PONG %s :%s",
758                 Client_ID(from), Client_ID(Client));
759 #else
760         /* Some clients depend on the argument being returned in the PONG
761          * reply (not mentioned in any RFC, though) */
762         return IRC_WriteStrClient(Client, "PONG %s :%s",
763                 Client_ID(from), Req->argv[0]);
764 #endif
765 } /* IRC_PING */
766
767 /**
768  * Handler for the IRC "PONG" command.
769  *
770  * @param Client The client from which this command has been received.
771  * @param Req Request structure with prefix and all parameters.
772  * @return CONNECTED or DISCONNECTED.
773  */
774 GLOBAL bool
775 IRC_PONG(CLIENT *Client, REQUEST *Req)
776 {
777         CLIENT *target, *from;
778         CONN_ID conn;
779 #ifndef STRICT_RFC
780         long auth_ping;
781 #endif
782         char *s;
783
784         assert(Client != NULL);
785         assert(Req != NULL);
786
787         /* Wrong number of arguments? */
788         if (Req->argc < 1) {
789                 if (Client_Type(Client) == CLIENT_USER)
790                         return IRC_WriteErrClient(Client, ERR_NOORIGIN_MSG,
791                                                   Client_ID(Client));
792                 else
793                         return CONNECTED;
794         }
795         if (Client_Type(Client) == CLIENT_USER) {
796                 _IRC_ARGC_LE_OR_RETURN_(Client, Req, 2)
797         }
798
799         /* Forward? */
800         if (Req->argc == 2 && Client_Type(Client) == CLIENT_SERVER) {
801                 target = Client_Search(Req->argv[0]);
802                 if (!target)
803                         return IRC_WriteErrClient(Client, ERR_NOSUCHSERVER_MSG,
804                                         Client_ID(Client), Req->argv[0]);
805
806                 from = Client_Search(Req->prefix);
807
808                 if (target != Client_ThisServer() && target != from) {
809                         /* Ok, we have to forward the message. */
810                         if (!from)
811                                 return IRC_WriteErrClient(Client,
812                                                 ERR_NOSUCHSERVER_MSG,
813                                                 Client_ID(Client), Req->prefix);
814
815                         if (Client_Type(Client_NextHop(target)) != CLIENT_SERVER)
816                                 s = Client_ID(from);
817                         else
818                                 s = Req->argv[0];
819                         return IRC_WriteStrClientPrefix(target, from,
820                                  "PONG %s :%s", s, Req->argv[1]);
821                 }
822         }
823
824         /* The connection timestamp has already been updated when the data has
825          * been read from so socket, so we don't need to update it here. */
826
827         conn = Client_Conn(Client);
828
829 #ifndef STRICT_RFC
830         /* Check authentication PING-PONG ... */
831         auth_ping = Conn_GetAuthPing(conn);
832         if (auth_ping) {
833                 LogDebug("AUTH PONG: waiting for token \"%ld\", got \"%s\" ...",
834                          auth_ping, Req->argv[0]);
835                 if (auth_ping == atoi(Req->argv[0])) {
836                         Conn_SetAuthPing(conn, 0);
837                         if (Client_Type(Client) == CLIENT_WAITAUTHPING)
838                                 Login_User(Client);
839                 } else
840                         if (!IRC_WriteStrClient(Client,
841                                         "To connect, type /QUOTE PONG %ld",
842                                         auth_ping))
843                                 return DISCONNECTED;
844         }
845 #endif
846
847         if (Client_Type(Client) == CLIENT_SERVER && Conn_LastPing(conn) == 0) {
848                 Log(LOG_INFO,
849                     "Synchronization with \"%s\" done (connection %d): %ld second%s [%ld users, %ld channels].",
850                     Client_ID(Client), conn, time(NULL) - Conn_GetSignon(conn),
851                     time(NULL) - Conn_GetSignon(conn) == 1 ? "" : "s",
852                     Client_UserCount(), Channel_CountVisible(NULL));
853                 Conn_UpdatePing(conn);
854         } else
855                 LogDebug("Connection %d: received PONG. Lag: %ld seconds.",
856                          conn, time(NULL) - Conn_LastPing(conn));
857
858         return CONNECTED;
859 } /* IRC_PONG */
860
861 /**
862  * Change the nickname of a client.
863  *
864  * @param Origin The client which caused the nickname change.
865  * @param Target The client of which the nickname should be changed.
866  * @param NewNick The new nickname.
867  */
868 static void
869 Change_Nick(CLIENT *Origin, CLIENT *Target, char *NewNick, bool InformClient)
870 {
871         if (Client_Conn(Target) > NONE) {
872                 /* Local client */
873                 Log(LOG_INFO,
874                     "%s \"%s\" changed nick (connection %d): \"%s\" -> \"%s\".",
875                     Client_TypeText(Target), Client_Mask(Target),
876                     Client_Conn(Target), Client_ID(Target), NewNick);
877                 Conn_UpdateIdle(Client_Conn(Target));
878         } else {
879                 /* Remote client */
880                 LogDebug("%s \"%s\" changed nick: \"%s\" -> \"%s\".",
881                          Client_TypeText(Target),
882                          Client_Mask(Target), Client_ID(Target), NewNick);
883         }
884
885         /* Inform all servers and users (which have to know) of the new name */
886         if (InformClient) {
887                 IRC_WriteStrClientPrefix(Target, Target, "NICK :%s", NewNick);
888                 IRC_WriteStrServersPrefix(NULL, Target, "NICK :%s", NewNick);
889         } else
890                 IRC_WriteStrServersPrefix(Origin, Target, "NICK :%s", NewNick);
891         IRC_WriteStrRelatedPrefix(Target, Target, false, "NICK :%s", NewNick);
892
893         /* Register old nickname for WHOWAS queries */
894         Client_RegisterWhowas(Target);
895
896         /* Save new nickname */
897         Client_SetID(Target, NewNick);
898 }
899
900 /* -eof- */