source file: irc/state.py
file stats: 1286 lines, 1286 executed: 100.0% covered
1. #!/usr/bin/env python
2.
3. import time
4. import p10.parser
5. #import threading
6.
7. # IRC masks are very similar to UNIX filename pattern matching, so we can cheat and use the same algorithm
8. import fnmatch
9.
10. class DummyLock:
11. def acquire(self):
12. return True
13. def release(self):
14. return True
15.
16. class state:
17. """ Holds the state for the current connection """
18.
19. _config = None
20. users = dict()
21. channels = dict()
22. servers = dict()
23. maxClientNumerics = dict()
24. _glines = dict()
25. _jupes = dict()
26. lock = None
27. _callbacks = dict()
28.
29. #
30. # Configuration of this server
31. #
32.
33. def __init__(self, config):
34. self.users = dict()
35. self.channels = dict()
36. self._config = config
37. self.servers = dict()
38. self.servers[self.getServerID()] = server(None, self.getServerID(), self.getServerName(), 262143, self.ts(), self.ts(), "P10", 0, [], "WISH on " + self.getServerName())
39. self.maxClientNumerics = dict({self.getServerID(): 262143})
40. self._glines = dict()
41. self._jupes = dict()
42. #self.lock = threading.RLock()
43. self.lock = DummyLock()
44. self._callbacks = dict()
45.
46. def getServerID(self):
47. return self._config.numericID
48.
49. def getServerName(self):
50. return self._config.serverName
51.
52. def getServerDescription(self):
53. return self._config.serverDescription
54.
55. def getAdminName(self):
56. return self._config.adminNick
57.
58. def getContactEmail(self):
59. return self._config.contactEmail
60.
61. def requestAdminInfo(self, origin, target):
62. if self.userExists(origin):
63. if self.serverExists(target[0]) and target[1] == None:
64. self._callback(self.CALLBACK_REQUESTADMIN, (origin, target))
65. else:
66. raise p10.parser.ProtocolError("Admin information can only be requested from servers")
67. else:
68. raise StateError("Received a request for admin info from a non-existant user")
69.
70. def requestServerInfo(self, origin, target):
71. if self.userExists(origin):
72. if self.serverExists(target[0]) and target[1] == None:
73. self._callback(self.CALLBACK_REQUESTINFO, (origin, target))
74. else:
75. raise p10.parser.ProtocolError("Server information can only be requested from servers")
76. else:
77. raise StateError("Received a request for server info from a non-existant user")
78.
79. def requestLusers(self, origin, target, dummy):
80. if self.userExists(origin):
81. if self.serverExists(target[0]) and target[1] == None:
82. self._callback(self.CALLBACK_REQUESTLUSERS, (origin, target, dummy))
83. else:
84. raise p10.parser.ProtocolError("Luser information can only be requested from servers")
85. else:
86. raise StateError("Received a request for Luser info from a non-existant user")
87.
88. def requestLinks(self, origin, target, mask):
89. if self.userExists(origin):
90. if self.serverExists(target[0]) and target[1] == None:
91. self._callback(self.CALLBACK_REQUESTLINKS, (origin, target, mask))
92. else:
93. raise p10.parser.ProtocolError("Links information can only be requested from servers")
94. else:
95. raise StateError("Received a request for links info from a non-existant user")
96.
97. def requestMOTD(self, origin, target):
98. if self.userExists(origin):
99. if self.serverExists(target[0]) and target[1] == None:
100. self._callback(self.CALLBACK_REQUESTMOTD, (origin, target))
101. else:
102. raise p10.parser.ProtocolError("MOTD can only be requested from servers")
103. else:
104. raise StateError("Received a request for MOTD from a non-existant user")
105.
106. def requestVersion(self, origin, target):
107. if self.userExists(origin):
108. if self.serverExists(target[0]) and target[1] == None:
109. self._callback(self.CALLBACK_REQUESTVERSION, (origin, target))
110. else:
111. raise p10.parser.ProtocolError("Version can only be requested from servers")
112. else:
113. raise StateError("Received a request for version from a non-existant user")
114.
115. def requestStats(self, origin, target, stat, arg):
116. if self.userExists(origin):
117. if self.serverExists(target[0]) and target[1] == None:
118. self._callback(self.CALLBACK_REQUESTSTATS, (origin, target, stat, arg))
119. else:
120. raise p10.parser.ProtocolError("Stats can only be requested from servers")
121. else:
122. raise StateError("Received a request for stats from a non-existant user")
123.
124. def trace(self, origin, search, target):
125. if self.userExists(origin):
126. if self.serverExists(target[0]) and target[1] == None:
127. self._callback(self.CALLBACK_TRACE, (origin, search, target))
128. else:
129. raise p10.parser.ProtocolError("Traces can only be requested from servers")
130. else:
131. raise StateError("Received a request for a trace from a non-existant user")
132.
133. def ts(self):
134. """ Returns our current timestamp """
135. return int(time.time())
136.
137. #
138. # Callbacks
139. #
140.
141. # Constants for Callbacks
142. CALLBACK_NEWUSER = "NewUser"
143. CALLBACK_QUIT = "Quit"
144. CALLBACK_KILL = "Kill"
145. CALLBACK_USERMODECHANGE = "UserModeChange"
146. CALLBACK_CHANGENICK = "ChangeNick"
147. CALLBACK_NEWSERVER = "NewServer"
148. CALLBACK_SERVERQUIT = "Squit"
149. CALLBACK_AUTHENTICATE = "Authenticate"
150. CALLBACK_AWAY = "Away"
151. CALLBACK_BACK = "Back"
152. CALLBACK_SILENCEADD = "SilenceAdd"
153. CALLBACK_SILENCEREMOVE = "SilenceRemove"
154. CALLBACK_CHANNELCREATE = "ChannelCreate"
155. CALLBACK_CHANNELDESTROY = "ChannelDestroy"
156. CALLBACK_CHANNELJOIN = "ChannelJoin"
157. CALLBACK_CHANNELPART = "ChannelPart"
158. CALLBACK_CHANNELPARTALL = "ChannelPartAll"
159. CALLBACK_CHANNELPARTZOMBIE = "ChannelPartZombie"
160. CALLBACK_CHANNELKICK = "ChannelKick"
161. CALLBACK_CHANNELMODECHANGE = "ChannelModeChange"
162. CALLBACK_CHANNELBANADD = "ChannelBanAdd"
163. CALLBACK_CHANNELBANREMOVE = "ChannelBanRemove"
164. CALLBACK_CHANNELBANCLEAR = "ChannelBanClear"
165. CALLBACK_CHANNELOP = "ChannelOp"
166. CALLBACK_CHANNELDEOP = "ChannelDeop"
167. CALLBACK_CHANNELCLEAROPS = "ChannelClearOps"
168. CALLBACK_CHANNELVOICE = "ChannelVoice"
169. CALLBACK_CHANNELDEVOICE = "ChannelDevoice"
170. CALLBACK_CHANNELCLEARVOICES = "ChannelClearVoices"
171. CALLBACK_CHANNELTOPIC = "ChannelTopic"
172. CALLBACK_GLINEADD = "GlineAdd"
173. CALLBACK_GLINEREMOVE = "GlineRemove"
174. CALLBACK_INVITE = "Invite"
175. CALLBACK_JUPEADD = "JupeAdd"
176. CALLBACK_JUPEREMOVE = "JupeRemove"
177. CALLBACK_REQUESTADMIN = "RequestAdmin"
178. CALLBACK_REQUESTINFO = "RequestInfo"
179. CALLBACK_REQUESTLUSERS = "RequestLusers"
180. CALLBACK_REQUESTLINKS = "RequestLinks"
181. CALLBACK_REQUESTMOTD = "RequestMOTD"
182. CALLBACK_REQUESTNAMES = "RequestNames"
183. CALLBACK_REQUESTVERSION = "RequestVersion"
184. CALLBACK_REQUESTSTATS = "RequestStats"
185. CALLBACK_TRACE = "Trace"
186. CALLBACK_PING = "Ping"
187. CALLBACK_PONG = "Pong"
188. CALLBACK_REQUESTWHOIS = "Whois"
189. CALLBACK_PRIVMSG = "Privmsg"
190. CALLBACK_OOBMSG = "Oobmsg"
191. CALLBACK_NOTICE = "Notice"
192. CALLBACK_WALLOPS = "Wallops"
193. CALLBACK_WALLUSERS = "Wallusers"
194. CALLBACK_WALLVOICES = "Wallvoices"
195. CALLBACK_WALLCHOPS = "Wallchops"
196.
197. def registerCallback(self, type, callbackfn):
198. if type in self._callbacks:
199. self._callbacks[type].append(callbackfn)
200. else:
201. self._callbacks[type] = [callbackfn]
202.
203. def deregisterCallback(self, type, callbackfn):
204. if type in self._callbacks:
205. if callbackfn in self._callbacks[type]:
206. self._callbacks[type].remove(callbackfn)
207.
208. def _callback(self, type, args):
209. if type in self._callbacks:
210. for callback in self._callbacks[type]:
211. callback(args)
212.
213. #
214. # Other servers
215. #
216.
217. def serverExists(self, numeric):
218. return numeric in self.servers
219.
220. def newServer(self, origin, numeric, name, maxclient, boot_ts, link_ts, protocol, hops, flags, description):
221. """ Add a new server """
222. # TODO: More stringent checks - do we have a name clash?
223. self.lock.acquire()
224. try:
225. if self.serverExists(numeric):
226. raise StateError("Attempted to add a duplicate server")
227. elif origin[1] != None:
228. raise p10.parser.ProtocolError("User attempted to add a server")
229. else:
230. uplink = origin[0]
231. if self.serverExists(uplink):
232. self.servers[numeric] = server(uplink, numeric, name, maxclient, boot_ts, link_ts, protocol, hops, flags, description)
233. self.maxClientNumerics[numeric] = maxclient
234. self.servers[uplink].addChild(numeric)
235. else:
236. raise StateError("Unknown server introduced a new server")
237. finally:
238. self.lock.release()
239. self._callback(self.CALLBACK_NEWSERVER, (origin, numeric, name, maxclient, boot_ts, link_ts, protocol, hops, flags, description))
240.
241. def _getAllChildrenOf(self, numeric):
242. ret = self.servers[numeric].children
243. for child in self.servers[numeric].children:
244. ret = ret | self._getAllChildrenOf(child)
245. return ret
246.
247. def quitServer(self, origin, numeric, reason, ts):
248. """ A server splits from the network """
249. callback = False
250. self.lock.acquire()
251. try:
252. if self.serverExists(numeric[0]):
253. # Disregard bad TS's
254. if ts == 0 or ts == self.servers[numeric[0]].link_ts:
255. # Build a set of all servers that will be lost in this split
256. serverstogo = self._getAllChildrenOf(numeric[0])
257. serverstogo.add(numeric[0])
258. # Quit every user that is on these servers. Channels are automatically cleaned up by quit.
259. for user in self.users.copy():
260. if user[0] in serverstogo:
261. self.quit(user, self.numeric2nick(numeric) + " split from the network", True)
262. self.servers[origin[0]].children.remove(numeric[0])
263. for server in serverstogo:
264. del self.servers[server]
265. callback = True
266. else:
267. raise StateError("Server that does not exist was just squitted")
268. finally:
269. self.lock.release()
270. if callback:
271. self._callback(self.CALLBACK_SERVERQUIT, (origin, numeric, reason, ts))
272.
273. def getNextHop(self, dest):
274. """ Return which direction an entity is from here """
275. if dest[0] == self.getServerID():
276. return None
277. if dest[0] in self.servers[self.getServerID()].children:
278. return dest[0]
279. for server in self.servers[self.getServerID()].children:
280. if dest[0] in self._getAllChildrenOf(server):
281. return server
282.
283. def registerPing(self, origin, source, target):
284. if self.serverExists(origin[0]) and origin[1] == None:
285. self._callback(self.CALLBACK_PING, (origin, source, target))
286. else:
287. raise StateError("Ping received by non-server")
288.
289. def registerPong(self, origin, source, target):
290. if self.serverExists(origin[0]) and origin[1] == None:
291. self._callback(self.CALLBACK_PONG, (origin, source, target))
292. else:
293. raise StateError("Pong received by non-server")
294.
295. #
296. # Jupes
297. #
298.
299. def jupes(self):
300. """ Returns a list of global jupes
301. The list of tuples (mask, description, expires, active, last modified time) """
302. self._cleanupJupes()
303. rj = []
304. for jupe in self._jupes:
305. if not self._jupes[jupe][4]:
306. rj.append((jupe, self._jupes[jupe][0], self._jupes[jupe][1], self._jupes[jupe][2], self._jupes[jupe][3]))
307. return rj
308.
309. def _deactivateJupe(self, jupe):
310. self._jupes[jupe] = (self._jupes[jupe][0], self._jupes[jupe][1], False, self._jupes[jupe][3], self._jupes[jupe][4])
311.
312. def _cleanupJupes(self):
313. for jupe in self._jupes.copy():
314. if self._jupes[jupe][1] < self.ts():
315. self._deactivateJupe(jupe)
316.
317. def isJuped(self, server):
318. self._cleanupJupes()
319. if server in self._jupes:
320. return self._jupes[server][2]
321. else:
322. return False
323.
324. def addJupe(self, origin, server, target, expire, ts, reason):
325. if target == None or target == self.getServerID():
326. self._jupes[server] = (reason, expire, True, ts, target == self.getServerID())
327. self._callback(self.CALLBACK_JUPEADD, (origin, server, target, expire, reason))
328.
329. def removeJupe(self, origin, server, target, ts):
330. if target == None or target == self.getServerID():
331. self.lock.acquire()
332. try:
333. if server in self._jupes:
334. self._deactivateJupe(server)
335. finally:
336. self.lock.release()
337. self._callback(self.CALLBACK_JUPEREMOVE, (origin, server, target))
338.
339. #
340. # User handling
341. #
342.
343. def userExists(self, numeric):
344. """ Check if the user is known to us """
345. return numeric in self.users
346.
347. def nick2numeric(self, nick):
348. for user in self.users:
349. if nick == self.users[user].nickname:
350. return user
351. for server in self.servers:
352. if nick == self.servers[server].name:
353. return (server, None)
354.
355. def numeric2nick(self, numeric):
356. if self.userExists(numeric):
357. return self.users[numeric].nickname
358. elif self.serverExists(numeric[0]) and numeric[1] == None:
359. return self.servers[numeric[0]].name
360.
361. def newUser(self, origin, numeric, nickname, username, hostname, modes, ip, hops, ts, fullname):
362. """ Change state to include a new user """
363. # TODO: Do we have a name clash?
364. self.lock.acquire()
365. try:
366. if self.serverExists(origin[0]):
367. if origin[1] == None:
368. if self.userExists(numeric):
369. raise StateError("Numeric collision - attempting to create second user with numeric we already know")
370. else:
371. self.users[numeric] = user(numeric, nickname, username, hostname, modes, ip, hops, ts, fullname)
372. else:
373. raise p10.parser.ProtocolError("Only servers can create users")
374. else:
375. raise StateError("A non-existant server tried to create a user")
376. finally:
377. self.lock.release()
378. self._callback(self.CALLBACK_NEWUSER, (origin, numeric, nickname, username, hostname, modes, ip, hops, ts, fullname))
379.
380. def partAllChannels(self, numeric):
381. """ A user parts all channels """
382. # Shallow copy to allow us to modify during loop
383. self.lock.acquire()
384. try:
385. for channel in self.users[numeric].channels.copy():
386. self.channels[channel].part(numeric)
387. self.users[numeric].part(channel)
388. self._cleanupChannel(channel)
389. finally:
390. self.lock.release()
391. self._callback(self.CALLBACK_CHANNELPARTALL, (numeric))
392.
393. def quit(self, numeric, reason, causedbysquit=False):
394. self.lock.acquire()
395. try:
396. if self.userExists(numeric):
397. for channel in self.users[numeric].channels:
398. self.channels[channel].part(numeric)
399. self._cleanupChannel(channel)
400. del self.users[numeric]
401. else:
402. raise StateError("Unknown user tried to quit")
403. finally:
404. self.lock.release()
405. self._callback(self.CALLBACK_QUIT, (numeric, reason, causedbysquit))
406.
407. def kill(self, origin, target, path, reason):
408. if target[0] == self.getServerID():
409. self.quit(target, "Killed (" + reason + ")")
410. else:
411. self._callback(self.CALLBACK_KILL, (origin, target, [self.servers[self.getNextHop(origin)].name] + path, reason))
412.
413. def changeNick(self, origin, numeric, newnick, newts):
414. """ Change the nickname of a user on the network """
415. # TODO: More stringent checks on new nickname, i.e., is it valid/already in use?
416. self.lock.acquire()
417. try:
418. if self.userExists(numeric):
419. self.users[numeric].nickname = newnick
420. self.users[numeric].ts = newts
421. else:
422. raise StateError('Nick change attempted for unknown user')
423. finally:
424. self.lock.release()
425. self._callback(self.CALLBACK_CHANGENICK, (origin, numeric, newnick, newts))
426.
427. def changeUserMode(self, numeric, modes):
428. self.lock.acquire()
429. try:
430. if self.userExists(numeric):
431. for mode in modes:
432. self.users[numeric].changeMode(mode)
433. else:
434. raise StateError("Attempted to change mode on a user that does not exist")
435. finally:
436. self.lock.release()
437. self._callback(self.CALLBACK_USERMODECHANGE, (numeric, modes))
438.
439. def authenticate(self, origin, numeric, acname):
440. """ Authenticate a user """
441. self.lock.acquire()
442. try:
443. if origin[1] == None:
444. if self.serverExists(origin[0]):
445. if self.userExists(numeric):
446. self.users[numeric].auth(acname)
447. else:
448. raise StateError("Authentication state change received for unknown user")
449. else:
450. raise StateError("Authentication from unknown server")
451. else:
452. raise p10.parser.ProtocolError("Only servers can change state")
453. finally:
454. self.lock.release()
455. self._callback(self.CALLBACK_AUTHENTICATE, (origin, numeric, acname))
456.
457. def getAccountName(self, numeric):
458. """ Get the account name for a user. Blank if not authenticated. """
459. return self.users[numeric].account
460.
461. def setAway(self, numeric, reason):
462. """ Mark a user as being away. Reason must be non-empty """
463. self.lock.acquire()
464. try:
465. if reason == "":
466. raise StateError("Attempted to set an empty away reason")
467. if self.userExists(numeric):
468. self.users[numeric].away_reason = reason
469. else:
470. raise StateError("Attempted to mark a user as away who does not exist")
471. finally:
472. self.lock.release()
473. self._callback(self.CALLBACK_AWAY, (numeric, reason))
474.
475. def setBack(self, numeric):
476. """ Mark a user as no longer being away """
477. self.lock.acquire()
478. try:
479. if self.userExists(numeric):
480. self.users[numeric].away_reason = None
481. else:
482. raise StateError("Attempted to mark a user as not away who does not exist")
483. finally:
484. self.lock.release()
485. self._callback(self.CALLBACK_BACK, (numeric))
486.
487. def addSilence(self, numeric, mask):
488. self.lock.acquire()
489. try:
490. if self.userExists(numeric):
491. self.users[numeric].addSilence(mask)
492. else:
493. raise StateError("Silence added for a user that does not exist")
494. finally:
495. self.lock.release()
496. self._callback(self.CALLBACK_SILENCEADD, (numeric, mask))
497.
498. def removeSilence(self, numeric, mask):
499. self.lock.acquire()
500. try:
501. if self.userExists(numeric):
502. self.users[numeric].removeSilence(mask)
503. else:
504. raise StateError("Silence removed from a user that does not exist")
505. finally:
506. self.lock.release()
507. self._callback(self.CALLBACK_SILENCEREMOVE, (numeric, mask))
508.
509. def requestWhois(self, origin, target, search):
510. if self.userExists(origin):
511. if self.serverExists(target[0]) and target[1] == None:
512. self._callback(self.CALLBACK_REQUESTWHOIS, (origin, target, search))
513. else:
514. raise StateError("Whois requested from non-server")
515. else:
516. raise StateError("Whois requested by non-existant user")
517.
518. #
519. # Channel handling
520. #
521.
522. def createChannel(self, origin, name, ts):
523. """ Create a channel. Returns false if the new channel is invalid (i.e., is newer than one already known about) """
524. # TODO: More stringent checks on whether or not this channel can be created (i.e., is badchan'd or juped)
525. create_success = False
526. callback = False
527. oldusers = []
528. self.lock.acquire()
529. try:
530. if self.userExists(origin) or (origin[1] == None and self.serverExists(origin[0])):
531. # Channel already exists
532. if name in self.channels:
533. # If our channel is older, disregard.
534. # If they're both the same, add new user as op
535. if self.channels[name].ts == ts:
536. # If the origin is a server, we have no-one joining a channel
537. if origin[1] != None:
538. self.joinChannel(origin, origin, name, ["o"], ts)
539. create_success = True
540. # Their channel is older, overrides ours and merge users
541. elif self.channels[name].ts > ts:
542. self.channels[name].ts = ts
543. self.clearChannelOps(origin, name)
544. self.clearChannelVoices(origin, name)
545. self.clearChannelBans(origin, name)
546. for mode in self.channels[name].modes():
547. self.changeChannelMode(origin, name, ("-" + mode[0], None))
548. if origin[1] != None:
549. self.joinChannel(origin, origin, name, ["o"], ts)
550. create_success = True
551. else:
552. self.channels[name] = channel(name, ts)
553. if origin[1] != None:
554. self.channels[name].join(origin, ["o"])
555. self.users[origin].join(name)
556. callback = True
557. create_success = True
558. else:
559. raise StateError("Unknown entity attempted to create a channel")
560. finally:
561. self.lock.release()
562. if callback:
563. self._callback(self.CALLBACK_CHANNELCREATE, (origin, name, ts))
564. return create_success
565.
566. def channelExists(self, name):
567. """ Returns if a channel exists or not """
568. return name in self.channels
569.
570. def requestChannelUsers(self, origin, target, channels):
571. """ Callback for a request for a list of users on a channel (/names) """
572. if self.userExists(origin):
573. if target[1] == None:
574. goodchannels = []
575. for channel in channels:
576. # The channel must exist, and the user must be allowed to view those names
577. # so the user can be an oper, or on the channel, or the channel is not private or secret
578. if self.channelExists(channel) and (self.channels[channel].ison(origin) or
579. self.users[origin].hasMode("o") or
580. (not self.channels[channel].hasMode("p") and not self.channels[channel].hasMode("s"))):
581. goodchannels.append(channel)
582. if len(goodchannels) > 0:
583. self._callback(self.CALLBACK_REQUESTNAMES, (origin, target, goodchannels))
584. else:
585. raise p10.parser.ProtocolError("Names information can only be requested from servers")
586. else:
587. raise StateError("Received a request for names info from a non-existant user")
588.
589. def _cleanupChannel(self, name):
590. self.lock.acquire()
591. try:
592. if len(self.channels[name].users()) == 0 and len(self.channels[name].zombies) == 0:
593. del self.channels[name]
594. for user in self.users:
595. # Remove any invites that a user may have to this channel
596. if self.users[user].isInvited(name):
597. self.users[user].invites.remove(name)
598. finally:
599. self.lock.release()
600.
601. def destroyChannel(self, origin, channel, ts):
602. callback = True
603. self.lock.acquire()
604. try:
605. if self.channelExists(channel):
606. if len(self.channels[channel].users()) == 0:
607. del self.channels[channel]
608. else:
609. callback = False
610. finally:
611. self.lock.release()
612. if callback:
613. self._callback(self.CALLBACK_CHANNELDESTROY, (origin, channel, ts))
614.
615. def changeChannelMode(self, origin, name, modes):
616. """ Change the modes on a channel. Modes are lists of tuples of the desired change and an optional argument, or None """
617. # TODO: More stringent checks on whether or not this user is allowed to make this mode change
618. self.lock.acquire()
619. try:
620. if self.userExists(origin) or (self.serverExists(origin[0]) and origin[1] == None):
621. if self.channelExists(name):
622. for mode in modes:
623. self.channels[name].changeMode(mode)
624. else:
625. raise StateError("Attempted to change the modes on a channel that does not exist")
626. else:
627. raise StateError("An invalid entity attempted to change a channel mode")
628. finally:
629. self.lock.release()
630. self._callback(self.CALLBACK_CHANNELMODECHANGE, (origin, name, modes))
631.
632. def addChannelBan(self, origin, name, mask):
633. """ Adds a ban to the channel. """
634. # TODO: More stringent checks on whether or not this user is allowed to make this mode change
635. self.lock.acquire()
636. try:
637. if self.userExists(origin) or (self.serverExists(origin[0]) and origin[1] == None):
638. if self.channelExists(name):
639. self.channels[name].addBan(mask)
640. else:
641. raise StateError("Attempted to add a ban to a channel that does not exist")
642. else:
643. raise StateError("An invalid entity attempted to add a channel ban")
644. finally:
645. self.lock.release()
646. self._callback(self.CALLBACK_CHANNELBANADD, (origin, name, mask))
647.
648. def removeChannelBan(self, origin, name, ban):
649. """ Removes a ban from the channel. """
650. # TODO: More stringent checks on whether or not this user is allowed to make this mode change
651. self.lock.acquire()
652. try:
653. if self.userExists(origin) or (self.serverExists(origin[0]) and origin[1] == None):
654. if self.channelExists(name):
655. self.channels[name].removeBan(ban)
656. else:
657. raise StateError("Attempted to remove a ban from a channel that does not exist")
658. else:
659. raise StateError("An invalid entity attempted to remove a channel ban")
660. finally:
661. self.lock.release()
662. self._callback(self.CALLBACK_CHANNELBANREMOVE, (origin, name, ban))
663.
664. def clearChannelBans(self, origin, name):
665. """ Clears all bans from the channel. """
666. # TODO: More stringent checks on whether or not this user is allowed to make this mode change
667. self.lock.acquire()
668. try:
669. if self.userExists(origin) or (self.serverExists(origin[0]) and origin[1] == None):
670. if self.channelExists(name):
671. self.channels[name].clearBans()
672. else:
673. raise StateError("Attempted to clear bans from a channel that does not exist")
674. else:
675. raise StateError("An invalid entity attempted to clear channel bans")
676. finally:
677. self.lock.release()
678. self._callback(self.CALLBACK_CHANNELBANCLEAR, (origin, name))
679.
680. def changeTopic(self, origin, channel, topic, topic_ts, channel_ts):
681. self.lock.acquire()
682. callback = False
683. try:
684. if self.userExists(origin) or self.serverExists(origin[0]):
685. if self.channelExists(channel):
686. # Disregard if new topic_ts is older than old one
687. if topic_ts >= self.channels[channel].topic_ts and channel_ts <= self.channels[channel].ts:
688. self.channels[channel].changeTopic(topic, topic_ts, self.numeric2nick(origin))
689. callback = True
690. else:
691. raise StateError("Topic change attempted on a channel that does not exist")
692. else:
693. raise StateError("Invalid origin attempted to change topic")
694. finally:
695. self.lock.release()
696. if callback:
697. self._callback(self.CALLBACK_CHANNELTOPIC, (origin, channel, topic, topic_ts, channel_ts))
698.
699. #
700. # User channel events
701. #
702.
703. def joinChannel(self, origin, numeric, name, modes, ts=1270080000):
704. """ A user joins a channel, with optional modes already set. If the channel does not exist, it is created. """
705. # TODO: More stringent checks on whether or not this user is allowed to join this channel
706. self.lock.acquire()
707. callback = False
708. try:
709. if self.userExists(numeric):
710. if self.channelExists(name):
711. self.channels[name].join(numeric, modes)
712. self.users[numeric].join(name)
713. callback = True
714. else:
715. # Channel doesn't exist, so it gets created
716. self.createChannel(numeric, name, ts)
717. # But, modes don't get propagated by create, it always assumes o
718. # So bounce deop if needed
719. if "o" not in modes:
720. self.deop(origin, name, numeric)
721. # And send voice if needed
722. if "v" in modes:
723. self.voice(origin, name, numeric)
724. else:
725. raise StateError("Unknown user (" + str(numeric) + ") attempted to join a channel")
726. finally:
727. self.lock.release()
728. if callback:
729. self._callback(self.CALLBACK_CHANNELJOIN, (origin, numeric, name, modes, ts))
730. if "o" in modes:
731. self._callback(self.CALLBACK_CHANNELOP, (origin, name, numeric))
732. if "v" in modes:
733. self._callback(self.CALLBACK_CHANNELVOICE, (origin, name, numeric))
734.
735. def partChannel(self, numeric, name, reason):
736. """ A user parts a channel """
737. self.lock.acquire()
738. callbackZombie = True
739. try:
740. if self.userExists(numeric):
741. if self.channelExists(name):
742. if self.channels[name].ison(numeric):
743. if numeric in self.channels[name].users():
744. callbackZombie = False
745. self.channels[name].part(numeric)
746. self.users[numeric].part(name)
747. self._cleanupChannel(name)
748. else:
749. raise StateError("User that was not on a channel attempted to leave it")
750. else:
751. raise StateError("User tried to leave a channel that does not exist")
752. else:
753. raise StateError("Unknown user attempted to leave a channel")
754. finally:
755. self.lock.release()
756. if callbackZombie:
757. self._callback(self.CALLBACK_CHANNELPARTZOMBIE, (numeric, name))
758. else:
759. self._callback(self.CALLBACK_CHANNELPART, (numeric, name, reason))
760.
761. def kick(self, origin, channel, target, reason):
762. self.lock.acquire()
763. bouncepart = False
764. try:
765. # Kick handling is weird. We zombify the user until we receive an upstream part
766. if self.channelExists(channel):
767. if self.channels[channel].ison(target):
768. self.channels[channel].kick(target)
769. if target[0] == self.getServerID():
770. bouncepart = True
771. self.channels[channel].part(target)
772. self.users[target].part(channel)
773. self._cleanupChannel(channel)
774. else:
775. raise StateError("Kick received for a user that is not on the channel")
776. else:
777. raise StateError("Kick received for a user on an unknown channel")
778. finally:
779. self.lock.release()
780. self._callback(self.CALLBACK_CHANNELKICK, (origin, channel, target, reason))
781. if bouncepart:
782. self._callback(self.CALLBACK_CHANNELPARTZOMBIE, (target, channel))
783.
784. def op(self, origin, channel, user):
785. self.lock.acquire()
786. try:
787. if self.userExists(origin) or (self.serverExists(origin[0]) and origin[1] == None):
788. if self.channelExists(channel):
789. if self.channels[channel].ison(user):
790. self.channels[channel].op(user)
791. else:
792. raise StateError("Attempted to op a user that was not on the channel")
793. else:
794. raise StateError("Attempted to op a user on a channel that does not exist")
795. finally:
796. self.lock.release()
797. self._callback(self.CALLBACK_CHANNELOP, (origin, channel, user))
798.
799. def deop(self, origin, channel, user):
800. """ Deops a user from the channel. """
801. # TODO: More stringent checks on whether or not this user is allowed to make this mode change
802. self.lock.acquire()
803. try:
804. if self.userExists(origin) or (self.serverExists(origin[0]) and origin[1] == None):
805. if self.channelExists(channel):
806. if self.channels[channel].isop(user):
807. self.channels[channel].deop(user)
808. else:
809. raise StateError('Attempted to deop a user that was not op on the channel')
810. else:
811. raise StateError('Attempted to deop from a channel that does not exist')
812. else:
813. raise StateError("An invalid entity attempted to deop a user")
814. finally:
815. self.lock.release()
816. self._callback(self.CALLBACK_CHANNELDEOP, (origin, channel, user))
817.
818. def clearChannelOps(self, origin, name):
819. """ Clears all ops from the channel. """
820. # TODO: More stringent checks on whether or not this user is allowed to make this mode change
821. self.lock.acquire()
822. try:
823. if self.userExists(origin) or (self.serverExists(origin[0]) and origin[1] == None):
824. if self.channelExists(name):
825. self.channels[name].clearOps()
826. else:
827. raise StateError("Attempted to clear ops from a channel that does not exist")
828. else:
829. raise StateError("An invalid entity attempted to clear channel ops")
830. finally:
831. self.lock.release()
832. self._callback(self.CALLBACK_CHANNELCLEAROPS, (origin, name))
833.
834. def voice(self, origin, channel, user):
835. self.lock.acquire()
836. try:
837. if self.userExists(origin) or (self.serverExists(origin[0]) and origin[1] == None):
838. if self.channelExists(channel):
839. if self.channels[channel].ison(user):
840. self.channels[channel].voice(user)
841. else:
842. raise StateError("Attempted to voice a user that was not on the channel")
843. else:
844. raise StateError("Attempted to voice a user on a channel that does not exist")
845. finally:
846. self.lock.release()
847. self._callback(self.CALLBACK_CHANNELVOICE, (origin, channel, user))
848.
849. def devoice(self, origin, channel, user):
850. """ Devoices a user from the channel. """
851. self.lock.acquire()
852. try:
853. # TODO: More stringent checks on whether or not this user is allowed to make this mode change
854. if self.userExists(origin) or (self.serverExists(origin[0]) and origin[1] == None):
855. if self.channelExists(channel):
856. if self.channels[channel].isvoice(user):
857. self.channels[channel].devoice(user)
858. else:
859. raise StateError('Attempted to devoice a user that was not voice on the channel')
860. else:
861. raise StateError('Attempted to devoice from a channel that does not exist')
862. else:
863. raise StateError("An invalid entity attempted to devoice a user")
864. finally:
865. self.lock.release()
866. self._callback(self.CALLBACK_CHANNELDEVOICE, (origin, channel, user))
867.
868. def clearChannelVoices(self, origin, name):
869. """ Clears all voices from the channel. """
870. # TODO: More stringent checks on whether or not this user is allowed to make this mode change
871. self.lock.acquire()
872. try:
873. if self.userExists(origin) or (self.serverExists(origin[0]) and origin[1] == None):
874. if self.channelExists(name):
875. self.channels[name].clearVoices()
876. else:
877. raise StateError("Attempted to clear voices from a channel that does not exist")
878. else:
879. raise StateError("An invalid entity attempted to clear channel voices")
880. finally:
881. self.lock.release()
882. self._callback(self.CALLBACK_CHANNELCLEARVOICES, (origin, name))
883.
884. def invite(self, origin, target, channel):
885. """ Origin invites Target to Channel """
886. # TODO: Check origin can actually send invites
887. self.lock.acquire()
888. try:
889. if self.userExists(target):
890. if self.channelExists(channel):
891. self.users[target].invite(channel)
892. else:
893. raise StateError("Attempted to invite a user into a non-existant channel")
894. else:
895. raise StateError("Attempted to invite a non-existant user to a channel")
896. finally:
897. self.lock.release()
898. self._callback(self.CALLBACK_INVITE, (origin, target, channel))
899.
900. #
901. # Messages
902. #
903.
904. def oobmsg(self, origin, target, type, args):
905. self._callback(self.CALLBACK_OOBMSG, (origin, target, type, args))
906.
907. def privmsg(self, origin, target, message):
908. if self.userExists(origin) or (self.serverExists(origin[0]) and origin[1] == None):
909. self._callback(self.CALLBACK_PRIVMSG, (origin, target, message))
910. else:
911. raise StateError("Privmsg received from non-existant entity")
912.
913. def notice(self, origin, target, message):
914. if self.userExists(origin) or (self.serverExists(origin[0]) and origin[1] == None):
915. self._callback(self.CALLBACK_NOTICE, (origin, target, message))
916. else:
917. raise StateError("Notice received from non-existant entity")
918.
919. def wallops(self, origin, message):
920. if self.userExists(origin) or (self.serverExists(origin[0]) and origin[1] == None):
921. self._callback(self.CALLBACK_WALLOPS, (origin, message))
922. else:
923. raise StateError("Wallops received from non-existant entity")
924.
925. def wallusers(self, origin, message):
926. if self.userExists(origin) or (self.serverExists(origin[0]) and origin[1] == None):
927. self._callback(self.CALLBACK_WALLUSERS, (origin, message))
928. else:
929. raise StateError("Wallusers received from non-existant entity")
930.
931. def wallvoices(self, origin, channel, message):
932. if self.userExists(origin) or (self.serverExists(origin[0]) and origin[1] == None):
933. self._callback(self.CALLBACK_WALLVOICES, (origin, channel, message))
934. else:
935. raise StateError("Wallvoices received from non-existant entity")
936.
937. def wallchops(self, origin, channel, message):
938. if self.userExists(origin) or (self.serverExists(origin[0]) and origin[1] == None):
939. self._callback(self.CALLBACK_WALLCHOPS, (origin, channel, message))
940. else:
941. raise StateError("Wallchops received from non-existant entity")
942.
943. #
944. # G-lines
945. #
946.
947. def glines(self):
948. """ Returns a list of global g-lines
949. The list of tuples (mask, description, expires, active, last modified time) """
950. self._cleanupGlines()
951. rg = []
952. for gline in self._glines:
953. if not self._glines[gline][4]:
954. rg.append((gline, self._glines[gline][0], self._glines[gline][1], self._glines[gline][2], self._glines[gline][3]))
955. return rg
956.
957. def _cleanupGlines(self):
958. """ Remove expired g-lines """
959. # Make shallow copy of dictionary so we can modify it during iteration
960. self.lock.acquire()
961. try:
962. for gline in self._glines.copy():
963. # Remove expired g-lines
964. if self._glines[gline][1] < self.ts():
965. self._deactivateGline(gline)
966. finally:
967. self.lock.release()
968.
969. def _deactivateGline(self, gline):
970. """ Deactivate a g-line """
971. self._glines[gline] = (self._glines[gline][0], self._glines[gline][1], False, self._glines[gline][3], self._glines[gline][4])
972.
973. def addGline(self, origin, mask, target, expires, ts, description):
974. """ Add a g-line """
975. # TODO: Check if origin can actually set g-lines
976. if target == None or target == self.getServerID():
977. self._glines[mask] = (description, expires, True, ts, target == self.getServerID())
978. self._callback(self.CALLBACK_GLINEADD, (origin, mask, target, expires, description))
979.
980. def isGlined(self, host):
981. """ Check if someone is g-lined """
982. self.lock.acquire()
983. try:
984. self._cleanupGlines()
985. for mask in self._glines:
986. if fnmatch.fnmatch(host, mask):
987. return self._glines[mask][2]
988. else:
989. return False
990. finally:
991. self.lock.release()
992.
993. def removeGline(self, origin, mask, target, ts):
994. """ Remove a g-line """
995. # TODO: Check if origin can actually remove g-lines
996. self.lock.acquire()
997. try:
998. if target == None or target == self.getServerID():
999. # Make shallow copy of dictionary so we can modify it during iteration
1000. for gline in self._glines.copy():
1001. # Remove any g-lines that match that mask
1002. if fnmatch.fnmatch(gline, mask):
1003. self._deactivateGline(gline)
1004. finally:
1005. self.lock.release()
1006. self._callback(self.CALLBACK_GLINEREMOVE, (origin, mask, target))
1007.
1008. class user:
1009. """ Represents a user internally """
1010.
1011. numeric = None
1012. nickname = ""
1013. username = ""
1014. hostname = ""
1015. _modes = dict()
1016. channels = set()
1017. ip = 0
1018. fullname = ""
1019. account = ""
1020. hops = 0
1021. ts = 0
1022. away_reason = None
1023. invites = set()
1024. silences = set()
1025.
1026. def __init__(self, numeric, nickname, username, hostname, modes, ip, hops, ts, fullname):
1027. self.numeric = numeric
1028. self.nickname = nickname
1029. self.username = username
1030. self.hostname = hostname
1031. self._modes = dict()
1032. for mode in modes:
1033. self.changeMode(mode)
1034. self.ip = ip
1035. self.hops = hops
1036. self.ts = ts
1037. self.fullname = fullname
1038. self.away_reason = None
1039. self.channels = set()
1040. self.invites = set()
1041. self.silences = set()
1042.
1043. def auth(self, account):
1044. """ Mark this user as authenticated """
1045. if self.account == "":
1046. self.account = account
1047. self._modes["r"] = account
1048. else:
1049. raise StateError("Authentication state change received for someone who is already authenticated")
1050.
1051. def changeMode(self, mode):
1052. """ Change a single mode associated with this user """
1053. if mode[0][0] == "+" and mode[1] == None:
1054. self._modes[mode[0][1]] = True
1055. elif mode[0][0] == "+" and mode[1] != None:
1056. self._modes[mode[0][1]] = mode[1]
1057. # If this mode is an authentication mode (i.e., in a burst)
1058. if mode[0][1] == "r":
1059. self.auth(mode[1])
1060. else:
1061. self._modes[mode[0][1]] = False
1062.
1063. def hasMode(self, mode):
1064. """ Return whether a user has a mode """
1065. if mode in self._modes:
1066. return self._modes[mode]
1067. else:
1068. return False
1069.
1070. def modes(self):
1071. """ Return the modes this user has """
1072. ml = []
1073. for mode in self._modes:
1074. if self._modes[mode] == True:
1075. ml.append(("+" + mode, None))
1076. elif self._modes[mode] == False:
1077. pass
1078. else:
1079. ml.append(("+" + mode, str(self._modes[mode])))
1080. return ml
1081.
1082. def isAway(self):
1083. """ Return whether a user is away or not """
1084. if self.away_reason == None:
1085. return False
1086. else:
1087. return True
1088.
1089. def join(self, channel):
1090. self.channels.add(channel)
1091. if self.isInvited(channel):
1092. self.invites.remove(channel)
1093.
1094. def part(self, channel):
1095. self.channels.remove(channel)
1096.
1097. def invite(self, channel):
1098. self.invites.add(channel)
1099.
1100. def isInvited(self, channel):
1101. return channel in self.invites
1102.
1103. def addSilence(self, mask):
1104. self.silences.add(mask)
1105.
1106. def removeSilence(self, mask):
1107. self.silences = self.silences - set([mask])
1108.
1109. def isSilenced(self, mask):
1110. """ Returns whether or not this user has silenced this host """
1111. for silence in self.silences:
1112. if fnmatch.fnmatch(mask, silence):
1113. return True
1114. return False
1115.
1116. class channel:
1117. """ Represents a channel internally """
1118.
1119. name = ""
1120. ts = 0
1121. _users = dict()
1122. zombies = set()
1123. _modes = dict()
1124. bans = []
1125. topic = ""
1126. topic_changer = ""
1127. topic_ts = 0
1128.
1129. def __init__(self, name, ts):
1130. self.name = name
1131. self.ts = ts
1132. self._users = dict()
1133. self.zombies = set()
1134. self._modes = dict()
1135. self.bans = []
1136. self.topic = ""
1137. self.topic_changer = ""
1138. self.topic_ts = 0
1139.
1140. def join(self, numeric, modes):
1141. """ Add a user to a channel """
1142. self._users[numeric] = set(modes)
1143.
1144. def part(self, numeric):
1145. """ A user is parting this channel """
1146. if numeric in self.zombies:
1147. self.zombies.remove(numeric)
1148. del self._users[numeric]
1149.
1150. def kick(self, numeric):
1151. self.zombies.add(numeric)
1152.
1153. def changeMode(self, mode):
1154. """ Change a single mode associated with this channel """
1155. if mode[0][0] == "+" and mode[1] == None:
1156. self._modes[mode[0][1]] = True
1157. elif mode[0][0] == "+" and mode[1] != None:
1158. self._modes[mode[0][1]] = mode[1]
1159. else:
1160. self._modes[mode[0][1]] = False
1161.
1162. def hasMode(self, mode):
1163. """ Return whether a channel has a mode (and if it's something with an option, what it is) """
1164. if mode in self._modes:
1165. return self._modes[mode]
1166. else:
1167. return False
1168.
1169. def modes(self):
1170. ml = []
1171. for mode in self._modes:
1172. if self._modes[mode] == True:
1173. ml.append(("+" + mode, None))
1174. elif self._modes[mode] == False:
1175. pass
1176. else:
1177. ml.append(("+" + mode, str(self._modes[mode])))
1178. return ml
1179.
1180. def clearBans(self):
1181. """ Clears bans from the channel """
1182. self.bans = []
1183.
1184. def addBan(self, mask):
1185. """ Adds a ban to the channel """
1186. self.bans.append(mask)
1187.
1188. def removeBan(self, mask):
1189. """ Removes a ban from the channel """
1190. for ban in self.bans:
1191. if fnmatch.fnmatch(ban, mask):
1192. self.bans.remove(ban)
1193.
1194. def ison(self, numeric):
1195. """ Returns whether a not a user is on a channel """
1196. return numeric in self._users
1197.
1198. def users(self):
1199. """ Return the list of users """
1200. r = self._users.copy()
1201. for z in self.zombies:
1202. del r[z]
1203. return r
1204.
1205. def isop(self, numeric):
1206. """ Check if a user is op on a channel """
1207. if self.ison(numeric):
1208. return "o" in self._users[numeric]
1209. else:
1210. return False
1211.
1212. def ops(self):
1213. ret = set()
1214. for user in self.users():
1215. if self.isop(user):
1216. ret.add(user)
1217. return ret
1218.
1219. def op(self, numeric):
1220. self._users[numeric].add("o")
1221.
1222. def deop(self, numeric):
1223. self._users[numeric].remove("o")
1224.
1225. def clearOps(self):
1226. for op in self.ops():
1227. self.deop(op)
1228.
1229. def isvoice(self, numeric):
1230. """ Check if a user is voice on a channel """
1231. if self.ison(numeric):
1232. return "v" in self._users[numeric]
1233. else:
1234. return False
1235.
1236. def voices(self):
1237. ret = set()
1238. for user in self.users():
1239. if self.isvoice(user):
1240. ret.add(user)
1241. return ret
1242.
1243. def voice(self, numeric):
1244. self._users[numeric].add("v")
1245.
1246. def devoice(self, numeric):
1247. self._users[numeric].remove("v")
1248.
1249. def clearVoices(self):
1250. for voice in self.voices():
1251. self.devoice(voice)
1252.
1253. def changeTopic(self, new_topic, ts, name):
1254. self.topic = new_topic
1255. self.topic_ts = ts
1256. self.topic_changer = name
1257.
1258. class server:
1259. """ Internally represent a server """
1260. numeric = 0
1261. origin = None
1262. name = ""
1263. maxclient = 0
1264. boot_ts = 0
1265. link_ts = 0
1266. protocol = ""
1267. hops = 0
1268. flags = set()
1269. description = ""
1270. children = set()
1271.
1272. def __init__(self, origin, numeric, name, maxclient, boot_ts, link_ts, protocol, hops, flags, description):
1273. self.numeric = numeric
1274. self.origin = origin
1275. self.name = name
1276. self.maxclient = maxclient
1277. self.boot_ts = boot_ts
1278. self.link_ts = link_ts
1279. self.protocol = protocol
1280. self.hops = hops
1281. self.flags = set(flags)
1282. self.description = description
1283. self.children = set()
1284.
1285. def addChild(self, child):
1286. self.children.add(child)
1287.
1288. class StateError(Exception):
1289. """ An exception raised if a state change would be impossible, generally suggesting we've gone out of sync """
1290. pass