0 /* See LICENSE file for copyright and license details. */

1 module proxy;

2 import std.socket : InternetAddress, Socket, SocketException, SocketSet, TcpSocket, SocketAcceptException;

3 import logger;

4 import client;

5 import std.format;

6 import std.algorithm : remove;

7 import std.file;

8 import std.json;

9 import std.stdio;

10 import std.exception;

11 import std.algorithm;

12 import core.time;

13 import core.thread;

14 import std.parallelism;

15 import std.conv;

16 import std.array;

17 import std.string;

18

19 /// Default port of the proxy server

20 const ushort DEFAULTPORT = 6787;

21

22 /// Default number of maximum connections on the proxy server

23 const ushort DEFAULTMAXCONNECTIONS = 512;

24

25 /// Number of maximum connections allowed for a process

26 const ushort MAXCONNECTIONS = 1024;

27

28 /// SOCKS5 proxy server

29 class Proxy {

30 private:

31

32 struct PortRange {

33 int start;

34 int end;

35 }

36

37 bool bound=false;

38 TcpSocket listener;

39 Client[] clients;

40 int maxConnections = DEFAULTMAXCONNECTIONS;

41 ushort port;

42 string[] blacklist;

43 string[] whitelist;

44 PortRange[] portBlacklist;

45 PortRange[] portWhitelist;

46 string[] clientBlacklist;

47 string[] clientWhitelist;

48 long timeoutEstablished;

49 long timeout;

50

51 bool isIn(string str1, string str2) {

52 long len = str2.length;

53 len--;

54 if(len<=0) {

55 if(len==0&&str2[0]=='*')

56 return true;

57 return false;

58 }

59 if(str2[0]=='*'&&str2[len]=='*') {

60 if(find(str1,str2[1..len]).length!=0) return true;

61 } else if(str2[0]=='*') {

62 if(str1.endsWith(str2[1..str2.length])) return true;

63 } else if(str2[len]=='*') {

64 if(str1.startsWith(str2[0..len])) return true;

65 } else if(str1==str2) {

66 return true;

67 }

68 return false;

69 }

70

71 bool isIn(int port, PortRange ports) {

72 return ports.start<=port && ports.end>=port;

73 }

74

75 PortRange stringToPort(string str) {

76 PortRange ports = {-1,-1};

77 auto index = indexOf(str,'-');

78 long len = str.length;

79 if(len<=0) return ports;

80 if(str=="*") {

81 ports.start=0;

82 ports.end=65_565;

83 } else if(isNumeric(str)) {

84 const int i = to!int(str);

85 if(i<0) {

86 ports.start=0;

87 ports.end=-i;

88 } else

89 ports.start=ports.end=i;

90 } else if(str[len-1]=='-' && isNumeric(str[0..len-1])) {

91 const int i = to!int(str[0..len-1]);

92 if(i>=0) {

93 ports.start=i;

94 ports.end=65_565;

95 }

96 } else if(index!=-1&&index==lastIndexOf(str,'-')&&isNumeric(str[0..index])&&

97 isNumeric(str[indexOf(str,'-')..str.length])) {

98 ports.start=to!int(str[0..index]);

99 ports.end=to!int(str[index+1..str.length]);

100 }

101 return ports;

102 }

103

104 public:

105 /// Initialize listener sockets and assign port to the default 6787

106 this() {

107 port = DEFAULTPORT;

108

109 listener = new TcpSocket();

110 assert(listener.isAlive);

111 listener.blocking = false;

112

113 }

114

115 /// Initialize the server by loading the config and binding the port

116 void initialize(string path) {

117 if(!loadConfig(path)) {

118 createConfig(path);

119 }

120 try {

121 listener.bind(new InternetAddress(port));

122 listener.listen(10);

123 Logger.info(format("Listening on port %d",port));

124 bound=true;

125 } catch(SocketException) {

126 Logger.error(format("Failed to bind socket on port %d", port));

127 }

128 }

129

130 /// Return timeout

131 long getTimeout(bool isEstablished) {

132 return isEstablished?timeoutEstablished:timeout;

133 }

134

135 /// Create config to path

136 bool createConfig(string path) {

137

138 JSONValue config = [ "Port": DEFAULTPORT ];

139 config["MaxConnections"] = DEFAULTMAXCONNECTIONS;

140 config["DisplayTime"] = true;

141 config["PrintLog"] = true;

142 config["WriteLog"] = false;

143 config["PathLog"] = "/var/log/rxproxy.log";

144 config["Timeout"] = ["Established":600,"Unestablished":5];

145 config["Whitelist"] = ["Client":"","Domain":"","Port":""];

146 config["Blacklist"] = ["Client":"","Domain":"","Port":""];

147 config["Whitelist"]["Client"] = JSONValue(["localhost"]);

148 config["Blacklist"]["Client"] = JSONValue(["*"]);

149 config["Whitelist"]["Domain"] = JSONValue(["youtube.googleapis.com"]);

150 config["Blacklist"]["Domain"] = JSONValue(["*googleapis.com"]);

151 config["Blacklist"]["Port"] = JSONValue(["*","1-65565"]);

152 config["Whitelist"]["Port"] = JSONValue(["80","443"]);

153 File f;

154 try {

155 f = File(path, "w");

156 f.write(config.toPrettyString());

157 } catch(ErrnoException) {

158 Logger.error("Failed to create config file");

159 return false;

160 }

161 Logger.info("Config file "~path~" created");

162 return true;

163 }

164

165 /// Load config file from path

166 bool loadConfig(string path) {

167 string data;

168 try {

169 data = readText(path);

170 } catch(FileException) {

171 Logger.error("Can't read config file "~path);

172 return false;

173 }

174 try {

175 JSONValue config = parseJSON(data);

176 port = cast(ushort)config["Port"].integer;

177 maxConnections = cast(ushort)config["MaxConnections"].integer;

178 Logger.setParameters(config["DisplayTime"].boolean, config["WriteLog"].boolean,

179 config["PrintLog"].boolean, config["PathLog"].str);

180 if(maxConnections>MAXCONNECTIONS) {

181 Logger.warning(format(

182 "Config file has a number of maximum connection above the allowed number. The value was set to %d",

183 MAXCONNECTIONS));

184 maxConnections=MAXCONNECTIONS;

185 }

186

187 if("Timeout" !in config) {

188 timeout = config["Timeout"]["Unestablished"].integer;

189 timeoutEstablished = config["Timeout"]["Established"].integer;

190 }

191

192 if("Whitelist" !in config) {

193 Logger.warning("Config file "~path~" contains no whitelist");

194 } else {

195 foreach (JSONValue str; config["Whitelist"]["Domain"].array)

196 whitelist = whitelist ~ str.str;

197 foreach (JSONValue str; config["Whitelist"]["Client"].array)

198 clientWhitelist = clientWhitelist ~ str.str;

199 foreach (JSONValue str; config["Whitelist"]["Port"].array) {

200 const PortRange range = stringToPort(str.str);

201 if(range.end!=-1&&range.start!=-1) {

202 portWhitelist = portWhitelist ~ range;

203 }

204 }

205 }

206

207 if("Blacklist" !in config) {

208 Logger.warning("Config file "~path~" contains no blacklist");

209 } else {

210 foreach (JSONValue str; config["Blacklist"]["Domain"].array)

211 blacklist = blacklist ~ str.str;

212 foreach (JSONValue str; config["Blacklist"]["Client"].array)

213 clientBlacklist = clientBlacklist ~ str.str;

214 foreach (JSONValue str; config["Blacklist"]["Port"].array) {

215 const PortRange range = stringToPort(str.str);

216 if(range.end!=-1&&range.start!=-1) {

217 portBlacklist = portBlacklist ~ range;

218 }

219 }

220 }

221

222 } catch(JSONException) {

223 Logger.error("Config file "~path~" is invalid");

224 return false;

225 }

226 Logger.info("Config file "~path~" loaded");

227 return true;

228 }

229

230 /// Return true if the domain is blacklisted

231 bool isBlacklisted(string domain) {

232 auto list = whitelist.filter!(a => isIn(domain,a)).map!(a => a)().array();

233 if(list.length>0) return false;

234 list = blacklist.filter!(a => isIn(domain,a)).map!(a => a)().array();

235 return list.length>0;

236 }

237

238 /// Return true if the client is blacklisted

239 bool isClientBlacklisted(string domain) {

240 auto list = clientWhitelist.filter!(a => isIn(domain,a)).map!(a => a)().array();

241 if(list.length>0) return false;

242 list = clientBlacklist.filter!(a => isIn(domain,a)).map!(a => a)().array();

243 return list.length>0;

244 }

245

246 /// Return true if the port is blacklisted

247 bool isPortBlacklisted(int port) {

248 foreach (PortRange p; portWhitelist) {

249 if(isIn(port,p)) return false;

250 }

251 foreach (PortRange p; portBlacklist) {

252 if(isIn(port,p)) return true;

253 }

254 return false;

255 }

256

257 /// Listen for incoming connections

258 bool listen() {

259 if(!bound) return false;

260 auto socketSet = new SocketSet(maxConnections);

261 auto connectSet = new SocketSet(maxConnections);

262

263 while (true)

264 {

265 socketSet.add(listener);

266

267 foreach (client; clients) {

268 socketSet.add(client.socket);

269 if(client.isConnecting()) {

270 connectSet.add(client.remote);

271 }

272 if(client.isEstablished()) {

273 socketSet.add(client.remote);

274 }

275 }

276

277 const int ret = Socket.select(socketSet, connectSet, null, dur!"seconds"(1));

278 if(ret==-1) {

279 return false;

280 } else if(ret==0) {

281 socketSet.reset();

282 connectSet.reset();

283 continue;

284 }

285

286 for (size_t i = 0; i < clients.length; i++)

287 {

288 if (clients[i].shouldDisconnect()) {

289 clients[i].destroy();

290 clients = clients.remove(i);

291 i--;

292 continue;

293 }

294 if (clients[i].isConnecting() && connectSet.isSet(clients[i].remote)) {

295 clients[i].confirm();

296 }

297 if (clients[i].isEstablished() && socketSet.isSet(clients[i].remote))

298 {

299 ubyte[1024] buf;

300 auto datLength = clients[i].remote.receive(buf[]);

301 if (datLength == Socket.ERROR)

302 Logger.error("Connection error.");

303 else if (datLength != 0)

304 {

305 try {

306 clients[i].socket.send(buf[0..datLength]);

307 continue;

308 } catch(SocketException) {

309 Logger.error(format("Connection closed. (Total connections: %d)",clients.length));

310 }

311 }

312 clients[i].destroy();

313 clients = clients.remove(i);

314 i--;

315 continue;

316 }

317 if (socketSet.isSet(clients[i].socket))

318 {

319 ubyte[1024] buf;

320 auto datLength = clients[i].socket.receive(buf[]);

321

322 if (datLength == Socket.ERROR)

323 Logger.error("Connection error.");

324 else if (datLength != 0 && clients[i].feed(buf,datLength))

325 {

326 continue;

327 }

328 clients[i].destroy();

329 clients = clients.remove(i);

330 i--;

331 }

332 }

333

334 if (socketSet.isSet(listener))

335 {

336 Socket sn = null;

337 scope (failure)

338 {

339 Logger.error("Error accepting");

340

341 if (sn)

342 sn.close();

343 }

344 try {

345 sn = listener.accept();

346 } catch(SocketAcceptException e) {

347 socketSet.reset();

348 connectSet.reset();

349 Logger.error(format("Too much sockets. (Total connections: %d)",clients.length));

350 continue;

351 }

352

353 assert(sn.isAlive);

354 assert(listener.isAlive);

355 auto remote = sn.remoteAddress().toHostNameString();

356

357 if (isClientBlacklisted(remote)) {

358 Logger.warning(remote~" tried to connect but is blacklisted");

359 sn.close();

360 }

361 else if (clients.length < maxConnections-1)

362 {

363 Logger.info(format("Connection from %s established. (Total connections: %d)",

364 sn.remoteAddress().toString(),clients.length));

365 sn.blocking = false;

366 clients ~= new Client(sn,this);

367 }

368 else

369 {

370 Logger.warning(format("Rejected connection from %s; too many connections.",

371 sn.remoteAddress().toString()));

372 sn.close();

373 assert(!sn.isAlive);

374 assert(listener.isAlive);

375 }

376 }

377

378 socketSet.reset();

379 connectSet.reset();

380 }

381 }

382 }

383