Repository: certbot/certbot

Issue:
Standalone should verify ports for suported challenges only
I ran the following commands and both complained that nginx was already using port 80.
`./letsencrypt-auto --standalone --dvsni-port 63443 --server https://acme-v01.api.letsencrypt.org/directory auth`
`./letsencrypt-auto --standalone --dvsni-port 63443 --server https://acme-v01.api.letsencrypt.org/directory --standalone-supported-challenges dvsni auth`

I was under the impression that using `--dvsni-port` would force dvsni only.

I had to supply a fake port with `--simple-http-port 9999` with the second commad to get a certificate.

I'm using haproxy in the front to redirect *.acme.invalid to port 63443.



Discussion:
Using `--dvsni-port` does not force DVSNI, `--standalone-supported-challenges dvsni` does (this is explained in http://letsencrypt.readthedocs.org/en/latest/using.html#plugins). And you should not need to specify `--simple-http-port`.

Anyhow, thanks for reporting. I can verify this is a bug in https://github.com/letsencrypt/letsencrypt/blob/v0.0.0.dev20151024/letsencrypt/plugins/standalone.py#L197-L198. This function should only verify ports that correspond to `self.supported_challenges`.


Resolving patch:
diff --git a/letsencrypt/plugins/standalone.py b/letsencrypt/plugins/standalone.py
--- a/letsencrypt/plugins/standalone.py
+++ b/letsencrypt/plugins/standalone.py
@@ -181,6 +181,15 @@ def supported_challenges(self):
         return set(challenges.Challenge.TYPES[name] for name in
                    self.conf("supported-challenges").split(","))
 
+    @property
+    def _necessary_ports(self):
+        necessary_ports = set()
+        if challenges.SimpleHTTP in self.supported_challenges:
+            necessary_ports.add(self.config.simple_http_port)
+        if challenges.DVSNI in self.supported_challenges:
+            necessary_ports.add(self.config.dvsni_port)
+        return necessary_ports
+
     def more_info(self):  # pylint: disable=missing-docstring
         return self.__doc__
 
@@ -194,8 +203,7 @@ def get_chall_pref(self, domain):
         return chall_pref
 
     def perform(self, achalls):  # pylint: disable=missing-docstring
-        if any(util.already_listening(port) for port in
-               (self.config.dvsni_port, self.config.simple_http_port)):
+        if any(util.already_listening(port) for port in self._necessary_ports):
             raise errors.MisconfigurationError(
                 "At least one of the (possibly) required ports is "
                 "already taken.")


Test patch:
diff --git a/letsencrypt/plugins/standalone_test.py b/letsencrypt/plugins/standalone_test.py
--- a/letsencrypt/plugins/standalone_test.py
+++ b/letsencrypt/plugins/standalone_test.py
@@ -107,10 +107,15 @@ def test_get_chall_pref(self):
                          set([challenges.DVSNI, challenges.SimpleHTTP]))
 
     @mock.patch("letsencrypt.plugins.standalone.util")
-    def test_perform_misconfiguration(self, mock_util):
-        mock_util.already_listening.return_value = True
-        self.assertRaises(errors.MisconfigurationError, self.auth.perform, [])
-        mock_util.already_listening.assert_called_once_with(1234)
+    def test_perform_alredy_listening(self, mock_util):
+        for chall, port in ((challenges.DVSNI.typ, 1234),
+                            (challenges.SimpleHTTP.typ, 4321)):
+            mock_util.already_listening.return_value = True
+            self.config.standalone_supported_challenges = chall
+            self.assertRaises(
+                errors.MisconfigurationError, self.auth.perform, [])
+            mock_util.already_listening.assert_called_once_with(port)
+            mock_util.already_listening.reset_mock()
 
     @mock.patch("letsencrypt.plugins.standalone.zope.component.getUtility")
     def test_perform(self, unused_mock_get_utility):