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