mirror of
https://github.com/certbot/certbot.git
synced 2026-08-01 19:14:54 +02:00
Move already_listening to plugins.util
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
:mod:`letsencrypt.plugins.util`
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
.. automodule:: letsencrypt.plugins.util
|
||||||
|
:members:
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
"""Standalone authenticator."""
|
"""Standalone authenticator."""
|
||||||
import os
|
import os
|
||||||
import psutil
|
|
||||||
import signal
|
import signal
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
@@ -289,47 +288,6 @@ class StandaloneAuthenticator(common.Plugin):
|
|||||||
# should terminate via sys.exit().
|
# should terminate via sys.exit().
|
||||||
return self.do_child_process(port)
|
return self.do_child_process(port)
|
||||||
|
|
||||||
def already_listening(self, port): # pylint: disable=no-self-use
|
|
||||||
"""Check if a process is already listening on the port.
|
|
||||||
|
|
||||||
If so, also tell the user via a display notification.
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
On some operating systems, this function can only usefully be
|
|
||||||
run as root.
|
|
||||||
|
|
||||||
:param int port: The TCP port in question.
|
|
||||||
:returns: True or False."""
|
|
||||||
|
|
||||||
listeners = [conn.pid for conn in psutil.net_connections()
|
|
||||||
if conn.status == 'LISTEN' and
|
|
||||||
conn.type == socket.SOCK_STREAM and
|
|
||||||
conn.laddr[1] == port]
|
|
||||||
try:
|
|
||||||
if listeners and listeners[0] is not None:
|
|
||||||
# conn.pid may be None if the current process doesn't have
|
|
||||||
# permission to identify the listening process! Additionally,
|
|
||||||
# listeners may have more than one element if separate
|
|
||||||
# sockets have bound the same port on separate interfaces.
|
|
||||||
# We currently only have UI to notify the user about one
|
|
||||||
# of them at a time.
|
|
||||||
pid = listeners[0]
|
|
||||||
name = psutil.Process(pid).name()
|
|
||||||
display = zope.component.getUtility(interfaces.IDisplay)
|
|
||||||
display.notification(
|
|
||||||
"The program {0} (process ID {1}) is already listening "
|
|
||||||
"on TCP port {2}. This will prevent us from binding to "
|
|
||||||
"that port. Please stop the {0} program temporarily "
|
|
||||||
"and then try again.".format(name, pid, port))
|
|
||||||
return True
|
|
||||||
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
|
||||||
# Perhaps the result of a race where the process could have
|
|
||||||
# exited or relinquished the port (NoSuchProcess), or the result
|
|
||||||
# of an OS policy where we're not allowed to look up the process
|
|
||||||
# name (AccessDenied).
|
|
||||||
pass
|
|
||||||
return False
|
|
||||||
|
|
||||||
# IAuthenticator method implementations follow
|
# IAuthenticator method implementations follow
|
||||||
|
|
||||||
def get_chall_pref(self, unused_domain): # pylint: disable=no-self-use
|
def get_chall_pref(self, unused_domain): # pylint: disable=no-self-use
|
||||||
@@ -383,7 +341,7 @@ class StandaloneAuthenticator(common.Plugin):
|
|||||||
if not self.tasks:
|
if not self.tasks:
|
||||||
raise ValueError("nothing for .perform() to do")
|
raise ValueError("nothing for .perform() to do")
|
||||||
|
|
||||||
if self.already_listening(self.config.dvsni_port):
|
if util.already_listening(self.config.dvsni_port):
|
||||||
# If we know a process is already listening on this port,
|
# If we know a process is already listening on this port,
|
||||||
# tell the user, and don't even attempt to bind it. (This
|
# tell the user, and don't even attempt to bind it. (This
|
||||||
# test is Linux-specific and won't indicate that the port
|
# test is Linux-specific and won't indicate that the port
|
||||||
|
|||||||
@@ -187,109 +187,6 @@ class SubprocSignalHandlerTest(unittest.TestCase):
|
|||||||
mock_exit.assert_called_once_with(0)
|
mock_exit.assert_called_once_with(0)
|
||||||
|
|
||||||
|
|
||||||
class AlreadyListeningTest(unittest.TestCase):
|
|
||||||
"""Tests for already_listening() method."""
|
|
||||||
def setUp(self):
|
|
||||||
from letsencrypt.plugins.standalone.authenticator import \
|
|
||||||
StandaloneAuthenticator
|
|
||||||
self.authenticator = StandaloneAuthenticator(config=CONFIG, name=None)
|
|
||||||
|
|
||||||
@mock.patch("letsencrypt.plugins.standalone.authenticator.psutil."
|
|
||||||
"net_connections")
|
|
||||||
@mock.patch("letsencrypt.plugins.standalone.authenticator.psutil.Process")
|
|
||||||
@mock.patch("letsencrypt.plugins.standalone.authenticator."
|
|
||||||
"zope.component.getUtility")
|
|
||||||
def test_race_condition(self, mock_get_utility, mock_process, mock_net):
|
|
||||||
# This tests a race condition, or permission problem, or OS
|
|
||||||
# incompatibility in which, for some reason, no process name can be
|
|
||||||
# found to match the identified listening PID.
|
|
||||||
from psutil._common import sconn
|
|
||||||
conns = [
|
|
||||||
sconn(fd=-1, family=2, type=1, laddr=("0.0.0.0", 30),
|
|
||||||
raddr=(), status="LISTEN", pid=None),
|
|
||||||
sconn(fd=3, family=2, type=1, laddr=("192.168.5.10", 32783),
|
|
||||||
raddr=("20.40.60.80", 22), status="ESTABLISHED", pid=1234),
|
|
||||||
sconn(fd=-1, family=10, type=1, laddr=("::1", 54321),
|
|
||||||
raddr=("::1", 111), status="CLOSE_WAIT", pid=None),
|
|
||||||
sconn(fd=3, family=2, type=1, laddr=("0.0.0.0", 17),
|
|
||||||
raddr=(), status="LISTEN", pid=4416)]
|
|
||||||
mock_net.return_value = conns
|
|
||||||
mock_process.side_effect = psutil.NoSuchProcess("No such PID")
|
|
||||||
# We simulate being unable to find the process name of PID 4416,
|
|
||||||
# which results in returning False.
|
|
||||||
self.assertFalse(self.authenticator.already_listening(17))
|
|
||||||
self.assertEqual(mock_get_utility.generic_notification.call_count, 0)
|
|
||||||
mock_process.assert_called_once_with(4416)
|
|
||||||
|
|
||||||
@mock.patch("letsencrypt.plugins.standalone.authenticator.psutil."
|
|
||||||
"net_connections")
|
|
||||||
@mock.patch("letsencrypt.plugins.standalone.authenticator.psutil.Process")
|
|
||||||
@mock.patch("letsencrypt.plugins.standalone.authenticator."
|
|
||||||
"zope.component.getUtility")
|
|
||||||
def test_not_listening(self, mock_get_utility, mock_process, mock_net):
|
|
||||||
from psutil._common import sconn
|
|
||||||
conns = [
|
|
||||||
sconn(fd=-1, family=2, type=1, laddr=("0.0.0.0", 30),
|
|
||||||
raddr=(), status="LISTEN", pid=None),
|
|
||||||
sconn(fd=3, family=2, type=1, laddr=("192.168.5.10", 32783),
|
|
||||||
raddr=("20.40.60.80", 22), status="ESTABLISHED", pid=1234),
|
|
||||||
sconn(fd=-1, family=10, type=1, laddr=("::1", 54321),
|
|
||||||
raddr=("::1", 111), status="CLOSE_WAIT", pid=None)]
|
|
||||||
mock_net.return_value = conns
|
|
||||||
mock_process.name.return_value = "inetd"
|
|
||||||
self.assertFalse(self.authenticator.already_listening(17))
|
|
||||||
self.assertEqual(mock_get_utility.generic_notification.call_count, 0)
|
|
||||||
self.assertEqual(mock_process.call_count, 0)
|
|
||||||
|
|
||||||
@mock.patch("letsencrypt.plugins.standalone.authenticator.psutil."
|
|
||||||
"net_connections")
|
|
||||||
@mock.patch("letsencrypt.plugins.standalone.authenticator.psutil.Process")
|
|
||||||
@mock.patch("letsencrypt.plugins.standalone.authenticator."
|
|
||||||
"zope.component.getUtility")
|
|
||||||
def test_listening_ipv4(self, mock_get_utility, mock_process, mock_net):
|
|
||||||
from psutil._common import sconn
|
|
||||||
conns = [
|
|
||||||
sconn(fd=-1, family=2, type=1, laddr=("0.0.0.0", 30),
|
|
||||||
raddr=(), status="LISTEN", pid=None),
|
|
||||||
sconn(fd=3, family=2, type=1, laddr=("192.168.5.10", 32783),
|
|
||||||
raddr=("20.40.60.80", 22), status="ESTABLISHED", pid=1234),
|
|
||||||
sconn(fd=-1, family=10, type=1, laddr=("::1", 54321),
|
|
||||||
raddr=("::1", 111), status="CLOSE_WAIT", pid=None),
|
|
||||||
sconn(fd=3, family=2, type=1, laddr=("0.0.0.0", 17),
|
|
||||||
raddr=(), status="LISTEN", pid=4416)]
|
|
||||||
mock_net.return_value = conns
|
|
||||||
mock_process.name.return_value = "inetd"
|
|
||||||
result = self.authenticator.already_listening(17)
|
|
||||||
self.assertTrue(result)
|
|
||||||
self.assertEqual(mock_get_utility.call_count, 1)
|
|
||||||
mock_process.assert_called_once_with(4416)
|
|
||||||
|
|
||||||
@mock.patch("letsencrypt.plugins.standalone.authenticator.psutil."
|
|
||||||
"net_connections")
|
|
||||||
@mock.patch("letsencrypt.plugins.standalone.authenticator.psutil.Process")
|
|
||||||
@mock.patch("letsencrypt.plugins.standalone.authenticator."
|
|
||||||
"zope.component.getUtility")
|
|
||||||
def test_listening_ipv6(self, mock_get_utility, mock_process, mock_net):
|
|
||||||
from psutil._common import sconn
|
|
||||||
conns = [
|
|
||||||
sconn(fd=-1, family=2, type=1, laddr=("0.0.0.0", 30),
|
|
||||||
raddr=(), status="LISTEN", pid=None),
|
|
||||||
sconn(fd=3, family=2, type=1, laddr=("192.168.5.10", 32783),
|
|
||||||
raddr=("20.40.60.80", 22), status="ESTABLISHED", pid=1234),
|
|
||||||
sconn(fd=-1, family=10, type=1, laddr=("::1", 54321),
|
|
||||||
raddr=("::1", 111), status="CLOSE_WAIT", pid=None),
|
|
||||||
sconn(fd=3, family=10, type=1, laddr=("::", 12345), raddr=(),
|
|
||||||
status="LISTEN", pid=4420),
|
|
||||||
sconn(fd=3, family=2, type=1, laddr=("0.0.0.0", 17),
|
|
||||||
raddr=(), status="LISTEN", pid=4416)]
|
|
||||||
mock_net.return_value = conns
|
|
||||||
mock_process.name.return_value = "inetd"
|
|
||||||
result = self.authenticator.already_listening(12345)
|
|
||||||
self.assertTrue(result)
|
|
||||||
self.assertEqual(mock_get_utility.call_count, 1)
|
|
||||||
mock_process.assert_called_once_with(4420)
|
|
||||||
|
|
||||||
|
|
||||||
class PerformTest(unittest.TestCase):
|
class PerformTest(unittest.TestCase):
|
||||||
"""Tests for perform() method."""
|
"""Tests for perform() method."""
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
"""Plugin utilities."""
|
||||||
|
import socket
|
||||||
|
|
||||||
|
import psutil
|
||||||
|
import zope.component
|
||||||
|
|
||||||
|
from letsencrypt import interfaces
|
||||||
|
|
||||||
|
|
||||||
|
def already_listening(port):
|
||||||
|
"""Check if a process is already listening on the port.
|
||||||
|
|
||||||
|
If so, also tell the user via a display notification.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
On some operating systems, this function can only usefully be
|
||||||
|
run as root.
|
||||||
|
|
||||||
|
:param int port: The TCP port in question.
|
||||||
|
:returns: True or False."""
|
||||||
|
|
||||||
|
listeners = [conn.pid for conn in psutil.net_connections()
|
||||||
|
if conn.status == 'LISTEN' and
|
||||||
|
conn.type == socket.SOCK_STREAM and
|
||||||
|
conn.laddr[1] == port]
|
||||||
|
try:
|
||||||
|
if listeners and listeners[0] is not None:
|
||||||
|
# conn.pid may be None if the current process doesn't have
|
||||||
|
# permission to identify the listening process! Additionally,
|
||||||
|
# listeners may have more than one element if separate
|
||||||
|
# sockets have bound the same port on separate interfaces.
|
||||||
|
# We currently only have UI to notify the user about one
|
||||||
|
# of them at a time.
|
||||||
|
pid = listeners[0]
|
||||||
|
name = psutil.Process(pid).name()
|
||||||
|
display = zope.component.getUtility(interfaces.IDisplay)
|
||||||
|
display.notification(
|
||||||
|
"The program {0} (process ID {1}) is already listening "
|
||||||
|
"on TCP port {2}. This will prevent us from binding to "
|
||||||
|
"that port. Please stop the {0} program temporarily "
|
||||||
|
"and then try again.".format(name, pid, port))
|
||||||
|
return True
|
||||||
|
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
||||||
|
# Perhaps the result of a race where the process could have
|
||||||
|
# exited or relinquished the port (NoSuchProcess), or the result
|
||||||
|
# of an OS policy where we're not allowed to look up the process
|
||||||
|
# name (AccessDenied).
|
||||||
|
pass
|
||||||
|
return False
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
"""Tests for letsencrypt.plugins.util."""
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import mock
|
||||||
|
import psutil
|
||||||
|
|
||||||
|
|
||||||
|
class AlreadyListeningTest(unittest.TestCase):
|
||||||
|
"""Tests for letsencrypt.plugins.already_listening."""
|
||||||
|
def _call(self, *args, **kwargs):
|
||||||
|
from letsencrypt.plugins.util import already_listening
|
||||||
|
return already_listening(*args, **kwargs)
|
||||||
|
|
||||||
|
@mock.patch("letsencrypt.plugins.util.psutil.net_connections")
|
||||||
|
@mock.patch("letsencrypt.plugins.util.psutil.Process")
|
||||||
|
@mock.patch("letsencrypt.plugins.util.zope.component.getUtility")
|
||||||
|
def test_race_condition(self, mock_get_utility, mock_process, mock_net):
|
||||||
|
# This tests a race condition, or permission problem, or OS
|
||||||
|
# incompatibility in which, for some reason, no process name can be
|
||||||
|
# found to match the identified listening PID.
|
||||||
|
from psutil._common import sconn
|
||||||
|
conns = [
|
||||||
|
sconn(fd=-1, family=2, type=1, laddr=("0.0.0.0", 30),
|
||||||
|
raddr=(), status="LISTEN", pid=None),
|
||||||
|
sconn(fd=3, family=2, type=1, laddr=("192.168.5.10", 32783),
|
||||||
|
raddr=("20.40.60.80", 22), status="ESTABLISHED", pid=1234),
|
||||||
|
sconn(fd=-1, family=10, type=1, laddr=("::1", 54321),
|
||||||
|
raddr=("::1", 111), status="CLOSE_WAIT", pid=None),
|
||||||
|
sconn(fd=3, family=2, type=1, laddr=("0.0.0.0", 17),
|
||||||
|
raddr=(), status="LISTEN", pid=4416)]
|
||||||
|
mock_net.return_value = conns
|
||||||
|
mock_process.side_effect = psutil.NoSuchProcess("No such PID")
|
||||||
|
# We simulate being unable to find the process name of PID 4416,
|
||||||
|
# which results in returning False.
|
||||||
|
self.assertFalse(self._call(17))
|
||||||
|
self.assertEqual(mock_get_utility.generic_notification.call_count, 0)
|
||||||
|
mock_process.assert_called_once_with(4416)
|
||||||
|
|
||||||
|
@mock.patch("letsencrypt.plugins.util.psutil.net_connections")
|
||||||
|
@mock.patch("letsencrypt.plugins.util.psutil.Process")
|
||||||
|
@mock.patch("letsencrypt.plugins.util.zope.component.getUtility")
|
||||||
|
def test_not_listening(self, mock_get_utility, mock_process, mock_net):
|
||||||
|
from psutil._common import sconn
|
||||||
|
conns = [
|
||||||
|
sconn(fd=-1, family=2, type=1, laddr=("0.0.0.0", 30),
|
||||||
|
raddr=(), status="LISTEN", pid=None),
|
||||||
|
sconn(fd=3, family=2, type=1, laddr=("192.168.5.10", 32783),
|
||||||
|
raddr=("20.40.60.80", 22), status="ESTABLISHED", pid=1234),
|
||||||
|
sconn(fd=-1, family=10, type=1, laddr=("::1", 54321),
|
||||||
|
raddr=("::1", 111), status="CLOSE_WAIT", pid=None)]
|
||||||
|
mock_net.return_value = conns
|
||||||
|
mock_process.name.return_value = "inetd"
|
||||||
|
self.assertFalse(self._call(17))
|
||||||
|
self.assertEqual(mock_get_utility.generic_notification.call_count, 0)
|
||||||
|
self.assertEqual(mock_process.call_count, 0)
|
||||||
|
|
||||||
|
@mock.patch("letsencrypt.plugins.util.psutil.net_connections")
|
||||||
|
@mock.patch("letsencrypt.plugins.util.psutil.Process")
|
||||||
|
@mock.patch("letsencrypt.plugins.util.zope.component.getUtility")
|
||||||
|
def test_listening_ipv4(self, mock_get_utility, mock_process, mock_net):
|
||||||
|
from psutil._common import sconn
|
||||||
|
conns = [
|
||||||
|
sconn(fd=-1, family=2, type=1, laddr=("0.0.0.0", 30),
|
||||||
|
raddr=(), status="LISTEN", pid=None),
|
||||||
|
sconn(fd=3, family=2, type=1, laddr=("192.168.5.10", 32783),
|
||||||
|
raddr=("20.40.60.80", 22), status="ESTABLISHED", pid=1234),
|
||||||
|
sconn(fd=-1, family=10, type=1, laddr=("::1", 54321),
|
||||||
|
raddr=("::1", 111), status="CLOSE_WAIT", pid=None),
|
||||||
|
sconn(fd=3, family=2, type=1, laddr=("0.0.0.0", 17),
|
||||||
|
raddr=(), status="LISTEN", pid=4416)]
|
||||||
|
mock_net.return_value = conns
|
||||||
|
mock_process.name.return_value = "inetd"
|
||||||
|
result = self._call(17)
|
||||||
|
self.assertTrue(result)
|
||||||
|
self.assertEqual(mock_get_utility.call_count, 1)
|
||||||
|
mock_process.assert_called_once_with(4416)
|
||||||
|
|
||||||
|
@mock.patch("letsencrypt.plugins.util.psutil.net_connections")
|
||||||
|
@mock.patch("letsencrypt.plugins.util.psutil.Process")
|
||||||
|
@mock.patch("letsencrypt.plugins.util.zope.component.getUtility")
|
||||||
|
def test_listening_ipv6(self, mock_get_utility, mock_process, mock_net):
|
||||||
|
from psutil._common import sconn
|
||||||
|
conns = [
|
||||||
|
sconn(fd=-1, family=2, type=1, laddr=("0.0.0.0", 30),
|
||||||
|
raddr=(), status="LISTEN", pid=None),
|
||||||
|
sconn(fd=3, family=2, type=1, laddr=("192.168.5.10", 32783),
|
||||||
|
raddr=("20.40.60.80", 22), status="ESTABLISHED", pid=1234),
|
||||||
|
sconn(fd=-1, family=10, type=1, laddr=("::1", 54321),
|
||||||
|
raddr=("::1", 111), status="CLOSE_WAIT", pid=None),
|
||||||
|
sconn(fd=3, family=10, type=1, laddr=("::", 12345), raddr=(),
|
||||||
|
status="LISTEN", pid=4420),
|
||||||
|
sconn(fd=3, family=2, type=1, laddr=("0.0.0.0", 17),
|
||||||
|
raddr=(), status="LISTEN", pid=4416)]
|
||||||
|
mock_net.return_value = conns
|
||||||
|
mock_process.name.return_value = "inetd"
|
||||||
|
result = self._call(12345)
|
||||||
|
self.assertTrue(result)
|
||||||
|
self.assertEqual(mock_get_utility.call_count, 1)
|
||||||
|
mock_process.assert_called_once_with(4420)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main() # pragma: no cover
|
||||||
Reference in New Issue
Block a user