def gab_shared_contacts(providersession): user = os.getenv('KOPANO_TEST_USER2') password = os.getenv('KOPANO_TEST_PASSWORD2') socket = os.getenv('KOPANO_SOCKET') session = OpenECSession(user, password, socket, providers=[b'ZARAFA6', b'ZCONTACTS']) store = GetDefaultStore(session) seid = store.GetProps([PR_ENTRYID], 0)[0] usereid = store.GetProps([PR_MAILBOX_OWNER_ENTRYID], 0)[0].Value root = store.OpenEntry(None, None, 0) ceid = root.GetProps([PR_IPM_CONTACT_ENTRYID], 0)[0].Value sharedfolder = session.OpenEntry(ceid, None, 0) shared_eid = sharedfolder.GetProps([PR_ENTRYID], 0)[0] add_folder_to_provider(providersession, seid.Value, shared_eid.Value, 'Shared Contacts') acltab = sharedfolder.OpenProperty(PR_ACL_TABLE, IID_IExchangeModifyTable, 0, MAPI_MODIFY) rowlist = [ ROWENTRY(ROW_ADD, [ SPropValue(PR_MEMBER_ENTRYID, usereid), SPropValue(PR_MEMBER_RIGHTS, ecRightsFolderVisible | ecRightsReadAny) ]) ] acltab.ModifyTable(ROWLIST_REPLACE, rowlist) yield sharedfolder # Reset ACL's acltab.ModifyTable(ROWLIST_REPLACE, [])
def test_restore_session(session): data = kc_session_save(session) restored_session = kc_session_restore(data) assert restored_session # Test if the session works assert GetDefaultStore(restored_session)
def on_get(self, req, resp, userid=None, folderid=None, itemid=None, photoid=None, method=None): server, store = _server_store(req, userid, self.options) photo = None if userid: photo = server.user(userid=userid).photo elif itemid: folder = _folder(store, folderid or 'contacts') photo = _item(folder, itemid).photo else: userid = kopano.Store(server=server, mapiobj=GetDefaultStore( server.mapisession)).user.userid photo = server.user(userid=userid).photo if not photo: raise falcon.HTTPNotFound(description="The photo wasn't found") if photoid: size = tuple(map(int, photoid.lower().split('x'))) photo = photo.scale(size) if method == '$value': resp.content_type = photo.mimetype resp.data = photo.data else: self.respond(req, resp, photo)
def on_get(self, req, resp, userid=None, method=None): server, store = _server_store(req, userid) if not method: if req.path.split('/')[-1] == 'me': userid = kopano.Store(server=server, mapiobj=GetDefaultStore( server.mapisession)).user.userid if userid: data = server.user(userid=userid) else: data = self.generator(req, server.users) self.respond(req, resp, data) elif method == 'mailFolders': data = self.generator(req, store.mail_folders, 0) self.respond(req, resp, data, MailFolderResource.fields) elif method == 'contactFolders': data = self.generator(req, store.contacts.folders, 0) self.respond(req, resp, data, ContactFolderResource.fields) elif method == 'calendars': data = self.generator(req, store.calendars, 0) self.respond(req, resp, data, CalendarResource.fields) elif method == 'calendar': data = store.calendar self.respond(req, resp, data, CalendarResource.fields) elif method == 'messages': inbox = store.inbox args = urlparse.parse_qs(req.query_string) # TODO generalize if '$search' in args: text = args['$search'][0] def yielder(**kwargs): for item in inbox.search(text): yield item data = self.generator(req, yielder, 0) else: data = self.generator(req, inbox.items, inbox.count) self.respond(req, resp, data, MessageResource.fields) elif method == 'contacts': contacts = store.contacts data = self.generator(req, contacts.items, contacts.count) self.respond(req, resp, data, ContactResource.fields) elif method == 'events': calendar = store.calendar data = self.generator(req, calendar.items, calendar.count) self.respond(req, resp, data, EventResource.fields)
def get_user_store(self, userid): imsgstore = self._find_user_store(userid) if not imsgstore: try: model = get_model('models_ldap.User') user = model.get_by_userid(userid) session = self.get_session(user.zarafaUserServer) store = GetDefaultStore(session) service_admin = store.QueryInterface(tags.IID_IECServiceAdmin) userEntryId = service_admin.ResolveUserName(userid, tags.MAPI_UNICODE) service_admin.CreateStore(tags.ECSTORE_TYPE_PRIVATE, userEntryId) except Exception as e: logger.exception(e) imsgstore = self._find_user_store(userid) return self._get_store_model(imsgstore)
def get_tenant_stores(self, tenant): # uses GetMailboxTable to list stores then open each IMsgStore store = GetDefaultStore(self.session) iems = store.QueryInterface(tags.IID_IExchangeManageStore) table = iems.GetMailboxTable(None, 0) # FIXME: set a filter on the table to filter by tenant, # needs ZCP support: ZCP-11417 table.SetColumns([ tags.PR_DISPLAY_NAME_W, tags.PR_EC_COMPANYID, tags.PR_EC_COMPANY_NAME_W, tags.PR_EC_STOREGUID, tags.PR_EC_STORETYPE, tags.PR_MESSAGE_SIZE_EXTENDED, tags.PR_STORE_ENTRYID, tags.PR_EC_USERNAME_W, tags.PR_ENTRYID, ], 0) stores = [] rows = table.QueryRows(-1, 0) for row in rows: from debug import pprint_propvaluelist #pprint_propvaluelist(row) entryId = row[6].Value st = self.session.OpenMsgStore(0, entryId, tags.IID_IMsgStore, tags.MDB_WRITE) try: store = self._get_store_model(st) # filter by tenant since there is no way to filter the table earlier if tenant.zarafaId == store.tenant.zarafaId: stores.append(store) #store.tenant = tenant except Exception as e: pass #print e return stores
def on_get(self, req, resp, userid=None, method=None): server, store = _server_store(req, userid if userid != 'delta' else None) if not method: if req.path.split('/')[-1] == 'me': userid = kopano.Store(server=server, mapiobj=GetDefaultStore( server.mapisession)).user.userid if userid: if userid == 'delta': self.delta(req, resp, server) return else: data = server.user(userid=userid) else: data = self.generator(req, server.users) self.respond(req, resp, data) elif method == 'mailFolders': data = self.generator(req, store.mail_folders, 0) self.respond(req, resp, data, MailFolderResource.fields) elif method == 'contactFolders': data = self.generator(req, store.contacts.folders, 0) self.respond(req, resp, data, ContactFolderResource.fields) elif method == 'messages': # TODO store-wide? data = self.folder_gen(req, store.inbox) self.respond(req, resp, data, MessageResource.fields) elif method == 'contacts': data = self.folder_gen(req, store.contacts) self.respond(req, resp, data, ContactResource.fields) elif method == 'calendar': data = store.calendar self.respond(req, resp, data, CalendarResource.fields) elif method == 'calendars': data = self.generator(req, store.calendars, 0) self.respond(req, resp, data, CalendarResource.fields) elif method == 'events': # TODO multiple calendars? calendar = store.calendar data = self.generator(req, calendar.items, calendar.count) self.respond(req, resp, data, EventResource.fields) elif method == 'calendarView': # TODO multiple calendars? start, end = _start_end(req) data = (store.calendar.occurrences(start, end), TOP, 0, 0) self.respond(req, resp, data, EventResource.fields)
def test_delegate_permissions_owner(publicfolder, session3, kind, rights, folder_access, message_access): print(kind) access = publicfolder.GetProps([PR_ACCESS], 0)[0].Value defaultAccess = MAPI_ACCESS_MODIFY | MAPI_ACCESS_READ | MAPI_ACCESS_DELETE | MAPI_ACCESS_CREATE_HIERARCHY | MAPI_ACCESS_CREATE_CONTENTS assert defaultAccess == access store = GetDefaultStore(session3) userid = session3.QueryIdentity() folderid = publicfolder.GetProps([PR_ENTRYID], 0)[0].Value acls = publicfolder.OpenProperty(PR_ACL_TABLE, IID_IExchangeModifyTable, 0, MAPI_MODIFY) rowlist = [ ROWENTRY(ROW_ADD, [ SPropValue(PR_MEMBER_ENTRYID, userid), SPropValue(PR_MEMBER_RIGHTS, rights) ]) ] acls.ModifyTable(ROWLIST_REPLACE, rowlist) message = publicfolder.CreateMessage(None, 0) message.SetProps([SPropValue(PR_SUBJECT, b'acl test')]) message.SaveChanges(0) eid = message.GetProps([PR_ENTRYID], 0)[0].Value access = message.GetProps([PR_ACCESS], 0)[0].Value assert access == MAPI_ACCESS_MODIFY | MAPI_ACCESS_READ | MAPI_ACCESS_DELETE # as user3, open public and get folder access public = GetPublicStore(session3) folder = public.OpenEntry(folderid, None, MAPI_BEST_ACCESS) access = folder.GetProps([PR_ACCESS], 0)[0].Value assert access == folder_access message = folder.OpenEntry(eid, None, 0) access = message.GetProps([PR_ACCESS], 0)[0].Value assert access == message_access
def _store(server, userid): if userid: return server.user(userid=userid).store else: return kopano.Store(server=server, mapiobj=GetDefaultStore(server.mapisession))
def notifystore(notifysession): return GetDefaultStore(notifysession)
def get_service_admin(self): store = GetDefaultStore(self.session) service_admin = store.QueryInterface(tags.IID_IECServiceAdmin) return service_admin
def adminstore(adminsession): return GetDefaultStore(adminsession)
def on_get(self, req, resp, userid=None, method=None): server, store = _server_store(req, userid if userid != 'delta' else None, self.options) if not userid and req.path.split('/')[-1] != 'users': userid = kopano.Store(server=server, mapiobj = GetDefaultStore(server.mapisession)).user.userid if method and not store: raise falcon.HTTPNotFound(description="The user store has no store") if not method: if userid: if userid == 'delta': req.context['deltaid'] = '{userid}' self.delta(req, resp, server) return else: data = server.user(userid=userid) else: args = self.parse_qs(req) userid = kopano.Store(server=server, mapiobj = GetDefaultStore(server.mapisession)).user.userid company = server.user(userid=userid).company query = None if '$search' in args: query = args['$search'][0] def yielder(**kwargs): yield from company.users(hidden=False, inactive=False, query=query, **kwargs) data = self.generator(req, yielder) self.respond(req, resp, data) elif method == 'mailFolders': data = self.generator(req, store.mail_folders, 0) self.respond(req, resp, data, MailFolderResource.fields) elif method == 'contactFolders': data = self.generator(req, store.contact_folders, 0) self.respond(req, resp, data, ContactFolderResource.fields) elif method == 'messages': # TODO store-wide? data = self.folder_gen(req, store.inbox) self.respond(req, resp, data, MessageResource.fields) elif method == 'contacts': data = self.folder_gen(req, store.contacts) self.respond(req, resp, data, ContactResource.fields) elif method == 'calendars': data = self.generator(req, store.calendars, 0) self.respond(req, resp, data, CalendarResource.fields) elif method == 'events': # TODO multiple calendars? calendar = store.calendar data = self.generator(req, calendar.items, calendar.count) self.respond(req, resp, data, EventResource.fields) elif method == 'calendarView': # TODO multiple calendars? merge code with calendar.py start, end = _start_end(req) def yielder(**kwargs): for occ in store.calendar.occurrences(start, end, **kwargs): yield occ data = self.generator(req, yielder) self.respond(req, resp, data, EventResource.fields) elif method == 'reminderView': # TODO multiple calendars? # TODO use restriction in pyko: calendar.reminders(start, end)? start, end = _start_end(req) def yielder(**kwargs): for occ in store.calendar.occurrences(start, end): if occ.reminder: yield occ data = self.generator(req, yielder) self.respond(req, resp, data, ReminderResource.fields) elif method == 'memberOf': user = server.user(userid=userid) data = (user.groups(), DEFAULT_TOP, 0, 0) self.respond(req, resp, data, GroupResource.fields) elif method == 'photos': # TODO multiple photos? user = server.user(userid=userid) def yielder(**kwargs): photo = user.photo if photo: yield photo data = self.generator(req, yielder) self.respond(req, resp, data, ProfilePhotoResource.fields) elif method: raise HTTPBadRequest("Unsupported segment '%s'" % method)
def mapistore(self): if self._mapistore is None: self._mapistore = GetDefaultStore(self.mapisession) return self._mapistore
def store(session): return GetDefaultStore(session)
class Server(object): """Server class""" def __init__(self, options=None, config=None, sslkey_file=None, sslkey_pass=None, server_socket=None, auth_user=None, auth_pass=None, log=None, service=None, mapisession=None, parse_args=True): """ Create Server instance. By default, tries to connect to a storage server as configured in ``/etc/kopano/admin.cfg`` or at UNIX socket ``/var/run/kopano/server.sock`` Looks at command-line to see if another server address or other related options were given (such as -c, -s, -k, -p) :param server_socket: similar to 'server_socket' option in config file :param sslkey_file: similar to 'sslkey_file' option in config file :param sslkey_pass: similar to 'sslkey_pass' option in config file :param config: path of configuration file containing common server options, for example ``/etc/kopano/admin.cfg`` :param auth_user: username to user for user authentication :param auth_pass: password to use for user authentication :param log: logger object to receive useful (debug) information :param options: OptionParser instance to get settings from (see :func:`parser`) :param parse_args: set this True if cli arguments should be parsed """ self.options = options self.config = config self.sslkey_file = sslkey_file self.sslkey_pass = sslkey_pass self.server_socket = server_socket self.service = service self.log = log self.mapisession = mapisession self._store_cache = {} if not self.mapisession: # get cmd-line options if parse_args and not self.options: self.options, args = parser().parse_args() # determine config file if config: pass elif getattr(self.options, 'config_file', None): config_file = os.path.abspath(self.options.config_file) config = _config.Config(None, filename=self.options.config_file) else: config_file = '/etc/kopano/admin.cfg' try: open(config_file) # check if accessible config = _config.Config(None, filename=config_file) except IOError: pass self.config = config # get defaults if os.getenv("KOPANO_SOCKET"): # env variable used in testset self.server_socket = os.getenv("KOPANO_SOCKET") elif config: if not (server_socket or getattr(self.options, 'server_socket')): # XXX generalize self.server_socket = config.get('server_socket') self.sslkey_file = config.get('sslkey_file') self.sslkey_pass = config.get('sslkey_pass') self.server_socket = self.server_socket or "default:" # override with explicit or command-line args self.server_socket = server_socket or getattr(self.options, 'server_socket', None) or self.server_socket self.sslkey_file = sslkey_file or getattr(self.options, 'sslkey_file', None) or self.sslkey_file self.sslkey_pass = sslkey_pass or getattr(self.options, 'sslkey_pass', None) or self.sslkey_pass # make actual connection. in case of service, wait until this succeeds. self.auth_user = auth_user or getattr(self.options, 'auth_user', None) or 'SYSTEM' # XXX override with args self.auth_pass = auth_pass or getattr(self.options, 'auth_pass', None) or '' flags = EC_PROFILE_FLAGS_NO_NOTIFICATIONS # Username and password was supplied, so let us do verfication # (OpenECSession will not check password unless this parameter is provided) if self.auth_user and self.auth_pass: flags |= EC_PROFILE_FLAGS_NO_UID_AUTH while True: try: self.mapisession = OpenECSession(self.auth_user, self.auth_pass, self.server_socket, sslkey_file=self.sslkey_file, sslkey_pass=self.sslkey_pass, flags=flags) break except (MAPIErrorNetworkError, MAPIErrorDiskError): if service: service.log.warn("could not connect to server at '%s', retrying in 5 sec" % self.server_socket) time.sleep(5) else: raise Error("could not connect to server at '%s'" % self.server_socket) except MAPIErrorLogonFailed: raise LogonError('Could not logon to server: username or password incorrect') # start talking dirty self.mapistore = GetDefaultStore(self.mapisession) self.sa = self.mapistore.QueryInterface(IID_IECServiceAdmin) self.ems = self.mapistore.QueryInterface(IID_IExchangeManageStore) self._ab = None self._admin_store = None self._gab = None entryid = HrGetOneProp(self.mapistore, PR_STORE_ENTRYID).Value self.pseudo_url = entryid[entryid.find(b'pseudo:'):-1] # XXX ECSERVER self.name = self.pseudo_url[9:].decode('ascii') # XXX encoding, get this kind of stuff from pr_ec_statstable_servers..? def nodes(self): # XXX delay mapi sessions until actually needed for row in self.table(PR_EC_STATSTABLE_SERVERS).dict_rows(): yield Server(options=self.options, config=self.config, sslkey_file=self.sslkey_file, sslkey_pass=self.sslkey_pass, server_socket=row[PR_EC_STATS_SERVER_HTTPSURL], log=self.log, service=self.service) def table(self, name, restriction=None, order=None, columns=None): return Table(self, self.mapistore.OpenProperty(name, IID_IMAPITable, MAPI_UNICODE, 0), name, restriction=restriction, order=order, columns=columns) def tables(self): for table in (PR_EC_STATSTABLE_SYSTEM, PR_EC_STATSTABLE_SESSIONS, PR_EC_STATSTABLE_USERS, PR_EC_STATSTABLE_COMPANY, PR_EC_STATSTABLE_SERVERS): try: yield self.table(table) except MAPIErrorNotFound: pass def gab_table(self): # XXX separate addressbook class? useful to add to self.tables? ct = self.gab.GetContentsTable(MAPI_DEFERRED_ERRORS) return Table(self, ct, PR_CONTAINER_CONTENTS) @property def ab(self): if not self._ab: self._ab = self.mapisession.OpenAddressBook(0, None, 0) # XXX return self._ab @property def admin_store(self): if not self._admin_store: self._admin_store = _store.Store(mapiobj=self.mapistore, server=self) return self._admin_store @property def gab(self): if not self._gab: self._gab = self.ab.OpenEntry(self.ab.GetDefaultDir(), None, 0) return self._gab @property def guid(self): """Server GUID.""" return bin2hex(HrGetOneProp(self.mapistore, PR_MAPPING_SIGNATURE).Value) def user(self, name=None, email=None, create=False): """Return :class:`user <User>` with given name or email address. :param name: user name :param email: email address :param create: create user if it doesn't exist (name required) """ try: return _user.User(name, email=email, server=self) except NotFoundError: if create and name: return self.create_user(name) else: raise def get_user(self, name): """Return :class:`user <User>` with given name or *None* if not found.""" try: return self.user(name) except NotFoundError: pass def users(self, remote=False, system=False, parse=True): """Return all :class:`users <User>` on server. :param remote: include users on remote server nodes :param system: include system users """ if parse and getattr(self.options, 'users', None): for username in self.options.users: yield _user.User(_decode(username), self) return try: for name in self._companylist(): for user in Company(name, self).users(): # XXX remote/system check yield user except MAPIErrorNoSupport: for ecuser in self.sa.GetUserList(None, MAPI_UNICODE): username = ecuser.Username if system or username != u'SYSTEM': if remote or ecuser.Servername in (self.name, ''): yield _user.User(server=self, ecuser=ecuser) def create_user(self, name, email=None, password=None, company=None, fullname=None, create_store=True): """Create a new :class:`user <User>` on the server. :param name: the login name of the user :param email: the email address of the user :param password: the login password of the user :param company: the company of the user :param fullname: the full name of the user :param create_store: should a store be created for the new user :return: :class:`<User>` """ name = _unicode(name) fullname = _unicode(fullname or '') if email: email = _unicode(email) else: email = u'%s@%s' % (name, socket.gethostname()) if password: password = _unicode(password) if company: company = _unicode(company) if company and company != u'Default': self.sa.CreateUser(ECUSER(u'%s@%s' % (name, company), password, email, fullname), MAPI_UNICODE) user = self.company(company).user(u'%s@%s' % (name, company)) else: try: self.sa.CreateUser(ECUSER(name, password, email, fullname), MAPI_UNICODE) except MAPIErrorNoSupport: raise NotSupportedError("cannot create users with configured user plugin") except MAPIErrorCollision: raise DuplicateError("user '%s' already exists" % name) user = self.user(name) if create_store: try: self.sa.CreateStore(ECSTORE_TYPE_PRIVATE, _unhex(user.userid)) except MAPIErrorCollision: pass # create-user userscript may already create store return user def remove_user(self, name): # XXX delete(object)? """Remove a user :param name: the login name of the user """ user = self.user(name) self.sa.DeleteUser(user._ecuser.UserID) def company(self, name, create=False): """Return :class:`company <Company>` with given name. :param name: company name :param create: create company if it doesn't exist """ try: return Company(name, self) except MAPIErrorNoSupport: raise NotFoundError('no such company: %s' % name) except NotFoundError: if create: return self.create_company(name) else: raise def get_company(self, name): """:class:`company <Company>` with given name :param name: the company name :return: :class:`company <Company>` with given name or *None* if not found. """ try: return self.company(name) except NotFoundError: pass def remove_company(self, name): # XXX delete(object)? company = self.company(name) if company.name == u'Default': raise NotSupportedError('cannot remove company in single-tenant mode') else: self.sa.DeleteCompany(company._eccompany.CompanyID) def _companylist(self): return [eccompany.Companyname for eccompany in self.sa.GetCompanyList(MAPI_UNICODE)] @property def multitenant(self): """The server is multi-tenant.""" try: self._companylist() return True except MAPIErrorNoSupport: return False def companies(self, remote=False, parse=True): # XXX remote? """Return all :class:`companies <Company>` on server. :param remote: include companies without users on this server node (default False) :param parse: take cli argument --companies into account (default True) :return: Generator of :class:`companies <Company>` on server. """ if parse and getattr(self.options, 'companies', None): for name in self.options.companies: name = _decode(name) # can optparse give us unicode? try: yield Company(name, self) except MAPIErrorNoSupport: raise NotFoundError('no such company: %s' % name) return try: for name in self._companylist(): yield Company(name, self) except MAPIErrorNoSupport: yield Company(u'Default', self) def create_company(self, name): """Create a new :class:`company <Company>` on the server. :param name: the name of the company """ name = _unicode(name) try: self.sa.CreateCompany(ECCOMPANY(name, None), MAPI_UNICODE) except MAPIErrorCollision: raise DuplicateError("company '%s' already exists" % name) except MAPIErrorNoSupport: raise NotSupportedError("cannot create company in single-tenant mode") return self.company(name) def _store(self, guid): if len(guid) != 32: raise Error("invalid store id: '%s'" % guid) try: storeid = _unhex(guid) except: raise Error("invalid store id: '%s'" % guid) table = self.ems.GetMailboxTable(None, 0) # XXX merge with Store.__init__ table.SetColumns([PR_ENTRYID], 0) table.Restrict(SPropertyRestriction(RELOP_EQ, PR_STORE_RECORD_KEY, SPropValue(PR_STORE_RECORD_KEY, storeid)), TBL_BATCH) for row in table.QueryRows(-1, 0): return self._store2(row[0].Value) raise NotFoundError("no such store: '%s'" % guid) @_lru_cache(128) # backend doesn't like more than 1000 stores open on certain multiserver setup def _store2(self, storeid): # XXX max lifetime return self.mapisession.OpenMsgStore(0, storeid, IID_IMsgStore, MDB_WRITE) def groups(self): """Return all :class:`groups <Group>` on server.""" try: for ecgroup in self.sa.GetGroupList(None, MAPI_UNICODE): yield Group(ecgroup.Groupname, self) except NotFoundError: # XXX what to do here (single-tenant..), as groups do exist? pass def group(self, name, create=False): """Return :class:`group <Group>` with given name. :param name: group name :param create: create group if it doesn't exist """ try: return Group(name, self) except NotFoundError: if create: return self.create_group(name) else: raise def create_group(self, name, fullname='', email='', hidden=False, groupid=None): """Create a new :class:`group <Group>` on the server. :param name: the name of the group :param fullname: the full name of the group (optional) :param email: the email address of the group (optional) :param hidden: hide the group (optional) :param groupid: the id of the group (optional) :return: :class:`group <Group>` the created group """ name = _unicode(name) # XXX: fullname/email unicode? email = _unicode(email) fullname = _unicode(fullname) try: self.sa.CreateGroup(ECGROUP(name, fullname, email, int(hidden), groupid), MAPI_UNICODE) except MAPIErrorCollision: raise DuplicateError("group '%s' already exists" % name) return self.group(name) def remove_group(self, name): group = self.group(name) self.sa.DeleteGroup(group._ecgroup.GroupID) def delete(self, objects): """Delete users, groups, companies or stores from server. :param objects: The object(s) to delete """ objects = _utils.arg_objects(objects, (_user.User, Group, Company, _store.Store), 'Server.delete') for item in objects: if isinstance(item, _user.User): self.remove_user(item.name) elif isinstance(item, Group): self.remove_group(item.name) elif isinstance(item, Company): self.remove_company(item.name) elif isinstance(item, _store.Store): self.remove_store(item) def _pubstore(self, name): if name == 'public': if not self.public_store: raise NotFoundError("no public store") return self.public_store else: company = Company(name.split('@')[1], self) if not company.public_store: raise NotFoundError("no public store for company '%s'" % company.name) return company.public_store def store(self, guid=None, entryid=None): """Return :class:`store <Store>` with given GUID.""" if _unicode(guid).split('@')[0] == 'public': return self._pubstore(guid) else: return _store.Store(guid=guid, entryid=entryid, server=self) def get_store(self, guid): """Return :class:`store <Store>` with given GUID or *None* if not found.""" try: return self.store(guid) except Error: pass def stores(self, system=False, remote=False, parse=True): # XXX implement remote """Return all :class:`stores <Store>` on server node. :param system: include system stores :param remote: include stores on other nodes """ if parse and getattr(self.options, 'stores', None): for guid in self.options.stores: if guid.split('@')[0] == 'public': yield self._pubstore(guid) else: yield _store.Store(guid, server=self) return table = self.ems.GetMailboxTable(None, 0) table.SetColumns([PR_DISPLAY_NAME_W, PR_ENTRYID], 0) for row in table.QueryRows(-1, 0): store = _store.Store(mapiobj=self._store2(row[1].Value), server=self) if system or store.public or (store.user and store.user.name != 'SYSTEM'): yield store def remove_store(self, store): try: self.sa.RemoveStore(_unhex(store.guid)) except MAPIErrorCollision: raise Error("cannot remove store with GUID '%s'" % store.guid) def sync_users(self): self.sa.SyncUsers(None) def clear_cache(self): # XXX specify one or more caches? self.sa.PurgeCache(PURGE_CACHE_ALL) def purge_softdeletes(self, days): self.sa.PurgeSoftDelete(days) def purge_deferred(self): # XXX purge all at once? try: return self.sa.PurgeDeferredUpdates() # remaining records except MAPIErrorNotFound: return 0 def _pubhelper(self): try: self.sa.GetCompanyList(MAPI_UNICODE) raise Error('request for server-wide public store in multi-tenant setup') except MAPIErrorNoSupport: return next(self.companies()) @property def public_store(self): """Public :class:`store <Store>` in single-tenant mode.""" return self._pubhelper().public_store def create_public_store(self): """Create public :class:`store <Store>` in single-tenant mode.""" return self._pubhelper().create_public_store() def hook_public_store(self, store): """Hook public :class:`store <Store>` in single-tenant mode. :param store: store to hook """ return self._pubhelper().hook_public_store(store) def unhook_public_store(self): """Unhook public :class:`store <Store>` in single-tenant mode.""" return self._pubhelper().unhook_public_store() @property def state(self): """Current server state.""" return _ics.state(self.mapistore) def sync(self, importer, state, log=None, max_changes=None, window=None, begin=None, end=None, stats=None): """Perform synchronization against server node. :param importer: importer instance with callbacks to process changes :param state: start from this state (has to be given) :log: logger instance to receive important warnings/errors """ importer.store = None return _ics.sync(self, self.mapistore, importer, state, log or self.log, max_changes, window=window, begin=begin, end=end, stats=stats) @_timed_cache(minutes=60) def _resolve_email(self, entryid=None): try: mailuser = self.mapisession.OpenEntry(entryid, None, 0) return self.user(HrGetOneProp(mailuser, PR_ACCOUNT_W).Value).email # XXX PR_SMTP_ADDRESS_W from mailuser? except (Error, MAPIErrorNotFound): # XXX deleted user return '' # XXX groups def id_to_name(self, proptag): """Give the name representation of an property id. For example 0x80710003 => 'task:33025'. :param proptag: the property identifier """ return _proptag_to_name(proptag, self.admin_store) def __unicode__(self): return u'Server(%s)' % self.server_socket def __repr__(self): return _repr(self)
def __init__(self, options=None, config=None, sslkey_file=None, sslkey_pass=None, server_socket=None, auth_user=None, auth_pass=None, log=None, service=None, mapisession=None, parse_args=True): """ Create Server instance. By default, tries to connect to a storage server as configured in ``/etc/kopano/admin.cfg`` or at UNIX socket ``/var/run/kopano/server.sock`` Looks at command-line to see if another server address or other related options were given (such as -c, -s, -k, -p) :param server_socket: similar to 'server_socket' option in config file :param sslkey_file: similar to 'sslkey_file' option in config file :param sslkey_pass: similar to 'sslkey_pass' option in config file :param config: path of configuration file containing common server options, for example ``/etc/kopano/admin.cfg`` :param auth_user: username to user for user authentication :param auth_pass: password to use for user authentication :param log: logger object to receive useful (debug) information :param options: OptionParser instance to get settings from (see :func:`parser`) :param parse_args: set this True if cli arguments should be parsed """ self.options = options self.config = config self.sslkey_file = sslkey_file self.sslkey_pass = sslkey_pass self.server_socket = server_socket self.service = service self.log = log self.mapisession = mapisession self._store_cache = {} if not self.mapisession: # get cmd-line options if parse_args and not self.options: self.options, args = parser().parse_args() # determine config file if config: pass elif getattr(self.options, 'config_file', None): config_file = os.path.abspath(self.options.config_file) config = _config.Config(None, filename=self.options.config_file) else: config_file = '/etc/kopano/admin.cfg' try: open(config_file) # check if accessible config = _config.Config(None, filename=config_file) except IOError: pass self.config = config # get defaults if os.getenv("KOPANO_SOCKET"): # env variable used in testset self.server_socket = os.getenv("KOPANO_SOCKET") elif config: if not (server_socket or getattr(self.options, 'server_socket')): # XXX generalize self.server_socket = config.get('server_socket') self.sslkey_file = config.get('sslkey_file') self.sslkey_pass = config.get('sslkey_pass') self.server_socket = self.server_socket or "default:" # override with explicit or command-line args self.server_socket = server_socket or getattr(self.options, 'server_socket', None) or self.server_socket self.sslkey_file = sslkey_file or getattr(self.options, 'sslkey_file', None) or self.sslkey_file self.sslkey_pass = sslkey_pass or getattr(self.options, 'sslkey_pass', None) or self.sslkey_pass # make actual connection. in case of service, wait until this succeeds. self.auth_user = auth_user or getattr(self.options, 'auth_user', None) or 'SYSTEM' # XXX override with args self.auth_pass = auth_pass or getattr(self.options, 'auth_pass', None) or '' flags = EC_PROFILE_FLAGS_NO_NOTIFICATIONS # Username and password was supplied, so let us do verfication # (OpenECSession will not check password unless this parameter is provided) if self.auth_user and self.auth_pass: flags |= EC_PROFILE_FLAGS_NO_UID_AUTH while True: try: self.mapisession = OpenECSession(self.auth_user, self.auth_pass, self.server_socket, sslkey_file=self.sslkey_file, sslkey_pass=self.sslkey_pass, flags=flags) break except (MAPIErrorNetworkError, MAPIErrorDiskError): if service: service.log.warn("could not connect to server at '%s', retrying in 5 sec" % self.server_socket) time.sleep(5) else: raise Error("could not connect to server at '%s'" % self.server_socket) except MAPIErrorLogonFailed: raise LogonError('Could not logon to server: username or password incorrect') # start talking dirty self.mapistore = GetDefaultStore(self.mapisession) self.sa = self.mapistore.QueryInterface(IID_IECServiceAdmin) self.ems = self.mapistore.QueryInterface(IID_IExchangeManageStore) self._ab = None self._admin_store = None self._gab = None entryid = HrGetOneProp(self.mapistore, PR_STORE_ENTRYID).Value self.pseudo_url = entryid[entryid.find(b'pseudo:'):-1] # XXX ECSERVER self.name = self.pseudo_url[9:].decode('ascii') # XXX encoding, get this kind of stuff from pr_ec_statstable_servers..?
def providerstore(providersession): return GetDefaultStore(providersession)