├── README ├── cert.pem ├── classes ├── channel.class.php ├── ircd.class.php ├── mode.class.php └── user.class.php ├── config.ini ├── include └── modes.php ├── ircd ├── motd.txt └── opers.ini /README: -------------------------------------------------------------------------------- 1 | phpircd v0.4.15 2 | this readme is a changelog lololol 3 | 4 | 06/27/2012 - 5 | * Added error code 464: ERR_PASSWDMISMATCH 6 | * Added error code 491: ERR_NOOPERHOST 7 | * Added OPER command functionality, minus userhost-checking and mode stuff. 8 | * Changed opers.ini: 'pass-enc' previously 'enc' 9 | * Removed some lingering single-quites in ircd::error() 10 | 11 | 01/13/2012 - 12 | * Fixed NAMES not adding prefixes correctly. 13 | * Fixed "Client Exited" not being shown when conditions are right. 14 | * Hushed ircd::write()'s fwrite(). 15 | * Added bitwise permissions system. 16 | - Added Channel::hasPrivs() method. 17 | - Added Mode::CHANNEL_* constants. 18 | - Added some hookpoint checks. 19 | * Fixed new channel create giving user +O when they should have +o. 20 | * Changed Channel::getUserPrefix() to use new Mode::$prefix. 21 | * Removed Channel::is[Qop|Aop|Op|Hop](). 22 | * Changed Channel::hasVoice() to use Mode::CHANNEL_VOICE privilege. 23 | * Added error 499 ERR_CHANOWNPRIVNEEDED. 24 | * Version is now 0.4.14 25 | 26 | 01/11/2012 - 27 | * Modes now have one of five types: A (list), B (requires parameter for set/unset), C (requires parameter for set), D (toggle), or P (list, special). 28 | * Converted all channel modes to the new format and added usermodes +is. 29 | * Re-wrote/created user::setModes(), user::setMode(), channel::setModes(), and channel::setMode(). setMode() sets ONE mode, setModes() is passed the mask. 30 | * Fixed issue with channel::getModes(). 31 | * Added user::getModes(). 32 | * Changed all instances of user::setMode() to user::setModes(), minus the one in user::setModes(). 33 | * Added ircd::isupport(). Handles creating and sending of the 005 RPL_ISUPPORT message. 34 | * MODE now works almost to RFC. You can check channel modes and your own user mode, as well as set your own user mode and channel modes (assuming privs). 35 | * Fixed an issue causing hostmasking to be applied twice upon connection. 36 | * Made 004 RPL_MYINFO list actual usermodes available. 37 | * Added missing config options. 38 | * Version is now 0.4.11 39 | (edit 1) 40 | * Added all usermodes that will be used. 41 | * Version is now 0.4.12 42 | (edit 2) 43 | * Fixed hostmasking- now masks IPv4 addresses, short addresses like slowbro.org, and long addresses like node01.phx.somecompany.com differently. 44 | * Fixed ircd::__destruct() still using a socket function. 45 | * Added ircd::$ipv4Regex and replaced faulty IPv4 regex in ircd::createSocket(). 46 | * Fixed Channel::hasMode(). 47 | * Fixed TYPE_A and TYPE_P modes not ignoring unsetting values that aren't set. 48 | * Version is now 0.4.13 49 | 50 | 01/10/2012 - 51 | * Cleaned up channel::setModes() a bit. 52 | * Added 'set' and 'unset' hookpoints for channel modes. 53 | * Fixed how modes are announced when doing multiple actions (like +oo-h slo slo2 slo) 54 | * Changed hook parameters. Now takes one array passed by ref. Can contain 'user','chan','extra' at the moment. 55 | * Added error code 482, ERR_CHANOPRIVSNEEDED, and 485, ERR_UNIQOPRIVSNEEDED. 56 | (edit 1) 57 | * Added hostmasking 58 | * Added user mode x and connect hook 59 | * Added hostmask* stuff in config.ini 60 | 61 | 01/09/2012 - 62 | * Slowly working on converting modes to class format 63 | * Added channel::is[Owner|Aop|Op|Hop](), channel::hasVoice(), channel::getUserPrefix() 64 | * Prefix is no longer stored in channel::$users, and is instead calculated by channel::getUserPrefix() 65 | * Made channel modes +qaohbvz work-ish. 66 | * Added channel::isBanned() debug shell function. 67 | * Added channel:nick() to update nicks in any array modes like +qaohv. 68 | * Added checking for unknown mode chars. 69 | 70 | 01/08/2012 - 71 | * Condense user and channel modes into one file. 72 | * Add new Mode class. 73 | * Add hookpoints for 'connect', 'privmsg', and 'join'. 74 | * Made +m, +R, and +v channel modes, as well as +z user mode, work-ish. 75 | * Fixed mis-spelling of User::disconnect() (was User::diconnect()). 76 | * Fixed ircd::$rnRegex to include the period as an allowed character. 77 | * Added User::hasMode() 78 | * Added testing User::setMode(). Will be fixed in a future push. 79 | * Fixed LUSERS issues. 80 | * Added $override arg to ircd::error() to override messages. 81 | * Fixed (read: silenced) some complaining in Channel::setMode() when un-setting modes. 82 | * Thanks to PwnFlakes for the help testing. 83 | 84 | 01/04-05/2012 - 85 | * Fixed MODE #channel outputting array modes. 86 | * LUSERS now reports half-decent information 87 | * Fixed issue #2. The server now doesn't assume line-buffering on the client side. 88 | * Added User::readBuffer 89 | 90 | 11/08/2011 - 91 | * Fixed user disconnect- using socket function on a stream. 92 | * Fixed user QUIT not being acknowledged due to not calling User::writeBuffer() before disconnect. 93 | 94 | 7/24/2011 - 95 | * Half of MODE works- at least the mode-setting part. Only for channels, too. 96 | * WHO now works. 97 | * Fixed bug where users attempting to join a channel they were already joined to was not ignored. 98 | 99 | 7/22/2011 - 100 | * SSL works now. 101 | * Switch to all stream_* functions. No more socket_*. 102 | * Probably going to do some fancy branching things in the future. 103 | (edit 1) 104 | * Work on channel modes. 105 | * MODE for channels works now- will display modes and created time. 106 | * MODE is very granular right now and only works for modes not requiring extra stuff likl +l or +L. 107 | * MODE still does nothing for user modes. Need to find a way to set mode related stuff- such as restricted (+aoO) and whatnot. 108 | (edit 2) 109 | * Unknown channel mode now throws error. 110 | * New file for storing channel modes and properies. 111 | * Channel mode missing extra (like +l, +L) is ignored. 112 | * Going to do regex checking for extra. 113 | (edit 3) 114 | * Added WHO support for channels. 115 | 116 | 7/21/2011 - 117 | * Fixed PRIVMSG when PMing (Issue #1). 118 | * Removed extraneous (and bugged) strpos in file 'ircd' (Issue #3). 119 | * Fixed posible issue when writing to sockets- now uses user::$buffer and user::writeBuffer() (Issue #4). 120 | * More core::* to ircd. 121 | * core::init is now ircd::__construct. 122 | * Moved on-die code to ircd::__destruct. 123 | * Trying to clean up the main file as much as possible. 124 | 125 | 7/20/2011 - 126 | * Cleaned up the main file (ircd) a bit- compiled an un-needed foreach into another, amongst other things. 127 | * Fixed checking for 'Client Exited' socket status when quitting spontaneously. 128 | * Fixed possible but in regex (sytax error?). 129 | * Using ircd::$allowed instead of ircd::$forbidden. 130 | * Cleaned up some stuff in the ircd class 131 | * Added ircd::nickInUse() for better nick checking. 132 | * Did away with core::$_nicks. 133 | * I don't remember what else :( 134 | (edit 1) 135 | * Fixed some bugs with PHP 5.1.6 - Regex escaping and finding users with keys (not preserved) 136 | * Added ircd::getUserBySocket() 137 | (edit 2) 138 | * Removed double read-loop in file 'ircd'- now all processed by 1. 139 | * Moved accept code to ircd::accept(). 140 | * Fixed NAMES- was trying to access Channel::$nick which does not exist. 141 | * Various bugfixes. 142 | 143 | 7/19/2011 - 144 | * Thanks again to cedr and Kuro for testing. 145 | * QUIT now works as expected. Need to tweak it for ping timeouts/etc. 146 | * TOPIC now properly announces. 147 | * NAMES fixed again. 148 | * More later! 149 | (edit 1) 150 | * Added fix for ipv6-enabled boxes with ipv4 addresses (adds ::ffff:). 151 | * Changed config.ini to all double-qoutes, as the single quotes broke everything on some boxes. 152 | * Changed core::close() to ircd::quit() when server full. 153 | (edit 2) 154 | * Fixed issue with NICK where leading colon was not stripped. 155 | * Added utility method ircd::stripColon 156 | * Added utility method ircd::checkRealName 157 | * Object-ized users. Each user is now stored as an object so it's cleaner (and easier) to do things. Will move methods later. 158 | * Added messages for being disconnected on Ping Timeout/etc. 159 | * New versioning concept: (major).(minor).(build). Still in major version 0 as we're not even a complete IRCd yet. Minor will change whenever I feel like flexing my version-changing muscle. 160 | (edit 3) 161 | * Thanks to Pedrobear for some pointers. 162 | * Class ircd now extends core. No more `global $core;'! 163 | * Added Channel class and methods. 164 | * Added methods to User class. 165 | * Almost all sending is now done through User::send() or Channel::send(). 166 | * Channel::send(msg[, excl]) accepts optional 'excl'- a User object to exclude in sending (as in PRIVMSG). 167 | 168 | 7/18/2011 - 169 | * Thanks for all the help from #Frantech: cedr, InsDel, Corbin, and others! 170 | * NAMES now announces on join and tracks nick changes properly 171 | * Fixed part of TOPIC having a wrong variable name, however TOPIC is still broken. 172 | * PART now announces, and passes through custom PART mesages 173 | * Some work on QUIT, however it's still hopelessly broken as well. 174 | * NICK now announces properly to the whole channel, instead of announcing to you once for everyone in the channel (oops). 175 | * NICK now ignores you when "changing" your nick to what it already is. 176 | * Removed NAMESX from message 005 since we don't actually support it, yet. 177 | 178 | 4/18/2011 - 179 | * Bored in Java class 180 | * Fixed some weird spacing 181 | * Added die() for config parse failure 182 | * Fixed core::write(): was turncating at 513, not 512, characters. Derp. 183 | * Hopefully I can make some headway on this soon 184 | 185 | 7/20/2010 - 186 | * JOIN progress. Error checking, you can now `join' a channel, +k doesn't work yet, you can't chat on the 187 | channel, no topic, no names etc.. but it's progress. 40+ hour work weeks have me a bit tied up so I'm 188 | doing what I can with the time I have available. 189 | (edit 1) 190 | * Added client field for ip address. 191 | * JOIN now creates the channel in $core->_channels if it doesn't exist. 192 | * TOPIC added, minus support for chaning the topic (so far). 193 | * PRIVMSG now supports sending messages to channels (YAY). Bug needs to be fixed: users remain in the 194 | channel array when disconnected, casuing errors galore. 195 | (edit 2) 196 | * Fixed issue with NICK where users were told their nick was erroneous if it was too long (instead of just 197 | shortening it for them). 198 | * NAMES support added. Splits messages that are >510 characters. 199 | * $core->write() automatically turncates strings at 512 characters (including \r\n) per rfc requirements. 200 | 201 | 7/17/2010 - 202 | * PRIVMSG works for inter-user communication on the same server only. 203 | * $core->_nicks array for keeping track of in-use nicks more efficiently. 204 | * More error codes added. 205 | * Fixed the PING timeout error for reals this time. 206 | * Typo fixes. 207 | (edit 1) 208 | * Fixed missing leading colon on PRIVMSG commands. 209 | * Made last activity (lastpong) update on every command, valid or not. 210 | * Fixed asthetic error where command errors were not returned uppercase. 211 | 212 | 7/13/2010 - 213 | * NICK command now works, minus sending it to channels, seeing as JOIN hasn't been made yet. 214 | * QUIT command also works, with the same current caveats of NICK. 215 | * Fixed space in 005 message after PREFIX 216 | * Fixed PING issue where command were not being treated as activity. 217 | * Added more error messages, including ERR_NOTREGISTERED, ERR_UNKNOWNCOMMAND and ERR_NICKNAMEINUSE. 218 | * Version is now 0.2b because I have no scheme to the version numbers and like to change them frequently. 219 | 220 | 7/12/2010 - 221 | * Sorta-finished welcome sequence. MOTD displays, if there is one (motd.txt), if not it throws ERR_NOMOTD. 222 | * Working on NOTICE AUTH to make the welcome complete. Not implementing ident, for reasoning read here: 223 | http://web.archive.org/web/20050828200336/http://www.clock.org/~fair/opinion/identd.html 224 | * More configuration options added to config.ini, not implemented yet. 225 | * opers.ini added with example syntax, though also not implemented yet. 226 | * fixed an error with the socket becoming unavailable for re-use if the program is inturrupted 227 | mid-communication with a client or shut down with clients still connected. 228 | (edit 1) 229 | * PING/PONG works now. Or at least the important parts (making sure the user still responds to PING). 230 | * Users can also now PING the server. 231 | * Pingfreq and pingout variables in the conf now utilized. 232 | * Fixed a bug where sending multiple commands in one message (i.e. NICK slowbro\r\nUSER h h h :Real Name) 233 | would cause immense amounts of errors and headaches. 234 | * Fixed MOTD display to remove extra newlines. 235 | * Fixed post-registration USER command; it now throws ERR_ALREADYREGISTERED. 236 | * NOTICE AUTH functionality complete, and working 100%. 237 | * Fixed an bug causing external networks to not be able to remain connected to the server due to using 238 | PHP_NORMAL_READ instead of PHP_BINARY_READ on the socket_read() function. 239 | * Version is now 0.2a 240 | 241 | 7/11/2010 - 242 | * Fixed security hole (already) in ircd.class.php that would allow clients to call functions they 243 | weren't supposed to such as newConnection(). 244 | * Fixed a performance issue with socket_select() and its if bracket. 245 | 246 | 7/10/2010 - 247 | * Error handling function introduced. 248 | (edit 1) 249 | * Welcome message(s) progress. Need to figure out the 005 numeric which is now RPL_ISUPPORT, probably 250 | going to just fill it with nonsense for now until the server is more complete and it is needed. 251 | * More error checking added on initial connect, check for parameter amount, and checks params against 252 | some regex. 253 | * Dicks everywhere. 254 | 255 | 7/9/2010 - 256 | * Basically a plain socket server at the moment. 257 | (edit 1) 258 | * Babysteps towards actual irc stuff. Closes link on ping timeout, working on initial connect sequence. 259 | * Expect ircd.class.php rolled out within a day or so. 260 | (edit 2) 261 | * Initial ircd class release. 262 | * NICK error checking working for initial connect. USER next. 263 | * Still need to legitimately NOTICE AUTH instead of just sending that junk. 264 | 265 | 7/8/2010 - 266 | * Initial alpha-absolutely-nothing-is-done-at-all release. 267 | -------------------------------------------------------------------------------- /cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAmkCkNcNEx60Cl0o9xZlMzN0aR+sf2UItak7fHm7yYkRJNlRN 3 | zMyu9it95e5m3Hd4eE2MXiedUtlWWTlyd++ph03s8iPtxrTG6aQ2iB0p2Tt4wdnk 4 | hXfcJQXof13V8KSgYxegUit8PyO+CWMnJ7r5qQ5ocGopAlPiyRmPG7xBwXcgS8zs 5 | RqDEVkVEf5Lv/4gYaukctgDpkMhw9NWZE/0b2eNWLbSwo0tfpfSyy+z8WTyQ49XA 6 | wg3N6AYM/WfWewSDytuSdSmO0EqYQ0hKlD0XWGURUhEB6Z+V9TtYm0mMksdTCU3Y 7 | PnOV9v5tFIdh7mjKzULkKp8XBd1jrU1ma/+3QQIDAQABAoIBAGWu6xDN0k9Wi3iz 8 | 5hiWP/xkXGjAZFRRvaJwr8BeHsYMgb6gwFCbNVLtxKaIC6KV1wJnDHsc3ia5QqS6 9 | q+zIPhcFlYbhjGMK1H08VP5RSz76Z1ZSjsQ1CRgW1vbvIpLp0xi/gF3qcJMmuqVx 10 | iWQ1a1K4PrysHrB8H+m1QZNk/GvNV6qPBo2UvXOr9+R4MS/zmaOWokx1Wz2VCGEH 11 | L57LxLmCcmQztBOf5NTmnGeZeo2A79pPYYYF/FlosGj0SW+cawaxY5gsAdTcnYw8 12 | 5kxMY0F76xEhV9DGO8CjDUrPpcjheGhwqU6sAQsWlJQ5c5xmDPeNYhtXAg/0QcKE 13 | sM5254ECgYEAyEfahudVMKxadTOAGTcHsRTk38bVepkDa2rb3are/soyWuRgS1+q 14 | 8gCwYpQVsQZA25FLOB1ClcVHXFKyS+KOf0YQgoS1Ie7BG4v5JK81Qxla/B4ehdrY 15 | U+X58MtLGWRp/4XFs9XcUtJFNlcUQ+CoH2bwaGSebMu3GM9GqlVs1nkCgYEAxSqg 16 | GxWt2oIlzuE4w+Qxgw+eQhtXbWLVKdDBCx9h5+OGxJCsiDSursaPenqHamFu2Pa4 17 | x04avUPW5DvlNHwN5nIErn0Vr5xMka0LFTNvSym5SgqqrATt/CfLdjZuWK2wAVBN 18 | 2+f/F94sxyGtDCvGIwFh8IpA6haO59VNE4ImVQkCgYEAjUaKMoD6CdR6FpRWdvLn 19 | fRVFAUkLi2hmDh6Zqzp+BHJypMTV3DVGq2CrC050IqRLmdZaneGd93HqjYhT9s2A 20 | QO0WdkrQTnhRRXIuM59XzdYT5SYF19vwnvXpod51Qhsf3jvpoppnyRBD/DnetKbD 21 | UStWx/y59/qXVGvZ6AtMM9kCgYAul58UJ3BZBY5nsANZD1C/9u3gE1P6rnNPGVy1 22 | AyqAwNy1ELckzMZG2PdI6zGa6wFl1g/eUU+yUBi1AI5uc65ZObJvhXGdeSMC/dBz 23 | px7ytR5rJ9AjLFsMzl6fRceXvxurg82SXOWEP2V3fTbGfIWq+gWp8RHb/46vrFE1 24 | ffkO+QKBgB/CMbDd822TgpIsRS15gOpR8GihTv8BJLqyk3FJZlGce9v7Q29R9uZO 25 | Vfh1OkujJgHMZBF7DQ5ohsWC7ypxdSlWHlBYYAAKcDD4F8kN1iWE43nJnrDyHNwz 26 | 3cnoooz38eD4NdIlA0ZGhZksI35xDeIZ02AEUUrzx5JuNKtM3T9G 27 | -----END RSA PRIVATE KEY----- 28 | -----BEGIN CERTIFICATE----- 29 | MIIEITCCAwmgAwIBAgIJAO5bUjWy2QHiMA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNV 30 | BAYTAlVTMRAwDgYDVQQIEwdQSFBJUkNkMQ8wDQYDVQQHEwZHaXRodWIxETAPBgNV 31 | BAoTCFBIUCBJUkNEMQ0wCwYDVQQLEwRERVJQMRQwEgYDVQQDEwtwaHBpcmNkLm9y 32 | ZzAeFw0xMTA3MjIwNTAxNTBaFw0xNDA0MTgwNTAxNTBaMGgxCzAJBgNVBAYTAlVT 33 | MRAwDgYDVQQIEwdQSFBJUkNkMQ8wDQYDVQQHEwZHaXRodWIxETAPBgNVBAoTCFBI 34 | UCBJUkNEMQ0wCwYDVQQLEwRERVJQMRQwEgYDVQQDEwtwaHBpcmNkLm9yZzCCASIw 35 | DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJpApDXDRMetApdKPcWZTMzdGkfr 36 | H9lCLWpO3x5u8mJESTZUTczMrvYrfeXuZtx3eHhNjF4nnVLZVlk5cnfvqYdN7PIj 37 | 7ca0xumkNogdKdk7eMHZ5IV33CUF6H9d1fCkoGMXoFIrfD8jvgljJye6+akOaHBq 38 | KQJT4skZjxu8QcF3IEvM7EagxFZFRH+S7/+IGGrpHLYA6ZDIcPTVmRP9G9njVi20 39 | sKNLX6X0ssvs/Fk8kOPVwMINzegGDP1n1nsEg8rbknUpjtBKmENISpQ9F1hlEVIR 40 | AemflfU7WJtJjJLHUwlN2D5zlfb+bRSHYe5oys1C5CqfFwXdY61NZmv/t0ECAwEA 41 | AaOBzTCByjAdBgNVHQ4EFgQUG9iRAIi0daF9wBgq8fZPNts3+0kwgZoGA1UdIwSB 42 | kjCBj4AUG9iRAIi0daF9wBgq8fZPNts3+0mhbKRqMGgxCzAJBgNVBAYTAlVTMRAw 43 | DgYDVQQIEwdQSFBJUkNkMQ8wDQYDVQQHEwZHaXRodWIxETAPBgNVBAoTCFBIUCBJ 44 | UkNEMQ0wCwYDVQQLEwRERVJQMRQwEgYDVQQDEwtwaHBpcmNkLm9yZ4IJAO5bUjWy 45 | 2QHiMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAEz9kC/eZMrHrCxt 46 | PaLxd8s+nPLMydUsGnAQgujVcIjqnhycmGCgN5UovBMFoLDhN7XDtW8AiNfyPseM 47 | bZjqTbHneQrrg89QjUiQM27uMBUsW7bk1QcEvACeUrUoifjdLHnrfTLPdM1w4Lqj 48 | djOhwf+kUQ9HTzpJZrDVBIKA6g1IC2TIaQPQMZgHKC/mTW3VfBrUZPrUU1V903bU 49 | D5htL9b0Dmec3OVSy+fpS3S3HoJeAKc2pSQF1+WGOXdRJH1IopkVw7U0vP9LBI1C 50 | Q8NeBxypexUa0k4IAOFWUlzypLI9HOQgCUHCC5lXkK9+nSHOb/ZACXOqJjQn1b1r 51 | J6vFXqE= 52 | -----END CERTIFICATE----- 53 | -------------------------------------------------------------------------------- /classes/channel.class.php: -------------------------------------------------------------------------------- 1 | id = $id; 19 | $this->name = $name; 20 | $this->created = time(); 21 | } 22 | 23 | function addUser(&$user, $creator=false){ 24 | $this->users[$user->id] = true; 25 | if($creator) 26 | $this->modes['o'][] = $user->nick; 27 | } 28 | 29 | function getModes(){ 30 | global $ircd; 31 | $modes = '+'; 32 | $extra = array(); 33 | foreach($this->modes as $m=>$e){ 34 | if(is_array($e)) 35 | continue; 36 | $modes .= "$m"; 37 | if($e !== true) 38 | $extra[] = $e; 39 | } 40 | return $modes.' '.implode(' ', $extra); 41 | } 42 | 43 | function getUserPrefix($user){ 44 | global $ircd; 45 | $pfx = ''; 46 | $n = 0; 47 | foreach($ircd->chanModes as $m) 48 | if($m->type == Mode::TYPE_P && isset($m->prefix) && $this->hasMode($m->letter, $user->nick)) 49 | if($m->privs > $n) 50 | $pfx = $m->prefix; 51 | return $pfx; 52 | } 53 | 54 | function hasMode($m, $t=false){ 55 | global $ircd; 56 | if(isset($this->modes[$m])) 57 | if($ircd->chanModes[$m]->type == Mode::TYPE_A || $ircd->chanModes[$m]->type == Mode::TYPE_P) 58 | return in_array($t, $this->modes[$m]); 59 | else 60 | return true; 61 | return false; 62 | } 63 | 64 | function hasPrivs($user, $priv){ 65 | global $ircd; 66 | $upriv = 0; 67 | foreach($ircd->chanModes as $m) 68 | if($m->type == Mode::TYPE_P && isset($m->privs) && $this->hasMode($m->letter, $user->nick)) 69 | $upriv = $upriv | $m->privs; 70 | if($upriv & $priv) 71 | return true; 72 | return false; 73 | } 74 | 75 | function isBanned($user){ 76 | return false; 77 | } 78 | 79 | function nick($user, $oldnick){ 80 | foreach($this->modes as &$m) 81 | if(is_array($m)) 82 | foreach($m as &$n) 83 | $n = ($n == $oldnick?$user->nick:$n); 84 | } 85 | 86 | function removeUser($user){ 87 | if(array_key_exists($user->id, $this->users)) 88 | unset($this->users[$user->id]); 89 | } 90 | 91 | function send($msg, $excl=""){ 92 | global $ircd; 93 | foreach($this->users as $id=>$m){ 94 | if(is_object($excl)) 95 | if($excl->id == $id) 96 | continue; 97 | $ircd->_clients[$id]->send($msg); 98 | } 99 | } 100 | 101 | function setMode(&$user, $act, $mode, &$parts, &$what){ 102 | global $ircd; 103 | $atext = ($act=='+'?'set':'unset'); 104 | //first, check set/unset hook 105 | if(isset($mode->hooks[$atext])){ 106 | $d = array('user'=>&$user, 'chan'=>&$this, 'extra'=>@$parts['0']); 107 | if(!$mode->hooks[$atext]($d)){ 108 | if(isset($d['errno'])) 109 | $ircd->error($d['errno'], $user, @$d['errstr']); 110 | return false; 111 | } 112 | } 113 | $ms = isset($this->modes[$mode->letter]); 114 | if($mode->type == Mode::TYPE_A || $mode->type == Mode::TYPE_P){ 115 | //modes like +beI and +qaohv 116 | $pt = array_shift($parts); 117 | // ignore if regex mismatch 118 | if(isset($mode->regex) && !preg_match(@$mode->regex, $pt)) return false; 119 | //if empty param, list 120 | if($pt === NULL){ 121 | 122 | } else { 123 | if($act == '+'){ 124 | $this->modes[$mode->letter][] = $pt; 125 | } else { 126 | if(($k = array_search($pt, @$this->modes[$mode->letter])) !== FALSE) 127 | unset($this->modes[$mode->letter][$k]); 128 | else 129 | return false; 130 | } 131 | $what['params'][] = $pt; 132 | } 133 | } elseif($mode->type == Mode::TYPE_B || $mode->type == Mode::TYPE_C){ 134 | //modes like +fJklL 135 | $pt = array_shift($parts); 136 | // 1: ignore if ((regex is set AND !match regex) OR param is missing) AND (act is add OR type is TYPE_B) OR... 137 | // 2: ignore if mode is already set AND act is add OR... 138 | // 3: ignore if mode is not set AND act is remove 139 | if(((isset($mode->regex) && !preg_match(@$mode->regex, $pt)) || $pt === NULL ) && ($act == '+' || $mode->type == Mode::TYPE_B)) return false; 140 | if($ms && $act == '+') return false; 141 | if(!$ms && $act == '-') return false; 142 | if($act == '+') 143 | $this->modes[$mode->letter] = $pt; 144 | else 145 | unset($this->modes[$mode->letter]); 146 | $what['params'][] = $pt; 147 | } elseif($mode->type == Mode::TYPE_D){ 148 | //other toggleable modes 149 | // ignore not already set 150 | if(($act == '+' && $ms) || ($act == '-' && !$ms)) 151 | return false; 152 | if($act == '+') 153 | $this->modes[$mode->letter] = true; 154 | else 155 | unset($this->modes[$mode->letter]); 156 | } 157 | $what[] = $act.$mode->letter; 158 | return true; 159 | } 160 | 161 | function setModes(&$user, $mask){ 162 | global $ircd; 163 | $parts = explode(" ", $mask); 164 | $mask = str_split(array_shift($parts)); 165 | $act = ""; 166 | $what = array(); 167 | //set the modes and gather info in $what 168 | foreach($mask as $c){ 169 | if($c == '+' || $c == '-'){ 170 | $act = $c; 171 | continue; 172 | } 173 | if($act == "") 174 | continue; 175 | if(!($mode = $ircd->chanModes[$c])){ 176 | $ircd->error(472, $user, $c); 177 | continue; 178 | } 179 | $this->setMode($user, $act, $mode, $parts, $what); 180 | } 181 | //parse $what and ooutput what modes were actually set 182 | $p = @implode(' ',@$what['params']); 183 | unset($what['params']); 184 | $ta = $str = ""; 185 | foreach($what as $w){ 186 | if($ta != ($a = substr($w, 0, 1))) 187 | $str .= $ta = $a; 188 | $str .= substr($w, 1, 2); 189 | } 190 | if(!empty($str)) 191 | $this->send(":{$user->prefix} MODE {$this->name} ".$str.(!empty($p)?' '.$p:'')); 192 | } 193 | 194 | function setTopic($user, $msg){ 195 | $this->topic = $msg; 196 | $this->topic_setby = $user->nick; 197 | $this->topic_seton = time(); 198 | } 199 | 200 | function userInChan($nick){ 201 | global $ircd; 202 | $u = $ircd->getUserByNick($nick); 203 | if(!$u) 204 | return false; 205 | if(isset($this->users[$u->id])) 206 | return true; 207 | return false; 208 | } 209 | 210 | } 211 | 212 | ?> 213 | -------------------------------------------------------------------------------- /classes/ircd.class.php: -------------------------------------------------------------------------------- 1 | 1){ 30 | $p = implode(" ", $p); 31 | } elseif(count($p) == 0){ 32 | $p = ""; 33 | } else { 34 | $p = $p['1']; 35 | } 36 | if($p[0] == ":"){ 37 | $p = substr($p, 1); 38 | } 39 | $this->quit($user, $p); 40 | break; 41 | case 'pass': 42 | 43 | break; 44 | case 'user': 45 | //USER nick mode unused :Real Name 46 | if(count($e) < 5){ 47 | $this->error('461', $user, 'USER'); 48 | break; 49 | } 50 | $err = FALSE; 51 | while($err == FALSE){ 52 | if(!$this->checkNick($e['1'])){ 53 | $err = "{$e['1']}:Illegal characters."; 54 | continue; 55 | } 56 | //if(modes n shit){ 57 | 58 | //} 59 | if(count($e) == 5){ 60 | $rn = $e['4']; 61 | if($rn[0] == ":"){ 62 | $rn = substr($rn, 1); 63 | } 64 | } else { 65 | for($i=4;$i < count($e);$i++){ 66 | $rn[] = $e[$i]; 67 | } 68 | $rn = implode(" ", $rn); 69 | if($rn[0] == ":"){ 70 | $rn = substr($rn, 1); 71 | } 72 | } 73 | if(!$this->checkRealName($rn)){ 74 | $err = "$rn:Illegal characters."; 75 | continue; 76 | } 77 | break; 78 | } 79 | if($err){ 80 | $this->error('432', $user,"$err"); 81 | } else { 82 | unset($e['3']); //unused 83 | $user->username = $e['1']; 84 | $user->usermode = ""; 85 | $user->realname = $rn; 86 | $user->namesx = FALSE; 87 | if($user->regbit ^ 1){ 88 | $user->regbit += 1; 89 | } 90 | if($user->regbit == 3){ 91 | $user->lastping = $user->lastpong = time(); 92 | $user->registered = TRUE; 93 | $user->prefix = $user->nick."!".$user->username."@".$user->address; 94 | $this->welcome($user); 95 | } 96 | } 97 | 98 | break; 99 | case 'nick': 100 | if(count($e) < 2){ 101 | $this->error('431', $user); 102 | break; 103 | } 104 | $this->stripColon($e['1']); 105 | if($this->nickInUse($e['1'])){ 106 | $this->error('433', $user, $e['1']); 107 | break; 108 | } 109 | if($this->checkNick(@$e['1'])){ 110 | $user->nick = substr($e['1'], 0, $this->config['ircd']['nicklen']); 111 | if($user->regbit ^ 2){ 112 | $user->regbit +=2; 113 | } 114 | if($user->regbit == 3){ 115 | $user->lastping = $user->lastpong = time(); 116 | $user->registered = TRUE; 117 | $user->prefix = $user->nick."!".$user->username."@".$user->address; 118 | $this->welcome($user); 119 | } 120 | } else { 121 | $this->error('432', $user, $e['1'].":Illegal characters."); 122 | } 123 | break; 124 | default: 125 | $this->error('451', $user, $e['0']); 126 | } 127 | 128 | } 129 | 130 | function process($in, $user){ 131 | $e = explode(" ", $in); 132 | $command = strtolower($e['0']); 133 | unset($e['0']); 134 | $params = implode (" ", $e); 135 | $user->lastpong = time(); 136 | if(method_exists(__CLASS__,$command) && array_search($command, $this->allowed) !== FALSE){ 137 | $this->$command($user, $params); 138 | } else { 139 | $this->error('421', $user, $command); 140 | } 141 | } 142 | 143 | function error($numeric, $user, $extra="", $override=false){ 144 | $target = (empty($user->nick)?"*":$user->nick); 145 | $prefix = ":".$this->servname." ".$numeric." ".$target." "; 146 | switch($numeric){ 147 | case 401: 148 | $message = "$extra :No such nick/channel."; 149 | break; 150 | case 402: 151 | $message = "$extra :No such server."; 152 | break; 153 | case 403: 154 | $message = "$extra :No such channel."; 155 | break; 156 | case 404: 157 | $message = "$extra ".(!$override?":Cannot send to channel.":$override); 158 | break; 159 | case 405: 160 | $message = "$extra :You have joined too many channels."; 161 | break; 162 | case 406: 163 | $message = "$extra :There was no such nickname."; 164 | break; 165 | case 407: 166 | $message = "$extra :Too many targets."; 167 | break; 168 | case 408: 169 | $message = "$extra :No such service."; 170 | break; 171 | case 409: 172 | $message = ":No origin specified."; 173 | break; 174 | //410 doesn't exist 175 | case 411: 176 | $message = ":No recipient given."; 177 | break; 178 | case 412: 179 | $message = ":No text to send."; 180 | break; 181 | case 421: 182 | $message = strtoupper($extra)." :Unknown command."; 183 | break; 184 | case 422: 185 | $message = ":MOTD file missing."; 186 | break; 187 | case 431: 188 | $message = ":No nickname given."; 189 | break; 190 | case 432: 191 | $extra = explode(":",$extra); 192 | $message = "{$extra['0']} :Erroneous nickname".($extra['1']?": ".$extra['1']:""); 193 | break; 194 | case 433: 195 | $message = "$extra :Nickname already in use."; 196 | break; 197 | case 442: 198 | $message = "$extra :You're not in that channel."; 199 | break; 200 | case 451: 201 | $message = $extra." :You have not registered."; 202 | break; 203 | case 461: 204 | $message = strtoupper($extra)." :Not enough parameters."; 205 | break; 206 | case 462: 207 | $message = ":You may not register more than once."; 208 | break; 209 | case 464: 210 | $message = ":Incorrect password."; 211 | break; 212 | case 472: 213 | $message = "$extra :is unknown mode char to me"; 214 | break; 215 | case 482: 216 | $message = ":You do not have the proper channel privileges to do that.".(isset($extra)?" ($extra)":""); 217 | break; 218 | case 485: 219 | $message = ":You are not the channel owner."; 220 | break; 221 | case 491: 222 | $message = ":No O-lines for your host."; 223 | break; 224 | case 499: 225 | $message = ":You're not a channel owner."; 226 | } 227 | $user->send($prefix.$message); 228 | } 229 | 230 | function welcome($user){ 231 | $user->send(":{$this->servname} 001 {$user->nick} :Welcome to the {$this->network} IRC network, {$user->prefix}"); 232 | $user->send(":{$this->servname} 002 {$user->nick} :Your host is {$this->servname}, running {$this->version}"); 233 | $user->send(":{$this->servname} 003 {$user->nick} :This server was created {$this->createdate}"); 234 | $user->send(":{$this->servname} 004 {$user->nick} {$this->servname} {$this->version} ".implode(array_keys($this->userModes))." ".implode(array_keys($this->chanModes))); 235 | $this->isupport($user); 236 | $this->lusers($user); 237 | $this->motd($user); 238 | $user->setModes("+iw"); 239 | $d = array('ircd'=>&$this, 'user'=>&$user); 240 | $this->runUserHooks('connect', $d); 241 | } 242 | 243 | function join($user, $p=""){ 244 | $joins = array(); 245 | if(empty($p)){ 246 | $this->error(461, $user, 'join'); 247 | return; 248 | } 249 | $ps = explode(" ", $p); 250 | if(count($ps) > 1){ //we have channel keys 251 | $chans = explode(",", $ps['0']); 252 | $keys = explode(",", $ps['1']); 253 | foreach($chans as $k => $v){ 254 | $joins[] = array($v, @$keys[$k]); 255 | } 256 | } else { 257 | $chans = explode(",", $p); 258 | foreach($chans as $k => $v){ 259 | $joins[] = array($v, ''); 260 | } 261 | } 262 | foreach($joins as $value){ 263 | $chan = $value['0']; 264 | $kee = @$value['1']; 265 | if(array_search($chan[0], str_split($this->config['ircd']['chantypes'])) === FALSE){ 266 | $this->error(403, $user, $chan); 267 | continue; 268 | } 269 | if(array_search($chan, $user->channels) !== FALSE) 270 | return; 271 | if(array_key_exists($chan, $this->_channels) === FALSE){ 272 | $nchan = new Channel($this->channel_num++, $chan); 273 | $d = array('user'=>&$user, 'chan'=>&$nchan); 274 | if(!$this->runChannelHooks('join', $d)){ 275 | $this->error($d['errno'], $user, $chan, $d['errstr']); 276 | return false; 277 | } 278 | $user->send(":{$user->prefix} JOIN $chan"); 279 | $nchan->addUser($user, true); 280 | $nchan->setTopic($user, "default topic!"); 281 | $this->_channels[$nchan->name] = $nchan; 282 | $user->addChannel($nchan); 283 | } else { 284 | $d = array('user'=>&$user, 'chan'=>&$this->_channels[$chan]); 285 | if(!$this->runChannelHooks('join', $d)){ 286 | $this->error($d['errno'], $user, $chan, $d['errstr']); 287 | return false; 288 | } 289 | $this->_channels[$chan]->addUser($user); 290 | $user->addChannel($this->_channels[$chan]); 291 | $this->_channels[$chan]->send(":{$user->prefix} JOIN $chan"); 292 | } 293 | $this->topic($user, $chan); 294 | $this->names($user, $chan); 295 | } 296 | } 297 | 298 | function lusers($user, $p=""){ 299 | $nick = $user->nick; 300 | $users = count($this->_clients); 301 | $lusers = ":{$this->servname} 251 $nick :There are $users users and 0 invisible on 1 servers\n". 302 | ":{$this->servname} 252 $nick ".count($this->operList())." :operator(s) online\n". 303 | ":{$this->servname} 254 $nick ".count($this->_channels)." :channels formed\n". 304 | ":{$this->servname} 255 $nick :I have $users clients and 1 servers\n". 305 | ":{$this->servname} 265 $nick :Current Local Users: $users Max: TODO\n". 306 | ":{$this->servname} 266 $nick :Current Global Users: $users Max: TODO"; 307 | foreach(explode("\n", trim($lusers)) as $s){ 308 | $user->send(trim($s)); 309 | } 310 | } 311 | 312 | function mode($user, $p){ 313 | if(empty($p)){ 314 | $this->error(461, $user, 'MODE'); 315 | return; 316 | } 317 | $p = explode(" ", $p); 318 | $target = $p['0']; 319 | if($this->channelExists($target)){ 320 | //chan mode(s) 321 | $channel = $this->_channels[$target]; 322 | if(count($p) == 1){ 323 | $user->send(":{$this->servname} 324 {$user->nick} {$channel->name} ".$channel->getModes()); 324 | $user->send(":{$this->servname} 329 {$user->nick} {$channel->name} ".$channel->created); 325 | } else { 326 | unset($p['0']); 327 | $modes = implode(" ", $p); 328 | $channel->setModes($user, $modes); 329 | } 330 | } else { 331 | //user mode(s) 332 | //1: mask (+a, +aa-o, +a-ooa, etc) 2...: params 333 | if(!$this->getUserByNick($target)){ 334 | $this->error(401, $user, $target); 335 | return false; 336 | } 337 | if($target != $user->nick) 338 | return false; 339 | if(count($p) == 1){ 340 | $user->send(":{$this->servname} 221 {$user->nick} ".$user->getModes()); 341 | } else { 342 | unset($p['0']); 343 | $modes = implode(" ", $p); 344 | $user->setModes($modes); 345 | } 346 | } 347 | } 348 | 349 | function motd($user, $p=""){ 350 | if(empty($p)){ 351 | if(file_exists("motd.txt")){ 352 | $user->send(":{$this->servname} 375 {$user->nick} :- {$this->servname} Message of the day -"); 353 | $motd = file("motd.txt"); 354 | foreach($motd as $value){ 355 | $user->send(":{$this->servname} 372 {$user->nick} :- ".rtrim($value)); 356 | } 357 | $user->send(":{$this->servname} 376 {$user->nick} :End of MOTD"); 358 | } else { 359 | $this->error('422', $user); 360 | } 361 | } 362 | } 363 | 364 | function names($user, $p){ 365 | $prefix = ":{$this->servname} 353 {$user->nick} "; 366 | if(empty($p)){ 367 | foreach($this->_clients as $val){ 368 | if(count($val->channels) == "0"){ 369 | $names[] = $val->nick; 370 | } 371 | } 372 | $prefix .= "= * :"; 373 | } else { 374 | $p = explode(" ", $p); 375 | if(array_key_exists($p['0'], $this->_channels) === FALSE){ 376 | $user->send(":{$this->servname} 366 {$user->nick} {$p['0']} :End of /NAMES list."); 377 | return; 378 | } 379 | $chan = $this->_channels[$p['0']]; 380 | $names = $chan->users; 381 | foreach($names as $k => $v){ 382 | $pfx = $chan->getUserPrefix($this->_clients[$k]); 383 | $names[$k] = str_replace("@@", "@", $pfx).$this->_clients[$k]->nick; 384 | } 385 | if(!$user->namesx){ 386 | 387 | } 388 | if(array_key_exists("p",$chan->modes) !== FALSE){ 389 | $prefix .= "* {$chan->name} :"; 390 | } elseif(array_key_exists("s",$chan->modes) !== FALSE){ 391 | $prefix .= "@ {$chan->name} :"; 392 | } else { 393 | $prefix .= "= {$chan->name} :"; 394 | } 395 | } 396 | if(count($names) == 0){ 397 | $user->send(":{$this->servname} 366 {$user->nick} * :End of /NAMES list."); 398 | return; 399 | } 400 | $names = implode(" ", $names); 401 | $len = strlen($prefix); 402 | if($len+strlen($names) <= 510){ 403 | $user->send($prefix.$names); 404 | } else { 405 | $max = 510 - $len; 406 | while(strlen($names) > 510){ 407 | $nsub = substr($names, 0, $max-1); 408 | if($names[strlen($nsub)-1] != " " || !empty($names[strlen($nsub)-1])){ 409 | $pos = strrpos($nsub, " "); 410 | $nsub = substr($nsub, 0, $pos); 411 | } 412 | $names = substr($names, strlen($nsub)+1); 413 | $user->send($prefix.$nsub); 414 | } 415 | $user->send($prefix.$names); 416 | } 417 | $user->send(":{$this->servname} 366 {$user->nick} ".(empty($chan->name)?"*":$chan->name)." :End of /NAMES list."); 418 | } 419 | 420 | function nick($user, $p){ 421 | $this->stripColon($p); 422 | if(empty($p)){ 423 | $this->error('461', $user, 'NICK'); 424 | return; 425 | } 426 | if($user->nick == $p) 427 | return; 428 | if($this->nickInUse($p)){ 429 | $this->error('433', $user, $p); 430 | return; 431 | } 432 | if($this->checkNick($p)){ 433 | $p = substr($p, 0, $this->config['ircd']['nicklen']); 434 | $user->send(":{$user->prefix} NICK $p"); 435 | $oldprefix = $user->prefix; 436 | $oldnick = $user->nick; 437 | $user->nick = $p; 438 | $user->prefix = $user->nick."!".$user->username."@".$user->address; 439 | foreach($user->channels as $chan){ 440 | $c = $this->_channels[$chan]; 441 | $c->nick($user, $oldnick); 442 | $c->send(":{$oldprefix} NICK $p", $user); 443 | } 444 | } else { 445 | $this->error('432', $user, $p.":"."Illegal characters."); 446 | } 447 | } 448 | 449 | function oper($user, $p){ 450 | $p = explode(" ", $p); 451 | if(count($p) != 2){ 452 | $this->error(461, $user, 'OPER'); 453 | return false; 454 | } 455 | $opers = parse_ini_file('opers.ini', true); 456 | //if that oper doesn't exist 457 | if(!isset($opers[$p[0]])){ 458 | $this->error(491, $user); 459 | return false; 460 | } 461 | $oper = $opers[$p[0]]; 462 | //if wrong host (placeholder so i don't forget the error code) 463 | if(false){ 464 | $this->error(491, $user); 465 | return false; 466 | } 467 | switch($oper['pass-enc']){ 468 | case 'sha1': 469 | $inpass = sha1($p[1]); 470 | break; 471 | case 'md5': 472 | $inpass = md5($p[1]); 473 | break; 474 | case 'plain': 475 | $inpass = $p[1]; 476 | } 477 | //wrong password 478 | if($inpass != $oper['pass']){ 479 | $this->error(464, $user); 480 | return false; 481 | } 482 | $user->oper = true; 483 | $user->send(":{$this->servname} 381 :You are now an IRC Operator."); 484 | } 485 | 486 | function part($user, $p){ 487 | $chans = explode(",", $p); 488 | foreach($chans as $k => $v){ 489 | $x = explode(" ", $v, 2); 490 | $v = trim($x['0']); 491 | $reason = (empty($x['1'])?"":trim($x['1'])); 492 | if(!array_key_exists($v, $this->_channels)){ 493 | $this->error('403', $user, $v); 494 | return; 495 | } 496 | if(!array_key_exists($user->id, $this->_channels[$v]->users)){ 497 | $this->error('442', $user, $v); 498 | return; 499 | } 500 | $this->_channels[$v]->send(":{$user->prefix} PART $v $reason"); 501 | $this->_channels[$v]->removeUser($user); 502 | $user->removeChannel($this->_channels[$v]); 503 | } 504 | } 505 | 506 | function ping($user, $p, $e=false){ 507 | if($e){ 508 | $user->send("PING :$p"); 509 | return; 510 | } 511 | if(empty($p)){ 512 | $this->error('461', $user, 'PING'); 513 | return; 514 | } 515 | $p = explode(" ", $p); 516 | if(count($p) == 1){ 517 | $p = $p['0']; 518 | if(strpos($p, ":") === 0){ 519 | $p = substr($p, 1); 520 | } 521 | $user->send(":{$this->servname} PONG {$this->servname} ".":$p"); 522 | $user->lastpong = time(); 523 | } else { 524 | //ping some server 525 | } 526 | } 527 | 528 | function pong($user, $p){ 529 | //PONG :samecrap 530 | if(strpos($p, ":") === 0){ 531 | $p = substr($p, 1); 532 | } 533 | if($p == $this->servname){ //respond to keepalive ping 534 | if($user->lastpong < $user->lastping){ 535 | $user->lastpong = time(); 536 | } 537 | } 538 | } 539 | 540 | function privmsg($user, $p){ 541 | // target ?:message 542 | $e = explode(" ", $p); 543 | $chantypes = str_split($this->config['ircd']['chantypes']); 544 | $target = $e['0']; 545 | if($target[0] == ":"){ 546 | //ERR_NORECIPIENT 547 | $this->error(411, $user); 548 | return; 549 | } 550 | if(count($e) < 2){ 551 | //ERR_NOTEXTTOSEND 552 | $this->error(412, $user, $target); 553 | return; 554 | } 555 | if($target[0] == "$"){ 556 | if($user->oper & 32){ //replace with actual oper bit 557 | //client is allowed to message $* 558 | } else { 559 | //ERR_NOSUCHNICK 560 | $this->error(401, $user, $target); 561 | return; 562 | } 563 | } 564 | $is_channel = (array_search($target[0], $chantypes) !== FALSE?TRUE:FALSE); 565 | if($is_channel){ 566 | if(!preg_match("/[\x01-\x07\x08-\x09\x0B-\x0C\x0E-\x1F\x21-\x2B\x2D-\x39\x3B-\xFF]{1,}/", $target)){ 567 | //ERR_NOSUCHNICK (illegal characters) 568 | $this->error(401, $user, $target); 569 | return; 570 | } 571 | if(array_key_exists($target, $this->_channels) === FALSE){ 572 | //ERR_NOSUCHNICK (channel doesnt exist) 573 | $this->error(401, $user, $target); 574 | return; 575 | } 576 | if(array_search($target, $user->channels) === FALSE){ 577 | //ERR_CANNOTSENDATOCHAN 578 | $this->error(404, $user, $target); 579 | return; 580 | } 581 | $d = array('user'=>&$user, 'chan'=>&$this->_channels[$target]); 582 | if(!$this->runChannelHooks('privmsg', $d)){ 583 | $this->error($d['errno'], $user, $target, $d['errstr']); 584 | return false; 585 | } 586 | $message = substr($p, strlen($target)+1); 587 | $message = ($message[0] == ":"?substr($message, 1):$message); 588 | //send to whole channel minus yourself 589 | $this->_channels[$target]->send(":{$user->prefix} PRIVMSG $target :$message", $user); 590 | } else { 591 | if(($tuser = $this->getUserByNick($target)) == FALSE){ 592 | //ERR_NOSUCHNICK (user doesnt exist) 593 | $this->error(401, $user, $target); 594 | return; 595 | } 596 | $message = substr($p, strlen($target)+1); 597 | $message = ($message[0] == ":"?substr($message, 1):$message); 598 | $tuser->send(":".$user->prefix." PRIVMSG ".$target." :$message"); 599 | } 600 | } 601 | 602 | function protoctl($user, $p){ 603 | if(empty($p)){ 604 | $this->error(461, $user, 'protoctl'); 605 | return; 606 | } 607 | if(strtolower($p) == "namesx"){ 608 | $user->namesx = TRUE; 609 | } 610 | } 611 | 612 | function quit($user, $p="Quit: Leaving"){ 613 | if(strpos($p, ":") === 0) 614 | $p = substr($p, 1); 615 | foreach(@$user->channels as $chan){ 616 | $this->_channels[$chan]->send(":{$user->prefix} QUIT :$p"); 617 | $this->_channels[$chan]->removeUser($user); 618 | } 619 | if($p !== false){ 620 | $user->send("ERROR: Closing Link: {$user->nick}[{$user->address}] ($p)"); 621 | $user->writeBuffer(); 622 | } 623 | $user->disconnect(); 624 | } 625 | 626 | function topic($user, $p){ 627 | if(empty($p)){ 628 | $this->error(461, $user, 'topic'); 629 | return; 630 | } 631 | $p = explode(" ", $p); 632 | if(array_key_exists($p['0'], $this->_channels) === FALSE){ 633 | $this->error(403, $user, $p['0']); 634 | return; 635 | } 636 | if(array_search($p['0'], $user->channels) === FALSE){ 637 | $this->error(442, $user, $p['0']); 638 | return; 639 | } 640 | if(count($p) == 1){ 641 | $chan = $this->_channels[$p['0']]; 642 | if(empty($chan->topic)){ 643 | $user->send(":{$this->servname} 331 ($chan->name} :No topic set."); 644 | return; 645 | } 646 | $user->send(":{$this->servname} 332 {$user->nick} {$chan->name} :{$chan->topic}"); 647 | $user->send(":{$this->servname} 333 {$user->nick} {$chan->name} {$chan->topic_setby} {$chan->topic_seton}"); 648 | } else { 649 | //change topic 650 | } 651 | } 652 | 653 | function user($user, $p){ 654 | $this->error('462', $user); 655 | } 656 | 657 | function who($user, $p){ 658 | if($this->channelExists($p)){ 659 | $channel = $this->_channels[$p]; 660 | foreach($channel->users as $id=>$m){ 661 | $u = $this->_clients[$id]; 662 | $m = str_replace('@@','@', $m); 663 | $user->send(":{$this->servname} 352 {$user->nick} {$p} {$u->username} {$u->address} {$this->servname} {$u->nick} H{$m} :0 {$u->realname}"); 664 | } 665 | } elseif($this->nickInUse($p)) { 666 | $u = $this->getUserByNick($p); 667 | $channel = $this->_channels[current($u->channels)]; 668 | $m = str_replace('@@','@', $channel->users[$u->id]); 669 | $user->send(":{$this->servname} 352 {$user->nick} {$channel->name} {$u->username} {$u->address} {$this->servname} {$u->nick} H{$m} :0 {$u->realname}"); 670 | } elseif(empty($p)){ 671 | $p = '*'; 672 | foreach($user->channels as $c){ 673 | $channel = $this->_channels[$c]; 674 | foreach($channel->users as $id=>$m){ 675 | $u = $this->_clients[$id]; 676 | $m = str_replace('@@','@', $m); 677 | $user->send(":{$this->servname} 352 {$user->nick} {$channel->name} {$u->username} {$u->address} {$this->servname} {$u->nick} H{$m} :0 {$u->realname}"); 678 | } 679 | } 680 | } 681 | $user->send(":{$this->servname} 315 {$user->nick} $p :End of /WHO list."); 682 | } 683 | 684 | //net methods 685 | 686 | function __construct($config){ 687 | $this->config = parse_ini_file($config, true); 688 | if(!$this->config) 689 | die("Config file parse failed: check your syntax!"); 690 | require("include/modes.php"); 691 | $this->servname = $this->config['me']['servername']; 692 | $this->network = $this->config['me']['network']; 693 | $this->createdate = $this->config['me']['created']; 694 | $this->chanModes = $channelModes; 695 | $this->userModes = $userModes; 696 | $listens = (empty($this->config['core']['listen'])?array():explode(',', $this->config['core']['listen'])); 697 | foreach($listens as $l){ 698 | $this->debug("bind to address $l"); 699 | $c = strrpos($l, ":") or die("...malformed address"); 700 | $addr = substr($l, 0, $c); 701 | $port = substr($l, $c+1); 702 | $this->createSocket($addr, $port); 703 | } 704 | $listens_ssl = explode(',', $this->config['core']['listen_ssl']); 705 | foreach($listens_ssl as $l){ 706 | $this->debug("bind to address $l (SSL)"); 707 | $c = strrpos($l, ":") or die("...malformed address"); 708 | $addr = substr($l, 0, $c); 709 | $port = substr($l, $c+1); 710 | $this->createSocket($addr, $port, true); 711 | } 712 | $this->debug("listening for new clients"); 713 | } 714 | 715 | function __destruct(){ 716 | foreach($this->_sockets as $socket) 717 | fclose($socket); 718 | } 719 | 720 | function accept($socket, $ssl=false){ 721 | $new = stream_socket_accept($socket, 0); 722 | if($ssl){ 723 | stream_socket_enable_crypto($new, true, STREAM_CRYPTO_METHOD_TLS_SERVER); 724 | if(!$new){ 725 | $this->debug("Closing connection: unknown (broken pipe)."); 726 | return false; 727 | } 728 | $client = new User($new, true); 729 | } else { 730 | $client = new User($new); 731 | } 732 | stream_set_blocking($new, 0); 733 | if(count($this->_clients) >= $this->config['ircd']['maxusers']){ 734 | $client->send('ERROR: Maximum clients reached. Please use a different server.'); 735 | $this->quit($client,'Error: Server Full'); 736 | return false; 737 | } 738 | $client->send(':'.$this->servname.' NOTICE AUTH :*** Looking up your hostname...'); 739 | $this->debug("new client: {$client->ip}"); 740 | $hn = gethostbyaddr($client->ip); 741 | if($hn == $client->ip){ 742 | $client->address = $client->ip; 743 | $client->send(':'.$this->servname.' NOTICE AUTH :*** Can\'t resolve your hotsname, using your IP instead.'); 744 | } else { 745 | $client->address = $hn; 746 | $client->send(':'.$this->servname.' NOTICE AUTH :*** Found your hostname.'); 747 | } 748 | $client->id = $this->client_num++; 749 | $this->_clients[$client->id] = $client; 750 | return true; 751 | } 752 | 753 | function createSocket($ip, $port, $ssl=false){ 754 | if(!preg_match($this->ipv4Regex, $ip)) 755 | $ip = '['.$ip.']'; 756 | if($ssl){ 757 | $arr = array('ssl'=> 758 | array( 759 | 'local_cert'=>'cert.pem', 760 | 'verify_peer'=>false, 761 | 'allow_self_signed'=>true, 762 | 'passphrase'=>'' 763 | ) 764 | ); 765 | $ctx = stream_context_create($arr); 766 | $s = stream_socket_server("tls://$ip:$port", $errno, $errstr, STREAM_SERVER_LISTEN|STREAM_SERVER_BIND, $ctx); 767 | if(!$s) 768 | die("Could not bind socket: ".$errstr."\n"); 769 | $this->_ssl_sockets[] = $s; 770 | } else { 771 | $s = stream_socket_server("tcp://$ip:$port", $errno, $errstr, STREAM_SERVER_LISTEN|STREAM_SERVER_BIND); 772 | if(!$s) 773 | die("Could not bind socket: ".$errstr."\n"); 774 | $this->_sockets[] = $s; 775 | } 776 | } 777 | 778 | function read($sock){ 779 | $buf = fread($sock, 1024); 780 | if($buf) 781 | $this->debug("<< ".trim($buf)); 782 | return $buf; 783 | } 784 | 785 | function write($sock, $data){ 786 | $this->debug(">> ".$data); 787 | $data = substr($data, 0, 509)."\r\n"; 788 | @fwrite($sock, $data); 789 | } 790 | 791 | function close($user, $sock="legacy"){ 792 | if(is_resource($user)){ 793 | fclose($user); 794 | } else { 795 | @fclose($user->socket); 796 | unset($this->_clients[$user->id]); 797 | } 798 | } 799 | 800 | function debug($msg){ 801 | if($this->config['core']['debug'] == true) 802 | echo $msg . "\n"; 803 | } 804 | 805 | //utility methods 806 | 807 | function channelExists($chan){ 808 | return (array_key_exists($chan, $this->_channels)===false?false:true); 809 | } 810 | 811 | function checkNick(&$nick){ 812 | if(empty($nick)) 813 | return false; 814 | if(!preg_match($this->nickRegex, $nick)) 815 | return false; 816 | $nick = substr($nick, 0, $this->config['ircd']['nicklen']); 817 | return true; 818 | } 819 | 820 | function checkRealName(&$nick){ 821 | if(empty($nick)) 822 | return false; 823 | if(!preg_match($this->rnRegex, $nick)) 824 | return false; 825 | return true; 826 | } 827 | 828 | function getPendingWrites(){ 829 | $ret = array(); 830 | foreach($this->_clients as $user){ 831 | if(count($user->buffer)!==0) 832 | $ret[] = $user; 833 | } 834 | return $ret; 835 | } 836 | 837 | function getUserByNick($n){ 838 | $n = strtolower($n); 839 | foreach($this->_clients as $u){ 840 | if(strtolower($u->nick) == $n) 841 | return $u; 842 | } 843 | return false; 844 | } 845 | 846 | function getUserBySocket($s){ 847 | foreach($this->_clients as $u){ 848 | if($u->socket == $s) 849 | return $u; 850 | } 851 | return false; 852 | } 853 | 854 | function isupport($user){ 855 | $chanmodes = array(); 856 | foreach($this->chanModes as $m) 857 | if($m->type != Mode::TYPE_P) 858 | @$chanmodes[$m->type] .= $m->letter; 859 | ksort($chanmodes); 860 | $chanmodes = implode(',', $chanmodes); 861 | $_005 = array( 862 | 'MAXCHANNELS' => 20, 863 | 'CHANLIMIT' => $this->config['ircd']['chanlimit'], 864 | 'MAXLIST' => "b:{$this->config['ircd']['maxbans']},e:{$this->config['ircd']['maxexcepts']},I:{$this->config['ircd']['maxinviteexcepts']}", 865 | 'NICKLEN' => $this->config['ircd']['nicklen'], 866 | 'CHANNELLEN' => $this->config['ircd']['chanlen'], 867 | 'TOPICLEN' => $this->config['ircd']['topiclen'], 868 | 'KICKLEN' => $this->config['ircd']['kicklen'], 869 | 'AWAYLEN' => $this->config['ircd']['awaylen'], 870 | 'MAXTARGETS' => 20, 871 | 'CHANTYPES' => $this->config['ircd']['chantypes'], 872 | 'PREFIX' => "(qaohv)~&@%+", 873 | 'NETWORK' => $this->config['me']['network'], 874 | 'CHANMODES' => $chanmodes, 875 | 'WALLCHOPS' => '', 876 | 'WATCH' => $this->config['ircd']['maxwatch'], 877 | 'WATCHOPTS' => 'A', 878 | 'SILENCE' => $this->config['ircd']['maxsilence'], 879 | 'MODES' => $this->config['ircd']['maxmodes'], 880 | 'CASEMAPPING' => "ascii", 881 | 'EXTBAN' => "~,cqnr", 882 | 'ELIST' => "MNUCT", 883 | 'STATUSMSG' => "~&@%+", 884 | 'EXCEPTS' => '', 885 | 'INVEX' => '' 886 | ); 887 | $pre = ":{$this->servname} 005 {$user->nick} "; 888 | $post = "are supported by this server"; 889 | while (count($_005) != 0){ 890 | $len = $nlen = strlen($pre.$post); 891 | $send = ""; 892 | for($i=0;$i<2;$i++){ 893 | foreach($_005 as $k=>$v){ 894 | $str = $k.(!empty($v)?"=$v":'').' '; 895 | if($nlen+strlen($str) <= 510){ 896 | $send .= $str; 897 | unset($_005[$k]); 898 | } 899 | } 900 | } 901 | $user->send($pre.$send.$post); 902 | } 903 | } 904 | 905 | function nickInUse($nick){ 906 | $nick = strtolower($nick); 907 | foreach($this->_clients as $id=>$user){ 908 | if(strtolower($user->nick) == $nick) 909 | return true; 910 | } 911 | return false; 912 | } 913 | 914 | function operList(){ 915 | $opers = array(); 916 | foreach($this->_clients as $i=>$c) 917 | if($c->oper) 918 | $opers[] = $i; 919 | return $opers; 920 | } 921 | 922 | function runUserHooks($hook, &$data){ 923 | foreach($this->userModes as $m) 924 | if(isset($m->hooks[$hook])) 925 | if(!$m->hooks[$hook]($data)) 926 | return false; 927 | return true; 928 | } 929 | function runChannelHooks($hook, &$data){ 930 | foreach($this->chanModes as $m) 931 | if(isset($m->hooks[$hook])) 932 | if(!$m->hooks[$hook]($data)) 933 | return false; 934 | return true; 935 | } 936 | 937 | function stripColon(&$p){ 938 | if($p[0] == ":") 939 | $p = substr($p, 1); 940 | } 941 | 942 | }// end class 943 | 944 | ?> 945 | -------------------------------------------------------------------------------- /classes/mode.class.php: -------------------------------------------------------------------------------- 1 | letter = $letter; 68 | $this->type = $type; 69 | $this->target = $target; 70 | foreach($hooks as $n => $f) 71 | $this->registerHook($n, $f); 72 | if($extra){ 73 | foreach($extra as $k=>$v) 74 | $this->{$k} = $v; 75 | } 76 | } 77 | 78 | function registerHook($n, $f){ 79 | $this->hooks[$n] = $f; 80 | } 81 | 82 | 83 | } 84 | 85 | ?> 86 | -------------------------------------------------------------------------------- /classes/user.class.php: -------------------------------------------------------------------------------- 1 | socket = $sock; 26 | $this->ssl = $ssl; 27 | $ip = stream_socket_get_name($this->socket, true); 28 | $c = strrpos($ip, ":"); 29 | $this->ip = substr($ip, 0, $c); 30 | $this->lastping = $this->lastpong = time(); 31 | } 32 | 33 | function __destruct(){ 34 | 35 | } 36 | 37 | function addChannel($chan){ 38 | $this->channels[] = $chan->name; 39 | } 40 | 41 | function disconnect(){ 42 | global $ircd; 43 | fclose($this->socket); 44 | unset($ircd->_clients[$this->id]); 45 | unset($this); 46 | } 47 | 48 | function getModes(){ 49 | $modes = '+'; 50 | $extra = array(); 51 | foreach($this->modes as $m=>$e){ 52 | if(is_array($e)) 53 | continue; 54 | $modes .= $m; 55 | if($e !== true) 56 | $extra[] = $e; 57 | } 58 | return $modes.' '.implode(' ', $extra); 59 | } 60 | 61 | function hasMode($m, $t=false){ 62 | global $ircd; 63 | if(isset($this->modes[$m])) 64 | if($ircd->userModes[$m]->type == Mode::TYPE_A || $ircd->userModes[$m]->type == Mode::TYPE_P) 65 | return in_array($t, $this->modes[$m]); 66 | else 67 | return true; 68 | return false; 69 | } 70 | 71 | function maskHost(){ 72 | global $ircd; 73 | $address = explode('.',$this->address); 74 | if(count($address) == 2 && strlen($address['1']) < 5){ 75 | //mask things like slowbro.org, minecraft.net, etc 76 | $address['0'] = $ircd->config['ircd']['hostmask_prefix']. 77 | '-'. 78 | strtoupper(substr(hash('sha512', $ircd->config['ircd']['hostmask_secret'].$address['0']), 0, $ircd->config['ircd']['hostmask_length'])); 79 | $address = implode('.', $address); 80 | } elseif((count($address) == 2 && strlen($address['1']) > 5) || count($address) == 1){ 81 | //mask things like localhost.localdomain, localhost, myhost, etc 82 | $address = implode('.', $address); 83 | $address = $ircd->config['ircd']['hostmask_prefix']. 84 | '-'. 85 | strtoupper(substr(hash('sha512', $ircd->config['ircd']['hostmask_secret'].$address), 0, $ircd->config['ircd']['hostmask_length'])); 86 | } elseif(preg_match($ircd->ipv4Regex, $this->address)){ 87 | // mask ipv4 address 88 | foreach($address as &$a){ 89 | $a = strtoupper(substr(hash('sha512', $ircd->config['ircd']['hostmask_secret'].$a), 0, $ircd->config['ircd']['hostmask_length'])); 90 | } 91 | $address['3'] = "IP"; 92 | $address = implode('.', $address); 93 | } else { 94 | //mask things like ip20-30-60-90.phx.tc.cox.net, ip20.30.60.90gr1.phx.west.verizon.net, ect 95 | $last = 0; 96 | $mask = array(); 97 | foreach($address as $k=>$a){ 98 | if(preg_match("[0-9]", $a)){ 99 | $last = $k; 100 | } 101 | } 102 | if($last == 0) 103 | $last = 2; 104 | if($last == count($address)-1) 105 | $last -=1; 106 | for($i=0;$i<=$last;$i++){ 107 | $mask[] = $address[$i]; 108 | unset($address[$i]); 109 | } 110 | $mask = $ircd->config['ircd']['hostmask_prefix']. 111 | '-'. 112 | strtoupper(substr(hash('sha512', $ircd->config['ircd']['hostmask_secret'].implode('.', $mask)), 0, $ircd->config['ircd']['hostmask_length'])); 113 | $address = $mask.'.'.implode('.', $address); 114 | } 115 | $this->prefix = $this->nick."!".$this->username."@".$address; 116 | $this->setModes("+x"); 117 | } 118 | 119 | function removeChannel($chan){ 120 | if(($k = array_search($chan->name, $this->channels)) !== FALSE) 121 | unset($this->channels[$k]); 122 | } 123 | 124 | function send($msg){ 125 | $this->buffer[] = $msg; 126 | } 127 | 128 | function setMode($act, $mode, &$parts, &$what){ 129 | global $ircd; 130 | $atext = ($act=='+'?'set':'unset'); 131 | //first, check set/unset hook 132 | if(isset($mode->hooks[$atext])){ 133 | $d = array('user'=>&$this, 'extra'=>@$parts['0']); 134 | if(!$mode->hooks[$atext]($d)){ 135 | if(isset($d['errno'])) 136 | $ircd->error($d['errno'], $this, @$d['errstr']); 137 | return false; 138 | } 139 | } 140 | $ms = isset($this->modes[$mode->letter]); 141 | if($mode->type == Mode::TYPE_A || $mode->type == Mode::TYPE_P){ 142 | // there are no TYPE_A or TYPE_P usermodes 143 | return false; 144 | } elseif($mode->type == Mode::TYPE_B || $mode->type == Mode::TYPE_C){ 145 | // only mode +s (TYPE_C) 146 | $pt = array_shift($parts); 147 | // 1: ignore if ((regex is set AND !match regex) OR param is missing) AND (act is add OR type is TYPE_B) OR... 148 | // 2: ignore if mode is already set AND act is add OR... 149 | // 3: ignore if mode is not set AND act is remove 150 | if(((isset($mode->regex) && !preg_match(@$mode->regex, $pt)) || $pt === NULL ) && ($act == '+' || $mode->type == Mode::TYPE_B)) return false; 151 | if($ms && $act == '+') return false; 152 | if(!$ms && $act == '-') return false; 153 | if($act == '+') 154 | $this->modes[$mode->letter] = $pt; 155 | else 156 | unset($this->modes[$mode->letter]); 157 | $what['params'][] = $pt; 158 | } elseif($mode->type == Mode::TYPE_D){ 159 | // ignore not already set 160 | if(($act == '+' && $ms) || ($act == '-' && !$ms)) 161 | return false; 162 | if($act == '+') 163 | $this->modes[$mode->letter] = true; 164 | else 165 | unset($this->modes[$mode->letter]); 166 | } 167 | $what[] = $act.$mode->letter; 168 | return true; 169 | } 170 | 171 | function setModes($mask){ 172 | global $ircd; 173 | $parts = explode(" ", $mask); 174 | $mask = str_split(array_shift($parts)); 175 | $act = ""; 176 | $what = array(); 177 | foreach($mask as $c){ 178 | if($c == '+' || $c == '-'){ 179 | $act = $c; 180 | continue; 181 | } 182 | if($act == "") 183 | continue; 184 | if(!($mode = $ircd->userModes[$c])){ 185 | $ircd->error(472, $user, $c); 186 | continue; 187 | } 188 | $this->setMode($act, $mode, $parts, $what); 189 | } 190 | $p = @implode(' ',@$what['params']); 191 | unset($what['params']); 192 | $ta = $str = ""; 193 | foreach($what as $w){ 194 | if($ta != ($a = substr($w, 0, 1))) 195 | $str .= $ta = $a; 196 | $str .= substr($w, 1, 2); 197 | } 198 | if(!empty($str)) 199 | $this->send(":{$this->nick} MODE {$this->nick} ".$str.(!empty($p)?' '.$p:'')); 200 | } 201 | 202 | function writeBuffer(){ 203 | global $ircd; 204 | foreach($this->buffer as $k=> $msg){ 205 | $ircd->write($this->socket, $msg); 206 | unset($this->buffer[$k]); 207 | } 208 | } 209 | 210 | } 211 | 212 | ?> 213 | -------------------------------------------------------------------------------- /config.ini: -------------------------------------------------------------------------------- 1 | [core] 2 | ; non-ssl listen address(es). 0.0.0.0 for all available interfaces, port after colon (:), separate by comma 3 | listen = "0.0.0.0:6600,0.0.0.0:6601" 4 | ; ssl listen address(es). same as above, leave blank for none. 5 | listen_ssl = "0.0.0.0:6602" 6 | ; debug? true/false 7 | debug = "true" 8 | 9 | [me] 10 | ; this server"s name 11 | servername = "irc.phpircd.org" 12 | ; the network"s name 13 | network = "phpircd" 14 | ; the owner of this server 15 | ownername = "slowbro" 16 | ; and their email 17 | owneremail = "slowbro@chatnets.net" 18 | ; when the server was created. you can put anything here, will be displayed on connect as "This server was created ____" 19 | created = "July 7, 2010 at 00:00:00 UTC" 20 | 21 | [ircd] 22 | ; how many seconds between pings 23 | pingfreq = 40 24 | ; how long to wait before a client is dropped for not responding to a PING 25 | pingout = 20 26 | ; max clients supported by the server. set 0 for no limit. 27 | maxusers = 1024 28 | ; channel prefixes allowed, valid types are #,& 29 | chantypes = "#" 30 | ; max channel length 31 | chanlen= 30 32 | ; max nick length 33 | nicklen = 30 34 | ; channel join limit. can be grouped, ie #&:30 or seperate #:30,&:10 35 | chanlimit = "#:50" 36 | ; max bans for any one channel 37 | maxbans = 30 38 | ; max excepts for any one channel 39 | maxexcepts = 30 40 | ; max invite exceptions for any one channel 41 | maxinviteexcepts = 30 42 | ; max modes with parameter accepted by the MODE command 43 | maxmodes = 12 44 | ; max WATCHes 45 | maxwatch = 128 46 | ; max SILENCE list length 47 | maxsilence = 15 48 | ; max channel topic length 49 | topiclen = 307 50 | ; max kick message length 51 | kicklen = 307 52 | ; max away message length 53 | awaylen = 307 54 | ; enable host masking? on/off 55 | hostmask = "on" 56 | ; host masking prefix. 57 | hostmask_prefix = "phpircd" 58 | ; host masking secret. give me a long string of random shit here 59 | hostmask_secret = "OJhc9L92qArRdfZKOPs2DG4sbBRXIETnCJ0gLJ8adKRLK9ceyWhhSxuDSFOP8L7tHN9vpj0HAqIPpmKj" 60 | ; host masking length. Determines length of the hashed mask that is added to the end of the prefix. 61 | hostmask_length = 7 62 | -------------------------------------------------------------------------------- /include/modes.php: -------------------------------------------------------------------------------- 1 | new Mode('a','user',Mode::TYPE_D), 5 | 'A' => new Mode('A','user',Mode::TYPE_D), 6 | 'b' => new Mode('b','user',Mode::TYPE_D), 7 | 'B' => new Mode('B','user',Mode::TYPE_D), 8 | 'C' => new Mode('C','user',Mode::TYPE_D), 9 | 'd' => new Mode('d','user',Mode::TYPE_D), 10 | 'g' => new Mode('g','user',Mode::TYPE_D), 11 | 'G' => new Mode('G','user',Mode::TYPE_D), 12 | 'h' => new Mode('h','user',Mode::TYPE_D), 13 | 'H' => new Mode('H','user',Mode::TYPE_D), 14 | 'i' => new Mode('i','user',Mode::TYPE_D), 15 | 'N' => new Mode('N','user',Mode::TYPE_D), 16 | 'o' => new Mode('o','user',Mode::TYPE_D), 17 | 'O' => new Mode('O','user',Mode::TYPE_D), 18 | 'p' => new Mode('p','user',Mode::TYPE_D), 19 | 'q' => new Mode('q','user',Mode::TYPE_D), 20 | 'r' => new Mode('r','user',Mode::TYPE_D), 21 | 'R' => new Mode('R','user',Mode::TYPE_D), 22 | 's' => new Mode('s','user',Mode::TYPE_C), 23 | 't' => new Mode('t','user',Mode::TYPE_D), 24 | 'T' => new Mode('T','user',Mode::TYPE_D), 25 | 'w' => new Mode('w','user',Mode::TYPE_D), 26 | 'W' => new Mode('W','user',Mode::TYPE_D), 27 | 'x' => new Mode('x','user',Mode::TYPE_D, array( 28 | 'connect' => function(&$d){ 29 | if($d['ircd']->config['ircd']['hostmask'] == "on") 30 | $d['user']->maskHost(); 31 | } 32 | )), 33 | 'z' => new Mode('z','user',Mode::TYPE_D, array( 34 | 'connect' => function(&$d){ 35 | if($d['user']->ssl) 36 | $d['user']->setModes('+z'); 37 | }, 38 | 'set' => function(&$d){ 39 | if(!$d['user']->ssl) 40 | return false; 41 | return true; 42 | }, 43 | 'unset' => function(&$d){ 44 | if($d['user']->ssl) 45 | return false; 46 | return true; 47 | } 48 | )) 49 | ); 50 | 51 | $channelModes = array( 52 | 'a' => new Mode('a','channel',Mode::TYPE_P, array( 53 | 'set' => function(&$d){ 54 | $d['errno'] = 499; 55 | if(!$d['chan']->hasPrivs($d['user'], Mode::CHANNEL_MODE_a)) 56 | return false; 57 | return true; 58 | } 59 | ), array( 60 | 'weight'=>10, 61 | 'prefix'=>'&', 62 | 'privs'=> Mode::CHANNEL_AOP | Mode::CHANNEL_OP | Mode::CHANNEL_HOP | Mode::CHANNEL_VOICE 63 | )), 64 | 'A' => new Mode('A','channel',Mode::TYPE_D), 65 | 'b' => new Mode('b','channel',Mode::TYPE_A, array( 66 | 'join'=> function(&$d){ 67 | $d['errno'] = 404; 68 | $d['errstr'] = ": You are banned (+b) ({$d['chan']->name})"; 69 | if($d['chan']->isBanned($d['user'])) 70 | return false; 71 | return true; 72 | } 73 | )), 74 | 'c' => new Mode('c','channel',Mode::TYPE_D), 75 | 'C' => new Mode('C','channel',Mode::TYPE_D), 76 | 'e' => new Mode('e','channel',Mode::TYPE_A), 77 | 'f' => new Mode('f','channel',Mode::TYPE_C), 78 | 'G' => new Mode('G','channel',Mode::TYPE_D), 79 | 'h' => new Mode('h','channel',Mode::TYPE_P, array(), array( 80 | 'weight'=>2, 81 | 'prefix'=>'%', 82 | 'privs'=> Mode::CHANNEL_HOP | Mode::CHANNEL_VOICE 83 | )), 84 | 'H' => new Mode('H','channel',Mode::TYPE_D), 85 | 'i' => new Mode('i','channel',Mode::TYPE_D), 86 | 'I' => new Mode('I','channel',Mode::TYPE_A), 87 | 'J' => new Mode('J','channel',Mode::TYPE_C), 88 | 'k' => new Mode('k','channel',Mode::TYPE_B), 89 | 'K' => new Mode('K','channel',Mode::TYPE_D), 90 | 'l' => new Mode('l','channel',Mode::TYPE_C), 91 | 'L' => new Mode('L','channel',Mode::TYPE_C), 92 | 'm' => new Mode('m','channel',Mode::TYPE_D, array( 93 | 'privmsg' => function(&$d){ 94 | $d['errno'] = 404; 95 | $d['errstr'] = ":You need voice (+v) ({$d['chan']->name})"; 96 | if($d['chan']->hasMode('m')) 97 | if(!$d['chan']->hasPrivs($d['user'], Mode::CHANNEL_VOICE)) 98 | return false; 99 | return true; 100 | } 101 | )), 102 | 'M' => new Mode('M','channel',Mode::TYPE_D), 103 | 'n' => new Mode('n','channel',Mode::TYPE_D), 104 | 'N' => new Mode('N','channel',Mode::TYPE_D), 105 | 'o' => new Mode('o','channel',Mode::TYPE_P, array( 106 | 'set' => function(&$d){ 107 | $d['errno'] = 482; 108 | if(!$d['chan']->hasPrivs($d['user'], Mode::CHANNEL_MODE_o)) 109 | return false; 110 | return true; 111 | } 112 | ), array( 113 | 'weight'=>5, 114 | 'prefix'=>'@', 115 | 'privs'=>Mode::CHANNEL_OP | Mode::CHANNEL_HOP | Mode::CHANNEL_VOICE 116 | )), 117 | 'O' => new Mode('O','channel',Mode::TYPE_D), 118 | 'p' => new Mode('p','channel',Mode::TYPE_D), 119 | 'q' => new Mode('q','channel',Mode::TYPE_P, array(), array( 120 | 'weight'=>20, 121 | 'prefix'=>'~', 122 | 'privs'=>Mode::CHANNEL_QOP | Mode::CHANNEL_AOP | Mode::CHANNEL_OP | Mode::CHANNEL_HOP | Mode::CHANNEL_VOICE 123 | )), 124 | 'Q' => new Mode('Q','channel',Mode::TYPE_D), 125 | 'r' => new Mode('r','channel',Mode::TYPE_D), 126 | 'R' => new Mode('R','channel',Mode::TYPE_D, array( 127 | 'join' => function(&$d){ 128 | $d['errno'] = 404; 129 | $d['errstr'] = ":You must be registered with services to join (+r)"; 130 | if($d['chan']->hasMode('R') && !$d['user']->hasMode('r')) 131 | return false; 132 | return true; 133 | } 134 | )), 135 | 's' => new Mode('s','channel',Mode::TYPE_D), 136 | 'S' => new Mode('S','channel',Mode::TYPE_D), 137 | 't' => new Mode('t','channel',Mode::TYPE_D), 138 | 'u' => new Mode('u','channel',Mode::TYPE_D), 139 | 'v' => new Mode('v','channel',Mode::TYPE_P, array( 140 | 'set' => function(&$d){ 141 | $d['errstr'] = "+v"; 142 | $d['errno'] = 482; 143 | if(!$d['chan']->hasPrivs($d['user'], Mode::CHANNEL_MODE_v)) 144 | return false; 145 | $d['errno'] = 431; 146 | if(!isset($d['extra'])) 147 | return false; 148 | $d['errstr'] = $d['extra']; 149 | $d['errno'] = 401; 150 | if(!$d['chan']->userInChan($d['extra'])) 151 | return false; 152 | return true; 153 | }, 154 | 'unset' => function(&$d){ 155 | $d['errstr'] = "-v"; 156 | if($d['user']->nick == $d['extra']) 157 | return true; 158 | $d['errno'] = 482; 159 | if(!$d['chan']->hasPrivs($d['user'], Mode::CHANNEL_MODE_v)) 160 | return false; 161 | $d['errno'] = 431; 162 | if(!isset($d['extra'])) 163 | return false; 164 | $d['errstr'] = $d['extra']; 165 | $d['errno'] = 401; 166 | if(!$d['chan']->userInChan($d['extra'])) 167 | return false; 168 | return true; 169 | } 170 | ), array( 171 | 'weight'=>1, 172 | 'prefix'=>'+', 173 | 'privs'=> Mode::CHANNEL_VOICE 174 | )), 175 | 'V' => new Mode('V','channel',Mode::TYPE_D, array( 176 | 'invite' => function(&$d){ 177 | $d['errno'] = 0; 178 | $d['errstr'] = false; 179 | if($d['chan']->hasMode('V') && !$d['chan']->hasPrivs($d['user'], Mode::CHANNEL_MODE_V)) 180 | return false; 181 | return true; 182 | } 183 | )), 184 | 'z' => new Mode('z','channel',Mode::TYPE_D, array( 185 | 'join' => function(&$d){ 186 | $d['errno'] = 404; 187 | $d['errstr'] = ":You must be connected via SSL to join (+z)"; 188 | if($d['chan']->hasMode('z') && !$d['user']->hasMode('z')) 189 | return false; 190 | return true; 191 | } 192 | )) 193 | ); 194 | 195 | ?> 196 | -------------------------------------------------------------------------------- /ircd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/php -q 2 | _sockets, $ircd->_ssl_sockets); 11 | foreach($ircd->_clients as $i => $c) 12 | $reads[] = $writes[] = $c->socket; 13 | if(stream_select($reads, $writes, $un=null, 0, 200) > 0){ 14 | //read-loop (plain) 15 | foreach($reads as $key => $value){ 16 | //accept new (plain) 17 | if(array_search($value, $ircd->_sockets) !== FALSE){ 18 | $ircd->accept($value); 19 | unset($reads[$key]); 20 | continue; 21 | } 22 | 23 | //accept new (ssl) 24 | if(array_search($value, $ircd->_ssl_sockets) !== FALSE){ 25 | $ircd->accept($value, true); 26 | unset($reads[$key]); 27 | continue; 28 | } 29 | 30 | //process user 31 | $user = $ircd->getUserBySocket($value); 32 | $buf = $ircd->read($value); 33 | if(false !== $buf){ 34 | if(!empty($buf)){ 35 | if(strpos($buf, "\n") === FALSE){ 36 | $user->readBuffer[] = $buf; 37 | unset($reads[$key]); 38 | continue; 39 | } 40 | if(count($user->readBuffer) != 0){ 41 | $buf = implode('', $user->readBuffer).$buf; 42 | $user->readBuffer = array(); 43 | } 44 | $data = trim($buf); 45 | $data = explode("\n", str_replace("\r","\n", $data)); 46 | foreach($data as $d){ 47 | $d = trim($d); 48 | if(!empty($d)) 49 | if($user->registered === TRUE) 50 | $ircd->process($d, $user); 51 | else 52 | $ircd->newConnection($d, $user); 53 | } 54 | unset($reads[$key]); 55 | } else { 56 | $ircd->debug("Closing Link: client ".(empty($user->prefix)?$user->address:$user->prefix).": Client Exited"); 57 | unset($reads[$key]); 58 | $ircd->quit($user, "Client Exited"); 59 | } 60 | } else { 61 | $ircd->debug("Closing Link: client {$user->prefix}: ".socket_strerror(socket_last_error($value))); 62 | unset($reads[$key]); 63 | $ircd->quit($user, socket_strerror(socket_last_error($value))); 64 | } 65 | }//end read-loop (plain) 66 | 67 | //write-loop (plain) 68 | $pending = $ircd->getPendingWrites(); 69 | foreach($pending as $user){ 70 | if(in_array($user->socket, $writes)){ 71 | $user->writeBuffer(); 72 | } 73 | }//end write-loop (plain) 74 | }//end if socket_select 75 | //do this every iteration regardless of socket state 76 | foreach($ircd->_clients as $id => $user){ 77 | $now = time(); 78 | if(($now - $user->lastpong) > $ircd->config['ircd']['pingfreq']){ 79 | if(!$user->registered){ 80 | $ircd->write($user->socket,"ERROR: Closing link: ".$user->prefix." (Ping timeout)"); 81 | $ircd->debug("Closing Link: client {$user->prefix} (Ping Timeout)"); 82 | $ircd->quit($user, 'Ping Timeout'); 83 | } else { 84 | if($user->lastping <= $user->lastpong){ 85 | $ircd->ping($user, $ircd->servname, TRUE); 86 | $user->lastping = $now; 87 | } 88 | } 89 | } 90 | if($now > ($user->lastpong + $ircd->config['ircd']['pingfreq'] + $ircd->config['ircd']['pingout'])){ 91 | $ircd->debug("Closing Link: client {$user->address} (Ping Timeout)"); 92 | $ircd->quit($user, 'Ping Timeout'); 93 | } 94 | } 95 | usleep(50); 96 | } 97 | ?> 98 | -------------------------------------------------------------------------------- /motd.txt: -------------------------------------------------------------------------------- 1 | _ ___ ____ ____ _ 2 | _ __ | |__ _ __|_ _| _ \ / ___|__| | 3 | | '_ \| '_ \| '_ \| || |_) | | / _` | 4 | | |_) | | | | |_) | || _ <| |__| (_| | 5 | | .__/|_| |_| .__/___|_| \_\\____\__,_| 6 | |_|holy crap|_|it's an ircd in php lol 7 | 8 | Rules: 9 | 10 | * Do good stuff * Don't do bad stuff 11 | 12 | * Chat Hard * Praise all opers 13 | -------------------------------------------------------------------------------- /opers.ini: -------------------------------------------------------------------------------- 1 | [slowbro] 2 | ; oper pass 3 | pass = 'phpircd' 4 | ; password encryption method: plain, md5, sha1 5 | pass-enc = 'plain' 6 | ; oper flags, see documentation 7 | flags = 'NaOoKkGg' 8 | ; must macth this host to oper up (use *@* for any host) 9 | userhost = '*@127.0.0.1' 10 | ; server notices 11 | snomask = '' 12 | 13 | --------------------------------------------------------------------------------