Skip to content

Commit ea23f97

Browse files
committed
1. fix bug in SMTP STARTTLS conversation 2. fix test reading from stdin
1 parent 2f9e00f commit ea23f97

File tree

4 files changed

+66
-7
lines changed

4 files changed

+66
-7
lines changed

showcert/__about__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '0.4.7'
1+
__version__ = '0.4.8'

showcert/getremote.py

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,74 @@
1010

1111
# not covering conversation because it's hard to find server which would produce protocol errors
1212

13-
def conversation(s, script):
13+
14+
15+
def recv_until_newline(sock: socket.socket, timeout: float = 5.0) -> bytes:
16+
"""Read from socket until '\n' seen or timeout expires."""
17+
sock.setblocking(False)
18+
data = bytearray()
19+
deadline = time.monotonic() + timeout
20+
21+
while time.monotonic() < deadline:
22+
try:
23+
chunk = sock.recv(4096)
24+
if not chunk: # connection closed
25+
break
26+
data.extend(chunk)
27+
if b'\n' in chunk:
28+
break
29+
except BlockingIOError:
30+
time.sleep(0.01)
31+
continue
32+
return bytes(data)
33+
34+
def recv_smtp(sock: socket.socket, timeout: float = 5.0) -> bytes:
35+
"""
36+
Read full SMTP reply (single or multi-line) until final line received or timeout.
37+
RFC 5321: lines start with 3 digits + ('-' for continuation or ' ' for end).
38+
"""
39+
sock.setblocking(False)
40+
data = bytearray()
41+
lines = []
42+
deadline = time.monotonic() + timeout
43+
code = None
44+
45+
while time.monotonic() < deadline:
46+
try:
47+
chunk = sock.recv(4096)
48+
if not chunk: # connection closed
49+
break
50+
data.extend(chunk)
51+
while b'\n' in data:
52+
line, _, rest = data.partition(b'\n')
53+
data = bytearray(rest)
54+
line = line.rstrip(b'\r')
55+
lines.append(line)
56+
57+
# parse reply code
58+
if len(line) >= 4 and line[:3].isdigit():
59+
code = line[:3]
60+
if line[3:4] == b' ': # final line
61+
return b'\n'.join(lines) + b'\n'
62+
except BlockingIOError:
63+
time.sleep(0.01)
64+
continue
65+
66+
return b'\n'.join(lines) + b'\n'
67+
68+
69+
def conversation(s, script, read_fn = None):
1470
verbose = False
1571
for ph in script:
1672
if ph.say is not None:
1773
if verbose:
1874
print(">", repr(ph.say)) # pragma: no cover
1975
s.sendall(ph.say.encode())
20-
reply = s.recv(2048).decode('utf8')
76+
if read_fn:
77+
reply = read_fn(s, timeout=5).decode('utf8')
78+
else:
79+
reply = recv_until_newline(s, timeout=5).decode('utf8')
80+
2181
if verbose:
2282
print("<", repr(reply)) # pragma: no cover
2383
print("wait:", repr(ph.wait)) # pragma: no cover
@@ -42,7 +102,7 @@ def starttls_smtp(s):
42102
phrase('EHLO www-security.com\n', '\n', 'STARTTLS'),
43103
phrase('STARTTLS\n','\n', None)
44104
)
45-
conversation(s, script)
105+
conversation(s, script, read_fn=recv_smtp)
46106

47107
def starttls_pop3(s):
48108
script = (

showcert/processcert.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,9 @@ def get_local_certs(CERT, insecure=False, password=None):
4444
else:
4545
rawcert = open(CERT, "rb").read()
4646

47-
4847
mime = magic.from_buffer(rawcert, mime=True)
4948

50-
if mime.startswith("text/") or b"BEGIN CERTIFICATE" in rawcert:
49+
if mime.startswith("text/") or "BEGIN CERTIFICATE" in str(rawcert):
5150
# default simple PEM part
5251
return [ load_certificate(FILETYPE_PEM, str(_c)) for _c in pem.parse(rawcert) if _c.__class__.__name__ == 'Certificate' ]
5352

tests/test_local.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def test_print(self):
4444
rc = process_cert(CERT=self.snakeoil, output='no', insecure=True)
4545
assert(rc == 0)
4646

47-
def test_stdin(self):
47+
def test_stdin(self):
4848
with open(self.ca_certs[0], "r") as f: # Read the certificate file
4949
mock_input = f.read()
5050
with mock.patch("sys.stdin", io.StringIO(mock_input)):

0 commit comments

Comments
 (0)