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 }