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