myl-discovery 0.2.0__py3-none-any.whl → 0.3.1__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.1
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=BojZR0UoOFX5d6j1nu3TTdIoyh4bVMgQ-Qj4z_8MXNs,3575
4
+ myldiscovery/main.py,sha256=sdh2FeE54uVYiUA8QVK-ocdK0-g5v9gGw9F4NVD4tyk,1732
5
+ myl_discovery-0.3.1.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
6
+ myl_discovery-0.3.1.dist-info/METADATA,sha256=PR-ONO9qKdGvu30HjHoAxa30fUDkZbF1kGu4NhNVWe4,41317
7
+ myl_discovery-0.3.1.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
8
+ myl_discovery-0.3.1.dist-info/entry_points.txt,sha256=nyyAyvgvu6iO9mPEA6uVrPfd0lIrUyo9AQWeH2asEY0,52
9
+ myl_discovery-0.3.1.dist-info/top_level.txt,sha256=v_h72JexaacqBNY6iOMD9PpGg8lnGoL-pkmUIzxdiVU,13
10
+ myl_discovery-0.3.1.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,134 @@
1
+ import logging
2
+ import os
3
+ import re
4
+ import shutil
5
+
6
+ import dns.resolver
7
+ import requests
8
+ import xmltodict
9
+
10
+ LOGGER = logging.getLogger(__name__)
11
+
12
+
13
+ def is_termux():
14
+ if os.getenv("TERMUX_VERSION"):
15
+ return True
16
+
17
+ return shutil.which("termux-info") is not None
18
+
19
+
20
+ def resolve(*args, **kwargs):
21
+ termux = is_termux()
22
+ dns.resolver.default_resolver = dns.resolver.Resolver(
23
+ # Do not attempt to read /etc/resolv.conf on Termux
24
+ configure=not termux
25
+ )
26
+ if termux:
27
+ # Default to Google DNS on Termux
28
+ dns.resolver.default_resolver.nameservers = [
29
+ "8.8.8.8",
30
+ "2001:4860:4860::8888",
31
+ "8.8.4.4",
32
+ "2001:4860:4860::8844",
33
+ ]
34
+ return dns.resolver.resolve(*args, **kwargs)
35
+
36
+
37
+ def resolve_txt(domain, criteria="^mailconf="):
38
+ regex = re.compile(criteria)
39
+ answers = resolve(domain, "TXT")
40
+ for rdata in answers:
41
+ for txt_string in rdata.strings:
42
+ txt_record = txt_string.decode("utf-8")
43
+ if re.search(regex, txt_record):
44
+ return txt_record
45
+
46
+
47
+ def resolve_srv(domain):
48
+ answers = resolve(domain, "SRV")
49
+ data = []
50
+ for rdata in answers:
51
+ entry = {
52
+ "hostname": ".".join(
53
+ [
54
+ x.decode("utf-8")
55
+ for x in rdata.target.labels
56
+ if x.decode("utf-8") != ""
57
+ ]
58
+ ),
59
+ "port": rdata.port,
60
+ }
61
+ data.append(entry)
62
+
63
+ return data
64
+
65
+
66
+ def autodiscover_txt(domain):
67
+ res = resolve_txt(domain, criteria="^mailconf=")
68
+ if not res:
69
+ return
70
+ return res.split("=")[1]
71
+
72
+
73
+ def autodiscover(email_addr, srv_only=False):
74
+ domain = email_addr.split("@")[-1]
75
+ if not domain:
76
+ raise ValueError(f"Invalid email address {email_addr}")
77
+
78
+ autoconfig = autodiscover_txt(domain) if not srv_only else None
79
+
80
+ if not autoconfig:
81
+ imap = resolve_srv(f"_imaps._tcp.{domain}")
82
+ smtp = resolve_srv(f"_submission._tcp.{domain}")
83
+
84
+ return {
85
+ "imap": {
86
+ "server": imap[0].get("hostname"),
87
+ "port": int(imap[0].get("port")),
88
+ # FIXME We might want to "smartly" guess if starttls should be
89
+ # enabled or not, depending on the port:
90
+ # 143 -> starttls
91
+ # 993 -> no
92
+ "starttls": False,
93
+ },
94
+ "smtp": {
95
+ "server": smtp[0].get("hostname"),
96
+ "port": int(smtp[0].get("port")),
97
+ # FIXME We might want to "smartly" guess if starttls should be
98
+ # enabled or not, depending on the port:
99
+ # 465 -> starttls
100
+ # 587 -> no
101
+ "starttls": False,
102
+ },
103
+ }
104
+
105
+ res = requests.get(autoconfig)
106
+ res.raise_for_status()
107
+
108
+ data = xmltodict.parse(res.text)
109
+ imap = (
110
+ data.get("clientConfig", {})
111
+ .get("emailProvider", {})
112
+ .get("incomingServer")
113
+ )
114
+ smtp = (
115
+ data.get("clientConfig", {})
116
+ .get("emailProvider", {})
117
+ .get("outgoingServer")
118
+ )
119
+
120
+ assert imap is not None
121
+ assert smtp is not None
122
+
123
+ return {
124
+ "imap": {
125
+ "server": imap.get("hostname"),
126
+ "port": int(imap.get("port")),
127
+ "starttls": imap.get("socketType") == "STARTTLS",
128
+ },
129
+ "smtp": {
130
+ "server": smtp.get("hostname"),
131
+ "port": int(smtp.get("port")),
132
+ "starttls": smtp.get("socketType") == "STARTTLS",
133
+ },
134
+ }
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,,