Esempio n. 1
0
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, [])
Esempio n. 2
0
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)
Esempio n. 3
0
    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)
Esempio n. 4
0
    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)
Esempio n. 5
0
    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)
Esempio n. 6
0
    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
Esempio n. 7
0
    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)
Esempio n. 8
0
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
Esempio n. 9
0
def _store(server, userid):
    if userid:
        return server.user(userid=userid).store
    else:
        return kopano.Store(server=server,
                            mapiobj=GetDefaultStore(server.mapisession))
Esempio n. 10
0
def notifystore(notifysession):
    return GetDefaultStore(notifysession)
Esempio n. 11
0
 def get_service_admin(self):
     store = GetDefaultStore(self.session)
     service_admin = store.QueryInterface(tags.IID_IECServiceAdmin)
     return service_admin
Esempio n. 12
0
def adminstore(adminsession):
    return GetDefaultStore(adminsession)
Esempio n. 13
0
    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)
Esempio n. 14
0
 def mapistore(self):
     if self._mapistore is None:
         self._mapistore = GetDefaultStore(self.mapisession)
     return self._mapistore
Esempio n. 15
0
def store(session):
    return GetDefaultStore(session)
Esempio n. 16
0
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)
Esempio n. 17
0
    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..?
Esempio n. 18
0
def providerstore(providersession):
    return GetDefaultStore(providersession)