1 /**
2  * A simple IRC server.
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <vladimir@thecybershadow.net>
12  */
13 
14 module ae.net.irc.server;
15 
16 import std.algorithm;
17 import std.conv;
18 import std.datetime;
19 import std.exception;
20 import std.range;
21 import std.regex;
22 import std.socket;
23 import std.string;
24 
25 import ae.net.asockets;
26 import ae.utils.array;
27 import ae.sys.log;
28 import ae.utils.exception;
29 import ae.utils.meta;
30 import ae.utils.text;
31 
32 import ae.net.irc.common;
33 
34 alias std..string.indexOf indexOf;
35 
36 class IrcServer
37 {
38 	// This class is currently intentionally written for readability, not performance.
39 	// Performance and scalability could be greatly improved by using numeric indices for users and channels
40 	// instead of associative arrays.
41 
42 	/// Server configuration
43 	string hostname, password, network;
44 	string nicknameValidationPattern = "^[a-zA-Z][a-zA-Z0-9\\-`\\|\\[\\]\\{\\}_^]{0,14}$";
45 	uint nicknameMaxLength = 15; /// For the announced capabilities
46 	string serverVersion = "ae.net.irc.server";
47 	string[] motd;
48 	string chanTypes = "#&";
49 	SysTime creationTime;
50 	string operPassword;
51 
52 	/// Channels can't be created by users, and don't disappear when they're empty
53 	bool staticChannels;
54 	/// If set, masks all IPs to the given mask
55 	string addressMask;
56 
57 	Logger log;
58 
59 	/// Client connection and information
60 	abstract static class Client
61 	{
62 		/// How to convert the IRC 8-bit data to and from UTF-8 (D strings must be valid UTF-8).
63 		string function(in char[]) decoder = &rawToUTF8, encoder = &UTF8ToRaw;
64 
65 		/// Registration details
66 		string nickname, password;
67 		string username, hostname, servername, realname;
68 		bool identified;
69 		string prefix, publicPrefix; /// Full nick!user@host
70 		string away;
71 
72 		bool registered;
73 		Modes modes;
74 		MonoTime lastActivity;
75 
76 		Channel[] getJoinedChannels()
77 		{
78 			Channel[] result;
79 			foreach (channel; server.channels)
80 				if (nickname.normalized in channel.members)
81 					result ~= channel;
82 			return result;
83 		}
84 
85 		string realHostname() { return remoteAddress.toAddrString; }
86 		string publicHostname() { return server.addressMask ? server.addressMask : realHostname; }
87 		bool realHostnameVisibleTo(Client viewer)
88 		{
89 			return server.addressMask is null
90 				|| viewer is this
91 				|| viewer.modes.flags['o']; // Oper
92 		}
93 		string hostnameAsVisibleTo(Client viewer) { return realHostnameVisibleTo(viewer) ? realHostname : publicHostname; }
94 		string prefixAsVisibleTo(Client viewer) { return realHostnameVisibleTo(viewer) ? prefix : publicPrefix; }
95 
96 	protected:
97 		IrcServer server;
98 		Address remoteAddress;
99 
100 		this(IrcServer server, Address remoteAddress)
101 		{
102 			this.server = server;
103 			lastActivity = MonoTime.currTime;
104 			server.clients.add(this);
105 
106 			this.remoteAddress = remoteAddress;
107 
108 			server.log("New IRC connection from " ~ remoteAddress.toString);
109 		}
110 
111 		void onReadLine(string line)
112 		{
113 			try
114 			{
115 				if (decoder) line = decoder(line);
116 
117 				if (!connConnected())
118 					return; // A previous line in the same buffer caused a disconnect
119 
120 				enforce(line.indexOf('\0')<0 && line.indexOf('\r')<0 && line.indexOf('\n')<0, "Forbidden character");
121 
122 				auto parameters = line.ircSplit();
123 				if (!parameters.length)
124 					return;
125 
126 				auto command = parameters.shift.toUpper();
127 				onCommand(command, parameters);
128 			}
129 			catch (CaughtException e)
130 			{
131 				if (connConnected())
132 					disconnect(e.msg);
133 			}
134 		}
135 
136 		void onCommand(string command, scope string[] parameters...)
137 		{
138 			switch (command)
139 			{
140 				case "PASS":
141 					if (registered)
142 						return sendReply(Reply.ERR_ALREADYREGISTRED, "You may not reregister");
143 					if (parameters.length != 1)
144 						return sendReply(Reply.ERR_NEEDMOREPARAMS, command, "Not enough parameters");
145 					password = parameters[0];
146 					break;
147 				case "NICK":
148 					if (parameters.length != 1)
149 						return sendReply(Reply.ERR_NONICKNAMEGIVEN, "No nickname given");
150 					if (!registered)
151 					{
152 						nickname = parameters[0];
153 						checkRegistration();
154 					}
155 					else
156 					{
157 						auto newNick = parameters[0];
158 						if (!newNick.match(server.nicknameValidationPattern))
159 							return sendReply(Reply.ERR_ERRONEUSNICKNAME, newNick, "Erroneous nickname");
160 						if (newNick.normalized in server.nicknames)
161 						{
162 							if (newNick.normalized != nickname.normalized)
163 								sendReply(Reply.ERR_NICKNAMEINUSE, newNick, "Nickname is already in use");
164 							return;
165 						}
166 
167 						changeNick(newNick);
168 					}
169 					break;
170 				case "USER":
171 					if (registered)
172 						return sendReply(Reply.ERR_ALREADYREGISTRED, "You may not reregister");
173 					if (parameters.length != 4)
174 						return sendReply(Reply.ERR_NEEDMOREPARAMS, command, "Not enough parameters");
175 					username   = parameters[0];
176 					hostname   = parameters[1];
177 					servername = parameters[2];
178 					realname   = parameters[3];
179 					checkRegistration();
180 					break;
181 
182 				case "PING":
183 					if (!registered)
184 						return sendReply(Reply.ERR_NOTREGISTERED, "You have not registered"); // KVIrc needs this.
185 					sendReply("PONG", parameters);
186 					break;
187 				case "PONG":
188 					break;
189 				case "QUIT":
190 					if (parameters.length)
191 						disconnect("Quit: " ~ parameters[0]);
192 					else
193 						disconnect("Quit");
194 					break;
195 				case "JOIN":
196 					if (!registered)
197 						return sendReply(Reply.ERR_NOTREGISTERED, "You have not registered");
198 					if (parameters.length < 1)
199 						return sendReply(Reply.ERR_NEEDMOREPARAMS, command, "Not enough parameters");
200 					string[] keys = parameters.length > 1 ? parameters[1].split(",") : null;
201 					foreach (i, channame; parameters[0].split(","))
202 					{
203 						auto key = i < keys.length ? keys[i] : null;
204 						if (!server.isChannelName(channame))
205 							{ sendReply(Reply.ERR_NOSUCHCHANNEL, channame, "No such channel"); continue; }
206 						auto normchan = channame.normalized;
207 						if (!mayJoin(normchan))
208 							continue;
209 						auto pchannel = normchan in server.channels;
210 						Channel channel;
211 						if (pchannel)
212 							channel = *pchannel;
213 						else
214 						{
215 							if (server.staticChannels)
216 								{ sendReply(Reply.ERR_NOSUCHCHANNEL, channame, "No such channel"); continue; }
217 							else
218 								channel = server.createChannel(channame);
219 						}
220 						if (nickname.normalized in channel.members)
221 							continue; // already on channel
222 						if (channel.modes.strings['k'] && channel.modes.strings['k'] != key)
223 							{ sendReply(Reply.ERR_BADCHANNELKEY, channame, "Cannot join channel (+k)"); continue; }
224 						if (channel.modes.masks['b'].any!(mask => prefix.maskMatch(mask)))
225 							{ sendReply(Reply.ERR_BANNEDFROMCHAN, channame, "Cannot join channel (+b)"); continue; }
226 						join(channel);
227 					}
228 					lastActivity = MonoTime.currTime;
229 					break;
230 				case "PART":
231 					if (!registered)
232 						return sendReply(Reply.ERR_NOTREGISTERED, "You have not registered");
233 					if (parameters.length < 1) // TODO: part reason
234 						return sendReply(Reply.ERR_NEEDMOREPARAMS, command, "Not enough parameters");
235 					string reason = parameters.length < 2 ? null : parameters[1];
236 					foreach (channame; parameters[0].split(","))
237 					{
238 						auto pchan = channame.normalized in server.channels;
239 						if (!pchan)
240 							{ sendReply(Reply.ERR_NOSUCHCHANNEL, channame, "No such channel"); continue; }
241 						auto chan = *pchan;
242 						if (nickname.normalized !in chan.members)
243 							{ sendReply(Reply.ERR_NOTONCHANNEL, channame, "You're not on that channel"); continue; }
244 						part(chan, reason);
245 					}
246 					lastActivity = MonoTime.currTime;
247 					break;
248 				case "MODE":
249 					if (!registered)
250 						return sendReply(Reply.ERR_NOTREGISTERED, "You have not registered");
251 					if (parameters.length < 1)
252 						return sendReply(Reply.ERR_NEEDMOREPARAMS, command, "Not enough parameters");
253 					auto target = parameters.shift;
254 					if (server.isChannelName(target))
255 					{
256 						auto pchannel = target.normalized in server.channels;
257 						if (!pchannel)
258 							return sendReply(Reply.ERR_NOSUCHNICK, target, "No such nick/channel");
259 						auto channel = *pchannel;
260 						auto pmember = nickname.normalized in channel.members;
261 						if (!pmember)
262 							return sendReply(Reply.ERR_NOTONCHANNEL, target, "You're not on that channel");
263 						if (!parameters.length)
264 							return sendChannelModes(channel);
265 						return setChannelModes(channel, parameters);
266 					}
267 					else
268 					{
269 						auto pclient = target.normalized in server.nicknames;
270 						if (!pclient)
271 							return sendReply(Reply.ERR_NOSUCHNICK, target, "No such nick/channel");
272 						auto client = *pclient;
273 						if (parameters.length)
274 						{
275 							if (client !is this)
276 								return sendReply(Reply.ERR_USERSDONTMATCH, "Cannot change mode for other users");
277 							return setUserModes(parameters);
278 						}
279 						else
280 							return sendUserModes(client);
281 					}
282 				case "LIST":
283 					if (!registered)
284 						return sendReply(Reply.ERR_NOTREGISTERED, "You have not registered");
285 					foreach (channel; getChannelList())
286 						if (!(channel.modes.flags['p'] || channel.modes.flags['s']) || nickname.normalized in channel.members)
287 							sendReply(Reply.RPL_LIST, channel.name, channel.members.length.text, channel.topic ? channel.topic : "");
288 					sendReply(Reply.RPL_LISTEND, "End of LIST");
289 					break;
290 				case "MOTD":
291 					if (!registered)
292 						return sendReply(Reply.ERR_NOTREGISTERED, "You have not registered");
293 					sendMotd();
294 					break;
295 				case "NAMES":
296 					if (!registered)
297 						return sendReply(Reply.ERR_NOTREGISTERED, "You have not registered");
298 					if (parameters.length < 1)
299 						return sendReply(Reply.ERR_NEEDMOREPARAMS, command, "Not enough parameters");
300 					foreach (channame; parameters[0].split(","))
301 					{
302 						auto pchan = channame.normalized in server.channels;
303 						if (!pchan)
304 							{ sendReply(Reply.ERR_NOSUCHCHANNEL, channame, "No such channel"); continue; }
305 						auto channel = *pchan;
306 						auto pmember = nickname.normalized in channel.members;
307 						if (!pmember)
308 							{ sendReply(Reply.ERR_NOTONCHANNEL, channame, "You're not on that channel"); continue; }
309 						sendNames(channel);
310 					}
311 					break;
312 				case "WHO":
313 				{
314 					if (!registered)
315 						return sendReply(Reply.ERR_NOTREGISTERED, "You have not registered");
316 					auto mask = parameters.length ? parameters[0].among("", "*", "0") ? null : parameters[0] : null;
317 					string[string] result;
318 					foreach (channel; server.channels)
319 					{
320 						auto inChannel = nickname.normalized in channel.members;
321 						if (!inChannel && channel.modes.flags['s'])
322 							continue;
323 						foreach (member; channel.members)
324 							if (inChannel || !member.client.modes.flags['i'])
325 								if (!mask || channel.name.maskMatch(mask) || member.client.nickname.maskMatch(mask) || member.client.hostnameAsVisibleTo(this).maskMatch(mask))
326 								{
327 									auto phit = member.client.nickname in result;
328 									if (phit)
329 										*phit = "*";
330 									else
331 										result[member.client.nickname] = channel.name;
332 								}
333 					}
334 
335 					foreach (client; server.nicknames)
336 						if (!client.modes.flags['i'])
337 							if (!mask || client.nickname.maskMatch(mask) || client.hostnameAsVisibleTo(this).maskMatch(mask))
338 								if (client.nickname !in result)
339 									result[client.nickname] = "*";
340 
341 					foreach (nickname, channel; result)
342 					{
343 						auto client = server.nicknames[nickname.normalized];
344 						sendReply(Reply.RPL_WHOREPLY,
345 							channel,
346 							client.username,
347 							safeHostname(hostnameAsVisibleTo(this)),
348 							server.hostname,
349 							nickname,
350 							"H",
351 							"0 " ~ client.realname,
352 						);
353 					}
354 					sendReply(Reply.RPL_ENDOFWHO, mask ? mask : "*", "End of WHO list");
355 					break;
356 				}
357 				case "WHOIS":
358 				{
359 					if (!registered)
360 						return sendReply(Reply.ERR_NOTREGISTERED, "You have not registered");
361 					// Contrary to the RFC, and similar to e.g. Freenode, we don't support masks here.
362 					if (parameters.length < 1)
363 						return sendReply(Reply.ERR_NEEDMOREPARAMS, command, "Not enough parameters");
364 					foreach (nick; parameters[0].split(","))
365 					{
366 						auto pclient = nick.normalized in server.nicknames;
367 						if (!pclient)
368 							sendReply(Reply.ERR_NOSUCHNICK, nick, "No such nick");
369 						auto client = *pclient;
370 
371 						// RPL_WHOISUSER
372 						sendReply(Reply.RPL_WHOISUSER,
373 							client.nickname,
374 							client.username,
375 							safeHostname(hostnameAsVisibleTo(this)),
376 							"*",
377 							client.realname,
378 						);
379 						// RPL_WHOISCHANNELS
380 						server.channels.byValue
381 							// Channel contents visible?
382 							.filter!(channel => !channel.modes.flags['s'] || this.nickname.normalized in channel.members)
383 							// Get channel member mode + name if target in channel, or null
384 							.map!(channel => (nick.normalized in channel.members).I!(pmember => pmember ? pmember.modeChar() ~ channel.name : null))
385 							.filter!(name => name !is null)
386 							.chunks(10)
387 							.each!(chunk => sendReply(Reply.RPL_WHOISCHANNELS, client.nickname, chunk.join(" ")));
388 						// RPL_WHOISOPERATOR
389 						if (client.modes.flags['o'])
390 							sendReply(Reply.RPL_WHOISOPERATOR, client.nickname, "is an IRC operator");
391 						// RPL_WHOISIDLE
392 						sendReply(Reply.RPL_WHOISIDLE, client.nickname,
393 							(MonoTime.currTime - client.lastActivity).total!"seconds".text,
394 							"seconds idle");
395 					}
396 					// RPL_ENDOFWHOIS
397 					sendReply(Reply.RPL_ENDOFWHOIS, parameters[0], "End of WHOIS list");
398 					break;
399 				}
400 				case "TOPIC":
401 				{
402 					if (!registered)
403 						return sendReply(Reply.ERR_NOTREGISTERED, "You have not registered");
404 					if (parameters.length < 1)
405 						return sendReply(Reply.ERR_NEEDMOREPARAMS, command, "Not enough parameters");
406 					auto target = parameters.shift;
407 					auto pchannel = target.normalized in server.channels;
408 					if (!pchannel)
409 						return sendReply(Reply.ERR_NOSUCHNICK, target, "No such nick/channel");
410 					auto channel = *pchannel;
411 					auto pmember = nickname.normalized in channel.members;
412 					if (!pmember)
413 						return sendReply(Reply.ERR_NOTONCHANNEL, target, "You're not on that channel");
414 					if (!parameters.length)
415 						return sendTopic(channel);
416 					if (channel.modes.flags['t'] && (pmember.modes & Channel.Member.Modes.op) == 0)
417 						return sendReply(Reply.ERR_CHANOPRIVSNEEDED, target, "You're not channel operator");
418 					return setChannelTopic(channel, parameters[0]);
419 				}
420 				case "ISON":
421 					if (!registered)
422 						return sendReply(Reply.ERR_NOTREGISTERED, "You have not registered");
423 					sendReply(Reply.RPL_ISON, parameters.filter!(nick => nick.normalized in server.nicknames).join(" "));
424 					break;
425 				case "USERHOST":
426 				{
427 					if (!registered)
428 						return sendReply(Reply.ERR_NOTREGISTERED, "You have not registered");
429 					string[] replies;
430 					foreach (nick; parameters)
431 					{
432 						auto pclient = nick.normalized in server.nicknames;
433 						if (!pclient)
434 							continue;
435 						auto client = *pclient;
436 						replies ~= "%s%s=%s%s@%s".format(
437 							nick,
438 							client.modes.flags['o'] ? "*" : "",
439 							client.away ? "+" : "-",
440 							client.username,
441 							hostnameAsVisibleTo(this),
442 						);
443 					}
444 					sendReply(Reply.RPL_USERHOST, replies.join(" "));
445 					break;
446 				}
447 				case "PRIVMSG":
448 				case "NOTICE":
449 				{
450 					if (!registered)
451 						return sendReply(Reply.ERR_NOTREGISTERED, "You have not registered");
452 					if (parameters.length < 2)
453 						return sendReply(Reply.ERR_NEEDMOREPARAMS, command, "Not enough parameters");
454 					auto message = parameters[1];
455 					if (!message.length)
456 						return sendReply(Reply.ERR_NOTEXTTOSEND, command, "No text to send");
457 					foreach (target; parameters[0].split(","))
458 					{
459 						if (server.isChannelName(target))
460 						{
461 							auto pchannel = target.normalized in server.channels;
462 							if (!pchannel)
463 								{ sendReply(Reply.ERR_NOSUCHNICK, target, "No such nick/channel"); continue; }
464 							auto channel = *pchannel;
465 							auto pmember = nickname.normalized in channel.members;
466 							if (pmember) // On channel?
467 							{
468 								if (channel.modes.flags['m'] && (pmember.modes & Channel.Member.Modes.bypassM) == 0)
469 									{ sendReply(Reply.ERR_CANNOTSENDTOCHAN, target, "Cannot send to channel"); continue; }
470 							}
471 							else
472 							{
473 								if (channel.modes.flags['n']) // No external messages
474 									{ sendReply(Reply.ERR_NOTONCHANNEL, target, "You're not on that channel"); continue; }
475 							}
476 							sendToChannel(channel, command, message);
477 						}
478 						else
479 						{
480 							auto pclient = target.normalized in server.nicknames;
481 							if (!pclient)
482 								{ sendReply(Reply.ERR_NOSUCHNICK, target, "No such nick/channel"); continue; }
483 							sendToClient(*pclient, command, message);
484 						}
485 					}
486 					lastActivity = MonoTime.currTime;
487 					break;
488 				}
489 				case "OPER":
490 					if (!registered)
491 						return sendReply(Reply.ERR_NOTREGISTERED, "You have not registered");
492 					if (parameters.length < 1)
493 						return sendReply(Reply.ERR_NEEDMOREPARAMS, command, "Not enough parameters");
494 					if (!server.operPassword || parameters[$-1] != server.operPassword)
495 						return sendReply(Reply.ERR_PASSWDMISMATCH, "Password incorrect");
496 					modes.flags['o'] = true;
497 					sendReply(Reply.RPL_YOUREOPER, "You are now an IRC operator");
498 					sendUserModes(this);
499 					foreach (channel; server.channels)
500 						if (nickname.normalized in channel.members)
501 							setChannelMode(channel, nickname, Channel.Member.Mode.op, true);
502 					break;
503 
504 				default:
505 					if (registered)
506 						return sendReply(Reply.ERR_UNKNOWNCOMMAND, command, "Unknown command");
507 			}
508 		}
509 
510 		final void onInactivity()
511 		{
512 			sendLine("PING %s".format(Clock.currTime.stdTime));
513 		}
514 
515 		void disconnect(string why)
516 		{
517 			if (registered)
518 				unregister(why);
519 			sendLine("ERROR :Closing Link: %s[%s@%s] (%s)".format(nickname, username, realHostname, why));
520 			connDisconnect(why);
521 		}
522 
523 		void onDisconnect(string reason, DisconnectType type)
524 		{
525 			if (registered)
526 				unregister(reason);
527 			server.clients.remove(this);
528 			server.log("IRC: %s disconnecting: %s".format(remoteAddress, reason));
529 		}
530 
531 		void checkRegistration()
532 		{
533 			assert(!registered);
534 
535 			if (nickname.length && username.length)
536 			{
537 				if (server.password && password != server.password)
538 				{
539 					password = null;
540 					return sendReply(Reply.ERR_PASSWDMISMATCH, "Password incorrect");
541 				}
542 				if (nickname.normalized in server.nicknames)
543 				{
544 					scope(exit) nickname = null;
545 					return sendReply(Reply.ERR_NICKNAMEINUSE, nickname, "Nickname is already in use");
546 				}
547 				if (!nickname.match(server.nicknameValidationPattern))
548 				{
549 					scope(exit) nickname = null;
550 					return sendReply(Reply.ERR_ERRONEUSNICKNAME, nickname, "Erroneous nickname");
551 				}
552 				if (!username.match(`[a-zA-Z]+`))
553 					return disconnect("Invalid username");
554 
555 				// All OK
556 				register();
557 			}
558 		}
559 
560 		void register()
561 		{
562 			if (!identified)
563 				username = "~" ~ username;
564 			update();
565 
566 			registered = true;
567 			server.nicknames[nickname.normalized] = this;
568 			auto userCount = server.nicknames.length;
569 			if (server.maxUsers < userCount)
570 				server.maxUsers = userCount;
571 			sendReply(Reply.RPL_WELCOME      , "Welcome, %s!".format(nickname));
572 			sendReply(Reply.RPL_YOURHOST     , "Your host is %s, running %s".format(server.hostname, server.serverVersion));
573 			sendReply(Reply.RPL_CREATED      , "This server was created %s".format(server.creationTime));
574 			sendReply(Reply.RPL_MYINFO       , server.hostname, server.serverVersion, UserModes.supported, ChannelModes.supported, null);
575 			sendReply(cast(Reply)         005, server.capabilities ~ ["are supported by this server"]);
576 			sendReply(Reply.RPL_LUSERCLIENT  , "There are %d users and %d invisible on %d servers".format(userCount, 0, 1));
577 			sendReply(Reply.RPL_LUSEROP      , 0.text, "IRC Operators online"); // TODO: OPER
578 			sendReply(Reply.RPL_LUSERCHANNELS, server.channels.length.text, "channels formed");
579 			sendReply(Reply.RPL_LUSERME      , "I have %d clients and %d servers".format(userCount, 0));
580 			sendReply(cast(Reply)         265, "Current local  users: %d  Max: %d".format(userCount, server.maxUsers));
581 			sendReply(cast(Reply)         266, "Current global users: %d  Max: %d".format(userCount, server.maxUsers));
582 			sendReply(cast(Reply)         250, "Highest connection count: %d (%d clients) (%d since server was (re)started)".format(server.maxUsers, server.maxUsers, server.totalConnections));
583 			sendMotd();
584 		}
585 
586 		void update()
587 		{
588 			prefix       = "%s!%s@%s".format(nickname, username, realHostname  );
589 			publicPrefix = "%s!%s@%s".format(nickname, username, publicHostname);
590 		}
591 
592 		void unregister(string why)
593 		{
594 			assert(registered);
595 			auto channels = getJoinedChannels();
596 			foreach (channel; channels)
597 				channel.remove(this);
598 			foreach (client; server.allClientsInChannels(channels))
599 				client.sendCommand(this, "QUIT", why);
600 			server.nicknames.remove(nickname.normalized);
601 			registered = false;
602 		}
603 
604 		void changeNick(string newNick)
605 		{
606 			auto channels = getJoinedChannels();
607 			auto witnesses = server.whoCanSee(this);
608 
609 			foreach (channel; channels)
610 			{
611 				auto pmember = nickname.normalized in channel.members;
612 				assert(pmember);
613 				auto member = *pmember;
614 				channel.members.remove(nickname.normalized);
615 				channel.members[newNick.normalized] = member;
616 			}
617 
618 			foreach (client; witnesses)
619 				client.sendCommand(this, "NICK", newNick, null);
620 
621 			server.nicknames.remove(nickname.normalized);
622 			server.nicknames[newNick.normalized] = this;
623 
624 			nickname = newNick;
625 			update();
626 		}
627 
628 		void sendMotd()
629 		{
630 			sendReply(Reply.RPL_MOTDSTART    , "- %s Message of the Day - ".format(server.hostname));
631 			foreach (line; server.motd)
632 				sendReply(Reply.RPL_MOTD, "- %s".format(line));
633 			sendReply(Reply.RPL_ENDOFMOTD    , "End of /MOTD command.");
634 		}
635 
636 		bool mayJoin(string name)
637 		{
638 			return true;
639 		}
640 
641 		void join(Channel channel)
642 		{
643 			channel.add(this);
644 			foreach (member; channel.members)
645 				member.client.sendCommand(this, "JOIN", channel.name);
646 			sendTopic(channel);
647 			sendNames(channel);
648 			auto pmember = nickname.normalized in channel.members;
649 			// Sync OPER status with (initial) channel op status
650 			if (server.staticChannels || modes.flags['o'])
651 				setChannelMode(channel, nickname, Channel.Member.Mode.op, modes.flags['o']);
652 		}
653 
654 		// For server-imposed mode changes.
655 		void setChannelMode(Channel channel, string nickname, Channel.Member.Mode mode, bool value)
656 		{
657 			auto pmember = nickname.normalized in channel.members;
658 			if (pmember.modeSet(mode) == value)
659 				return;
660 
661 			pmember.setMode(mode, value);
662 			auto c = ChannelModes.memberModeChars[mode];
663 			foreach (member; channel.members)
664 				member.client.sendCommand(server.hostname, "MODE", channel.name, [value ? '+' : '-', c], nickname, null);
665 			server.channelChanged(channel);
666 		}
667 
668 		void part(Channel channel, string reason=null)
669 		{
670 			foreach (member; channel.members)
671 				member.client.sendCommand(this, "PART", channel.name, reason);
672 			channel.remove(this);
673 		}
674 
675 		void sendToChannel(Channel channel, string command, string message)
676 		{
677 			foreach (member; channel.members)
678 				if (member.client !is this)
679 					member.client.sendCommand(this, command, channel.name, message);
680 		}
681 
682 		void sendToClient(Client client, string command, string message)
683 		{
684 			client.sendCommand(this, command, client.nickname, message);
685 		}
686 
687 		void sendTopic(Channel channel)
688 		{
689 			if (channel.topic)
690 				sendReply(Reply.RPL_TOPIC, channel.name, channel.topic);
691 			else
692 				sendReply(Reply.RPL_NOTOPIC, channel.name, "No topic is set");
693 		}
694 
695 		void sendNames(Channel channel)
696 		{
697 			foreach (chunk; channel.members.values.chunks(10)) // can't use byValue - https://issues.dlang.org/show_bug.cgi?id=11761
698 				sendReply(Reply.RPL_NAMREPLY, channel.modes.flags['s'] ? "@" : channel.modes.flags['p'] ? "*" : "=", channel.name, chunk.map!q{a.displayName}.join(" "));
699 			sendReply(Reply.RPL_ENDOFNAMES, channel.name, "End of /NAMES list");
700 		}
701 
702 		/// For LIST
703 		Channel[] getChannelList()
704 		{
705 			return server.channels.values;
706 		}
707 
708 		void sendChannelModes(Channel channel)
709 		{
710 			string modes = "+";
711 			string[] modeParams;
712 			foreach (c; 0..char.max)
713 				final switch (ChannelModes.modeTypes[c])
714 				{
715 					case ChannelModes.Type.none:
716 					case ChannelModes.Type.member:
717 						break;
718 					case ChannelModes.Type.mask:
719 						// sent after RPL_CHANNELMODEIS
720 						break;
721 					case ChannelModes.Type.flag:
722 						if (channel.modes.flags[c])
723 							modes ~= c;
724 						break;
725 					case ChannelModes.Type.str:
726 						if (channel.modes.strings[c])
727 						{
728 							modes ~= c;
729 							modeParams ~= channel.modes.strings[c];
730 						}
731 						break;
732 					case ChannelModes.Type.number:
733 						if (channel.modes.numbers[c])
734 						{
735 							modes ~= c;
736 							modeParams ~= channel.modes.numbers[c].text;
737 						}
738 						break;
739 				}
740 			sendReply(Reply.RPL_CHANNELMODEIS, channel.name, ([modes] ~ modeParams).join(" "), null);
741 		}
742 
743 		void sendChannelModeMasks(Channel channel, char mode)
744 		{
745 			switch (mode)
746 			{
747 				case 'b':
748 					sendChannelMaskList(channel, channel.modes.masks[mode], Reply.RPL_BANLIST, Reply.RPL_ENDOFBANLIST, "End of channel ban list");
749 					break;
750 				default:
751 					assert(false);
752 			}
753 		}
754 
755 		void sendChannelMaskList(Channel channel, string[] masks, Reply lineReply, Reply endReply, string endText)
756 		{
757 			foreach (mask; masks)
758 				sendReply(lineReply, channel.name, mask, null);
759 			sendReply(endReply, channel.name, endText);
760 		}
761 
762 		void setChannelTopic(Channel channel, string topic)
763 		{
764 			channel.topic = topic;
765 			foreach (ref member; channel.members)
766 				member.client.sendCommand(this, "TOPIC", channel.name, topic);
767 			server.channelChanged(channel);
768 		}
769 
770 		void setChannelModes(Channel channel, string[] modes)
771 		{
772 			auto pself = nickname.normalized in channel.members;
773 			bool op = (pself.modes & Channel.Member.Modes.op) != 0;
774 
775 			string[2] effectedChars;
776 			string[][2] effectedParams;
777 
778 			scope(exit) // Broadcast effected options
779 			{
780 				string[] parameters;
781 				foreach (adding; 0..2)
782 					if (effectedChars[adding].length)
783 						parameters ~= [(adding ? "+" : "-") ~ effectedChars[adding]] ~ effectedParams[adding];
784 				if (parameters.length)
785 				{
786 					assert(op);
787 					parameters = ["MODE", channel.name] ~ parameters ~ [string.init];
788 					foreach (ref member; channel.members)
789 						member.client.sendCommand(this, parameters);
790 				}
791 			}
792 
793 			while (modes.length)
794 			{
795 				auto chars = modes.shift;
796 
797 				bool adding = true;
798 				foreach (c; chars)
799 					if (c == '+')
800 						adding = true;
801 					else
802 					if (c == '-')
803 						adding = false;
804 					else
805 					final switch (ChannelModes.modeTypes[c])
806 					{
807 						case ChannelModes.Type.none:
808 							sendReply(Reply.ERR_UNKNOWNMODE, [c], "is unknown mode char to me for %s".format(channel.name));
809 							break;
810 						case ChannelModes.Type.flag:
811 							if (!op) return sendReply(Reply.ERR_CHANOPRIVSNEEDED, channel.name, "You're not channel operator");
812 							if (adding != channel.modes.flags[c])
813 							{
814 								channel.modes.flags[c] = adding;
815 								effectedChars[adding] ~= c;
816 							}
817 							break;
818 						case ChannelModes.Type.member:
819 						{
820 							if (!op) return sendReply(Reply.ERR_CHANOPRIVSNEEDED, channel.name, "You're not channel operator");
821 							if (!modes.length)
822 								{ sendReply(Reply.ERR_NEEDMOREPARAMS, "MODE", "Not enough parameters"); continue; }
823 							auto memberName = modes.shift;
824 							auto pmember = memberName.normalized in channel.members;
825 							if (!pmember)
826 								{ sendReply(Reply.ERR_USERNOTINCHANNEL, memberName, channel.name, "They aren't on that channel"); continue; }
827 							auto mode = ChannelModes.memberModes[c];
828 							if (pmember.modeSet(mode) != adding)
829 							{
830 								pmember.setMode(mode, adding);
831 								effectedChars[adding] ~= c;
832 								effectedParams[adding] ~= memberName;
833 							}
834 							break;
835 						}
836 						case ChannelModes.Type.mask:
837 						{
838 							if (!modes.length)
839 								return sendChannelModeMasks(channel, c);
840 							if (!op) return sendReply(Reply.ERR_CHANOPRIVSNEEDED, channel.name, "You're not channel operator");
841 							auto mask = modes.shift;
842 							if (adding)
843 							{
844 								if (channel.modes.masks[c].canFind(mask))
845 									continue;
846 								channel.modes.masks[c] ~= mask;
847 							}
848 							else
849 							{
850 								auto index = channel.modes.masks[c].countUntil(mask);
851 								if (index < 0)
852 									continue;
853 								channel.modes.masks[c] = channel.modes.masks[c][0..index] ~ channel.modes.masks[c][index+1..$];
854 							}
855 							effectedChars[adding] ~= c;
856 							effectedParams[adding] ~= mask;
857 							break;
858 						}
859 						case ChannelModes.Type.str:
860 							if (!op) return sendReply(Reply.ERR_CHANOPRIVSNEEDED, channel.name, "You're not channel operator");
861 							if (adding)
862 							{
863 								if (!modes.length)
864 									{ sendReply(Reply.ERR_NEEDMOREPARAMS, "MODE", "Not enough parameters"); continue; }
865 								auto str = modes.shift;
866 								if (channel.modes.strings[c] == str)
867 									continue;
868 								channel.modes.strings[c] = str;
869 								effectedChars[adding] ~= c;
870 								effectedParams[adding] ~= str;
871 							}
872 							else
873 							{
874 								if (!channel.modes.strings[c])
875 									continue;
876 								channel.modes.strings[c] = null;
877 								effectedChars[adding] ~= c;
878 							}
879 							break;
880 						case ChannelModes.Type.number:
881 							if (!op) return sendReply(Reply.ERR_CHANOPRIVSNEEDED, channel.name, "You're not channel operator");
882 							if (adding)
883 							{
884 								if (!modes.length)
885 									{ sendReply(Reply.ERR_NEEDMOREPARAMS, "MODE", "Not enough parameters"); continue; }
886 								auto numText = modes.shift;
887 								auto num = numText.to!long;
888 								if (channel.modes.numbers[c] == num)
889 									continue;
890 								channel.modes.numbers[c] = num;
891 								effectedChars[adding] ~= c;
892 								effectedParams[adding] ~= numText;
893 							}
894 							else
895 							{
896 								if (!channel.modes.numbers[c])
897 									continue;
898 								channel.modes.numbers[c] = 0;
899 								effectedChars[adding] ~= c;
900 							}
901 							break;
902 					}
903 			}
904 			server.channelChanged(channel);
905 		}
906 
907 		void setUserModes(string[] modes)
908 		{
909 			while (modes.length)
910 			{
911 				auto chars = modes.shift;
912 
913 				bool adding = true;
914 				foreach (c; chars)
915 					if (c == '+')
916 						adding = true;
917 					else
918 					if (c == '-')
919 						adding = false;
920 					else
921 					final switch (UserModes.modeTypes[c])
922 					{
923 						case UserModes.Type.none:
924 							sendReply(Reply.ERR_UMODEUNKNOWNFLAG, "Unknown MODE flag");
925 							break;
926 						case UserModes.Type.flag:
927 							if (UserModes.isSettable[c])
928 								this.modes.flags[c] = adding;
929 							break;
930 					}
931 			}
932 		}
933 
934 		void sendUserModes(Client client)
935 		{
936 			string modeString = "+";
937 			foreach (char c, on; modes.flags)
938 				if (on)
939 					modeString ~= c;
940 			return sendReply(Reply.RPL_UMODEIS, modeString, null);
941 		}
942 
943 		void sendCommand(Client from, string[] parameters...)
944 		{
945 			return sendCommand(this is from ? prefix : from.prefixAsVisibleTo(this), parameters);
946 		}
947 
948 		void sendCommand(string from, string[] parameters...)
949 		{
950 			assert(parameters.length, "At least one parameter expected");
951 			foreach (parameter; parameters[0..$-1])
952 				assert(parameter.length && parameter[0] != ':' && parameter.indexOf(' ') < 0, "Invalid parameter: " ~ parameter);
953 			if (parameters[$-1] is null)
954 				parameters = parameters[0..$-1];
955 			else
956 				parameters = parameters[0..$-1] ~ [":" ~ parameters[$-1]];
957 			auto line = ":%s %-(%s %)".format(from, parameters);
958 			sendLine(line);
959 		}
960 
961 		void sendReply(Reply reply, string[] parameters...)
962 		{
963 			return sendReply("%03d".format(reply), parameters);
964 		}
965 
966 		void sendReply(string command, string[] parameters...)
967 		{
968 			return sendCommand(server.hostname, [command, nickname ? nickname : "*"] ~ parameters);
969 		}
970 
971 		void sendServerNotice(string text)
972 		{
973 			sendReply("NOTICE", "*** Notice -- " ~ text);
974 		}
975 
976 		void sendLine(string line)
977 		{
978 			if (encoder) line = encoder(line);
979 			connSendLine(line);
980 		}
981 
982 		abstract bool connConnected();
983 		abstract void connSendLine(string line);
984 		abstract void connDisconnect(string reason);
985 	}
986 
987 	static class NetworkClient : Client
988 	{
989 	protected:
990 		IrcConnection conn;
991 
992 		this(IrcServer server, IrcConnection incoming, Address remoteAddress)
993 		{
994 			super(server, remoteAddress);
995 
996 			conn = incoming;
997 			conn.handleReadLine = &onReadLine;
998 			conn.handleInactivity = &onInactivity;
999 			conn.handleDisconnect = &onDisconnect;
1000 		}
1001 
1002 		override bool connConnected()
1003 		{
1004 			return conn.state == ConnectionState.connected;
1005 		}
1006 
1007 		override void connSendLine(string line)
1008 		{
1009 			conn.send(line);
1010 		}
1011 
1012 		override void connDisconnect(string reason)
1013 		{
1014 			conn.disconnect(reason);
1015 		}
1016 	}
1017 
1018 	HashSet!Client clients; /// All clients
1019 	Client[string] nicknames; /// Registered clients only
1020 
1021 	/// Statistics
1022 	ulong maxUsers, totalConnections;
1023 
1024 	final class Channel
1025 	{
1026 		string name;
1027 		string topic;
1028 
1029 		Modes modes;
1030 
1031 		struct Member
1032 		{
1033 			enum Mode
1034 			{
1035 				op,
1036 				voice,
1037 				max
1038 			}
1039 
1040 			enum Modes
1041 			{
1042 				none  = 0,
1043 				op    = 1 << Mode.op,
1044 				voice = 1 << Mode.voice,
1045 
1046 				bypassM = op | voice, // which modes bypass +m
1047 			}
1048 
1049 			Client client;
1050 			Modes modes;
1051 
1052 			bool modeSet(Mode mode) { return (modes & (1 << mode)) != 0; }
1053 			void setMode(Mode mode, bool value)
1054 			{
1055 				auto modeMask = 1 << mode;
1056 				if (value)
1057 					modes |= modeMask;
1058 				else
1059 					modes &= ~modeMask;
1060 			}
1061 
1062 			string modeChar()
1063 			{
1064 				foreach (mode; Mode.init..Mode.max)
1065 					if ((1 << mode) & modes)
1066 						return [ChannelModes.memberModePrefixes[mode]];
1067 				return "";
1068 			}
1069 			string displayName() { return modeChar ~ client.nickname; }
1070 		}
1071 
1072 		Member[string] members;
1073 
1074 		this(string name)
1075 		{
1076 			this.name = name;
1077 			modes.flags['t'] = modes.flags['n'] = true;
1078 		}
1079 
1080 		void add(Client client)
1081 		{
1082 			auto modes = staticChannels || members.length ? Member.Modes.none : Member.Modes.op;
1083 			members[client.nickname.normalized] = Member(client, modes);
1084 		}
1085 
1086 		void remove(Client client)
1087 		{
1088 			members.remove(client.nickname.normalized);
1089 			if (!staticChannels && !members.length && !modes.flags['P'])
1090 				channels.remove(name.normalized);
1091 		}
1092 	}
1093 
1094 	Channel[string] channels;
1095 
1096 	TcpServer conn;
1097 
1098 	this()
1099 	{
1100 		conn = new TcpServer;
1101 		conn.handleAccept = &onAccept;
1102 
1103 		hostname = Socket.hostName;
1104 		creationTime = Clock.currTime;
1105 	}
1106 
1107 	ushort listen(ushort port=6667, string addr = null)
1108 	{
1109 		port = conn.listen(port, addr);
1110 		return port;
1111 	}
1112 
1113 	Channel createChannel(string name)
1114 	{
1115 		return channels[name.normalized] = new Channel(name);
1116 	}
1117 
1118 	void close(string reason)
1119 	{
1120 		conn.close();
1121 		foreach (client; clients.keys)
1122 			client.disconnect("Server is shutting down" ~ (reason.length ? ": " ~ reason : ""));
1123 	}
1124 
1125 protected:
1126 	Client createClient(TcpConnection incoming)
1127 	{
1128 		return new NetworkClient(this, new IrcConnection(incoming), incoming.remoteAddress);
1129 	}
1130 
1131 	void onAccept(TcpConnection incoming)
1132 	{
1133 		createClient(incoming);
1134 		totalConnections++;
1135 	}
1136 
1137 	Client[string] allClientsInChannels(Channel[] channels)
1138 	{
1139 		Client[string] result;
1140 		foreach (channel; channels)
1141 			foreach (ref member; channel.members)
1142 				result[member.client.nickname.normalized] = member.client;
1143 		return result;
1144 	}
1145 
1146 	/// Clients who can see the given client (are in the same channer).
1147 	/// Includes the target client himself.
1148 	Client[string] whoCanSee(Client who)
1149 	{
1150 		auto clients = allClientsInChannels(who.getJoinedChannels());
1151 		clients[who.nickname.normalized] = who;
1152 		return clients;
1153 	}
1154 
1155 	bool isChannelName(string target)
1156 	{
1157 		foreach (prefix; chanTypes)
1158 			if (target.startsWith(prefix))
1159 				return true;
1160 		return false;
1161 	}
1162 
1163 	string[] capabilities()
1164 	{
1165 		string[] result;
1166 		result ~= "PREFIX=(%s)%s".format(ChannelModes.memberModeChars, ChannelModes.memberModePrefixes);
1167 		result ~= "CHANTYPES=" ~ chanTypes;
1168 		result ~= "CHANMODES=%-(%s,%)".format(
1169 			[ChannelModes.Type.mask, ChannelModes.Type.str, ChannelModes.Type.number, ChannelModes.Type.flag].map!(type => ChannelModes.byType(type))
1170 		);
1171 		if (network)
1172 			result ~= "NETWORK=" ~ network;
1173 		result ~= "CASEMAPPING=rfc1459";
1174 		result ~= "NICKLEN=" ~ text(nicknameMaxLength);
1175 		return result;
1176 	}
1177 
1178 	/// Persistence hook
1179 	void channelChanged(Channel channel)
1180 	{
1181 	}
1182 }
1183 
1184 bool maskMatch(string subject, string mask)
1185 {
1186 	import std.path;
1187 	return globMatch!(CaseSensitive.no)(subject, mask);
1188 }
1189 
1190 
1191 string safeHostname(string s)
1192 {
1193 	assert(s.length);
1194 	if (s[0] == ':')
1195 		s = '0' ~ s;
1196 	return s;
1197 }
1198 
1199 alias rfc1459toUpper normalized;
1200 
1201 string[] ircSplit(string line)
1202 {
1203 	auto colon = line.indexOf(":");
1204 	if (colon < 0)
1205 		return line.split;
1206 	else
1207 		return line[0..colon].strip.split ~ [line[colon+1..$]];
1208 }
1209 
1210 struct Modes
1211 {
1212 	bool[char.max] flags;
1213 	string[char.max] strings;
1214 	long[char.max] numbers;
1215 	string[][char.max] masks;
1216 }
1217 
1218 mixin template CommonModes()
1219 {
1220 //static immutable:
1221 	Type[char.max] modeTypes;
1222 	string supported()       pure { return modeTypes.length.iota.filter!(m => modeTypes[m]        ).map!(m => cast(char)m).array; }
1223 	string byType(Type type) pure { return modeTypes.length.iota.filter!(m => modeTypes[m] == type).map!(m => cast(char)m).array; }
1224 }
1225 
1226 struct ChannelModes
1227 {
1228 static immutable:
1229 	enum Type { none, flag, member, mask, str, number }
1230 	mixin CommonModes;
1231 	IrcServer.Channel.Member.Mode[char.max] memberModes;
1232 	char[IrcServer.Channel.Member.Mode.max] memberModeChars, memberModePrefixes;
1233 
1234 	shared static this()
1235 	{
1236 		foreach (c; "ntpsP")
1237 			modeTypes[c] = Type.flag;
1238 		foreach (c; "ov")
1239 			modeTypes[c] = Type.member;
1240 		foreach (c; "b")
1241 			modeTypes[c] = Type.mask;
1242 		foreach (c; "k")
1243 			modeTypes[c] = Type.str;
1244 
1245 		memberModes['o'] = IrcServer.Channel.Member.Mode.op;
1246 		memberModes['v'] = IrcServer.Channel.Member.Mode.voice;
1247 
1248 		memberModeChars    = "ov";
1249 		memberModePrefixes = "@+";
1250 	}
1251 }
1252 
1253 struct UserModes
1254 {
1255 static immutable:
1256 	enum Type { none, flag }
1257 	mixin CommonModes;
1258 	bool[char.max] isSettable;
1259 
1260 	shared static this()
1261 	{
1262 		foreach (c; "io")
1263 			modeTypes[c] = Type.flag;
1264 		foreach (c; "i")
1265 			isSettable[c] = true;
1266 	}
1267 }