-
Notifications
You must be signed in to change notification settings - Fork 13
/
server.php
177 lines (146 loc) · 5.62 KB
/
server.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
<?php
// IRCd (IRC Server) written in PHP
// written and copyright 2008 Daniel Danopia (danopia) <[email protected]> http://danopia.net/
// originally MonoNet protocol version 0.1 server
// originally written and copyright 2008 Nick Markwell (RockerMONO/duck)
// feel free to edit and distribute as long as you leave all original credit for this
require 'config.php'; // Open config file, read it over before you run the server!
$conn = array(); // Array of connections/clients
$channels = array(); // Info on each channel
/*
* Info on each user. Referenced by objects in $conn.
* Used to bugfix a problem concerning outdated user arrays in channels.
* Bug reported by b4, June 16, 2008 while testing /oper
* Bug fixed by danopia, June 16, 2008
*/
$u_info = array();
// socket_normal_read() was pasted off php.net, thanks to the
// poster. used to correctly read to newlines on win32
$sockets = array();
$queues = array();
$sock_num = 0;
function socket_normal_read($socket): bool|string
{
global $config, $sockets, $queues, $sock_num;
for ($i = 0; isset($sockets[$i]) && $socket != $sockets[$i]; $i++){
if (!isset($sockets[$i])) {
$sockets [$sock_num] = $socket;
$queues [$sock_num++] = "";
}
}
try {
$recv = socket_read($socket, $config['max_len']);
} catch (Error $e) {
return false;
}
//$recv = str_replace($recv, "\r", "");
if ($recv === "") {
if (!str_contains($queues[$i], $config['line_ending'])){
return false;
}
}
else if ($recv !== false) {
$queues[$i] .= $recv;
}
$pos = strpos($queues[$i], $config['line_ending']);
if ($pos === false){
return "";
}
$ret = substr($queues[$i], 0, $pos);
$queues[$i] = substr($queues[$i], $pos + 1);
return $ret;
}
// Validates a nick using regex (also controls max nick length)
function validate_nick($nick): bool|int
{
return preg_match('/^[a-zA-Z\[\]_|`^][a-zA-Z0-9\[\]_|`^]{0,29}$/', $nick);
}
// Validates a channel name using regex (also controls max channel length)
function validate_chan($chan): bool|int
{
return preg_match('/^#[a-zA-Z0-9`~!@#$%^&*()\'";|}{\]\[.<>?]{0,29}$/', $chan);
}
// Removes a value from array, thanks to duck for providing this function
function array_removal($val, &$arr): bool
{
$i = array_search($val, $arr);
if ($i === false)
return false;
$arr = array_merge(array_slice($arr, 0, $i), array_slice($arr, $i + 1));
return true;
}
// Removes a client with a certain nick from an array of clients, using array_removal
function nick_removal($nick, &$arr): bool | null
{
foreach ($arr as $id => $him) {
if (strtolower($him['nick']) == strtolower($nick)) {
return array_removal($him, $arr);
}
}
return null;
}
// Lookup a DNS record, with a timeout. Namely used for reverse DNS.
function dns_timeout($ip) {
$res = `nslookup -timeout=3 -retry=1 $ip`;
if (preg_match('/\nName:(.*)\n/', $res, $out)) {
return trim($out[1]);
} else {
return $ip;
}
}
// Kills a certian user, used by /kill, QUIT, netsplits, etc.
function kill($who, $reason) {
global $channels, $conn, $u_info;
$sentto = array($who); // Who received a QUIT packet already?
foreach ($channels as &$channel) { // Loop through channels
if (in_array($who, $channel['nicks'])) { // Was user x in this channel?
foreach ($channel['nicks'] as $user) { // Loop through channel nick list
if (!in_array($user, $sentto)) { // Make sure user didn't get QUIT yet
send($user, ':' . $who['nick'] . '!' . $who['ident'] . '@' . $u_info[spl_object_id($who['sock'])]['cloak'] . ' QUIT :' . $reason);
$sentto[] = $user;
}
}
nick_removal($who['nick'], $channel['nicks']); // Remove the killed user from the channel
}
}
send($who, 'ERROR :Closing Link: ' . $who['nick'] . '[' . $who['host'] . '] (' . $reason . ')');
try {
socket_close($who['sock']); // Close socket
} catch (Error $e) {
echo "Socket already closed.";
}
array_removal($u_info[spl_object_id($who['sock'])], $u_info); // Remove from the info array - part of the bugfix stated above
array_removal($who, $conn); // Remove socket from listing
}
// Send a packet to a user and log to console
function send($who, $text) {
try {
socket_write($who['sock'], $text . "\r\n");
echo $who['nick'] . ' >>> ' . $text . "\r\n";
} catch (Error $e) {
echo "sending message to client " . $who['nick'] . " failed";
echo $who['nick'] . ' >>> ' . $text . "\r\n";
}
}
// Level of error reporting in console
//error_reporting(E_ERROR | E_PARSE);
error_reporting(E_ALL);
set_time_limit(0); // Run forever
// Create listen socket and bind
$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
$sock_bind = socket_bind($sock, $config['bind_ip'], $config['port']);
if (false === $sock_bind) {
$error_code = socket_last_error();
$error_msg = socket_strerror($errorcode);
die("Could not create socket $error_code $error_msg");
}
socket_listen($sock, $config['max_users']);
// Nonblocking socket
socket_set_nonblock($sock);
// Old code, can be used to see the server status, removed as soon as danopia saw it xD
//file_put_contents("status.txt", "up");
echo "Running...";
// Main loop
while (socket_listen($sock, 5) !== false){
require 'connection_handler.php';
}