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