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