Files
bongo/src/libs/python/bongo/store/StoreClient.py
T
2007-12-20 20:48:20 +00:00

896 lines
25 KiB
Python

import logging
from CommandStream import *
from StoreConnection import StoreConnection
from cStringIO import StringIO
import bongo
import re
import time
import random
import md5
import socket
class DocTypes:
Unknown = 0x0001
Mail = 0x0002
Event = 0x0003
Addressbook = 0x0004
Conversation = 0x0005
Calendar = 0x0006
Config = 0x0007
Folder = 0x1000
Shared = 0x2000
SharedFolder = Folder | Shared
Purged = 0x4000
class DocFlags:
Purged = (1 << 0)
Seen = (1 << 1)
Answered = (1 << 2)
Flagged = (1 << 3)
Deleted = (1 << 4)
Draft = (1 << 5)
Recent = (1 << 6)
Junk = (1 << 7)
Sent = (1 << 8)
Starred = (3 << 16)
names = {
Purged : "purged",
Seen : "read",
Answered : "answered",
Flagged : "flagged",
Deleted : "deleted",
Draft : "draft",
Recent : "recent",
Junk : "junk",
Sent : "sent",
Starred : "starred"
}
# reverse the names hash so we can look up flags by name
revNames = {}
for name, flag in names.items():
revNames[flag] = name
# some helpful functions
def GetName(flag):
return DocFlags.names[flag]
GetName = staticmethod(GetName)
def ByName(name):
return DocFlags.revNames[name]
ByName = staticmethod(ByName)
def AllFlags():
return DocFlags.names.keys()
AllFlags = staticmethod(AllFlags)
def GetMask(hash):
flags = mask = 0
for flag, status in hash.items():
if status:
flags = flags | flag
mask = mask | flag
return flags, mask
GetMask = staticmethod(GetMask)
class FlagMode:
Show = 0
Add = 1
Remove = 2
Replace = 3
class ACL :
def __init__(self, acl = None) :
self.entries = []
if acl :
split = acl.split(';')
for entry in split :
if entry == '' :
continue
tmp = re.split('\s+', entry)
self.entries.append((tmp[0], tmp[1], tmp[2]))
self.changed = False
def __str__(self) :
acl = ''
for entry in self.entries :
acl = acl + "%s %s %s;" % entry
return acl
def Clear(self) :
if (self.entries != []) :
self.entries = []
self.changed = True
def Grant(self, principal, priv) :
newEntry = ("grant", principal, priv)
for entry in self.entries :
if entry == newEntry :
return
self.changed = True
self.entries.append(newEntry)
def AddEntries(self, entries) :
self.changed = True
self.entries.extend(entries)
def Revoke(self, principal, priv) :
self.RevokeEntry(("grant", principal, priv))
def RevokeEntry(self, revokeEntry) :
newEntries = []
for entry in self.entries :
if entry != revokeEntry :
newEntries.append(entry)
else :
self.changed = True
self.entries = newEntries
def RevokeEntries(self, entries) :
for entry in entries :
self.RevokeEntry(entry)
class CalendarACL :
class Rights :
Read = (1 << 0)
Write = (1 << 1)
ReadProps = (1 << 2)
def __init__(self, acl) :
self.acl = acl
self.summary = None
def __str__(self) :
return str(self.acl)
def GetACL(self) :
return self.acl
def _GetUid (self, *args):
t = long(time.time() * 1000)
r = long(random.random() * 100000000000000000L)
try:
a = socket.gethostbyname(socket.gethostname())
except:
# if we can't get a network address, just imagine one
a = random.random() * 100000000000000000L
data = str(t) + ' ' + str (r) + ' ' + str(a) + ' ' + str(args)
data = md5.md5(data).hexdigest()
return data
def _GetRights(self, name) :
rights = 0
if name == "read" :
rights = rights | CalendarACL.Rights.Read
elif name == "write" :
rights = rights | CalendarACL.Rights.Write
elif name == "read-props" :
rights = rights | CalendarACL.Rights.ReadProps
return rights
def RightsToString(self, rights) :
names = []
if rights == 0 :
return "none"
if rights & CalendarACL.Rights.Read :
names.append("read")
if rights & CalendarACL.Rights.Write :
names.append("write")
if rights & CalendarACL.Rights.ReadProps :
names.append("read-props")
return ", ".join(names)
def SetPublic(self, rights) :
self.summary = None
entries = self._Find("all")
if (entries) :
self.acl.RevokeEntries(entries)
if (rights & CalendarACL.Rights.Read) :
self.acl.Grant("all", "read")
if (rights & CalendarACL.Rights.Write) :
self.acl.Grant("all", "write")
if (rights & CalendarACL.Rights.ReadProps) :
self.acl.Grant("all", "read-props")
def GetPublic(self) :
entries = self._Find("all")
rights = 0
for entry in entries :
rights = rights | self._GetRights(entry[2])
return rights
def AddPassword(self, user, password, rights) :
self.summary = None
if (rights & CalendarACL.Rights.Read) :
self.acl.Grant("token:pw:" + user + ":" + password, "read")
if (rights & CalendarACL.Rights.Write) :
self.acl.Grant("token:pw:" + user + ":" + password, "write")
def CheckPassword(self, user, password) :
entries = self._Find("token:pw:" + user + ":" + password)
if entries :
return True
else :
return False
def HasPassword(self, user = None) :
entries = self._FindPasswords()
if entries :
return True
else :
return False
def RemovePassword(self, user, password) :
self.summary = None
self.acl.RemoveEntries(self._Find("token:pw:" + user + ":" + password))
def RemovePasswords(self) :
self.summary = None
self.acl.RemoveEntries(self._FindPasswords())
def GetPasswords(self) :
entries = self._FindPasswords()
ret = {}
for entry in entries :
split = entry[1].split(":")
ret[split[2]] = split[3]
return ret
def Share(self, address, rights, password = None) :
self.summary = None
entries = self._FindAddress(address)
if (entries) :
if not password :
token = entries[0][1]
else :
token = "token:email:%s:%s" % (address, password)
self.acl.RevokeEntries(entries)
else :
if not password :
password = self._GetUid()
token = "token:email:%s:%s" % (address, password)
if (rights & CalendarACL.Rights.Read) :
self.acl.Grant(token, "read")
if (rights & CalendarACL.Rights.Write) :
self.acl.Grant(token, "write")
if (rights & CalendarACL.Rights.ReadProps) :
self.acl.Grant(token, "read-props")
return token[len("token:"):]
def Unshare(self, address) :
entries = self._FindAddress(address)
if (entries) :
self.acl.RevokeEntries(entries)
def GetAddressToken(self, address) :
entries = self._FindAddress(address)
token = entries[0][1]
return token[len("token:"):]
def UnshareAll(self) :
rights = self.GetPublic()
self.acl.Clear()
self.SetPublic(rights)
def Clear(self) :
self.acl.Clear()
def Summarize(self) :
if self.summary :
return self.summary
public = 0
addresses = {}
passwords = {}
for entry in self.acl.entries :
principal = entry[1]
rights = self._GetRights(entry[2])
if principal == "all" :
public = public | rights
elif principal.startswith("token:email:") :
remaining = principal[len("token:email:"):]
split = remaining.split(':', 2)
address = split[0]
password = split[1]
if addresses.has_key(address) :
entry = addresses[address]
else :
entry = { "password": password, "rights": 0 }
addresses[address] = entry
entry["token"] = principal[len("token:"):]
entry["rights"] = entry["rights"] | rights
elif principal.startswith("token:") :
password = principal[len("token:"):]
if (passwords.has_key(password)) :
passwords[password] = passwords[password] | rights
else :
passwords[password] = rights
ret = {}
ret["passwords"] = passwords
ret["addresses"] = addresses
ret["public"] = public
self.summary = ret
return ret
def _FindAddress(self, address) :
"""Find acls with a given email token prefix"""
ret = []
for entry in self.acl.entries :
if entry[1].startswith ("token:email:" + address + ":") :
ret.append(entry)
return ret
def _Find(self, principal) :
"""Find acls with a given principal"""
ret = []
for entry in self.acl.entries :
if entry[1] == principal :
ret.append(entry)
return ret
def _FindPasswords(self) :
"""Find all password acls"""
ret = []
for entry in self.acl.entries :
if entry[1].startswith("token:pw:") :
ret.append(entry)
return ret
class StoreClient:
def __init__(self, user, owner, authToken=None, authCookie=None,
authPassword=None):
#from libbongo.libs import msgapi
self.owner = self.user = None
#addr = msgapi.FindUserNmap(owner)
#if addr is None:
# raise bongo.BongoError ("Could not look up nmap host for user %s" % owner)
#(host, port) = addr
host = "localhost"
port = 689
if authCookie:
self.connection = StoreConnection(host, port, systemAuth=False)
if not self.connection.CookieAuth(user, authCookie):
raise bongo.BongoError("Failed cookie authentication with store")
elif authPassword:
self.connection = StoreConnection(host, port, systemAuth=False)
if not self.connection.UserAuth(user, authPassword):
raise bongo.BongoError("Failed user authentication with store")
else:
self.connection = StoreConnection(host, port)
self.stream = self.connection.stream
# if we authed with a cookie or password, we don't need to USER
if not authCookie and not authPassword and user is not None:
self.User(user)
self.Store(owner)
if authToken is not None:
self.Token(authToken)
def _escape_arg(self, arg):
return "\"" + self._escape_quotes(arg) + "\""
def _escape_quotes(self, s):
return s.replace('"', '\\"')
def _escape_query(self, s):
return self._escape_quotes(s)
def Collections(self, path=""):
command = "COLLECTIONS %s" % path
self.stream.Write(command)
return CollectionIterator(self.stream)
def CookieDelete(self, token):
command = "COOKIE DELETE %s" % token
stream.log.debug("SEND: COOKIE DELETE ****************")
self.stream.Write(command, nolog=1)
r = self.stream.GetResponse()
if r.code != 1000:
raise CommandError(r)
def CookieNew(self, timeout):
command = "COOKIE NEW %d" % (time.time() + timeout)
self.stream.Write(command)
r = self.stream.GetResponse(nolog=True)
if r.code != 1000:
self.stream.log.debug("READ: %s %s", r.code, r.message)
raise CommandError(r)
self.stream.log.debug("READ: 1000 ****************")
return r.message
def Create(self, path, guid="", existingOk=False):
command = "CREATE %s %s" % (path, guid)
self.stream.Write(command)
r = self.stream.GetResponse()
if r.code != 1000:
if r.code != 4226 or not existingOk:
raise CommandError(r)
return r
def Conversation(self, uid, props=[]):
command = "CONVERSATION %s" % uid
if len(props) > 0:
command = command + " P" + ",".join(props)
self.stream.Write(command)
return ItemIterator(self.stream, len(props))
def Conversations(self, source=None, props=[], query=None, start=-1, end=-1,
center=None, showTotal=False, flags=None, mask=None):
command = "CONVERSATIONS"
if source is not None:
command = command + " S" + source
if start == -1 and end == -1:
command = command + " Rall"
else:
command = command + " R%d-%d" % (start, end)
if len(props) > 0:
command = command + " P" + ",".join(props)
if query is not None:
command = command + " \"Q" + self._escape_query(query) + "\""
if center is not None:
command = command + " C" + center
if flags is not None:
command = command + " F" + str(flags)
if mask is not None:
command = command + " M" + str(mask)
if showTotal:
command = command + " T"
self.stream.Write(command)
return ItemIterator(self.stream, len(props))
def Delete(self, doc):
self.stream.Write("DELETE %s" % (doc))
r = self.stream.GetResponse()
if r.code != 1000:
raise CommandError(r)
def Events(self, calendar=None, props=[], query=None, start=None, end=None):
command = "EVENTS"
if calendar is not None:
command = command + " C" + calendar
if len(props) > 0:
command = command + " P" + ",".join(props)
if query is not None:
command = command + " \"Q" + self._escape_query(query) + "\""
if start != None and end != None:
command = command + " \"D%s-%s\"" % (start.strftime('%Y%m%dT%H%M%S'), end.strftime('%Y%m%dT%H%M%S'))
self.stream.Write(command)
return ItemIterator(self.stream, len(props))
def Flag(self, guid, flags=None, mode=None):
command = "FLAG %s" % guid
if flags is None:
mode = FlagMode.Show
elif mode is None:
mode = FlagMode.Replace
command = "%s %d" % (command, flags)
elif mode == FlagMode.Add:
command = "%s +%d" % (command, flags)
elif mode == FlagMode.Remove:
command = "%s -%d" % (command, flags)
self.stream.Write(command)
r = self.stream.GetResponse()
if r.code != 1000:
raise CommandError(r)
(old, new) = r.message.split(" ", 1)
return (old, new)
def GetACL(self, doc) :
command = "PROPGET \"%s\" nmap.access-control" % doc
self.stream.Write(command)
r = self.stream.GetResponse()
if r.code != 2001 and r.code != 3245:
raise CommandError(r)
acl = ""
if r.code == 2001 :
(key, length) = r.message.split(" ", 2)
acl = self.stream.Read(int(length))
# eat the \r\n afterward
self.stream.Read(2)
return ACL(acl)
def Info(self, path, props=[]):
command = "INFO %s" % self._escape_arg(path)
if len(props) > 0:
command = command + " P" + ",".join(props)
self.stream.Write(command)
r = self.stream.GetResponse()
if r.code == 2001:
item = Item(r)
for i in range(len(props)):
r = self.stream.GetResponse()
if r.code == 2001:
(key, length) = r.message.split(" ", 2)
item.props[key] = self.stream.Read(int(length))
# eat the \r\n afterward
self.stream.Read(2)
# eat the response line
self.stream.GetResponse()
return item
elif r.code == 1000:
return None
raise CommandError(r)
def ISearch(self, query, props = [], start=-1, end=-1):
command = "ISEARCH "
if start == -1 and end == -1:
command = command + " Rall"
else:
command = command + " R%d-%d" % (start, end)
command = "%s \"%s\"" % (command, self._escape_query(query))
if len(props) > 0:
command = command + " P" + ",".join(props)
self.stream.Write(command)
return ItemIterator(self.stream, len(props))
def Link(self, calendar, doc) :
self.stream.Write("LINK \"%s\" %s" % (calendar, doc))
r = self.stream.GetResponse()
if r.code != 1000 :
raise CommandError(r)
def List(self, path, props=[], start=-1, end=-1, flags=None, mask=None):
command = "LIST %s" % path
if start == -1 and end == -1:
command = command + " Rall"
else:
command = command + " R%d-%d" % (start, end)
if len(props) > 0:
command = command + " P" + ",".join(props)
if flags is not None:
command = command + " F" + str(flags)
if mask is not None:
command = command + " M" + str(mask)
self.stream.Write(command)
return ItemIterator(self.stream, len(props))
def MailingLists(self, source):
command = "MAILINGLISTS %s" % source
self.stream.Write(command)
return ResponseIterator(self.stream)
def Mime(self, uid):
command = "MIME %s" % uid
self.stream.Write(command)
parts = []
r = self.stream.GetResponse()
while r.code in (2002, 2003, 2004):
parts.append(MimeItem(r))
r = self.stream.GetResponse()
if len(parts) == 0:
return None
topPart = parts[0]
if topPart.multipart:
parent = topPart
for child in parts[1:]:
child.parent = parent
if child.end:
parent = parent.parent
if parent is None:
break
else:
parent.children.append(child)
if child.multipart:
parent = child
if r.code != 1000:
raise CommandError(r)
return topPart
def Move(self, uid, path, filename=None):
command = "MOVE %s \"%s\"" % (uid, path)
if filename is not None:
command = "%s \"%s\"" % (command, filename)
self.stream.Write(command)
r = self.stream.GetResponse()
if r.code != 1000:
raise CommandError(r)
def PropGet(self, doc, name=None):
command = "PROPGET %s" % doc
if name is not None:
self.stream.Write("%s %s" % (command, name))
r = self.stream.GetResponse()
if r.code != 2001:
raise CommandError(r)
(key, length) = r.message.split(" ", 2)
data = unicode(self.stream.Read(int(length)), "utf-8")
# eat the \r\n afterward
self.stream.Read(2)
return data
self.stream.Write(command)
props = {}
r = self.stream.GetResponse()
while r.code == 2001 or r.code == 3245:
if r.code == 3245:
# set empty properties to None
props[key] = None
r = self.stream.GetResponse()
continue
(key, length) = r.message.split(" ", 2)
props[key] = unicode(self.stream.Read(int(length)), "utf-8")
# eat the \r\n afterward
self.stream.Read(2)
r = self.stream.GetResponse()
return props
def PropSet(self, doc, name, value=None):
if value is None:
value = ""
self.stream.Write("PROPSET %s %s %d" % (doc, name, len(value)))
r = self.stream.GetResponse()
if r.code != 2002:
raise CommandError(r)
self.stream.WriteRaw(value)
r = self.stream.GetResponse()
if r.code != 1000:
raise CommandError(r)
def Quit(self):
self.stream.Write("QUIT")
self.connection.Close()
def Read(self, doc):
self.stream.Write("READ %s" % (doc))
r = self.stream.GetResponse()
if r.code != 2001:
raise CommandError(r)
length = r.message.split(" ")[-1]
s = self.stream.Read(int(length))
# eat the \r\n afterward
self.stream.Read(2)
return s
def ReadMimePart(self, doc, part):
self.stream.Write("READ %s %d %d" % (doc, part.start, part.length))
r = self.stream.GetResponse()
if r.code != 2001:
raise CommandError(r)
length = r.message.split(" ")[-1]
s = self.stream.Read(int(length))
# eat the \r\n afterward
self.stream.Read(2)
return StringIO(s)
def Remove(self, collection):
self.stream.Write("REMOVE %s" % collection)
r = self.stream.GetResponse()
if r.code != 1000:
raise CommandError(r)
def Rename(self, oldname, newname):
self.stream.Write("RENAME %s %s" % (oldname, newname))
r = self.stream.GetResponse()
if r.code != 1000:
raise CommandError(r)
def Replace(self, doc, data):
self.stream.Write("REPLACE %s %d" % (doc, len(data)))
r = self.stream.GetResponse()
if r.code != 2002:
raise CommandError(r)
self.stream.WriteRaw(data)
r = self.stream.GetResponse()
(guid, junk) = r.message.split(" ", 1)
return guid
def Reset(self):
self.stream.Write("RESET")
r = self.stream.GetResponse()
if r.code != 1000:
raise CommandError(r)
def SetACL(self, doc, acl, force=False) :
if force or acl.changed :
self.PropSet(doc, "nmap.access-control", str(acl))
def Store(self, name):
self.stream.Write("STORE %s" % (name))
r = self.stream.GetResponse()
if r.code != 1000:
raise CommandError(r)
self.owner = name
return r
def Token(self, token):
self.stream.Write("TOKEN %s" % (token))
r = self.stream.GetResponse()
if r.code != 1000:
raise CommandError(r)
return r
def Unlink(self, calendar, event):
self.stream.Write("UNLINK %s %s" % (calendar, event))
r = self.stream.GetResponse()
if r.code != 1000:
raise CommandError(r)
return r
def User(self, name):
self.stream.Write("USER %s" % (name))
r = self.stream.GetResponse()
if r.code != 1000:
raise CommandError(r)
self.user = name
return r
def WriteImap(self, collection, type, imap, num, filename=None, index=None, guid=None, timeCreated=None, flags=None, link=None):
typ, data = imap.fetch(num, '(RFC822.SIZE)')
cnt = int(re.findall(r'RFC822\.SIZE (\d+)', data[0])[0])
command = "WRITE \"%s\" %d %d" % (collection, type, cnt)
if index is not None:
command = command + " I%s" % index
if filename is not None:
command = command + " \"F%s\"" % filename
if guid is not None:
command = command + " G%s" % guid
if timeCreated is not None:
command += " T" + timeCreated
if flags is not None:
command = command + " Z%d" % flags
if link is not None:
command = command + " \"L%s\"" % link
self.stream.Write(command)
r = self.stream.GetResponse()
if r.code != 2002:
raise CommandError(r)
self.stream.WriteRaw(imap.fetch(num, '(body[])')[1][0][1])
r = self.stream.GetResponse()
if r.code != 1000:
raise CommandError(r)
(guid, junk) = r.message.split(" ", 1)
return guid
def Write(self, collection, type, data, filename=None, index=None, guid=None, timeCreated=None, flags=None, link=None):
if data is None:
data = ""
command = "WRITE \"%s\" %d %d" % (collection, type, len(data))
if index is not None:
command = command + " I%s" % index
if filename is not None:
command = command + " \"F%s\"" % filename
if guid is not None:
command = command + " G%s" % guid
if timeCreated is not None:
command += " T" + timeCreated
if flags is not None:
command = command + " Z%d" % flags
if link is not None:
command = command + " \"L%s\"" % link
self.stream.Write(command)
r = self.stream.GetResponse()
if r.code != 2002:
raise CommandError(r)
self.stream.WriteRaw(data)
r = self.stream.GetResponse()
if r.code != 1000:
raise CommandError(r)
(guid, junk) = r.message.split(" ", 1)
return guid
def Reset(self) :
self.stream.Write("RESET\r\n")
self.stream.GetResponse()