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