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