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