diff --git a/src/ftppr.c b/src/ftppr.c index 609f904..049bc93 100644 --- a/src/ftppr.c +++ b/src/ftppr.c @@ -64,6 +64,13 @@ void * ftpprchild(struct clientparam* param) { } else if (!strncasecmp((char *)buf, "PASS ", 5)){ + /* The user name carries the server to log in to, and it arrives + with USER. Without it there is nothing to log in to, and what + follows would read the name and the host as if there were. */ + if(!param->hostname || !param->extusername){ + socksend(param, param->ctrlsock, (unsigned char *)"503 Login with USER first\r\n", 27, conf.timeouts[STRING_S]); + RETURN(805); + } param->extpassword = (unsigned char *)strdup((char *)buf+5); inbuf = BUFSIZE; res = ftplogin(param, (char *)buf, &inbuf); diff --git a/src/smtpp.c b/src/smtpp.c index e82200e..3905bf0 100644 --- a/src/smtpp.c +++ b/src/smtpp.c @@ -159,7 +159,10 @@ void * smtppchild(struct clientparam* param) { i = de64(buf,username,255); if(i < 1) {RETURN(664);} username[i] = 0; - parseconnusername((char *)username, param, 0, 587); + /* The name has to carry the host to connect to, and the answer says + whether it did: without one there is nowhere to go, and what follows + reads the name as if there were. */ + if(parseconnusername((char *)username, param, 0, 587)) {RETURN(669);} socksend(param, param->clisock, (unsigned char *)"334 UGFzc3dvcmQ6\r\n", 18,conf.timeouts[STRING_S]); i = sockgetlinebuf(param, CLIENT, buf, sizeof(buf) - 10, '\n', conf.timeouts[STRING_S]); if(i < 2) {RETURN(665);} @@ -184,7 +187,7 @@ void * smtppchild(struct clientparam* param) { } if(i < 3 || *username) {RETURN(668);} username[i] = 0; - parseconnusername((char *)username+1, param, 0, 587); + if(parseconnusername((char *)username+1, param, 0, 587)) {RETURN(670);} res = (int)strlen((char *)username+1) + 2; if(res < i){ if(param->extpassword) free(param->extpassword); diff --git a/tests/harness.py b/tests/harness.py index 8912b69..163255b 100644 --- a/tests/harness.py +++ b/tests/harness.py @@ -22,6 +22,7 @@ configurations it needs, starts them, and states what it expects: import base64 import http.client import os +import random import shutil import socket import ssl @@ -33,6 +34,11 @@ import threading import time +# Ports handed out in this run: a service which has finished may still be +# in TIME_WAIT, and another case binding the same port would fail for it. +_PORTS_TAKEN = set() + + class Response: """A reply, or the reason there wasn't one.""" @@ -146,14 +152,30 @@ class Tester: sock.close() def free_port(self): - """A port nothing is listening on. Closed again before it is used, - which is racy in principle and reliable enough in practice.""" - s = socket.socket() - try: - s.bind(("127.0.0.1", 0)) - return s.getsockname()[1] - finally: - s.close() + """A port nothing is listening on, and nothing is likely to take. + + Asking the system for an ephemeral port hands back one out of the + range it also draws outgoing connections from - 32768 up on Linux, + 49152 up on Windows - so between the check here and the bind in the + service, a connection somewhere else in the suite can take it. That + shows up as a service which never listens, or a bind() error deep in + a case which has nothing to do with ports. Ports are taken from below + both ranges instead, and none is handed out twice in a run. + """ + for _ in range(200): + port = random.randint(10000, 19999) + if port in _PORTS_TAKEN: + continue + sock = socket.socket() + try: + sock.bind(("127.0.0.1", port)) + except OSError: + continue + finally: + sock.close() + _PORTS_TAKEN.add(port) + return port + raise RuntimeError("no free port in the range the suite uses") def write_config(self, name, config): path = os.path.join(self.tmpdir, name + ".cfg")