myl-discovery 0.5.8__py3-none-any.whl → 0.6.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.5.8
3
+ Version: 0.6.0
4
4
  Summary: email autodiscovery
5
5
  Author-email: Philipp Schmitt <philipp@schmitt.co>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -678,16 +678,20 @@ License: GNU GENERAL PUBLIC LICENSE
678
678
  Public License instead of this License. But first, please read
679
679
  <https://www.gnu.org/licenses/why-not-lgpl.html>.
680
680
 
681
+ Project-URL: homepage, https://github.com/pschmitt/myl-discovery
682
+ Project-URL: documentation, https://github.com/pschmitt/myl-discovery/blob/head/readme.md
683
+ Project-URL: repository, https://github.com/pschmitt/myl-discovery
684
+ Project-URL: issues, https://github.com/pschmitt/myl-discovery/issues
681
685
  Keywords: email-discovery,autodiscover,autoconfig,smtp,imap,email,discovery
682
686
  Classifier: Programming Language :: Python :: 3
683
687
  Requires-Python: >=3.8
684
688
  Description-Content-Type: text/markdown
685
689
  License-File: LICENSE
686
- Requires-Dist: dnspython <3.0.0,>=2.7.0
690
+ Requires-Dist: dnspython <3.0.0,>=2.0.0
687
691
  Requires-Dist: exchangelib <6.0.0,>=5.0.0
688
692
  Requires-Dist: requests <3.0.0,>=2.0.0
689
693
  Requires-Dist: rich <14.0.0,>=13.0.0
690
- Requires-Dist: xmltodict <1.0.0,>=0.14.1
694
+ Requires-Dist: xmltodict <1.0.0,>=0.13.0
691
695
 
692
696
  # 📩 myl-discovery
693
697
 
@@ -734,12 +738,14 @@ Here's an example:
734
738
  "imap": {
735
739
  "server": "imap.yourdomain.com",
736
740
  "port": 993,
737
- "starttls": false
741
+ "starttls": false,
742
+ "ssl": false
738
743
  },
739
744
  "smtp": {
740
745
  "server": "smtp.yourdomain.com",
741
746
  "port": 587,
742
- "starttls": true
747
+ "starttls": true,
748
+ "ssl": false
743
749
  }
744
750
  }
