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(client.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/channel"); continue; }
369 						auto client = *pclient;
370 
371 						// RPL_WHOISUSER
372 						sendReply(Reply.RPL_WHOISUSER,
373 							client.nickname,
374 							client.username,
375 							safeHostname(client.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 							client.hostnameAsVisibleTo(this),
442 						);
443 					}
444 					sendReply(Reply.RPL_USERHOST, replies.join(" "));
445 					break;
446 				}
447 				case "LUSERS":
448 					sendLusers();
449 					break;
450 				case "PRIVMSG":
451 				case "NOTICE":
452 				{
453 					if (!registered)
454 						return sendReply(Reply.ERR_NOTREGISTERED, "You have not registered");
455 					if (parameters.length < 2)
456 						return sendReply(Reply.ERR_NEEDMOREPARAMS, command, "Not enough parameters");
457 					auto message = parameters[1];
458 					if (!message.length)
459 						return sendReply(Reply.ERR_NOTEXTTOSEND, command, "No text to send");
460 					foreach (target; parameters[0].split(","))
461 					{
462 						if (server.isChannelName(target))
463 						{
464 							auto pchannel = target.normalized in server.channels;
465 							if (!pchannel)
466 								{ sendReply(Reply.ERR_NOSUCHNICK, target, "No such nick/channel"); continue; }
467 							auto channel = *pchannel;
468 							auto pmember = nickname.normalized in channel.members;
469 							if (pmember) // On channel?
470 							{
471 								if (channel.modes.flags['m'] && (pmember.modes & Channel.Member.Modes.bypassM) == 0)
472 									{ sendReply(Reply.ERR_CANNOTSENDTOCHAN, target, "Cannot send to channel"); continue; }
473 							}
474 							else
475 							{
476 								if (channel.modes.flags['n']) // No external messages
477 									{ sendReply(Reply.ERR_NOTONCHANNEL, target, "You're not on that channel"); continue; }
478 							}
479 							sendToChannel(channel, command, message);
480 						}
481 						else
482 						{
483 							auto pclient = target.normalized in server.nicknames;
484 							if (!pclient)
485 								{ sendReply(Reply.ERR_NOSUCHNICK, target, "No such nick/channel"); continue; }
486 							sendToClient(*pclient, command, message);
487 						}
488 					}
489 					lastActivity = MonoTime.currTime;
490 					break;
491 				}
492 				case "OPER":
493 					if (!registered)
494 						return sendReply(Reply.ERR_NOTREGISTERED, "You have not registered");
495 					if (parameters.length < 1)
496 						return sendReply(Reply.ERR_NEEDMOREPARAMS, command, "Not enough parameters");
497 					if (!server.operPassword || parameters[$-1] != server.operPassword)
498 						return sendReply(Reply.ERR_PASSWDMISMATCH, "Password incorrect");
499 					modes.flags['o'] = true;
500 					sendReply(Reply.RPL_YOUREOPER, "You are now an IRC operator");
501 					sendUserModes(this);
502 					foreach (channel; server.channels)
503 						if (nickname.normalized in channel.members)
504 							setChannelMode(channel, nickname, Channel.Member.Mode.op, true);
505 					break;
506 
507 				default:
508 					if (registered)
509 						return sendReply(Reply.ERR_UNKNOWNCOMMAND, command, "Unknown command");
510 			}
511 		}
512 
513 		final void onInactivity()
514 		{
515 			sendLine("PING %s".format(Clock.currTime.stdTime));
516 		}
517 
518 		void disconnect(string why)
519 		{
520 			if (registered)
521 				unregister(why);
522 			sendLine("ERROR :Closing Link: %s[%s@%s] (%s)".format(nickname, username, realHostname, why));
523 			connDisconnect(why);
524 		}
525 
526 		void onDisconnect(string reason, DisconnectType type)
527 		{
528 			if (registered)
529 				unregister(reason);
530 			server.clients.remove(this);
531 			server.log("IRC: %s disconnecting: %s".format(remoteAddress, reason));
532 		}
533 
534 		void checkRegistration()
535 		{
536 			assert(!registered);
537 
538 			if (nickname.length && username.length)
539 			{
540 				if (server.password && password != server.password)
541 				{
542 					password = null;
543 					return sendReply(Reply.ERR_PASSWDMISMATCH, "Password incorrect");
544 				}
545 				if (nickname.normalized in server.nicknames)
546 				{
547 					scope(exit) nickname = null;
548 					return sendReply(Reply.ERR_NICKNAMEINUSE, nickname, "Nickname is already in use");
549 				}
550 				if (!nickname.match(server.nicknameValidationPattern))
551 				{
552 					scope(exit) nickname = null;
553 					return sendReply(Reply.ERR_ERRONEUSNICKNAME, nickname, "Erroneous nickname");
554 				}
555 				if (!username.match(`[a-zA-Z]+`))
556 					return disconnect("Invalid username");
557 
558 				// All OK
559 				register();
560 			}
561 		}
562 
563 		void register()
564 		{
565 			if (!identified)
566 				username = "~" ~ username;
567 			update();
568 
569 			registered = true;
570 			server.nicknames[nickname.normalized] = this;
571 			auto userCount = server.nicknames.length;
572 			if (server.maxUsers < userCount)
573 				server.maxUsers = userCount;
574 			sendReply(Reply.RPL_WELCOME      , "Welcome, %s!".format(nickname));
575 			sendReply(Reply.RPL_YOURHOST     , "Your host is %s, running %s".format(server.hostname, server.serverVersion));
576 			sendReply(Reply.RPL_CREATED      , "This server was created %s".format(server.creationTime));
577 			sendReply(Reply.RPL_MYINFO       , server.hostname, server.serverVersion, UserModes.supported, ChannelModes.supported, null);
578 			sendReply(cast(Reply)         005, server.capabilities ~ ["are supported by this server"]);
579 			sendLusers();
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 		void sendLusers()
637 		{
638 			sendReply(Reply.RPL_LUSERCLIENT  , "There are %d users and %d services on %d servers".format(
639 				server.clients.byKey.filter!(client => cast(NetworkClient)client && client.registered).walkLength,
640 				server.clients.byKey.filter!(client => !cast(NetworkClient)client).walkLength,
641 				1,
642 			));
643 			sendReply(Reply.RPL_LUSEROP      , server.clients.byKey.filter!(client => client.modes.flags['o']).walkLength.text, "IRC Operators online");
644 			sendReply(Reply.RPL_LUSERUNKNOWN , server.clients.byKey.filter!(client => cast(NetworkClient)client && !client.registered).walkLength.text, "unknown connection(s)");
645 			sendReply(Reply.RPL_LUSERCHANNELS, server.channels.length.text, "channels formed");
646 			sendReply(Reply.RPL_LUSERME      , "I have %d clients and %d servers".format(
647 				server.clients.byKey.filter!(client => client.registered).walkLength,
648 				0,
649 			));
650 		}
651 
652 		bool mayJoin(string name)
653 		{
654 			return true;
655 		}
656 
657 		void join(Channel channel)
658 		{
659 			channel.add(this);
660 			foreach (member; channel.members)
661 				member.client.sendCommand(this, "JOIN", channel.name);
662 			sendTopic(channel);
663 			sendNames(channel);
664 			auto pmember = nickname.normalized in channel.members;
665 			// Sync OPER status with (initial) channel op status
666 			if (server.staticChannels || modes.flags['o'])
667 				setChannelMode(channel, nickname, Channel.Member.Mode.op, modes.flags['o']);
668 		}
669 
670 		// For server-imposed mode changes.
671 		void setChannelMode(Channel channel, string nickname, Channel.Member.Mode mode, bool value)
672 		{
673 			auto pmember = nickname.normalized in channel.members;
674 			if (pmember.modeSet(mode) == value)
675 				return;
676 
677 			pmember.setMode(mode, value);
678 			auto c = ChannelModes.memberModeChars[mode];
679 			foreach (member; channel.members)
680 				member.client.sendCommand(server.hostname, "MODE", channel.name, [value ? '+' : '-', c], nickname, null);
681 			server.channelChanged(channel);
682 		}
683 
684 		void part(Channel channel, string reason=null)
685 		{
686 			foreach (member; channel.members)
687 				member.client.sendCommand(this, "PART", channel.name, reason);
688 			channel.remove(this);
689 		}
690 
691 		void sendToChannel(Channel channel, string command, string message)
692 		{
693 			foreach (member; channel.members)
694 				if (member.client !is this)
695 					member.client.sendCommand(this, command, channel.name, message);
696 		}
697 
698 		void sendToClient(Client client, string command, string message)
699 		{
700 			client.sendCommand(this, command, client.nickname, message);
701 		}
702 
703 		void sendTopic(Channel channel)
704 		{
705 			if (channel.topic)
706 				sendReply(Reply.RPL_TOPIC, channel.name, channel.topic);
707 			else
708 				sendReply(Reply.RPL_NOTOPIC, channel.name, "No topic is set");
709 		}
710 
711 		void sendNames(Channel channel)
712 		{
713 			foreach (chunk; channel.members.values.chunks(10)) // can't use byValue - https://issues.dlang.org/show_bug.cgi?id=11761
714 				sendReply(Reply.RPL_NAMREPLY, channel.modes.flags['s'] ? "@" : channel.modes.flags['p'] ? "*" : "=", channel.name, chunk.map!q{a.displayName}.join(" "));
715 			sendReply(Reply.RPL_ENDOFNAMES, channel.name, "End of /NAMES list");
716 		}
717 
718 		/// For LIST
719 		Channel[] getChannelList()
720 		{
721 			return server.channels.values;
722 		}
723 
724 		void sendChannelModes(Channel channel)
725 		{
726 			string modes = "+";
727 			string[] modeParams;
728 			foreach (c; 0..char.max)
729 				final switch (ChannelModes.modeTypes[c])
730 				{
731 					case ChannelModes.Type.none:
732 					case ChannelModes.Type.member:
733 						break;
734 					case ChannelModes.Type.mask:
735 						// sent after RPL_CHANNELMODEIS
736 						break;
737 					case ChannelModes.Type.flag:
738 						if (channel.modes.flags[c])
739 							modes ~= c;
740 						break;
741 					case ChannelModes.Type.str:
742 						if (channel.modes.strings[c])
743 						{
744 							modes ~= c;
745 							modeParams ~= channel.modes.strings[c];
746 						}
747 						break;
748 					case ChannelModes.Type.number:
749 						if (channel.modes.numbers[c])
750 						{
751 							modes ~= c;
752 							modeParams ~= channel.modes.numbers[c].text;
753 						}
754 						break;
755 				}
756 			sendReply(Reply.RPL_CHANNELMODEIS, channel.name, ([modes] ~ modeParams).join(" "), null);
757 		}
758 
759 		void sendChannelModeMasks(Channel channel, char mode)
760 		{
761 			switch (mode)
762 			{
763 				case 'b':
764 					sendChannelMaskList(channel, channel.modes.masks[mode], Reply.RPL_BANLIST, Reply.RPL_ENDOFBANLIST, "End of channel ban list");
765 					break;
766 				default:
767 					assert(false);
768 			}
769 		}
770 
771 		void sendChannelMaskList(Channel channel, string[] masks, Reply lineReply, Reply endReply, string endText)
772 		{
773 			foreach (mask; masks)
774 				sendReply(lineReply, channel.name, mask, null);
775 			sendReply(endReply, channel.name, endText);
776 		}
777 
778 		void setChannelTopic(Channel channel, string topic)
779 		{
780 			channel.topic = topic;
781 			foreach (ref member; channel.members)
782 				member.client.sendCommand(this, "TOPIC", channel.name, topic);
783 			server.channelChanged(channel);
784 		}
785 
786 		void setChannelModes(Channel channel, string[] modes)
787 		{
788 			auto pself = nickname.normalized in channel.members;
789 			bool op = (pself.modes & Channel.Member.Modes.op) != 0;
790 
791 			string[2] effectedChars;
792 			string[][2] effectedParams;
793 
794 			scope(exit) // Broadcast effected options
795 			{
796 				string[] parameters;
797 				foreach (adding; 0..2)
798 					if (effectedChars[adding].length)
799 						parameters ~= [(adding ? "+" : "-") ~ effectedChars[adding]] ~ effectedParams[adding];
800 				if (parameters.length)
801 				{
802 					assert(op);
803 					parameters = ["MODE", channel.name] ~ parameters ~ [string.init];
804 					foreach (ref member; channel.members)
805 						member.client.sendCommand(this, parameters);
806 				}
807 			}
808 
809 			while (modes.length)
810 			{
811 				auto chars = modes.shift;
812 
813 				bool adding = true;
814 				foreach (c; chars)
815 					if (c == '+')
816 						adding = true;
817 					else
818 					if (c == '-')
819 						adding = false;
820 					else
821 					final switch (ChannelModes.modeTypes[c])
822 					{
823 						case ChannelModes.Type.none:
824 							sendReply(Reply.ERR_UNKNOWNMODE, [c], "is unknown mode char to me for %s".format(channel.name));
825 							break;
826 						case ChannelModes.Type.flag:
827 							if (!op) return sendReply(Reply.ERR_CHANOPRIVSNEEDED, channel.name, "You're not channel operator");
828 							if (adding != channel.modes.flags[c])
829 							{
830 								channel.modes.flags[c] = adding;
831 								effectedChars[adding] ~= c;
832 							}
833 							break;
834 						case ChannelModes.Type.member:
835 						{
836 							if (!op) return sendReply(Reply.ERR_CHANOPRIVSNEEDED, channel.name, "You're not channel operator");
837 							if (!modes.length)
838 								{ sendReply(Reply.ERR_NEEDMOREPARAMS, "MODE", "Not enough parameters"); continue; }
839 							auto memberName = modes.shift;
840 							auto pmember = memberName.normalized in channel.members;
841 							if (!pmember)
842 								{ sendReply(Reply.ERR_USERNOTINCHANNEL, memberName, channel.name, "They aren't on that channel"); continue; }
843 							auto mode = ChannelModes.memberModes[c];
844 							if (pmember.modeSet(mode) != adding)
845 							{
846 								pmember.setMode(mode, adding);
847 								effectedChars[adding] ~= c;
848 								effectedParams[adding] ~= memberName;
849 							}
850 							break;
851 						}
852 						case ChannelModes.Type.mask:
853 						{
854 							if (!modes.length)
855 								return sendChannelModeMasks(channel, c);
856 							if (!op) return sendReply(Reply.ERR_CHANOPRIVSNEEDED, channel.name, "You're not channel operator");
857 							auto mask = modes.shift;
858 							if (adding)
859 							{
860 								if (channel.modes.masks[c].canFind(mask))
861 									continue;
862 								channel.modes.masks[c] ~= mask;
863 							}
864 							else
865 							{
866 								auto index = channel.modes.masks[c].countUntil(mask);
867 								if (index < 0)
868 									continue;
869 								channel.modes.masks[c] = channel.modes.masks[c][0..index] ~ channel.modes.masks[c][index+1..$];
870 							}
871 							effectedChars[adding] ~= c;
872 							effectedParams[adding] ~= mask;
873 							break;
874 						}
875 						case ChannelModes.Type.str:
876 							if (!op) return sendReply(Reply.ERR_CHANOPRIVSNEEDED, channel.name, "You're not channel operator");
877 							if (adding)
878 							{
879 								if (!modes.length)
880 									{ sendReply(Reply.ERR_NEEDMOREPARAMS, "MODE", "Not enough parameters"); continue; }
881 								auto str = modes.shift;
882 								if (channel.modes.strings[c] == str)
883 									continue;
884 								channel.modes.strings[c] = str;
885 								effectedChars[adding] ~= c;
886 								effectedParams[adding] ~= str;
887 							}
888 							else
889 							{
890 								if (!channel.modes.strings[c])
891 									continue;
892 								channel.modes.strings[c] = null;
893 								effectedChars[adding] ~= c;
894 							}
895 							break;
896 						case ChannelModes.Type.number:
897 							if (!op) return sendReply(Reply.ERR_CHANOPRIVSNEEDED, channel.name, "You're not channel operator");
898 							if (adding)
899 							{
900 								if (!modes.length)
901 									{ sendReply(Reply.ERR_NEEDMOREPARAMS, "MODE", "Not enough parameters"); continue; }
902 								auto numText = modes.shift;
903 								auto num = numText.to!long;
904 								if (channel.modes.numbers[c] == num)
905 									continue;
906 								channel.modes.numbers[c] = num;
907 								effectedChars[adding] ~= c;
908 								effectedParams[adding] ~= numText;
909 							}
910 							else
911 							{
912 								if (!channel.modes.numbers[c])
913 									continue;
914 								channel.modes.numbers[c] = 0;
915 								effectedChars[adding] ~= c;
916 							}
917 							break;
918 					}
919 			}
920 			server.channelChanged(channel);
921 		}
922 
923 		void setUserModes(string[] modes)
924 		{
925 			while (modes.length)
926 			{
927 				auto chars = modes.shift;
928 
929 				bool adding = true;
930 				foreach (c; chars)
931 					if (c == '+')
932 						adding = true;
933 					else
934 					if (c == '-')
935 						adding = false;
936 					else
937 					final switch (UserModes.modeTypes[c])
938 					{
939 						case UserModes.Type.none:
940 							sendReply(Reply.ERR_UMODEUNKNOWNFLAG, "Unknown MODE flag");
941 							break;
942 						case UserModes.Type.flag:
943 							if (UserModes.isSettable[c])
944 								this.modes.flags[c] = adding;
945 							break;
946 					}
947 			}
948 		}
949 
950 		void sendUserModes(Client client)
951 		{
952 			string modeString = "+";
953 			foreach (char c, on; modes.flags)
954 				if (on)
955 					modeString ~= c;
956 			return sendReply(Reply.RPL_UMODEIS, modeString, null);
957 		}
958 
959 		void sendCommand(Client from, string[] parameters...)
960 		{
961 			return sendCommand(from.prefixAsVisibleTo(this), parameters);
962 		}
963 
964 		void sendCommand(string from, string[] parameters...)
965 		{
966 			assert(parameters.length, "At least one parameter expected");
967 			foreach (parameter; parameters[0..$-1])
968 				assert(parameter.length && parameter[0] != ':' && parameter.indexOf(' ') < 0, "Invalid parameter: " ~ parameter);
969 			if (parameters[$-1] is null)
970 				parameters = parameters[0..$-1];
971 			else
972 				parameters = parameters[0..$-1] ~ [":" ~ parameters[$-1]];
973 			auto line = ":%s %-(%s %)".format(from, parameters);
974 			sendLine(line);
975 		}
976 
977 		void sendReply(Reply reply, string[] parameters...)
978 		{
979 			return sendReply("%03d".format(reply), parameters);
980 		}
981 
982 		void sendReply(string command, string[] parameters...)
983 		{
984 			return sendCommand(server.hostname, [command, nickname ? nickname : "*"] ~ parameters);
985 		}
986 
987 		void sendServerNotice(string text)
988 		{
989 			sendReply("NOTICE", "*** Notice -- " ~ text);
990 		}
991 
992 		void sendLine(string line)
993 		{
994 			if (encoder) line = encoder(line);
995 			connSendLine(line);
996 		}
997 
998 		abstract bool connConnected();
999 		abstract void connSendLine(string line);
1000 		abstract void connDisconnect(string reason);
1001 	}
1002 
1003 	static class NetworkClient : Client
1004 	{
1005 	protected:
1006 		IrcConnection conn;
1007 
1008 		this(IrcServer server, IrcConnection incoming, Address remoteAddress)
1009 		{
1010 			super(server, remoteAddress);
1011 
1012 			conn = incoming;
1013 			conn.handleReadLine = &onReadLine;
1014 			conn.handleInactivity = &onInactivity;
1015 			conn.handleDisconnect = &onDisconnect;
1016 		}
1017 
1018 		override bool connConnected()
1019 		{
1020 			return conn.state == ConnectionState.connected;
1021 		}
1022 
1023 		override void connSendLine(string line)
1024 		{
1025 			conn.send(line);
1026 		}
1027 
1028 		override void connDisconnect(string reason)
1029 		{
1030 			conn.disconnect(reason);
1031 		}
1032 	}
1033 
1034 	HashSet!Client clients; /// All clients
1035 	Client[string] nicknames; /// Registered clients only
1036 
1037 	/// Statistics
1038 	ulong maxUsers, totalConnections;
1039 
1040 	final class Channel
1041 	{
1042 		string name;
1043 		string topic;
1044 
1045 		Modes modes;
1046 
1047 		struct Member
1048 		{
1049 			enum Mode
1050 			{
1051 				op,
1052 				voice,
1053 				max
1054 			}
1055 
1056 			enum Modes
1057 			{
1058 				none  = 0,
1059 				op    = 1 << Mode.op,
1060 				voice = 1 << Mode.voice,
1061 
1062 				bypassM = op | voice, // which modes bypass +m
1063 			}
1064 
1065 			Client client;
1066 			Modes modes;
1067 
1068 			bool modeSet(Mode mode) { return (modes & (1 << mode)) != 0; }
1069 			void setMode(Mode mode, bool value)
1070 			{
1071 				auto modeMask = 1 << mode;
1072 				if (value)
1073 					modes |= modeMask;
1074 				else
1075 					modes &= ~modeMask;
1076 			}
1077 
1078 			string modeChar()
1079 			{
1080 				foreach (mode; Mode.init..Mode.max)
1081 					if ((1 << mode) & modes)
1082 						return [ChannelModes.memberModePrefixes[mode]];
1083 				return "";
1084 			}
1085 			string displayName() { return modeChar ~ client.nickname; }
1086 		}
1087 
1088 		Member[string] members;
1089 
1090 		this(string name)
1091 		{
1092 			this.name = name;
1093 			modes.flags['t'] = modes.flags['n'] = true;
1094 		}
1095 
1096 		void add(Client client)
1097 		{
1098 			auto modes = staticChannels || members.length ? Member.Modes.none : Member.Modes.op;
1099 			members[client.nickname.normalized] = Member(client, modes);
1100 		}
1101 
1102 		void remove(Client client)
1103 		{
1104 			members.remove(client.nickname.normalized);
1105 			if (!staticChannels && !members.length && !modes.flags['P'])
1106 				channels.remove(name.normalized);
1107 		}
1108 	}
1109 
1110 	Channel[string] channels;
1111 
1112 	TcpServer conn;
1113 
1114 	this()
1115 	{
1116 		conn = new TcpServer;
1117 		conn.handleAccept = &onAccept;
1118 
1119 		hostname = Socket.hostName;
1120 		creationTime = Clock.currTime;
1121 	}
1122 
1123 	ushort listen(ushort port=6667, string addr = null)
1124 	{
1125 		port = conn.listen(port, addr);
1126 		return port;
1127 	}
1128 
1129 	Channel createChannel(string name)
1130 	{
1131 		return channels[name.normalized] = new Channel(name);
1132 	}
1133 
1134 	void close(string reason)
1135 	{
1136 		conn.close();
1137 		foreach (client; clients.keys)
1138 			client.disconnect("Server is shutting down" ~ (reason.length ? ": " ~ reason : ""));
1139 	}
1140 
1141 protected:
1142 	Client createClient(TcpConnection incoming)
1143 	{
1144 		return new NetworkClient(this, new IrcConnection(incoming), incoming.remoteAddress);
1145 	}
1146 
1147 	void onAccept(TcpConnection incoming)
1148 	{
1149 		createClient(incoming);
1150 		totalConnections++;
1151 	}
1152 
1153 	Client[string] allClientsInChannels(Channel[] channels)
1154 	{
1155 		Client[string] result;
1156 		foreach (channel; channels)
1157 			foreach (ref member; channel.members)
1158 				result[member.client.nickname.normalized] = member.client;
1159 		return result;
1160 	}
1161 
1162 	/// Clients who can see the given client (are in the same channer).
1163 	/// Includes the target client himself.
1164 	Client[string] whoCanSee(Client who)
1165 	{
1166 		auto clients = allClientsInChannels(who.getJoinedChannels());
1167 		clients[who.nickname.normalized] = who;
1168 		return clients;
1169 	}
1170 
1171 	bool isChannelName(string target)
1172 	{
1173 		foreach (prefix; chanTypes)
1174 			if (target.startsWith(prefix))
1175 				return true;
1176 		return false;
1177 	}
1178 
1179 	string[] capabilities()
1180 	{
1181 		string[] result;
1182 		result ~= "PREFIX=(%s)%s".format(ChannelModes.memberModeChars, ChannelModes.memberModePrefixes);
1183 		result ~= "CHANTYPES=" ~ chanTypes;
1184 		result ~= "CHANMODES=%-(%s,%)".format(
1185 			[ChannelModes.Type.mask, ChannelModes.Type.str, ChannelModes.Type.number, ChannelModes.Type.flag].map!(type => ChannelModes.byType(type))
1186 		);
1187 		if (network)
1188 			result ~= "NETWORK=" ~ network;
1189 		result ~= "CASEMAPPING=rfc1459";
1190 		result ~= "NICKLEN=" ~ text(nicknameMaxLength);
1191 		return result;
1192 	}
1193 
1194 	/// Persistence hook
1195 	void channelChanged(Channel channel)
1196 	{
1197 	}
1198 }
1199 
1200 bool maskMatch(string subject, string mask)
1201 {
1202 	import std.path;
1203 	return globMatch!(CaseSensitive.no)(subject, mask);
1204 }
1205 
1206 
1207 string safeHostname(string s)
1208 {
1209 	assert(s.length);
1210 	if (s[0] == ':')
1211 		s = '0' ~ s;
1212 	return s;
1213 }
1214 
1215 alias rfc1459toUpper normalized;
1216 
1217 string[] ircSplit(string line)
1218 {
1219 	auto colon = line.indexOf(":");
1220 	if (colon < 0)
1221 		return line.split;
1222 	else
1223 		return line[0..colon].strip.split ~ [line[colon+1..$]];
1224 }
1225 
1226 struct Modes
1227 {
1228 	bool[char.max] flags;
1229 	string[char.max] strings;
1230 	long[char.max] numbers;
1231 	string[][char.max] masks;
1232 }
1233 
1234 mixin template CommonModes()
1235 {
1236 //static immutable:
1237 	Type[char.max] modeTypes;
1238 	string supported()       pure { return modeTypes.length.iota.filter!(m => modeTypes[m]        ).map!(m => cast(char)m).array; }
1239 	string byType(Type type) pure { return modeTypes.length.iota.filter!(m => modeTypes[m] == type).map!(m => cast(char)m).array; }
1240 }
1241 
1242 struct ChannelModes
1243 {
1244 static immutable:
1245 	enum Type { none, flag, member, mask, str, number }
1246 	mixin CommonModes;
1247 	IrcServer.Channel.Member.Mode[char.max] memberModes;
1248 	char[IrcServer.Channel.Member.Mode.max] memberModeChars, memberModePrefixes;
1249 
1250 	shared static this()
1251 	{
1252 		foreach (c; "ntpsP")
1253 			modeTypes[c] = Type.flag;
1254 		foreach (c; "ov")
1255 			modeTypes[c] = Type.member;
1256 		foreach (c; "b")
1257 			modeTypes[c] = Type.mask;
1258 		foreach (c; "k")
1259 			modeTypes[c] = Type.str;
1260 
1261 		memberModes['o'] = IrcServer.Channel.Member.Mode.op;
1262 		memberModes['v'] = IrcServer.Channel.Member.Mode.voice;
1263 
1264 		memberModeChars    = "ov";
1265 		memberModePrefixes = "@+";
1266 	}
1267 }
1268 
1269 struct UserModes
1270 {
1271 static immutable:
1272 	enum Type { none, flag }
1273 	mixin CommonModes;
1274 	bool[char.max] isSettable;
1275 
1276 	shared static this()
1277 	{
1278 		foreach (c; "io")
1279 			modeTypes[c] = Type.flag;
1280 		foreach (c; "i")
1281 			isSettable[c] = true;
1282 	}
1283 }