myl-discovery 0.1.0__py3-none-any.whl → 0.3.0__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.1.0.dist-info → myl_discovery-0.3.0.dist-info}/METADATA +5 -3
- myl_discovery-0.3.0.dist-info/RECORD +10 -0
- myl_discovery-0.3.0.dist-info/entry_points.txt +2 -0
- myldiscovery/__init__.py +3 -84
- myldiscovery/__main__.py +4 -0
- myldiscovery/discovery.py +108 -0
- myldiscovery/main.py +64 -0
- myl_discovery-0.1.0.dist-info/RECORD +0 -6
- {myl_discovery-0.1.0.dist-info → myl_discovery-0.3.0.dist-info}/LICENSE +0 -0
- {myl_discovery-0.1.0.dist-info → myl_discovery-0.3.0.dist-info}/WHEEL +0 -0
- {myl_discovery-0.1.0.dist-info → myl_discovery-0.3.0.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.
|
3
|
+
Version: 0.3.0
|
4
4
|
Summary: email autodiscovery
|
5
5
|
Author-email: Philipp Schmitt <philipp@schmitt.co>
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
@@ -685,6 +685,7 @@ Description-Content-Type: text/markdown
|
|
685
685
|
License-File: LICENSE
|
686
686
|
Requires-Dist: dnspython
|
687
687
|
Requires-Dist: requests
|
688
|
+
Requires-Dist: rich
|
688
689
|
Requires-Dist: xmltodict
|
689
690
|
|
690
691
|
# myl-discovery
|
@@ -697,12 +698,13 @@ Email autoconfig library
|
|
697
698
|
pip install myl-discovery
|
698
699
|
```
|
699
700
|
|
700
|
-
## Usage
|
701
|
+
## Usage
|
701
702
|
|
702
703
|
```python
|
703
704
|
from myldiscovery import autodiscover
|
704
705
|
autodiscover("me@example.com")
|
705
|
-
# {'server': 'mail.example.com', 'port':
|
706
|
+
# {'imap': {'server': 'mail.example.com', 'port': 993, 'starttls': False},
|
707
|
+
# 'smtp': {'server': 'mail.example.com', 'port': 587, 'starttls': False}}
|
706
708
|
```
|
707
709
|
|
708
710
|
|
@@ -0,0 +1,10 @@
|
|
1
|
+
myldiscovery/__init__.py,sha256=zzTVte4vTiupxCcjgQBkifnFjkNnZX_Sf4Ys2C5ufZQ,95
|
2
|
+
myldiscovery/__main__.py,sha256=fr-CFAsvUaq-F1nICslExsfLggcTfrfCuU_MUBrHZdQ,58
|
3
|
+
myldiscovery/discovery.py,sha256=LGCExQPRldvsQ3WtI8IYTGjVa7ez807caV9SMwgcctU,2951
|
4
|
+
myldiscovery/main.py,sha256=sdh2FeE54uVYiUA8QVK-ocdK0-g5v9gGw9F4NVD4tyk,1732
|
5
|
+
myl_discovery-0.3.0.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
6
|
+
myl_discovery-0.3.0.dist-info/METADATA,sha256=httmQt7oKBH8xdZPjwzge8iAwglmK9GOHkpnNk41sp4,41317
|
7
|
+
myl_discovery-0.3.0.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
8
|
+
myl_discovery-0.3.0.dist-info/entry_points.txt,sha256=nyyAyvgvu6iO9mPEA6uVrPfd0lIrUyo9AQWeH2asEY0,52
|
9
|
+
myl_discovery-0.3.0.dist-info/top_level.txt,sha256=v_h72JexaacqBNY6iOMD9PpGg8lnGoL-pkmUIzxdiVU,13
|
10
|
+
myl_discovery-0.3.0.dist-info/RECORD,,
|
myldiscovery/__init__.py
CHANGED
@@ -1,85 +1,4 @@
|
|
1
|
-
import
|
2
|
-
import
|
1
|
+
from .discovery import autodiscover
|
2
|
+
from .main import main
|
3
3
|
|
4
|
-
|
5
|
-
import requests
|
6
|
-
import xmltodict
|
7
|
-
|
8
|
-
LOGGER = logging.getLogger(__name__)
|
9
|
-
|
10
|
-
|
11
|
-
def resolve_txt(domain, criteria="^mailconf="):
|
12
|
-
regex = re.compile(criteria)
|
13
|
-
answers = dns.resolver.resolve(domain, "TXT")
|
14
|
-
for rdata in answers:
|
15
|
-
for txt_string in rdata.strings:
|
16
|
-
txt_record = txt_string.decode("utf-8")
|
17
|
-
if re.search(regex, txt_record):
|
18
|
-
return txt_record
|
19
|
-
|
20
|
-
|
21
|
-
def resolve_srv(domain):
|
22
|
-
answers = dns.resolver.resolve(domain, "SRV")
|
23
|
-
data = []
|
24
|
-
for rdata in answers:
|
25
|
-
entry = {
|
26
|
-
"hostname": ".".join(
|
27
|
-
[
|
28
|
-
x.decode("utf-8")
|
29
|
-
for x in rdata.target.labels
|
30
|
-
if x.decode("utf-8") != ""
|
31
|
-
]
|
32
|
-
),
|
33
|
-
"port": rdata.port,
|
34
|
-
}
|
35
|
-
data.append(entry)
|
36
|
-
|
37
|
-
return data
|
38
|
-
|
39
|
-
|
40
|
-
def autodiscover_txt(domain):
|
41
|
-
res = resolve_txt(domain, criteria="^mailconf=")
|
42
|
-
if not res:
|
43
|
-
return
|
44
|
-
return res.split("=")[1]
|
45
|
-
|
46
|
-
|
47
|
-
def autodiscover(email_addr):
|
48
|
-
domain = email_addr.split("@")[-1]
|
49
|
-
if not domain:
|
50
|
-
raise ValueError(f"Invalid email address {email_addr}")
|
51
|
-
|
52
|
-
autoconfig = autodiscover_txt(domain)
|
53
|
-
|
54
|
-
if not autoconfig:
|
55
|
-
srv = resolve_srv(f"_imaps._tcp.{domain}")
|
56
|
-
return {
|
57
|
-
"server": srv[0].get("hostname"),
|
58
|
-
"port": int(srv[0].get("port")),
|
59
|
-
# FIXME We might want to "smartly" guess if starttls should be
|
60
|
-
# enabled or not, depending on the port (143 -> starttls, 993 -> no)
|
61
|
-
"starttls": False,
|
62
|
-
}
|
63
|
-
|
64
|
-
res = requests.get(autoconfig)
|
65
|
-
res.raise_for_status()
|
66
|
-
|
67
|
-
data = xmltodict.parse(res.text)
|
68
|
-
imap = (
|
69
|
-
data.get("clientConfig", {})
|
70
|
-
.get("emailProvider", {})
|
71
|
-
.get("incomingServer")
|
72
|
-
)
|
73
|
-
# smtp = (
|
74
|
-
# data.get("clientConfig", {})
|
75
|
-
# .get("emailProvider", {})
|
76
|
-
# .get("outgoingServer")
|
77
|
-
# )
|
78
|
-
|
79
|
-
assert imap is not None
|
80
|
-
|
81
|
-
return {
|
82
|
-
"server": imap.get("hostname"),
|
83
|
-
"port": int(imap.get("port")),
|
84
|
-
"starttls": imap.get("socketType") == "STARTTLS",
|
85
|
-
}
|
4
|
+
__all__ = ["main", "autodiscover"]
|
myldiscovery/__main__.py
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
import logging
|
2
|
+
import re
|
3
|
+
|
4
|
+
import dns.resolver
|
5
|
+
import requests
|
6
|
+
import xmltodict
|
7
|
+
|
8
|
+
LOGGER = logging.getLogger(__name__)
|
9
|
+
|
10
|
+
|
11
|
+
def resolve_txt(domain, criteria="^mailconf="):
|
12
|
+
regex = re.compile(criteria)
|
13
|
+
answers = dns.resolver.resolve(domain, "TXT")
|
14
|
+
for rdata in answers:
|
15
|
+
for txt_string in rdata.strings:
|
16
|
+
txt_record = txt_string.decode("utf-8")
|
17
|
+
if re.search(regex, txt_record):
|
18
|
+
return txt_record
|
19
|
+
|
20
|
+
|
21
|
+
def resolve_srv(domain):
|
22
|
+
answers = dns.resolver.resolve(domain, "SRV")
|
23
|
+
data = []
|
24
|
+
for rdata in answers:
|
25
|
+
entry = {
|
26
|
+
"hostname": ".".join(
|
27
|
+
[
|
28
|
+
x.decode("utf-8")
|
29
|
+
for x in rdata.target.labels
|
30
|
+
if x.decode("utf-8") != ""
|
31
|
+
]
|
32
|
+
),
|
33
|
+
"port": rdata.port,
|
34
|
+
}
|
35
|
+
data.append(entry)
|
36
|
+
|
37
|
+
return data
|
38
|
+
|
39
|
+
|
40
|
+
def autodiscover_txt(domain):
|
41
|
+
res = resolve_txt(domain, criteria="^mailconf=")
|
42
|
+
if not res:
|
43
|
+
return
|
44
|
+
return res.split("=")[1]
|
45
|
+
|
46
|
+
|
47
|
+
def autodiscover(email_addr, srv_only=False):
|
48
|
+
domain = email_addr.split("@")[-1]
|
49
|
+
if not domain:
|
50
|
+
raise ValueError(f"Invalid email address {email_addr}")
|
51
|
+
|
52
|
+
autoconfig = autodiscover_txt(domain) if not srv_only else None
|
53
|
+
|
54
|
+
if not autoconfig:
|
55
|
+
imap = resolve_srv(f"_imaps._tcp.{domain}")
|
56
|
+
smtp = resolve_srv(f"_submission._tcp.{domain}")
|
57
|
+
|
58
|
+
return {
|
59
|
+
"imap": {
|
60
|
+
"server": imap[0].get("hostname"),
|
61
|
+
"port": int(imap[0].get("port")),
|
62
|
+
# FIXME We might want to "smartly" guess if starttls should be
|
63
|
+
# enabled or not, depending on the port:
|
64
|
+
# 143 -> starttls
|
65
|
+
# 993 -> no
|
66
|
+
"starttls": False,
|
67
|
+
},
|
68
|
+
"smtp": {
|
69
|
+
"server": smtp[0].get("hostname"),
|
70
|
+
"port": int(smtp[0].get("port")),
|
71
|
+
# FIXME We might want to "smartly" guess if starttls should be
|
72
|
+
# enabled or not, depending on the port:
|
73
|
+
# 465 -> starttls
|
74
|
+
# 587 -> no
|
75
|
+
"starttls": False,
|
76
|
+
},
|
77
|
+
}
|
78
|
+
|
79
|
+
res = requests.get(autoconfig)
|
80
|
+
res.raise_for_status()
|
81
|
+
|
82
|
+
data = xmltodict.parse(res.text)
|
83
|
+
imap = (
|
84
|
+
data.get("clientConfig", {})
|
85
|
+
.get("emailProvider", {})
|
86
|
+
.get("incomingServer")
|
87
|
+
)
|
88
|
+
smtp = (
|
89
|
+
data.get("clientConfig", {})
|
90
|
+
.get("emailProvider", {})
|
91
|
+
.get("outgoingServer")
|
92
|
+
)
|
93
|
+
|
94
|
+
assert imap is not None
|
95
|
+
assert smtp is not None
|
96
|
+
|
97
|
+
return {
|
98
|
+
"imap": {
|
99
|
+
"server": imap.get("hostname"),
|
100
|
+
"port": int(imap.get("port")),
|
101
|
+
"starttls": imap.get("socketType") == "STARTTLS",
|
102
|
+
},
|
103
|
+
"smtp": {
|
104
|
+
"server": smtp.get("hostname"),
|
105
|
+
"port": int(smtp.get("port")),
|
106
|
+
"starttls": smtp.get("socketType") == "STARTTLS",
|
107
|
+
},
|
108
|
+
}
|
myldiscovery/main.py
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
import argparse
|
5
|
+
import logging
|
6
|
+
|
7
|
+
from rich import print_json
|
8
|
+
from rich.console import Console
|
9
|
+
from rich.logging import RichHandler
|
10
|
+
from rich.table import Table
|
11
|
+
|
12
|
+
from myldiscovery import autodiscover
|
13
|
+
|
14
|
+
LOGGER = logging.getLogger(__name__)
|
15
|
+
|
16
|
+
|
17
|
+
def parse_args():
|
18
|
+
parser = argparse.ArgumentParser()
|
19
|
+
parser.add_argument("-j", "--json", action="store_true", default=False)
|
20
|
+
parser.add_argument("-d", "--debug", action="store_true", default=False)
|
21
|
+
parser.add_argument("EMAIL")
|
22
|
+
return parser.parse_args()
|
23
|
+
|
24
|
+
|
25
|
+
def main():
|
26
|
+
console = Console()
|
27
|
+
args = parse_args()
|
28
|
+
|
29
|
+
logging.basicConfig(
|
30
|
+
handlers=[RichHandler(console=console, show_time=False)],
|
31
|
+
level=logging.DEBUG if args.debug else logging.INFO,
|
32
|
+
)
|
33
|
+
LOGGER.debug(args)
|
34
|
+
|
35
|
+
try:
|
36
|
+
res = autodiscover(args.EMAIL)
|
37
|
+
if args.json:
|
38
|
+
print_json(data=res)
|
39
|
+
else:
|
40
|
+
table = Table(
|
41
|
+
expand=True,
|
42
|
+
show_header=True,
|
43
|
+
header_style="bold",
|
44
|
+
show_lines=False,
|
45
|
+
box=None,
|
46
|
+
)
|
47
|
+
table.add_column("Service", style="red")
|
48
|
+
table.add_column("Host", style="blue")
|
49
|
+
table.add_column("Port", style="green")
|
50
|
+
table.add_column("TLS", style="yellow")
|
51
|
+
for svc in ["imap", "smtp"]:
|
52
|
+
table.add_row(
|
53
|
+
svc,
|
54
|
+
res[svc]["server"],
|
55
|
+
str(res[svc]["port"]),
|
56
|
+
"starttls" if res[svc]["starttls"] else "tls",
|
57
|
+
)
|
58
|
+
console.print(table)
|
59
|
+
except Exception:
|
60
|
+
console.print_exception(show_locals=True)
|
61
|
+
|
62
|
+
|
63
|
+
if __name__ == "__main__":
|
64
|
+
main()
|
@@ -1,6 +0,0 @@
|
|
1
|
-
myldiscovery/__init__.py,sha256=xalUlWBnrOfGhkQL6ipCF2qJrTPF1yjkKpD4pibkMrk,2149
|
2
|
-
myl_discovery-0.1.0.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
3
|
-
myl_discovery-0.1.0.dist-info/METADATA,sha256=8U3XgyA-Mudg3Y3lxnlh1JkdbB3MKEi3IEzNzWT1-lA,41212
|
4
|
-
myl_discovery-0.1.0.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
5
|
-
myl_discovery-0.1.0.dist-info/top_level.txt,sha256=v_h72JexaacqBNY6iOMD9PpGg8lnGoL-pkmUIzxdiVU,13
|
6
|
-
myl_discovery-0.1.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|