haxcurses.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  1. #!/usr/bin/env python3
  2. #
  3. # Internet Delay Chat client written in Python.
  4. #
  5. # Written by: Test_User <hax@andrewyu.org>
  6. #
  7. # This is free and unencumbered software released into the public
  8. # domain.
  9. #
  10. # Anyone is free to copy, modify, publish, use, compile, sell, or
  11. # distribute this software, either in source code form or as a compiled
  12. # binary, for any purpose, commercial or non-commercial, and by any
  13. # means.
  14. #
  15. # In jurisdictions that recognize copyright laws, the author or authors
  16. # of this software dedicate any and all copyright interest in the
  17. # software to the public domain. We make this dedication for the benefit
  18. # of the public at large and to the detriment of our heirs and
  19. # successors. We intend this dedication to be an overt act of
  20. # relinquishment in perpetuity of all present and future rights to this
  21. # software under copyright law.
  22. #
  23. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  24. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  25. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  26. # IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
  27. # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
  28. # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  29. # OTHER DEALINGS IN THE SOFTWARE.
  30. import threading
  31. import string
  32. import curses
  33. import socket
  34. import time
  35. def send(s, msg):
  36. return s.sendall(msg.encode("UTF-8", "surrogateescape") + b"\r\n")
  37. def recv(s):
  38. return s.recv(1024).decode("UTF-8", "surrogateescape")
  39. lock = threading.Lock()
  40. input_str = ""
  41. input_index = 0
  42. message_list = []
  43. message_index = 0
  44. prompt = ""
  45. prompt_len = 0
  46. def update_screen(stdscr, net_scr, user_scr):
  47. global message_list
  48. global message_index
  49. global input_str
  50. global input_index
  51. global prompt
  52. global prompt_len
  53. if len(message_list) > 0:
  54. net_scr.erase()
  55. i = 0
  56. for line in message_list[message_index]["messages"][::-1]:
  57. offset = -(
  58. -len("<" + line["source"] + "> " + line["message"])
  59. // curses.COLS
  60. )
  61. i += offset
  62. if i >= curses.LINES:
  63. if offset > 1 and i + 1 - offset < curses.LINES:
  64. tmp = curses.LINES + offset - i - 1
  65. net_scr.addstr(0, 0, str(tmp))
  66. break
  67. net_scr.addstr(
  68. curses.LINES - i - 1,
  69. 0,
  70. "<" + line["source"] + "> " + line["message"],
  71. )
  72. net_scr.refresh()
  73. prompt = "[to " + message_list[message_index]["username"] + "] "
  74. prompt_len = len(prompt)
  75. user_scr.erase()
  76. user_scr.addstr(0, 0, prompt + input_str)
  77. user_scr.move(0, prompt_len + input_index)
  78. user_scr.refresh()
  79. else:
  80. net_scr.erase()
  81. net_scr.refresh()
  82. prompt = "[no open buffers] "
  83. prompt_len = len(prompt)
  84. user_scr.erase()
  85. user_scr.addstr(0, 0, prompt + input_str)
  86. user_scr.move(0, prompt_len + input_index)
  87. user_scr.refresh()
  88. def listen_to_user(s, stdscr, net_scr, user_scr):
  89. global input_str
  90. global input_index
  91. global lock
  92. global message_list
  93. global message_index
  94. global prompt
  95. global prompt_len
  96. disallowed = ["\n", "\r", "\t", "\x0B", "\x0C"]
  97. lock.acquire()
  98. while True:
  99. lock.release()
  100. input = stdscr.getkey()
  101. lock.acquire()
  102. if len(input) > 1:
  103. if input == "KEY_DOWN":
  104. message_index = message_index + 1
  105. if (
  106. message_index >= len(message_list)
  107. and message_index != 0
  108. ):
  109. message_index = 0
  110. update_screen(stdscr, net_scr, user_scr)
  111. elif input == "KEY_UP":
  112. message_index = message_index - 1
  113. if message_index < 0:
  114. message_index = max(0, len(message_list) - 1)
  115. update_screen(stdscr, net_scr, user_scr)
  116. elif input == "KEY_LEFT":
  117. input_index = max(
  118. 0, min(len(input_str), input_index - 1)
  119. )
  120. user_scr.move(0, prompt_len + input_index)
  121. user_scr.refresh()
  122. elif input == "KEY_RIGHT":
  123. input_index = max(
  124. 0, min(len(input_str), input_index + 1)
  125. )
  126. user_scr.move(0, prompt_len + input_index)
  127. user_scr.refresh()
  128. elif input == "KEY_BACKSPACE":
  129. if input_index > 0:
  130. input_str = (
  131. input_str[: input_index - 1]
  132. + input_str[input_index:]
  133. )
  134. input_index -= 1
  135. user_scr.erase()
  136. user_scr.addstr(0, 0, prompt + input_str)
  137. user_scr.move(0, prompt_len + input_index)
  138. user_scr.refresh()
  139. elif input == "KEY_RESIZE":
  140. stdscr.refresh()
  141. net_scr.refresh()
  142. user_scr.refresh()
  143. elif input in string.printable and input not in disallowed:
  144. input_str = (
  145. input_str[:input_index]
  146. + input
  147. + input_str[input_index:]
  148. )
  149. input_index += 1
  150. user_scr.erase()
  151. user_scr.addstr(0, 0, prompt + input_str)
  152. user_scr.move(0, prompt_len + input_index)
  153. user_scr.refresh()
  154. elif input == "\n" and input_str != "":
  155. input = input_str
  156. input_str = ""
  157. input_index = 0
  158. if input.startswith("/"):
  159. command = input[1:].split(" ")[0]
  160. args = input[1:].split(" ")[1:]
  161. if command == "query":
  162. if not any(
  163. e["username"] == args[0] for e in message_list
  164. ):
  165. try:
  166. message_list.append(
  167. {
  168. "username": args[0],
  169. "messages": [],
  170. }
  171. )
  172. except IndexError:
  173. continue
  174. message_index = len(message_list) - 1
  175. elif len(message_list) > 0:
  176. send(
  177. s,
  178. "PRIVMSG\tTARGET="
  179. + message_list[message_index]["username"]
  180. + "\tMESSAGE="
  181. + input.replace("\\", "\\\\"),
  182. )
  183. update_screen(stdscr, net_scr, user_scr)
  184. def main(stdscr):
  185. global input_str
  186. global input_index
  187. global lock
  188. global message_list
  189. global message_index
  190. global prompt
  191. global prompt_len
  192. stdscr.erase()
  193. stdscr.refresh()
  194. server_scr = curses.newwin(1, curses.COLS, 0, 0)
  195. server_scr.addstr(0, 0, "Server address: ")
  196. server_scr.refresh()
  197. port_scr = curses.newwin(1, curses.COLS, 1, 0)
  198. port_scr.addstr(0, 0, "Server port: ")
  199. port_scr.refresh()
  200. name_scr = curses.newwin(1, curses.COLS, 2, 0)
  201. name_scr.addstr(0, 0, "Username: ")
  202. name_scr.refresh()
  203. pass_scr = curses.newwin(1, curses.COLS, 3, 0)
  204. pass_scr.addstr(0, 0, "Password: ")
  205. pass_scr.refresh()
  206. line = 0
  207. index = 0
  208. config = [
  209. [server_scr, "", len("Server address: "), "Server address: "],
  210. [port_scr, "", len("Server port: "), "Server port: "],
  211. [name_scr, "", len("Username: "), "Username: "],
  212. [pass_scr, "", len("Password: "), "Password: "],
  213. ]
  214. maxlines = len(config) - 1
  215. disallowed = ["\n", "\r", "\t", "\x0B", "\x0C"]
  216. err_scr = curses.newwin(1, curses.COLS, maxlines + 2, 0)
  217. err_scr.erase()
  218. err_scr.refresh()
  219. config[line][0].move(0, config[line][2])
  220. config[line][0].refresh()
  221. s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  222. while True:
  223. input = stdscr.getkey()
  224. if len(input) > 1:
  225. if input == "KEY_DOWN":
  226. line = min(line + 1, maxlines)
  227. index = min(index, len(config[line][1]))
  228. config[line][0].move(0, config[line][2] + index)
  229. config[line][0].refresh()
  230. elif input == "KEY_UP":
  231. line = max(line - 1, 0)
  232. index = min(index, len(config[line][1]))
  233. config[line][0].move(0, config[line][2] + index)
  234. config[line][0].refresh()
  235. elif input == "KEY_LEFT":
  236. index = max(index - 1, 0)
  237. config[line][0].move(0, config[line][2] + index)
  238. config[line][0].refresh()
  239. elif input == "KEY_RIGHT":
  240. index = min(index + 1, len(config[line][1]))
  241. config[line][0].move(0, config[line][2] + index)
  242. config[line][0].refresh()
  243. elif input == "KEY_BACKSPACE":
  244. if index > 0:
  245. tmp_str = config[line][1]
  246. tmp_str = tmp_str[: index - 1] + tmp_str[index:]
  247. config[line][1] = tmp_str
  248. index -= 1
  249. scr = config[line][0]
  250. scr.erase()
  251. scr.addstr(0, 0, config[line][3])
  252. scr.addstr(0, config[line][2], tmp_str)
  253. scr.move(0, config[line][2] + index)
  254. scr.refresh()
  255. elif input == "KEY_RESIZE":
  256. stdscr.refresh()
  257. for c in config:
  258. c[0].refresh()
  259. err_scr.refresh()
  260. config[line][0].move(0, config[line][2] + index)
  261. config[line][0].refresh()
  262. elif input in string.printable and input not in disallowed:
  263. tmp_str = config[line][1]
  264. tmp_str = tmp_str[:index] + input + tmp_str[index:]
  265. config[line][1] = tmp_str
  266. index += 1
  267. scr = config[line][0]
  268. scr.erase()
  269. scr.addstr(0, 0, config[line][3])
  270. scr.addstr(0, config[line][2], tmp_str)
  271. scr.move(0, config[line][2] + index)
  272. scr.refresh()
  273. elif input == "\n":
  274. if not all(c[1] != "" for c in config):
  275. err_scr.erase()
  276. err_scr.addstr(
  277. 0, 0, "Not all required settings have been filled."
  278. )
  279. err_scr.refresh()
  280. config[line][0].move(0, config[line][2] + index)
  281. config[line][0].refresh()
  282. else:
  283. try:
  284. port = int(config[1][1])
  285. except ValueError:
  286. err_scr.erase()
  287. err_scr.addstr(0, 0, "Invalid port.")
  288. err_scr.refresh()
  289. config[line][0].move(0, config[line][2] + index)
  290. config[line][0].refresh()
  291. continue
  292. address = config[0][1]
  293. username = config[2][1]
  294. password = config[3][1]
  295. try:
  296. s.connect((address, port))
  297. except ConnectionRefusedError:
  298. err_scr.erase()
  299. err_scr.addstr(0, 0, "Connection refused.")
  300. err_scr.refresh()
  301. config[line][0].move(0, config[line][2] + index)
  302. config[line][0].refresh()
  303. continue
  304. except socket.gaierror as err:
  305. err_scr.erase()
  306. err_scr.addstr(0, 0, str(err))
  307. err_scr.refresh()
  308. config[line][0].move(0, config[line][2] + index)
  309. config[line][0].refresh()
  310. continue
  311. break
  312. net_scr = curses.newwin(curses.LINES - 1, curses.COLS, 0, 0)
  313. user_scr = curses.newwin(1, curses.COLS, curses.LINES - 1, 0)
  314. net_scr.erase()
  315. net_scr.refresh()
  316. user_scr.erase()
  317. user_scr.refresh()
  318. send(s, "LOGIN\tUSERNAME=" + username + "\tPASSWORD=" + password)
  319. update_screen(stdscr, net_scr, user_scr)
  320. threading.Thread(
  321. target=listen_to_user,
  322. args=(s, stdscr, net_scr, user_scr),
  323. daemon=True,
  324. ).start()
  325. msg = ""
  326. lock.acquire()
  327. while True:
  328. lock.release()
  329. newmsg = recv(s)
  330. lock.acquire()
  331. if newmsg == "":
  332. break
  333. msg += newmsg
  334. split_msg = msg.split("\n")
  335. if len(split_msg) < 2:
  336. continue
  337. lines = split_msg[0:-1]
  338. msg = split_msg[-1]
  339. for line in lines:
  340. command = "\\".join(
  341. c.replace("\\t", "\t")
  342. .replace("\\r", "\r")
  343. .replace("\\n", "\n")
  344. for c in line.split("\t")[0].split("\\\\")
  345. ).upper()
  346. args = {}
  347. for x in [
  348. "\\".join(
  349. c.replace("\\t", "\t")
  350. .replace("\\r", "\r")
  351. .replace("\\n", "\n")
  352. for c in a.split("\\\\")
  353. )
  354. for a in line.split("\t")[1:]
  355. ]:
  356. try:
  357. args[x.split("=", 1)[0].upper()] = x.split("=", 1)[
  358. 1
  359. ]
  360. except IndexError:
  361. continue # log an error here eventually
  362. if command == "PRIVMSG":
  363. if all(
  364. args.get(x) != None
  365. for x in ["TARGET", "SOURCE", "MESSAGE"]
  366. ):
  367. if args["TARGET"] == username:
  368. buffer = args["SOURCE"]
  369. else:
  370. buffer = args["TARGET"]
  371. if not any(
  372. c["username"] == buffer for c in message_list
  373. ):
  374. message_list.append(
  375. {
  376. "username": buffer,
  377. "messages": [
  378. {
  379. "source": "".join(
  380. a
  381. for a in args[
  382. "SOURCE"
  383. ].replace("\t", " ")
  384. if a in string.printable
  385. and a not in disallowed
  386. ),
  387. "message": "".join(
  388. a
  389. for a in args[
  390. "MESSAGE"
  391. ].replace("\t", " ")
  392. if a in string.printable
  393. and a not in disallowed
  394. ),
  395. },
  396. ],
  397. }
  398. )
  399. update_screen(stdscr, net_scr, user_scr)
  400. else:
  401. for c in message_list:
  402. if c["username"] == buffer:
  403. c["messages"].append(
  404. {
  405. "source": "".join(
  406. a
  407. for a in args[
  408. "SOURCE"
  409. ].replace("\t", " ")
  410. if a in string.printable
  411. and a not in disallowed
  412. ),
  413. "message": "".join(
  414. a
  415. for a in args[
  416. "MESSAGE"
  417. ].replace("\t", " ")
  418. if a in string.printable
  419. and a not in disallowed
  420. ),
  421. }
  422. )
  423. update_screen(stdscr, net_scr, user_scr)
  424. if __name__ == "__main__":
  425. curses.wrapper(main)
  426. else:
  427. raise SystemExit("Unable to install backdoor, exiting...")