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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: myl-discovery
3
- Version: 0.1.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': 143, 'starttls': True}
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,,
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ myl-discovery = myldiscovery:main
myldiscovery/__init__.py CHANGED
@@ -1,85 +1,4 @@
1
- import logging
2
- import re
1
+ from .discovery import autodiscover
2
+ from .main import main
3
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):
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"]
@@ -0,0 +1,4 @@
1
+ from . import main
2
+
3
+ if __name__ == '__main__':
4
+ main()
@@ -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,,