myl-discovery 0.5.2__py3-none-any.whl → 0.5.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {myl_discovery-0.5.2.dist-info → myl_discovery-0.5.4.dist-info}/METADATA +65 -103
- myl_discovery-0.5.4.dist-info/RECORD +10 -0
- myldiscovery/__init__.py +17 -2
- myldiscovery/discovery.py +74 -53
- myl_discovery-0.5.2.dist-info/RECORD +0 -10
- {myl_discovery-0.5.2.dist-info → myl_discovery-0.5.4.dist-info}/LICENSE +0 -0
- {myl_discovery-0.5.2.dist-info → myl_discovery-0.5.4.dist-info}/WHEEL +0 -0
- {myl_discovery-0.5.2.dist-info → myl_discovery-0.5.4.dist-info}/entry_points.txt +0 -0
- {myl_discovery-0.5.2.dist-info → myl_discovery-0.5.4.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: myl-discovery
|
3
|
-
Version: 0.5.
|
3
|
+
Version: 0.5.4
|
4
4
|
Summary: email autodiscovery
|
5
5
|
Author-email: Philipp Schmitt <philipp@schmitt.co>
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
@@ -689,130 +689,92 @@ Requires-Dist: requests (==2.31.0)
|
|
689
689
|
Requires-Dist: rich (==13.4.2)
|
690
690
|
Requires-Dist: xmltodict (==0.13.0)
|
691
691
|
|
692
|
-
# myl-discovery
|
692
|
+
# 📩 myl-discovery
|
693
693
|
|
694
|
-
|
694
|
+
myl-discovery is a Python library designed to detect email settings of a given
|
695
|
+
email address or domain.
|
695
696
|
|
696
|
-
## Installation
|
697
|
+
## 📥 Installation
|
697
698
|
|
698
|
-
|
699
|
+
To install myl-discovery, run the following command:
|
700
|
+
|
701
|
+
```bash
|
699
702
|
pip install myl-discovery
|
700
703
|
```
|
701
704
|
|
702
|
-
## Usage
|
705
|
+
## 📖 Usage
|
706
|
+
|
707
|
+
After installing the package, you can use the `autodiscover` function to
|
708
|
+
discover the email settings for a domain. Here's an example:
|
703
709
|
|
704
710
|
```python
|
705
711
|
from myldiscovery import autodiscover
|
706
|
-
autodiscover("me@example.com")
|
707
|
-
# {'imap': {'server': 'mail.example.com', 'port': 993, 'starttls': False},
|
708
|
-
# 'smtp': {'server': 'mail.example.com', 'port': 587, 'starttls': False}}
|
709
|
-
```
|
710
|
-
|
711
712
|
|
712
|
-
|
713
|
+
settings = autodiscover("yourdomain.com") # or me@yourdomain.com
|
714
|
+
print(settings)
|
713
715
|
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
716
|
+
# For Exchange autodiscovery you need to provide credentials
|
717
|
+
settings = autodiscover(
|
718
|
+
'me@yourdomain.com',
|
719
|
+
username='WORKGROUP\me',
|
720
|
+
password='mypassword1234'
|
721
|
+
)
|
720
722
|
```
|
721
723
|
|
722
|
-
|
723
|
-
|
724
|
-
```xml
|
725
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
726
|
-
<clientConfig version="1.1">
|
727
|
-
<emailProvider id="example.com">
|
728
|
-
<domain>example.com</domain>
|
724
|
+
## 📄 Output
|
729
725
|
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
<hostname>mail.example.com</hostname>
|
734
|
-
<port>143</port>
|
735
|
-
<socketType>STARTTLS</socketType>
|
736
|
-
<authentication>password-cleartext</authentication>
|
737
|
-
<username>%EMAILADDRESS%</username>
|
738
|
-
</incomingServer>
|
739
|
-
<outgoingServer type="smtp">
|
740
|
-
<hostname>mail.example.com</hostname>
|
741
|
-
<port>587</port>
|
742
|
-
<socketType>STARTTLS</socketType>
|
743
|
-
<authentication>password-cleartext</authentication>
|
744
|
-
<username>%EMAILADDRESS%</username>
|
745
|
-
</outgoingServer>
|
746
|
-
<documentation url="https://autodiscover.example.com">
|
747
|
-
<descr lang="en">Generic settings page</descr>
|
748
|
-
<descr lang="fr">Paramètres généraux</descr>
|
749
|
-
<descr lang="es">Configuraciones genéricas</descr>
|
750
|
-
<descr lang="de">Allgemeine Beschreibung der Einstellungen</descr>
|
751
|
-
<descr lang="ru">Страница общих настроек</descr>
|
752
|
-
</documentation>
|
753
|
-
</emailProvider>
|
754
|
-
</clientConfig>
|
755
|
-
```
|
726
|
+
The `autodiscover` function returns a dictionary with the detected settings.
|
727
|
+
The dictionary contains two keys, `imap` and `smtp`, each containing a
|
728
|
+
dictionary with the keys `server`, `port`, and `starttls`.
|
756
729
|
|
757
|
-
|
730
|
+
Here's an example:
|
758
731
|
|
759
|
-
```
|
760
|
-
|
732
|
+
```json
|
733
|
+
{
|
734
|
+
"imap": {
|
735
|
+
"server": "imap.yourdomain.com",
|
736
|
+
"port": 993,
|
737
|
+
"starttls": false
|
738
|
+
},
|
739
|
+
"smtp": {
|
740
|
+
"server": "smtp.yourdomain.com",
|
741
|
+
"port": 587,
|
742
|
+
"starttls": true
|
743
|
+
}
|
744
|
+
}
|
761
745
|
```
|
762
746
|
|
763
|
-
|
764
|
-
|
765
|
-
```xml
|
766
|
-
<?xml version="1.0" encoding="utf-8" ?>
|
767
|
-
<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006">
|
768
|
-
<Response xmlns="http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006">
|
769
|
-
<User>
|
770
|
-
<DisplayName>example.com Email</DisplayName>
|
771
|
-
</User>
|
772
|
-
<Account>
|
773
|
-
<AccountType>email</AccountType>
|
774
|
-
<Action>settings</Action>
|
775
|
-
<ServiceHome>https://autodiscover.example.com</ServiceHome>
|
776
|
-
|
777
|
-
<Protocol>
|
778
|
-
<Type>IMAP</Type>
|
779
|
-
<TTL>1</TTL>
|
780
|
-
|
781
|
-
<Server>mail.example.com</Server>
|
782
|
-
<Port>143</Port>
|
747
|
+
## 🧩 Autodiscover Functions
|
783
748
|
|
784
|
-
|
749
|
+
myl-discovery exposes several functions to discover email settings:
|
785
750
|
|
786
|
-
|
787
|
-
|
751
|
+
- `autodiscover`: This function wraps the below function do automatically detect
|
752
|
+
the right settings. (See Autodiscover strategy for more information)
|
753
|
+
- `autodiscover_srv`: This function attempts to resolve SRV records for
|
754
|
+
the domain to discover IMAP and SMTP servers.
|
755
|
+
- `autodiscover_exchange`: This function attempts to use the Exchange
|
756
|
+
Autodiscover service to discover email settings. It requires a username and
|
757
|
+
password.
|
758
|
+
- `autodiscover_autoconfig`: This function attempts to fetch and parse an
|
759
|
+
autoconfig XML file from a URL specified in the domain's TXT records.
|
760
|
+
- `autodiscover_port_scan`: This function performs a port scan on the domain
|
761
|
+
to discover open IMAP and SMTP ports.
|
788
762
|
|
789
|
-
|
790
|
-
<Encryption>TLS</Encryption>
|
791
|
-
<AuthRequired>on</AuthRequired>
|
792
|
-
</Protocol>
|
793
|
-
</Account>
|
794
|
-
<Account>
|
795
|
-
<AccountType>email</AccountType>
|
796
|
-
<Action>settings</Action>
|
797
|
-
<ServiceHome>https://autodiscover.example.com</ServiceHome>
|
763
|
+
## 🧠 Autodiscover Strategy
|
798
764
|
|
799
|
-
|
800
|
-
|
801
|
-
<TTL>1</TTL>
|
765
|
+
The `autodiscover` function uses the following strategy to discover
|
766
|
+
email settings:
|
802
767
|
|
803
|
-
|
804
|
-
|
768
|
+
1. It first attempts to use `autodiscover_autoconfig` to discover settings
|
769
|
+
from an autoconfig/autodiscover URL specified in the domain's TXT records.
|
770
|
+
2. If that fails, it attempts to use `autodiscover_srv` to discover settings
|
771
|
+
from the domain's SRV records.
|
772
|
+
3. If that fails and a password is provided, it attempts to use
|
773
|
+
`autodiscover_exchange` to discover settings using the
|
774
|
+
Exchange Autodiscover service (only if credentials were provided)
|
775
|
+
4. If all else fails, it uses `autodiscover_port_scan` to discover settings by
|
776
|
+
performing a port scan on the domain.
|
805
777
|
|
806
|
-
|
807
|
-
|
808
|
-
<DomainRequired>on</DomainRequired>
|
809
|
-
<DomainName>example.com</DomainName>
|
810
|
-
|
811
|
-
<SPA>off</SPA>
|
812
|
-
<Encryption>TLS</Encryption>
|
813
|
-
<AuthRequired>on</AuthRequired>
|
814
|
-
</Protocol>
|
815
|
-
</Account></Response>
|
816
|
-
</Autodiscover>
|
817
|
-
```
|
778
|
+
## 📜 License
|
818
779
|
|
780
|
+
myl-discovery is licensed under the [GNU General Public License v3.0](LICENSE).
|
@@ -0,0 +1,10 @@
|
|
1
|
+
myldiscovery/__init__.py,sha256=L_XVC06ZVdjhnV5up1MBnywTuGUIyjt8PUoQDUOzfAk,381
|
2
|
+
myldiscovery/__main__.py,sha256=5BjNuyet8AY-POwoF5rGt722rHQ7tJ0Vf0UFUfzzi-I,58
|
3
|
+
myldiscovery/discovery.py,sha256=YnEJ54CdwaA6PluMSC0ba7uGhoM__-ejtPTCawa9cto,7358
|
4
|
+
myldiscovery/main.py,sha256=dBvJULlnULiJOCtios2hF1A2lNu6EWMKpTSjC9fs2Ss,2016
|
5
|
+
myl_discovery-0.5.4.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
6
|
+
myl_discovery-0.5.4.dist-info/METADATA,sha256=Bj7FHNAlBwBWSDj3JF6-enWBXe-dpzbszClTWe9n7tk,43775
|
7
|
+
myl_discovery-0.5.4.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
8
|
+
myl_discovery-0.5.4.dist-info/entry_points.txt,sha256=nyyAyvgvu6iO9mPEA6uVrPfd0lIrUyo9AQWeH2asEY0,52
|
9
|
+
myl_discovery-0.5.4.dist-info/top_level.txt,sha256=v_h72JexaacqBNY6iOMD9PpGg8lnGoL-pkmUIzxdiVU,13
|
10
|
+
myl_discovery-0.5.4.dist-info/RECORD,,
|
myldiscovery/__init__.py
CHANGED
@@ -1,4 +1,19 @@
|
|
1
|
-
from .discovery import
|
1
|
+
from .discovery import (
|
2
|
+
autodiscover,
|
3
|
+
autodiscover_autoconfig,
|
4
|
+
autodiscover_exchange,
|
5
|
+
autodiscover_port_scan,
|
6
|
+
autodiscover_srv,
|
7
|
+
autodiscover_txt,
|
8
|
+
)
|
2
9
|
from .main import main
|
3
10
|
|
4
|
-
__all__ = [
|
11
|
+
__all__ = [
|
12
|
+
"main",
|
13
|
+
"autodiscover",
|
14
|
+
"autodiscover_autoconfig",
|
15
|
+
"autodiscover_exchange",
|
16
|
+
"autodiscover_port_scan",
|
17
|
+
"autodiscover_srv",
|
18
|
+
"autodiscover_txt",
|
19
|
+
]
|
myldiscovery/discovery.py
CHANGED
@@ -66,10 +66,13 @@ def resolve_srv(domain):
|
|
66
66
|
|
67
67
|
|
68
68
|
def autodiscover_txt(domain):
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
69
|
+
try:
|
70
|
+
res = resolve_txt(domain, criteria="^mailconf=")
|
71
|
+
if not res:
|
72
|
+
return
|
73
|
+
return res.split("=")[1]
|
74
|
+
except Exception:
|
75
|
+
LOGGER.warning("Failed to resolve TXT record")
|
73
76
|
|
74
77
|
|
75
78
|
def parse_autoconfig(content):
|
@@ -147,29 +150,35 @@ def parse_autodiscover(content):
|
|
147
150
|
|
148
151
|
|
149
152
|
def autodiscover_srv(domain):
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
153
|
+
try:
|
154
|
+
imap = resolve_srv(f"_imaps._tcp.{domain}")
|
155
|
+
smtp = resolve_srv(f"_submission._tcp.{domain}")
|
156
|
+
|
157
|
+
assert imap is not None
|
158
|
+
assert smtp is not None
|
159
|
+
|
160
|
+
return {
|
161
|
+
"imap": {
|
162
|
+
"server": imap[0].get("hostname"),
|
163
|
+
"port": int(imap[0].get("port")),
|
164
|
+
# FIXME We might want to "smartly" guess if starttls should be
|
165
|
+
# enabled or not, depending on the port:
|
166
|
+
# 143 -> starttls
|
167
|
+
# 993 -> no
|
168
|
+
"starttls": False,
|
169
|
+
},
|
170
|
+
"smtp": {
|
171
|
+
"server": smtp[0].get("hostname"),
|
172
|
+
"port": int(smtp[0].get("port")),
|
173
|
+
# FIXME We might want to "smartly" guess if starttls should be
|
174
|
+
# enabled or not, depending on the port:
|
175
|
+
# 465 -> starttls
|
176
|
+
# 587 -> no
|
177
|
+
"starttls": False,
|
178
|
+
},
|
179
|
+
}
|
180
|
+
except Exception as e:
|
181
|
+
LOGGER.warning(f"Failed to resolve SRV records: {e}")
|
173
182
|
|
174
183
|
|
175
184
|
def port_check(host, port):
|
@@ -187,14 +196,19 @@ def check_email_ports(host):
|
|
187
196
|
|
188
197
|
|
189
198
|
def autodiscover_exchange(email, password, username=None):
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
199
|
+
try:
|
200
|
+
if not username:
|
201
|
+
username = email
|
202
|
+
creds = Credentials(username=username, password=password)
|
203
|
+
account = Account(
|
204
|
+
primary_smtp_address=email, credentials=creds, autodiscover=True
|
205
|
+
)
|
206
|
+
return autodiscover_port_scan(account.protocol.server)
|
207
|
+
except Exception as e:
|
208
|
+
LOGGER.warning(f"Failed to autodiscover Exchange: {e}")
|
209
|
+
|
210
|
+
|
211
|
+
def autodiscover_port_scan(server):
|
198
212
|
portscan = check_email_ports(server)
|
199
213
|
LOGGER.info(f"Port scan results: {portscan}")
|
200
214
|
|
@@ -228,27 +242,12 @@ def autodiscover_exchange(email, password, username=None):
|
|
228
242
|
}
|
229
243
|
|
230
244
|
|
231
|
-
def
|
232
|
-
domain = email_addr.split("@")[-1]
|
233
|
-
if not domain:
|
234
|
-
raise ValueError(f"Invalid email address {email_addr}")
|
235
|
-
|
236
|
-
if srv_only:
|
237
|
-
return autodiscover_srv(domain)
|
238
|
-
|
245
|
+
def autodiscover_autoconfig(domain):
|
239
246
|
autoconfig = autodiscover_txt(domain)
|
240
247
|
|
241
248
|
if not autoconfig:
|
242
|
-
|
243
|
-
|
244
|
-
except Exception:
|
245
|
-
LOGGER.warning("Failed to autodiscover using SRV records")
|
246
|
-
if password:
|
247
|
-
LOGGER.info("Trying autodiscover using Exchange credentials")
|
248
|
-
return autodiscover_exchange(
|
249
|
-
email=email_addr, username=username, password=password
|
250
|
-
)
|
251
|
-
return
|
249
|
+
LOGGER.warning("Failed to autodiscover using TXT records")
|
250
|
+
return
|
252
251
|
|
253
252
|
res = requests.get(autoconfig)
|
254
253
|
res.raise_for_status()
|
@@ -258,3 +257,25 @@ def autodiscover(email_addr, srv_only=False, username=None, password=None):
|
|
258
257
|
except Exception:
|
259
258
|
LOGGER.warning("Failed to parse autoconfig, trying autodiscover")
|
260
259
|
return parse_autodiscover(res.text)
|
260
|
+
|
261
|
+
|
262
|
+
def autodiscover(email_addr, username=None, password=None):
|
263
|
+
domain = email_addr.split("@")[-1]
|
264
|
+
if not domain:
|
265
|
+
raise ValueError(f"Invalid email address {email_addr}")
|
266
|
+
|
267
|
+
res = autodiscover_autoconfig(domain)
|
268
|
+
|
269
|
+
if not res:
|
270
|
+
res = autodiscover_srv(domain)
|
271
|
+
|
272
|
+
if not res and password:
|
273
|
+
res = autodiscover_srv(domain)
|
274
|
+
autodiscover_exchange(
|
275
|
+
email=email_addr, username=username, password=password
|
276
|
+
)
|
277
|
+
|
278
|
+
if not res:
|
279
|
+
res = autodiscover_port_scan(domain)
|
280
|
+
|
281
|
+
return res
|
@@ -1,10 +0,0 @@
|
|
1
|
-
myldiscovery/__init__.py,sha256=uYjN41LLdBJtkSU1JOn_Ryi6jxiN0D99WPVrhIvQt-Q,143
|
2
|
-
myldiscovery/__main__.py,sha256=5BjNuyet8AY-POwoF5rGt722rHQ7tJ0Vf0UFUfzzi-I,58
|
3
|
-
myldiscovery/discovery.py,sha256=wuV_9-Hmomt-g_iIJ2mqecR1gYXsbabgklcZSdNO820,6833
|
4
|
-
myldiscovery/main.py,sha256=dBvJULlnULiJOCtios2hF1A2lNu6EWMKpTSjC9fs2Ss,2016
|
5
|
-
myl_discovery-0.5.2.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
6
|
-
myl_discovery-0.5.2.dist-info/METADATA,sha256=LlbCj0P2mWvjqUx73LtYl9o-k5aHtEROeU6bhyMKDSk,44965
|
7
|
-
myl_discovery-0.5.2.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
8
|
-
myl_discovery-0.5.2.dist-info/entry_points.txt,sha256=nyyAyvgvu6iO9mPEA6uVrPfd0lIrUyo9AQWeH2asEY0,52
|
9
|
-
myl_discovery-0.5.2.dist-info/top_level.txt,sha256=v_h72JexaacqBNY6iOMD9PpGg8lnGoL-pkmUIzxdiVU,13
|
10
|
-
myl_discovery-0.5.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|