myl-discovery 0.2.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.2.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
@@ -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,108 +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, 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
- }
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=pGTsuY2G9_Az6DKftLsQM9vBbcNIAy8q2vPRh3486XA,2950
2
- myl_discovery-0.2.0.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
3
- myl_discovery-0.2.0.dist-info/METADATA,sha256=rEmMa99hvijh3l8VCmvDui8DyugVVIAu4c1IL49q9nA,41297
4
- myl_discovery-0.2.0.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
5
- myl_discovery-0.2.0.dist-info/top_level.txt,sha256=v_h72JexaacqBNY6iOMD9PpGg8lnGoL-pkmUIzxdiVU,13
6
- myl_discovery-0.2.0.dist-info/RECORD,,