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