Automatically migrated from Gitolite
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

327 lines
9.4 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  1. #!/usr/bin/python
  2. import math, socket, ssl, select, time, threading, random, string, os
  3. from collections import deque
  4. EOC = "\r\n"
  5. config_ownhost = "ircd.local"
  6. config_password = ""
  7. config_netname = "Cryto IRC"
  8. config_version = "circd 0.1"
  9. config_motd = "sample.motd"
  10. def remove_from_list(ls, val):
  11. return [value for value in ls if value is not val]
  12. def split_irc(message):
  13. if ":" in message:
  14. first, second = message.split(":", 2)
  15. return first.rstrip().split(" ") + [second]
  16. else:
  17. return message.split(" ")
  18. class autodict(dict):
  19. # http://stackoverflow.com/a/652284
  20. def __getitem__(self, item):
  21. try:
  22. return dict.__getitem__(self, item)
  23. except KeyError:
  24. value = self[item] = type(self)()
  25. return value
  26. class ircd:
  27. channels = {}
  28. users = {}
  29. motd = ""
  30. def __init__(self):
  31. if config_motd != "":
  32. try:
  33. self.motd = open(config_motd, "r").read()
  34. except IOError:
  35. print "WARNING: Could not read MOTD file."
  36. class listener:
  37. ssl = False
  38. server = None
  39. client_list = []
  40. client_map = {}
  41. select_inputs = []
  42. select_outputs = []
  43. def __init__(self, server):
  44. self.server = server
  45. def start(self, interface, port, cert_path, key_path):
  46. bindsocket = socket.socket()
  47. bindsocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  48. bindsocket.bind((interface, port))
  49. bindsocket.listen(5)
  50. self.select_inputs = [ bindsocket ]
  51. while self.select_inputs:
  52. readable, writable, error = select.select(self.select_inputs, self.select_outputs, self.select_inputs)
  53. if len(readable) > 0:
  54. for sock in readable:
  55. try:
  56. if sock is bindsocket:
  57. newsocket, fromaddr = bindsocket.accept()
  58. remote_ip, remote_port = fromaddr
  59. if self.ssl == True:
  60. connstream = ssl.wrap_socket(newsocket, server_side=True, certfile=cert_path, keyfile=key_path, ssl_version=ssl.PROTOCOL_TLSv1)
  61. else:
  62. connstream = newsocket
  63. new_client = client(connstream, remote_ip, server, self)
  64. self.select_inputs.append(connstream)
  65. self.select_outputs.append(connstream)
  66. self.client_map[connstream.fileno()] = new_client
  67. self.client_list.append(new_client)
  68. else:
  69. cur_client = self.client_map[sock.fileno()]
  70. try:
  71. data = sock.recv(1024)
  72. except socket.error, (value, message):
  73. cur_client.abort(message)
  74. else:
  75. if data:
  76. cur_client.process_data(data)
  77. else:
  78. cur_client.end()
  79. self.select_inputs = remove_from_list(self.select_inputs, sock)
  80. print "NOTICE: Client disconnected"
  81. except ssl.SSLError, err:
  82. if err.args[0] == ssl.SSL_ERROR_WANT_READ:
  83. select.select([sock], [], [])
  84. elif err.args[0] == ssl.SSL_ERROR_WANT_WRITE:
  85. select.select([], [sock], [])
  86. else:
  87. raise
  88. else:
  89. time.sleep(0.010)
  90. class client:
  91. buff = ""
  92. stream = None
  93. user = None
  94. listener = None
  95. ip = ""
  96. def __init__(self, connstream, ip, server, listener):
  97. self.ip = ip
  98. self.stream = connstream
  99. self.user = user(self, server)
  100. self.listener = listener
  101. def abort(self, reason):
  102. # TODO: Log quit reason
  103. try:
  104. self.stream.shutdown(2)
  105. self.close()
  106. except socket.error:
  107. pass
  108. self.end()
  109. self.listener.select_inputs = remove_from_list(self.listener.select_inputs, self.stream)
  110. print "NOTICE: Client disconnected, possibly due to socket error: %s" % reason
  111. def send_chunk(self, chunk):
  112. #print chunk
  113. try:
  114. self.stream.send(chunk + EOC)
  115. except socket.error, (value, message):
  116. self.abort(message)
  117. def send_global_notice(self, notice):
  118. self.send_chunk(":%s NOTICE %s" % (config_ownhost, notice))
  119. def send_numeric(self, numeric, notice):
  120. self.send_chunk(":%s %s %s %s" % (config_ownhost, numeric, self.user.nickname, notice))
  121. def send_event(self, origin, event, message):
  122. self.send_chunk(":%s %s %s" % (origin, event, message))
  123. def process_data(self, data):
  124. self.buff += data
  125. stack = self.buff.split("\n")
  126. self.buff = stack.pop()
  127. for chunk in stack:
  128. print chunk
  129. self.process_chunk(chunk.rstrip())
  130. def process_chunk(self, chunkdata):
  131. data = split_irc(chunkdata)
  132. if data[0].upper() == "PING":
  133. self.send_chunk("PONG %s" % data[1])
  134. else:
  135. self.user.process_data(data)
  136. def end(self):
  137. if self.user is not None:
  138. self.user.end()
  139. class channel:
  140. presences = {}
  141. name = ""
  142. registered = False
  143. def __init__(self, channelname):
  144. self.name = channelname
  145. class user:
  146. client = None
  147. server = None
  148. registered = 0
  149. registered_nick = False
  150. registered_user = False
  151. presences = {}
  152. nickname = "*"
  153. ident = ""
  154. realname = ""
  155. masked_host = ""
  156. real_host = ""
  157. ip = ""
  158. def __init__(self, client, server):
  159. self.server = server
  160. self.client = client
  161. self.client.send_global_notice("AUTH :*** Looking up your hostname...")
  162. try:
  163. hostname, aliaslist, ipaddrlist = socket.gethostbyaddr(self.client.ip)
  164. self.client.send_global_notice("AUTH :*** Found your hostname")
  165. except socket.herror:
  166. hostname = self.client.ip
  167. self.client.send_global_notice("AUTH :*** Could not find your hostname, using IP address instead")
  168. self.real_host = hostname
  169. self.masked_host = hostname
  170. if config_password == "":
  171. self.registered = 1
  172. def __str__(self):
  173. return "%s!%s@%s" % (self.nickname, self.ident, self.masked_host)
  174. def process_data(self, data):
  175. data = deque(data)
  176. if data[0].startswith(":"):
  177. origin = data.popleft()
  178. else:
  179. origin = ""
  180. data[0] = data[0].upper()
  181. if data[0] in ["USER", "NICK"] and self.registered == 1:
  182. if data[0] == "USER":
  183. if len(data) >= 5:
  184. self.ident = data[1]
  185. self.realname = data[4]
  186. self.registered_user = True
  187. self.verify_registration()
  188. else:
  189. self.client.send_numeric("461", "USER :Not enough parameters.")
  190. elif data[0] == "NICK":
  191. if len(data) >= 2:
  192. if data[1] not in self.server.users:
  193. self.nickname = data[1]
  194. self.registered_nick = True
  195. self.verify_registration()
  196. else:
  197. self.client.send_numeric("433", "%s :Nickname is already in use." % data[1])
  198. else:
  199. self.client.send_numeric("461", "NICK :Not enough parameters.")
  200. elif self.registered == 2 and data[0] == "PONG":
  201. if data[1] == self.challenge:
  202. self.finish_registration()
  203. elif self.registered < 2:
  204. self.client.send_numeric("451", "%s :You have not registered." % data[0])
  205. elif self.registered < 3:
  206. self.client.send_numeric("451", "%s :You have not completed the challenge PING." % data[0])
  207. else:
  208. if data[0] == "LUSERS":
  209. self.send_lusers()
  210. elif data[0] == "JOIN":
  211. self.join_channel(data[1])
  212. else:
  213. self.client.send_numeric("421", "%s :Unknown command." % data[0])
  214. def verify_registration(self):
  215. if self.registered_nick == True and self.registered_user == True:
  216. self.registered = 2
  217. print "Client %s registered from IP %s, sending challenge string." % (self.nickname, self.client.ip)
  218. self.send_challenge()
  219. def send_challenge(self):
  220. self.challenge = ''.join(random.choice(string.ascii_uppercase + string.digits) for i in xrange(8))
  221. self.client.send_chunk("PING :%s" % self.challenge)
  222. def finish_registration(self):
  223. self.registered = 3
  224. self.server.users[self.nickname] = self
  225. self.client.send_numeric("001", ":Welcome to %s, %s!%s@%s" % (config_netname, self.nickname, self.ident, self.real_host))
  226. self.client.send_numeric("002", ":Your host is %s, running %s." % (config_ownhost, config_version))
  227. self.client.send_numeric("003", ":This server has been running since unknown.")
  228. self.client.send_numeric("004", ":%s %s %s %s" % (config_ownhost, config_version, "", ""))
  229. self.send_lusers()
  230. self.send_motd()
  231. def send_lusers(self):
  232. self.client.send_numeric("251", ":There are %d users and 0 invisible on 1 server." % len(self.server.users))
  233. self.client.send_numeric("252", "%d :operator(s) online" % 0)
  234. self.client.send_numeric("254", "%d :channel(s) formed" % 0)
  235. self.client.send_numeric("255", ":I have %d clients and 1 server." % len(self.server.users)) # TODO: Sum all clients of all listenersm rather than taking the usercount.
  236. def send_motd(self):
  237. if server.motd == "":
  238. self.client.send_numeric("422", ":No MOTD was set.")
  239. else:
  240. self.client.send_numeric("375", ":- %s Message of the day -" % config_ownhost)
  241. for line in server.motd.rstrip().split("\n"):
  242. self.client.send_numeric("372", ":- %s" % line.rstrip())
  243. self.client.send_numeric("375", ":End of MOTD for %s." % config_ownhost)
  244. def join_channel(self, channelname):
  245. if channelname not in self.server.channels:
  246. self.server.channels[channelname] = channel(channelname)
  247. targetchannel = self.server.channels[channelname]
  248. if self.nickname not in targetchannel.presences:
  249. newpresence = presence(targetchannel, self)
  250. self.server.channels[channelname].presences[self.nickname] = newpresence
  251. self.presences[channelname] = newpresence
  252. self.client.send_event(self, "JOIN", ":%s" % channelname)
  253. self.client.send_numeric("353", "= %s :%s" % (channelname, "@hai blah"))
  254. self.client.send_numeric("366", "%s :End of userlist." % channelname)
  255. print self.server.channels[channelname].presences
  256. print self.presences[channelname]
  257. def end(self):
  258. del self.server.users[self.nickname]
  259. class presence:
  260. user = None
  261. channel = None
  262. status = ""
  263. joined = 0
  264. def __init__(self, targetchannel, targetuser):
  265. self.user = targetuser
  266. self.channel = targetchannel
  267. self.status = ""
  268. server = ircd()
  269. l = listener(server)
  270. l.start("0.0.0.0", 6667, "sample.cert", "sample.key")