745
751
  ```
@@ -818,3 +824,8 @@ $ myl-discovery user01@gmail.com
818
824
  ## 📜 License
819
825
 
820
826
  myl-discovery is licensed under the [GNU General Public License v3.0](LICENSE).
827
+
828
+ ## 📑 Upstream docs
829
+
830
+ - https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat
831
+ - https://datatracker.ietf.org/doc/html/rfc6186
@@ -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=eSvTFEwzJU6qf3PLhXxn1xZs9SJlPdAn5SJUMYpmcco,9195
4
+ myldiscovery/main.py,sha256=BQlblFEsPx3-JSXImWc1i2SsRED2IiEWD3cEPTszwac,2034
5
+ myl_discovery-0.6.0.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
6
+ myl_discovery-0.6.0.dist-info/METADATA,sha256=Nu0m2olCS_9eAMuuJ2ZyDcVDzoF8kUxAq9xi0ZWjGkc,45161
7
+ myl_discovery-0.6.0.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
8
+ myl_discovery-0.6.0.dist-info/entry_points.txt,sha256=nyyAyvgvu6iO9mPEA6uVrPfd0lIrUyo9AQWeH2asEY0,52
9
+ myl_discovery-0.6.0.dist-info/top_level.txt,sha256=v_h72JexaacqBNY6iOMD9PpGg8lnGoL-pkmUIzxdiVU,13
10
+ myl_discovery-0.6.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.1.0)
2
+ Generator: setuptools (75.3.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
myldiscovery/discovery.py CHANGED
@@ -75,6 +75,7 @@ def autodiscover_txt(domain):
75
75
  LOGGER.warning("Failed to resolve TXT record")
76
76
 
77
77
 
78
+ # https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat
78
79
  def parse_autoconfig(content):
79
80
  data = xmltodict.parse(content)
80
81
 
@@ -100,11 +101,13 @@ def parse_autoconfig(content):
100
101
  "server": imap.get("hostname"),
101
102
  "port": int(imap.get("port")),
102
103
  "starttls": imap.get("socketType") == "STARTTLS",
104
+ "ssl": imap.get("socketType") == "SSL",
103
105
  },
104
106
  "smtp": {
105
107
  "server": smtp.get("hostname"),
106
108
  "port": int(smtp.get("port")),
107
109
  "starttls": smtp.get("socketType") == "STARTTLS",
110
+ "ssl": smtp.get("socketType") == "SSL",
108
111
  },
109
112
  }
110
113
 
@@ -140,19 +143,41 @@ def parse_autodiscover(content):
140
143
  "server": imap.get("Server"),
141
144
  "port": int(imap.get("Port")),
142
145
  "starttls": imap.get("Encryption", "").lower() == "tls",
146
+ # FIXME Is that really the expected value for SSL?
147
+ "ssl": imap.get("Encryption", "").lower() == "ssl",
143
148
  },
144
149
  "smtp": {
145
150
  "server": smtp.get("Server"),
146
151
  "port": int(smtp.get("Port")),
147
152
  "starttls": smtp.get("Encryption", "").lower() == "tls",
153
+ # FIXME Is that really the expected value for SSL?
154
+ "ssl": smtp.get("Encryption", "").lower() == "ssl",
148
155
  },
149
156
  }
150
157
 
151
158
 
159
+ # https://datatracker.ietf.org/doc/html/rfc6186
152
160
  def autodiscover_srv(domain):
153
161
  try:
162
+ # Start by looking for IMAPS and SUBMISSIONS (ie SSL)
163
+ imap_ssl = True
164
+ smtp_ssl = True
154
165
  imap = resolve_srv(f"_imaps._tcp.{domain}")
155
- smtp = resolve_srv(f"_submission._tcp.{domain}")
166
+ smtp = resolve_srv(f"_submissions._tcp.{domain}")
167
+
168
+ if not imap:
169
+ LOGGER.warning("No imaps SRV found, trying imap (starttls)")
170
+ imap_ssl = False
171
+ imap = resolve_srv(f"_imap._tcp.{domain}")
172
+ if not smtp:
173
+ LOGGER.warning(
174
+ "No submissions SRV found, trying submission (starttls)"
175
+ )
176
+ imap_ssl = False
177
+ smtp = resolve_srv(f"_submission._tcp.{domain}")
178
+
179
+ imap_starttls = not imap_ssl
180
+ smtp_starttls = not smtp_ssl
156
181
 
157
182
  assert imap is not None
158
183
  assert smtp is not None
@@ -161,31 +186,36 @@ def autodiscover_srv(domain):
161
186
  "imap": {
162
187
  "server": imap[0].get("hostname"),
163
188
  "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,
189
+ "starttls": imap_starttls,
190
+ "ssl": imap_ssl,
169
191
  },
170
192
  "smtp": {
171
193
  "server": smtp[0].get("hostname"),
172
194
  "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,
195
+ "starttls": smtp_starttls,
196
+ "ssl": smtp_ssl,
178
197
  },
179
198
  }
180
199
  except Exception as e:
181
200
  LOGGER.warning(f"Failed to resolve SRV records: {e}")
182
201
 
183
202
 
184
- def port_check(host, port):
203
+ def port_check(host, port, timeout=5.0):
185
204
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
186
- result = sock.connect_ex((host, int(port)))
187
- sock.close()
188
- return result == 0
205
+ sock.settimeout(float(timeout))
206
+ try:
207
+ result = sock.connect_ex((host, port))
208
+ return result == 0
209
+ except socket.timeout:
210
+ LOGGER.warning(
211
+ f"Connection to {host}:{port} timed out after {timeout} seconds."
212
+ )
213
+ return False
214
+ except socket.error as e:
215
+ LOGGER.warning(f"Socket error occurred: {e}")
216
+ return False
217
+ finally:
218
+ sock.close()
189
219
 
190
220
 
191
221
  def check_email_ports(host):
@@ -212,32 +242,40 @@ def autodiscover_port_scan(server):
212
242
  portscan = check_email_ports(server)
213
243
  LOGGER.info(f"Port scan results: {portscan}")
214
244
 
215
- imap_port = imap_starttls = None
245
+ imap_port = imap_starttls = imap_ssl = None
216
246
  if portscan.get(993):
217
247
  imap_port = 993
218
- imap_starttls = False
248
+ imap_ssl = True
219
249
  elif portscan.get(143):
220
250
  imap_port = 143
221
- imap_starttls = True
251
+ imap_ssl = False
222
252
 
223
- smtp_port = smtp_starttls = None
253
+ smtp_port = smtp_starttls = smtp_ssl = None
224
254
  if portscan.get(465):
225
255
  smtp_port = 465
226
- smtp_starttls = False
256
+ smtp_ssl = True
227
257
  elif portscan.get(587):
228
258
  smtp_port = 587
229
- smtp_starttls = True
259
+ smtp_ssl = False
260
+ elif portscan.get(25):
261
+ smtp_port = 25
262
+ smtp_ssl = False
263
+
264
+ imap_starttls = not imap_ssl if imap_ssl is not None else None
265
+ smtp_starttls = not smtp_ssl if smtp_ssl is not None else None
230
266
 
231
267
  return {
232
268
  "imap": {
233
269
  "server": server,
234
270
  "port": imap_port,
235
271
  "starttls": imap_starttls,
272
+ "ssl": imap_ssl,
236
273
  },
237
274
  "smtp": {
238
275
  "server": server,
239
276
  "port": smtp_port,
240
277
  "starttls": smtp_starttls,
278
+ "ssl": smtp_ssl,
241
279
  },
242
280
  }
243
281
 
@@ -263,6 +301,22 @@ def autodiscover(email_addr, username=None, password=None):
263
301
  domain = email_addr.split("@")[-1]
264
302
  if not domain:
265
303
  raise ValueError(f"Invalid email address {email_addr}")
304
+ if domain == "gmail.com":
305
+ LOGGER.debug("Gmail detected, skipping autodiscover")
306
+ return {
307
+ "imap": {
308
+ "server": "imap.gmail.com",
309
+ "port": 993,
310
+ "starttls": False,
311
+ "ssl": True,
312
+ },
313
+ "smtp": {
314
+ "server": "smtp.gmail.com",
315
+ "port": 465,
316
+ "starttls": False,
317
+ "ssl": True,
318
+ },
319
+ }
266
320
 
267
321
  res = autodiscover_autoconfig(domain)
268
322
 
myldiscovery/main.py CHANGED
@@ -60,7 +60,7 @@ def main():
60
60
  table.add_row(
61
61
  svc,
62
62
  res[svc]["server"],
63
- str(res[svc]["port"]),
63
+ str(res[svc].get("port", "N/A")),
64
64
  "starttls" if res[svc]["starttls"] else "tls",
65
65
  )
66
66
  console.print(table)
@@ -1,10 +0,0 @@
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=wuo-kxoqsgX0r1ebrr86P4WNJJOhpemR7w5JT4F-ecw,2023
5
- myl_discovery-0.5.8.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
6
- myl_discovery-0.5.8.dist-info/METADATA,sha256=Lj1QxtUK4jVe4rHMA02RdWgrdgUgyOExTEDIjXPh16w,44687
7
- myl_discovery-0.5.8.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
8
- myl_discovery-0.5.8.dist-info/entry_points.txt,sha256=nyyAyvgvu6iO9mPEA6uVrPfd0lIrUyo9AQWeH2asEY0,52
9
- myl_discovery-0.5.8.dist-info/top_level.txt,sha256=v_h72JexaacqBNY6iOMD9PpGg8lnGoL-pkmUIzxdiVU,13
10
- myl_discovery-0.5.8.dist-info/RECORD,,