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