def test_get_elements(self): # Test that we can handle SOAP-level error messages # TODO: The request actually raises ErrorInvalidRequest, but we interpret that to mean a wrong API version and # end up throwing ErrorInvalidServerVersion. We should make a more direct test. svc = ResolveNames(self.account.protocol) with self.assertRaises(ErrorInvalidServerVersion): list(svc._get_elements(create_element("XXX")))
def test_resolvenames_parsing(self): # Test static XML since server has no roomlists ws = ResolveNames(self.account.protocol) xml = b'''\ <?xml version="1.0" encoding="utf-8"?> <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Header> <h:ServerVersionInfo MajorVersion="15" MinorVersion="0" MajorBuildNumber="1293" MinorBuildNumber="4" Version="V2_23" xmlns:h="http://schemas.microsoft.com/exchange/services/2006/types" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/> </s:Header> <s:Body> <m:ResolveNamesResponse xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"> <m:ResponseMessages> <m:ResolveNamesResponseMessage ResponseClass="Warning"> <m:MessageText>Multiple results were found.</m:MessageText> <m:ResponseCode>ErrorNameResolutionMultipleResults</m:ResponseCode> <m:DescriptiveLinkKey>0</m:DescriptiveLinkKey> <m:ResolutionSet TotalItemsInView="2" IncludesLastItemInRange="true"> <t:Resolution> <t:Mailbox> <t:Name>John Doe</t:Name> <t:EmailAddress>[email protected]</t:EmailAddress> <t:RoutingType>SMTP</t:RoutingType> <t:MailboxType>Mailbox</t:MailboxType> </t:Mailbox> </t:Resolution> <t:Resolution> <t:Mailbox> <t:Name>John Deer</t:Name> <t:EmailAddress>[email protected]</t:EmailAddress> <t:RoutingType>SMTP</t:RoutingType> <t:MailboxType>Mailbox</t:MailboxType> </t:Mailbox> </t:Resolution> </m:ResolutionSet> </m:ResolveNamesResponseMessage> </m:ResponseMessages> </m:ResolveNamesResponse> </s:Body> </s:Envelope>''' header, body = ws._get_soap_parts(response=MockResponse(xml)) res = ws._get_elements_in_response(response=ws._get_soap_messages( body=body)) self.assertSetEqual( { Mailbox.from_xml(elem=elem.find(Mailbox.response_tag()), account=None).email_address for elem in res }, {'*****@*****.**', '*****@*****.**'})
def test_exceeded_connection_count(self): # Test server repeatedly returning ErrorExceededConnectionCount svc = ResolveNames(self.account.protocol) tmp = svc._get_soap_messages try: # We need to fail fast so we don't end up in an infinite loop svc._get_soap_messages = Mock( side_effect=ErrorExceededConnectionCount("XXX")) with self.assertRaises(ErrorExceededConnectionCount) as e: list(svc.call(unresolved_entries=["XXX"])) self.assertEqual(e.exception.args[0], "XXX") finally: svc._get_soap_messages = tmp
def test_handle_backoff(self): # Test that we can handle backoff messages svc = ResolveNames(self.account.protocol) tmp = svc._response_generator orig_policy = self.account.protocol.config.retry_policy try: # We need to fail fast so we don't end up in an infinite loop self.account.protocol.config.retry_policy = FailFast() svc._response_generator = Mock( side_effect=ErrorServerBusy("XXX", back_off=1)) with self.assertRaises(ErrorServerBusy) as e: list(svc._get_elements(create_element("XXX"))) self.assertEqual(e.exception.args[0], "XXX") finally: svc._response_generator = tmp self.account.protocol.config.retry_policy = orig_policy
def gal(dump, search, verbose): """ Dump GAL using EWS. The slower technique used by https://github.com/dafthack/MailSniper default searches from "aa" to "zz" and prints them all. EWS only returns batches of 100 There will be doubles, so uniq after. """ if verbose: logging.basicConfig(level=logging.DEBUG, handlers=[PrettyXmlHandler()]) credentials = Credentials(tbestate.username, tbestate.password) username = tbestate.username if tbestate.exch_host: config = Configuration(server=tbestate.exch_host, credentials=credentials) account = Account(username, config=config, autodiscover=False, access_type=DELEGATE) else: account = Account(username, credentials=credentials, autodiscover=True, access_type=DELEGATE) atoz = [ ''.join(x) for x in itertools.product(string.ascii_lowercase, repeat=2) ] if search: for names in ResolveNames( account.protocol).call(unresolved_entries=(search, )): click.secho(f'{names}') else: atoz = [ ''.join(x) for x in itertools.product(string.ascii_lowercase, repeat=2) ] for entry in atoz: for names in ResolveNames( account.protocol).call(unresolved_entries=(entry, )): click.secho(f'{names}') click.secho(f'-------------------------------------\n', dim=True)
def test_resolvenames_parsing(self): # Test static XML since server has no roomlists xml = b'''\ <?xml version="1.0" encoding="utf-8"?> <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Body> <m:ResolveNamesResponse xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"> <m:ResponseMessages> <m:ResolveNamesResponseMessage ResponseClass="Warning"> <m:MessageText>Multiple results were found.</m:MessageText> <m:ResponseCode>ErrorNameResolutionMultipleResults</m:ResponseCode> <m:DescriptiveLinkKey>0</m:DescriptiveLinkKey> <m:ResolutionSet TotalItemsInView="2" IncludesLastItemInRange="true"> <t:Resolution> <t:Mailbox> <t:Name>John Doe</t:Name> <t:EmailAddress>[email protected]</t:EmailAddress> <t:RoutingType>SMTP</t:RoutingType> <t:MailboxType>Mailbox</t:MailboxType> </t:Mailbox> </t:Resolution> <t:Resolution> <t:Mailbox> <t:Name>John Deer</t:Name> <t:EmailAddress>[email protected]</t:EmailAddress> <t:RoutingType>SMTP</t:RoutingType> <t:MailboxType>Mailbox</t:MailboxType> </t:Mailbox> </t:Resolution> </m:ResolutionSet> </m:ResolveNamesResponseMessage> </m:ResponseMessages> </m:ResolveNamesResponse> </s:Body> </s:Envelope>''' ws = ResolveNames(self.account.protocol) ws.return_full_contact_data = False self.assertSetEqual({m.email_address for m in ws.parse(xml)}, {'*****@*****.**', '*****@*****.**'})
def test_element_container(self): ws = ResolveNames(self.account.protocol) xml = b"""\ <?xml version="1.0" encoding="utf-8" ?> <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Body> <m:ResolveNamesResponse xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages"> <m:ResponseMessages> <m:ResolveNamesResponseMessage ResponseClass="Success"> <m:ResponseCode>NoError</m:ResponseCode> </m:ResolveNamesResponseMessage> </m:ResponseMessages> </m:ResolveNamesResponse> </s:Body> </s:Envelope>""" with self.assertRaises(TransportError) as e: # Missing ResolutionSet elements list(ws.parse(xml)) self.assertIn("ResolutionSet elements in ResponseMessage", e.exception.args[0])
def test_element_container(self): svc = ResolveNames(self.account.protocol) soap_xml = b"""\ <?xml version="1.0" encoding="utf-8" ?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <m:ResolveNamesResponse xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages"> <m:ResponseMessages> <m:ResolveNamesResponseMessage ResponseClass="Success"> <m:ResponseCode>NoError</m:ResponseCode> </m:ResolveNamesResponseMessage> </m:ResponseMessages> </m:ResolveNamesResponse> </soap:Body> </soap:Envelope>""" header, body = svc._get_soap_parts(response=MockResponse(soap_xml)) resp = svc._get_soap_messages(body=body) with self.assertRaises(TransportError) as e: # Missing ResolutionSet elements list(svc._get_elements_in_response(response=resp)) self.assertIn('ResolutionSet elements in ResponseMessage', e.exception.args[0])
def test_resolvenames(self): with self.assertRaises(ValueError) as e: self.account.protocol.resolve_names(names=[], search_scope="XXX") self.assertEqual( e.exception.args[0], f"'search_scope' 'XXX' must be one of {sorted(SEARCH_SCOPE_CHOICES)}" ) with self.assertRaises(ValueError) as e: self.account.protocol.resolve_names(names=[], shape="XXX") self.assertEqual( e.exception.args[0], "'contact_data_shape' 'XXX' must be one of ['AllProperties', 'Default', 'IdOnly']" ) with self.assertRaises(ValueError) as e: ResolveNames(protocol=self.account.protocol, chunk_size=500).call(unresolved_entries=None) self.assertEqual( e.exception.args[0], "Chunk size 500 is too high. ResolveNames supports returning at most 100 candidates for a lookup", ) tmp = self.account.protocol.version self.account.protocol.config.version = Version(EXCHANGE_2010_SP1) with self.assertRaises(NotImplementedError) as e: self.account.protocol.resolve_names(names=["*****@*****.**"], shape="IdOnly") self.account.protocol.config.version = tmp self.assertEqual( e.exception.args[0], "'contact_data_shape' is only supported for Exchange 2010 SP2 servers and later" ) self.assertGreaterEqual( self.account.protocol.resolve_names(names=["*****@*****.**"]), []) self.assertGreaterEqual( self.account.protocol.resolve_names( names=["*****@*****.**"], search_scope="ActiveDirectoryContacts"), []) self.assertGreaterEqual( self.account.protocol.resolve_names(names=["*****@*****.**"], shape="AllProperties"), []) self.assertGreaterEqual( self.account.protocol.resolve_names( names=["*****@*****.**"], parent_folders=[self.account.contacts]), []) self.assertEqual( self.account.protocol.resolve_names( names=[self.account.primary_smtp_address]), [Mailbox(email_address=self.account.primary_smtp_address)], ) # Test something that's not an email self.assertEqual( self.account.protocol.resolve_names(names=["foo\\bar"]), [ErrorNameResolutionNoResults("No results were found.")], ) # Test return_full_contact_data mailbox, contact = self.account.protocol.resolve_names( names=[self.account.primary_smtp_address], return_full_contact_data=True)[0] self.assertEqual( mailbox, Mailbox(email_address=self.account.primary_smtp_address)) self.assertListEqual( [ e.email.replace("SMTP:", "") for e in contact.email_addresses if e.label == "EmailAddress1" ], [self.account.primary_smtp_address], )
def test_soap_error(self): soap_xml = """\ <?xml version="1.0" encoding="utf-8" ?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <soap:Header> <t:ServerVersionInfo MajorVersion="8" MinorVersion="0" MajorBuildNumber="685" MinorBuildNumber="8" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types" /> </soap:Header> <soap:Body> <soap:Fault> <faultcode>{faultcode}</faultcode> <faultstring>{faultstring}</faultstring> <faultactor>https://CAS01.example.com/EWS/Exchange.asmx</faultactor> <detail> <ResponseCode xmlns="http://schemas.microsoft.com/exchange/services/2006/errors">{responsecode}</ResponseCode> <Message xmlns="http://schemas.microsoft.com/exchange/services/2006/errors">{message}</Message> </detail> </soap:Fault> </soap:Body> </soap:Envelope>""" header, body = ResolveNames._get_soap_parts(response=MockResponse(soap_xml.format( faultcode='YYY', faultstring='AAA', responsecode='XXX', message='ZZZ' ).encode('utf-8'))) with self.assertRaises(SOAPError) as e: ResolveNames._get_soap_messages(body=body) self.assertIn('AAA', e.exception.args[0]) self.assertIn('YYY', e.exception.args[0]) self.assertIn('ZZZ', e.exception.args[0]) header, body = ResolveNames._get_soap_parts(response=MockResponse(soap_xml.format( faultcode='ErrorNonExistentMailbox', faultstring='AAA', responsecode='XXX', message='ZZZ' ).encode('utf-8'))) with self.assertRaises(ErrorNonExistentMailbox) as e: ResolveNames._get_soap_messages(body=body) self.assertIn('AAA', e.exception.args[0]) header, body = ResolveNames._get_soap_parts(response=MockResponse(soap_xml.format( faultcode='XXX', faultstring='AAA', responsecode='ErrorNonExistentMailbox', message='YYY' ).encode('utf-8'))) with self.assertRaises(ErrorNonExistentMailbox) as e: ResolveNames._get_soap_messages(body=body) self.assertIn('YYY', e.exception.args[0]) # Test bad XML (no body) soap_xml = b"""\ <?xml version="1.0" encoding="utf-8" ?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Header> <t:ServerVersionInfo MajorVersion="8" MinorVersion="0" MajorBuildNumber="685" MinorBuildNumber="8" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types" /> </soap:Header> </soap:Body> </soap:Envelope>""" with self.assertRaises(MalformedResponseError): ResolveNames._get_soap_parts(response=MockResponse(soap_xml)) # Test bad XML (no fault) soap_xml = b"""\ <?xml version="1.0" encoding="utf-8" ?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Header> <t:ServerVersionInfo MajorVersion="8" MinorVersion="0" MajorBuildNumber="685" MinorBuildNumber="8" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types" /> </soap:Header> <soap:Body> <soap:Fault> </soap:Fault> </soap:Body> </soap:Envelope>""" header, body = ResolveNames._get_soap_parts(response=MockResponse(soap_xml)) with self.assertRaises(TransportError): ResolveNames._get_soap_messages(body=body)
def gal(dump, search, verbose, full, output): """ Dump GAL using EWS. The slower technique used by https://github.com/dafthack/MailSniper default searches from "aa" to "zz" and prints them all. EWS only returns batches of 100 There will be doubles, so uniq after. """ if verbose: logging.basicConfig(level=logging.DEBUG, handlers=[PrettyXmlHandler()]) credentials = Credentials(tbestate.username, tbestate.password) username = tbestate.username try: if tbestate.exch_host: config = Configuration(server=tbestate.exch_host, credentials=credentials) account = Account(username, config=config, autodiscover=False, access_type=DELEGATE) else: account = Account(username, credentials=credentials, autodiscover=True, access_type=DELEGATE) except exchangelib.errors.ErrorNonExistentMailbox as neb: print(f'[!] Mailbox does not exist for user: {username}') exit() except exchangelib.errors.UnauthorizedError as err: print(f'[!] Invalid credentials for user: {username}') exit() except exchangelib.errors.TransportError: print(f'[!] Can not reach target Exchange server: {tbestate.exch_host}') exit() except Exception as err: print(f'[!] Something went wrong: {err}') atoz = [''.join(x) for x in itertools.product(string.ascii_lowercase, repeat=2)] if search: for names in ResolveNames(account.protocol).call(unresolved_entries=(search,)): if output: click.secho(f'{names}') output.write(f'{names}\n') else: click.secho(f'{names}') elif full: atoz = [''.join(x) for x in itertools.product(string.ascii_lowercase, repeat=2)] for entry in atoz: for names in ResolveNames(account.protocol).call(unresolved_entries=(entry,)): stringed = str(names) found = re.findall(r'[\w.+-]+@[\w-]+\.[\w.-]+', stringed) for i in found: if output: click.secho(f'{i}') output.write(f'{i}\n') else: click.secho(f'{i}') else: atoz = [''.join(x) for x in itertools.product(string.ascii_lowercase, repeat=2)] for entry in atoz: for names in ResolveNames(account.protocol).call(unresolved_entries=(entry,)): if output: click.secho(f'{names}') output.write(f'{names}\n') else: click.secho(f'{names}') click.secho(f'-------------------------------------\n', dim=True)