Current File : //proc/self/root/usr/lib/python3/dist-packages/twisted/conch/test/test_ssh.py
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.

"""
Tests for L{twisted.conch.ssh}.
"""


import struct
from itertools import chain
from typing import Dict, List, Tuple

from twisted.conch.test.keydata import (
    privateDSA_openssh,
    privateRSA_openssh,
    publicDSA_openssh,
    publicRSA_openssh,
)
from twisted.conch.test.loopback import LoopbackRelay
from twisted.cred import portal
from twisted.cred.error import UnauthorizedLogin
from twisted.internet import defer, protocol, reactor
from twisted.internet.error import ProcessTerminated
from twisted.python import failure, log
from twisted.python.reflect import requireModule
from twisted.trial import unittest

cryptography = requireModule("cryptography")

if cryptography:
    from twisted.conch import avatar, error
    from twisted.conch.ssh import _kex, common, forwarding, session
else:

    class avatar:  # type: ignore[no-redef]
        class ConchUser:
            pass


class ConchTestRealm:
    """
    A realm which expects a particular avatarId to log in once and creates a
    L{ConchTestAvatar} for that request.

    @ivar expectedAvatarID: The only avatarID that this realm will produce an
        avatar for.

    @ivar avatar: A reference to the avatar after it is requested.
    """

    avatar = None

    def __init__(self, expectedAvatarID):
        self.expectedAvatarID = expectedAvatarID

    def requestAvatar(self, avatarID, mind, *interfaces):
        """
        Return a new L{ConchTestAvatar} if the avatarID matches the expected one
        and this is the first avatar request.
        """
        if avatarID == self.expectedAvatarID:
            if self.avatar is not None:
                raise UnauthorizedLogin("Only one login allowed")
            self.avatar = ConchTestAvatar()
            return interfaces[0], self.avatar, self.avatar.logout
        raise UnauthorizedLogin(
            f"Only {self.expectedAvatarID!r} may log in, not {avatarID!r}"
        )


class ConchTestAvatar(avatar.ConchUser):
    """
    An avatar against which various SSH features can be tested.

    @ivar loggedOut: A flag indicating whether the avatar logout method has been
        called.
    """

    if not cryptography:
        skip = "cannot run without cryptography"

    loggedOut = False

    def __init__(self):
        avatar.ConchUser.__init__(self)
        self.listeners = {}
        self.globalRequests = {}
        self.channelLookup.update(
            {
                b"session": session.SSHSession,
                b"direct-tcpip": forwarding.openConnectForwardingClient,
            }
        )
        self.subsystemLookup.update({b"crazy": CrazySubsystem})

    def global_foo(self, data):
        self.globalRequests["foo"] = data
        return 1

    def global_foo_2(self, data):
        self.globalRequests["foo_2"] = data
        return 1, b"data"

    def global_tcpip_forward(self, data):
        host, port = forwarding.unpackGlobal_tcpip_forward(data)
        try:
            listener = reactor.listenTCP(
                port,
                forwarding.SSHListenForwardingFactory(
                    self.conn, (host, port), forwarding.SSHListenServerForwardingChannel
                ),
                interface=host,
            )
        except BaseException:
            log.err(None, "something went wrong with remote->local forwarding")
            return 0
        else:
            self.listeners[(host, port)] = listener
            return 1

    def global_cancel_tcpip_forward(self, data):
        host, port = forwarding.unpackGlobal_tcpip_forward(data)
        listener = self.listeners.get((host, port), None)
        if not listener:
            return 0
        del self.listeners[(host, port)]
        listener.stopListening()
        return 1

    def logout(self):
        self.loggedOut = True
        for listener in self.listeners.values():
            log.msg("stopListening %s" % listener)
            listener.stopListening()


class ConchSessionForTestAvatar:
    """
    An ISession adapter for ConchTestAvatar.
    """

    def __init__(self, avatar):
        """
        Initialize the session and create a reference to it on the avatar for
        later inspection.
        """
        self.avatar = avatar
        self.avatar._testSession = self
        self.cmd = None
        self.proto = None
        self.ptyReq = False
        self.eof = 0
        self.onClose = defer.Deferred()

    def getPty(self, term, windowSize, attrs):
        log.msg("pty req")
        self._terminalType = term
        self._windowSize = windowSize
        self.ptyReq = True

    def openShell(self, proto):
        log.msg("opening shell")
        self.proto = proto
        EchoTransport(proto)
        self.cmd = b"shell"

    def execCommand(self, proto, cmd):
        self.cmd = cmd
        self.proto = proto
        f = cmd.split()[0]
        if f == b"false":
            t = FalseTransport(proto)
            # Avoid disconnecting this immediately.  If the channel is closed
            # before execCommand even returns the caller gets confused.
            reactor.callLater(0, t.loseConnection)
        elif f == b"echo":
            t = EchoTransport(proto)
            t.write(cmd[5:])
            t.loseConnection()
        elif f == b"secho":
            t = SuperEchoTransport(proto)
            t.write(cmd[6:])
            t.loseConnection()
        elif f == b"eecho":
            t = ErrEchoTransport(proto)
            t.write(cmd[6:])
            t.loseConnection()
        else:
            raise error.ConchError("bad exec")
        self.avatar.conn.transport.expectedLoseConnection = 1

    def eofReceived(self):
        self.eof = 1

    def closed(self):
        log.msg('closed cmd "%s"' % self.cmd)
        self.remoteWindowLeftAtClose = self.proto.session.remoteWindowLeft
        self.onClose.callback(None)


from twisted.python import components

if cryptography:
    components.registerAdapter(
        ConchSessionForTestAvatar, ConchTestAvatar, session.ISession
    )


class CrazySubsystem(protocol.Protocol):
    def __init__(self, *args, **kw):
        pass

    def connectionMade(self):
        """
        good ... good
        """


class FalseTransport:
    """
    False transport should act like a /bin/false execution, i.e. just exit with
    nonzero status, writing nothing to the terminal.

    @ivar proto: The protocol associated with this transport.
    @ivar closed: A flag tracking whether C{loseConnection} has been called yet.
    """

    def __init__(self, p):
        """
        @type p L{twisted.conch.ssh.session.SSHSessionProcessProtocol} instance
        """
        self.proto = p
        p.makeConnection(self)
        self.closed = 0

    def loseConnection(self):
        """
        Disconnect the protocol associated with this transport.
        """
        if self.closed:
            return
        self.closed = 1
        self.proto.inConnectionLost()
        self.proto.outConnectionLost()
        self.proto.errConnectionLost()
        self.proto.processEnded(failure.Failure(ProcessTerminated(255, None, None)))


class EchoTransport:
    def __init__(self, p):
        self.proto = p
        p.makeConnection(self)
        self.closed = 0

    def write(self, data):
        log.msg(repr(data))
        self.proto.outReceived(data)
        self.proto.outReceived(b"\r\n")
        if b"\x00" in data:  # mimic 'exit' for the shell test
            self.loseConnection()

    def loseConnection(self):
        if self.closed:
            return
        self.closed = 1
        self.proto.inConnectionLost()
        self.proto.outConnectionLost()
        self.proto.errConnectionLost()
        self.proto.processEnded(failure.Failure(ProcessTerminated(0, None, None)))


class ErrEchoTransport:
    def __init__(self, p):
        self.proto = p
        p.makeConnection(self)
        self.closed = 0

    def write(self, data):
        self.proto.errReceived(data)
        self.proto.errReceived(b"\r\n")

    def loseConnection(self):
        if self.closed:
            return
        self.closed = 1
        self.proto.inConnectionLost()
        self.proto.outConnectionLost()
        self.proto.errConnectionLost()
        self.proto.processEnded(failure.Failure(ProcessTerminated(0, None, None)))


class SuperEchoTransport:
    def __init__(self, p):
        self.proto = p
        p.makeConnection(self)
        self.closed = 0

    def write(self, data):
        self.proto.outReceived(data)
        self.proto.outReceived(b"\r\n")
        self.proto.errReceived(data)
        self.proto.errReceived(b"\r\n")

    def loseConnection(self):
        if self.closed:
            return
        self.closed = 1
        self.proto.inConnectionLost()
        self.proto.outConnectionLost()
        self.proto.errConnectionLost()
        self.proto.processEnded(failure.Failure(ProcessTerminated(0, None, None)))


if cryptography is not None:
    from twisted.conch import checkers
    from twisted.conch.ssh import (
        channel,
        connection,
        factory,
        keys,
        transport,
        userauth,
    )

    class ConchTestPasswordChecker:
        credentialInterfaces = (checkers.IUsernamePassword,)

        def requestAvatarId(self, credentials):
            if (
                credentials.username == b"testuser"
                and credentials.password == b"testpass"
            ):
                return defer.succeed(credentials.username)
            return defer.fail(Exception("Bad credentials"))

    class ConchTestSSHChecker(checkers.SSHProtocolChecker):
        def areDone(self, avatarId):
            if avatarId != b"testuser" or len(self.successfulCredentials[avatarId]) < 2:
                return False
            return True

    class ConchTestServerFactory(factory.SSHFactory):
        noisy = False

        services = {
            b"ssh-userauth": userauth.SSHUserAuthServer,
            b"ssh-connection": connection.SSHConnection,
        }

        def buildProtocol(self, addr):
            proto = ConchTestServer()
            proto.supportedPublicKeys = list(
                chain.from_iterable(
                    key.supportedSignatureAlgorithms()
                    for key in self.privateKeys.values()
                )
            )
            proto.factory = self

            if hasattr(self, "expectedLoseConnection"):
                proto.expectedLoseConnection = self.expectedLoseConnection

            self.proto = proto
            return proto

        def getPublicKeys(self):
            return {
                b"ssh-rsa": keys.Key.fromString(publicRSA_openssh),
                b"ssh-dss": keys.Key.fromString(publicDSA_openssh),
            }

        def getPrivateKeys(self):
            return {
                b"ssh-rsa": keys.Key.fromString(privateRSA_openssh),
                b"ssh-dss": keys.Key.fromString(privateDSA_openssh),
            }

        def getPrimes(self) -> Dict[int, List[Tuple[int, int]]]:
            """
            Diffie-Hellman primes that can be used for the
            diffie-hellman-group-exchange-sha1 key exchange.

            @return: The primes and generators.
            @rtype: L{dict} mapping the key size to a C{list} of
                C{(generator, prime)} tupple.
            """
            # In these tests, we hardwire the prime values to those defined by
            # the diffie-hellman-group14-sha1 key exchange algorithm, to avoid
            # requiring a moduli file when running tests.
            # See OpenSSHFactory.getPrimes.
            return {2048: [_kex.getDHGeneratorAndPrime(b"diffie-hellman-group14-sha1")]}

        def getService(self, trans, name):
            return factory.SSHFactory.getService(self, trans, name)

    class ConchTestBase:
        done = 0

        def connectionLost(self, reason):
            if self.done:
                return
            if not hasattr(self, "expectedLoseConnection"):
                raise unittest.FailTest(
                    f"unexpectedly lost connection {self}\n{reason}"
                )
            self.done = 1

        def receiveError(self, reasonCode, desc):
            self.expectedLoseConnection = 1
            # Some versions of OpenSSH (for example, OpenSSH_5.3p1) will
            # send a DISCONNECT_BY_APPLICATION error before closing the
            # connection.  Other, older versions (for example,
            # OpenSSH_5.1p1), won't.  So accept this particular error here,
            # but no others.
            if reasonCode != transport.DISCONNECT_BY_APPLICATION:
                log.err(
                    Exception(
                        "got disconnect for %s: reason %s, desc: %s"
                        % (self, reasonCode, desc)
                    )
                )
            self.loseConnection()

        def receiveUnimplemented(self, seqID):
            raise unittest.FailTest(f"got unimplemented: seqid {seqID}")

    class ConchTestServer(ConchTestBase, transport.SSHServerTransport):
        def connectionLost(self, reason):
            ConchTestBase.connectionLost(self, reason)
            transport.SSHServerTransport.connectionLost(self, reason)

    class ConchTestClient(ConchTestBase, transport.SSHClientTransport):
        """
        @ivar _channelFactory: A callable which accepts an SSH connection and
            returns a channel which will be attached to a new channel on that
            connection.
        """

        def __init__(self, channelFactory):
            self._channelFactory = channelFactory

        def connectionLost(self, reason):
            ConchTestBase.connectionLost(self, reason)
            transport.SSHClientTransport.connectionLost(self, reason)

        def verifyHostKey(self, key, fp):
            keyMatch = key == keys.Key.fromString(publicRSA_openssh).blob()
            fingerprintMatch = fp == b"85:25:04:32:58:55:96:9f:57:ee:fb:a8:1a:ea:69:da"
            if keyMatch and fingerprintMatch:
                return defer.succeed(1)
            return defer.fail(Exception("Key or fingerprint mismatch"))

        def connectionSecure(self):
            self.requestService(
                ConchTestClientAuth(
                    b"testuser", ConchTestClientConnection(self._channelFactory)
                )
            )

    class ConchTestClientAuth(userauth.SSHUserAuthClient):
        hasTriedNone = 0  # have we tried the 'none' auth yet?
        canSucceedPublicKey = 0  # can we succeed with this yet?
        canSucceedPassword = 0

        def ssh_USERAUTH_SUCCESS(self, packet):
            if not self.canSucceedPassword and self.canSucceedPublicKey:
                raise unittest.FailTest(
                    "got USERAUTH_SUCCESS before password and publickey"
                )
            userauth.SSHUserAuthClient.ssh_USERAUTH_SUCCESS(self, packet)

        def getPassword(self):
            self.canSucceedPassword = 1
            return defer.succeed(b"testpass")

        def getPrivateKey(self):
            self.canSucceedPublicKey = 1
            return defer.succeed(keys.Key.fromString(privateDSA_openssh))

        def getPublicKey(self):
            return keys.Key.fromString(publicDSA_openssh)

    class ConchTestClientConnection(connection.SSHConnection):
        """
        @ivar _completed: A L{Deferred} which will be fired when the number of
            results collected reaches C{totalResults}.
        """

        name = b"ssh-connection"
        results = 0
        totalResults = 8

        def __init__(self, channelFactory):
            connection.SSHConnection.__init__(self)
            self._channelFactory = channelFactory

        def serviceStarted(self):
            self.openChannel(self._channelFactory(conn=self))

    class SSHTestChannel(channel.SSHChannel):
        def __init__(self, name, opened, *args, **kwargs):
            self.name = name
            self._opened = opened
            self.received = []
            self.receivedExt = []
            self.onClose = defer.Deferred()
            channel.SSHChannel.__init__(self, *args, **kwargs)

        def openFailed(self, reason):
            self._opened.errback(reason)

        def channelOpen(self, ignore):
            self._opened.callback(self)

        def dataReceived(self, data):
            self.received.append(data)

        def extReceived(self, dataType, data):
            if dataType == connection.EXTENDED_DATA_STDERR:
                self.receivedExt.append(data)
            else:
                log.msg(f"Unrecognized extended data: {dataType!r}")

        def request_exit_status(self, status):
            [self.status] = struct.unpack(">L", status)

        def eofReceived(self):
            self.eofCalled = True

        def closed(self):
            self.onClose.callback(None)

    def conchTestPublicKeyChecker():
        """
        Produces a SSHPublicKeyChecker with an in-memory key mapping with
        a single use: 'testuser'

        @return: L{twisted.conch.checkers.SSHPublicKeyChecker}
        """
        conchTestPublicKeyDB = checkers.InMemorySSHKeyDB(
            {b"testuser": [keys.Key.fromString(publicDSA_openssh)]}
        )
        return checkers.SSHPublicKeyChecker(conchTestPublicKeyDB)


class SSHProtocolTests(unittest.TestCase):
    """
    Tests for communication between L{SSHServerTransport} and
    L{SSHClientTransport}.
    """

    if not cryptography:
        skip = "can't run without cryptography"

    def _ourServerOurClientTest(self, name=b"session", **kwargs):
        """
        Create a connected SSH client and server protocol pair and return a
        L{Deferred} which fires with an L{SSHTestChannel} instance connected to
        a channel on that SSH connection.
        """
        result = defer.Deferred()
        self.realm = ConchTestRealm(b"testuser")
        p = portal.Portal(self.realm)
        sshpc = ConchTestSSHChecker()
        sshpc.registerChecker(ConchTestPasswordChecker())
        sshpc.registerChecker(conchTestPublicKeyChecker())
        p.registerChecker(sshpc)
        fac = ConchTestServerFactory()
        fac.portal = p
        fac.startFactory()
        self.server = fac.buildProtocol(None)
        self.clientTransport = LoopbackRelay(self.server)
        self.client = ConchTestClient(
            lambda conn: SSHTestChannel(name, result, conn=conn, **kwargs)
        )

        self.serverTransport = LoopbackRelay(self.client)

        self.server.makeConnection(self.serverTransport)
        self.client.makeConnection(self.clientTransport)
        return result

    def test_subsystemsAndGlobalRequests(self):
        """
        Run the Conch server against the Conch client.  Set up several different
        channels which exercise different behaviors and wait for them to
        complete.  Verify that the channels with errors log them.
        """
        channel = self._ourServerOurClientTest()

        def cbSubsystem(channel):
            self.channel = channel
            return self.assertFailure(
                channel.conn.sendRequest(
                    channel, b"subsystem", common.NS(b"not-crazy"), 1
                ),
                Exception,
            )

        channel.addCallback(cbSubsystem)

        def cbNotCrazyFailed(ignored):
            channel = self.channel
            return channel.conn.sendRequest(
                channel, b"subsystem", common.NS(b"crazy"), 1
            )

        channel.addCallback(cbNotCrazyFailed)

        def cbGlobalRequests(ignored):
            channel = self.channel
            d1 = channel.conn.sendGlobalRequest(b"foo", b"bar", 1)

            d2 = channel.conn.sendGlobalRequest(b"foo-2", b"bar2", 1)
            d2.addCallback(self.assertEqual, b"data")

            d3 = self.assertFailure(
                channel.conn.sendGlobalRequest(b"bar", b"foo", 1), Exception
            )

            return defer.gatherResults([d1, d2, d3])

        channel.addCallback(cbGlobalRequests)

        def disconnect(ignored):
            self.assertEqual(
                self.realm.avatar.globalRequests, {"foo": b"bar", "foo_2": b"bar2"}
            )
            channel = self.channel
            channel.conn.transport.expectedLoseConnection = True
            channel.conn.serviceStopped()
            channel.loseConnection()

        channel.addCallback(disconnect)

        return channel

    def test_shell(self):
        """
        L{SSHChannel.sendRequest} can open a shell with a I{pty-req} request,
        specifying a terminal type and window size.
        """
        channel = self._ourServerOurClientTest()

        data = session.packRequest_pty_req(b"conch-test-term", (24, 80, 0, 0), b"")

        def cbChannel(channel):
            self.channel = channel
            return channel.conn.sendRequest(channel, b"pty-req", data, 1)

        channel.addCallback(cbChannel)

        def cbPty(ignored):
            # The server-side object corresponding to our client side channel.
            session = self.realm.avatar.conn.channels[0].session
            self.assertIs(session.avatar, self.realm.avatar)
            self.assertEqual(session._terminalType, b"conch-test-term")
            self.assertEqual(session._windowSize, (24, 80, 0, 0))
            self.assertTrue(session.ptyReq)
            channel = self.channel
            return channel.conn.sendRequest(channel, b"shell", b"", 1)

        channel.addCallback(cbPty)

        def cbShell(ignored):
            self.channel.write(b"testing the shell!\x00")
            self.channel.conn.sendEOF(self.channel)
            return defer.gatherResults(
                [self.channel.onClose, self.realm.avatar._testSession.onClose]
            )

        channel.addCallback(cbShell)

        def cbExited(ignored):
            if self.channel.status != 0:
                log.msg("shell exit status was not 0: %i" % (self.channel.status,))
            self.assertEqual(
                b"".join(self.channel.received), b"testing the shell!\x00\r\n"
            )
            self.assertTrue(self.channel.eofCalled)
            self.assertTrue(self.realm.avatar._testSession.eof)

        channel.addCallback(cbExited)
        return channel

    def test_failedExec(self):
        """
        If L{SSHChannel.sendRequest} issues an exec which the server responds to
        with an error, the L{Deferred} it returns fires its errback.
        """
        channel = self._ourServerOurClientTest()

        def cbChannel(channel):
            self.channel = channel
            return self.assertFailure(
                channel.conn.sendRequest(channel, b"exec", common.NS(b"jumboliah"), 1),
                Exception,
            )

        channel.addCallback(cbChannel)

        def cbFailed(ignored):
            # The server logs this exception when it cannot perform the
            # requested exec.
            errors = self.flushLoggedErrors(error.ConchError)
            self.assertEqual(errors[0].value.args, ("bad exec", None))

        channel.addCallback(cbFailed)
        return channel

    def test_falseChannel(self):
        """
        When the process started by a L{SSHChannel.sendRequest} exec request
        exits, the exit status is reported to the channel.
        """
        channel = self._ourServerOurClientTest()

        def cbChannel(channel):
            self.channel = channel
            return channel.conn.sendRequest(channel, b"exec", common.NS(b"false"), 1)

        channel.addCallback(cbChannel)

        def cbExec(ignored):
            return self.channel.onClose

        channel.addCallback(cbExec)

        def cbClosed(ignored):
            # No data is expected
            self.assertEqual(self.channel.received, [])
            self.assertNotEqual(self.channel.status, 0)

        channel.addCallback(cbClosed)
        return channel

    def test_errorChannel(self):
        """
        Bytes sent over the extended channel for stderr data are delivered to
        the channel's C{extReceived} method.
        """
        channel = self._ourServerOurClientTest(localWindow=4, localMaxPacket=5)

        def cbChannel(channel):
            self.channel = channel
            return channel.conn.sendRequest(
                channel, b"exec", common.NS(b"eecho hello"), 1
            )

        channel.addCallback(cbChannel)

        def cbExec(ignored):
            return defer.gatherResults(
                [self.channel.onClose, self.realm.avatar._testSession.onClose]
            )

        channel.addCallback(cbExec)

        def cbClosed(ignored):
            self.assertEqual(self.channel.received, [])
            self.assertEqual(b"".join(self.channel.receivedExt), b"hello\r\n")
            self.assertEqual(self.channel.status, 0)
            self.assertTrue(self.channel.eofCalled)
            self.assertEqual(self.channel.localWindowLeft, 4)
            self.assertEqual(
                self.channel.localWindowLeft,
                self.realm.avatar._testSession.remoteWindowLeftAtClose,
            )

        channel.addCallback(cbClosed)
        return channel

    def test_unknownChannel(self):
        """
        When an attempt is made to open an unknown channel type, the L{Deferred}
        returned by L{SSHChannel.sendRequest} fires its errback.
        """
        d = self.assertFailure(
            self._ourServerOurClientTest(b"crazy-unknown-channel"), Exception
        )

        def cbFailed(ignored):
            errors = self.flushLoggedErrors(error.ConchError)
            self.assertEqual(errors[0].value.args, (3, "unknown channel"))
            self.assertEqual(len(errors), 1)

        d.addCallback(cbFailed)
        return d

    def test_maxPacket(self):
        """
        An L{SSHChannel} can be configured with a maximum packet size to
        receive.
        """
        # localWindow needs to be at least 11 otherwise the assertion about it
        # in cbClosed is invalid.
        channel = self._ourServerOurClientTest(localWindow=11, localMaxPacket=1)

        def cbChannel(channel):
            self.channel = channel
            return channel.conn.sendRequest(
                channel, b"exec", common.NS(b"secho hello"), 1
            )

        channel.addCallback(cbChannel)

        def cbExec(ignored):
            return self.channel.onClose

        channel.addCallback(cbExec)

        def cbClosed(ignored):
            self.assertEqual(self.channel.status, 0)
            self.assertEqual(b"".join(self.channel.received), b"hello\r\n")
            self.assertEqual(b"".join(self.channel.receivedExt), b"hello\r\n")
            self.assertEqual(self.channel.localWindowLeft, 11)
            self.assertTrue(self.channel.eofCalled)

        channel.addCallback(cbClosed)
        return channel

    def test_echo(self):
        """
        Normal standard out bytes are sent to the channel's C{dataReceived}
        method.
        """
        channel = self._ourServerOurClientTest(localWindow=4, localMaxPacket=5)

        def cbChannel(channel):
            self.channel = channel
            return channel.conn.sendRequest(
                channel, b"exec", common.NS(b"echo hello"), 1
            )

        channel.addCallback(cbChannel)

        def cbEcho(ignored):
            return defer.gatherResults(
                [self.channel.onClose, self.realm.avatar._testSession.onClose]
            )

        channel.addCallback(cbEcho)

        def cbClosed(ignored):
            self.assertEqual(self.channel.status, 0)
            self.assertEqual(b"".join(self.channel.received), b"hello\r\n")
            self.assertEqual(self.channel.localWindowLeft, 4)
            self.assertTrue(self.channel.eofCalled)
            self.assertEqual(
                self.channel.localWindowLeft,
                self.realm.avatar._testSession.remoteWindowLeftAtClose,
            )

        channel.addCallback(cbClosed)
        return channel


class SSHFactoryTests(unittest.TestCase):
    if not cryptography:
        skip = "can't run without cryptography"

    def makeSSHFactory(self, primes=None):
        sshFactory = factory.SSHFactory()
        sshFactory.getPrimes = lambda: primes
        sshFactory.getPublicKeys = lambda: {
            b"ssh-rsa": keys.Key.fromString(publicRSA_openssh)
        }
        sshFactory.getPrivateKeys = lambda: {
            b"ssh-rsa": keys.Key.fromString(privateRSA_openssh)
        }
        sshFactory.startFactory()
        return sshFactory

    def test_buildProtocol(self):
        """
        By default, buildProtocol() constructs an instance of
        SSHServerTransport.
        """
        factory = self.makeSSHFactory()
        protocol = factory.buildProtocol(None)
        self.assertIsInstance(protocol, transport.SSHServerTransport)

    def test_buildProtocolRespectsProtocol(self):
        """
        buildProtocol() calls 'self.protocol()' to construct a protocol
        instance.
        """
        calls = []

        def makeProtocol(*args):
            calls.append(args)
            return transport.SSHServerTransport()

        factory = self.makeSSHFactory()
        factory.protocol = makeProtocol
        factory.buildProtocol(None)
        self.assertEqual([()], calls)

    def test_buildProtocolSignatureAlgorithms(self):
        """
        buildProtocol() sets supportedPublicKeys to the list of supported
        signature algorithms.
        """
        f = factory.SSHFactory()
        f.getPublicKeys = lambda: {
            b"ssh-rsa": keys.Key.fromString(publicRSA_openssh),
            b"ssh-dss": keys.Key.fromString(publicDSA_openssh),
        }
        f.getPrivateKeys = lambda: {
            b"ssh-rsa": keys.Key.fromString(privateRSA_openssh),
            b"ssh-dss": keys.Key.fromString(privateDSA_openssh),
        }
        f.startFactory()
        p = f.buildProtocol(None)
        self.assertEqual(
            [b"rsa-sha2-512", b"rsa-sha2-256", b"ssh-rsa", b"ssh-dss"],
            p.supportedPublicKeys,
        )

    def test_buildProtocolNoPrimes(self):
        """
        Group key exchanges are not supported when we don't have the primes
        database.
        """
        f1 = self.makeSSHFactory(primes=None)

        p1 = f1.buildProtocol(None)

        self.assertNotIn(
            b"diffie-hellman-group-exchange-sha1", p1.supportedKeyExchanges
        )
        self.assertNotIn(
            b"diffie-hellman-group-exchange-sha256", p1.supportedKeyExchanges
        )

    def test_buildProtocolWithPrimes(self):
        """
        Group key exchanges are supported when we have the primes database.
        """
        f2 = self.makeSSHFactory(primes={1: (2, 3)})

        p2 = f2.buildProtocol(None)

        self.assertIn(b"diffie-hellman-group-exchange-sha1", p2.supportedKeyExchanges)
        self.assertIn(b"diffie-hellman-group-exchange-sha256", p2.supportedKeyExchanges)

    def test_buildProtocolKexECDSA(self):
        """
        ECDSA key exchanges are listed with 256 having a higher priority among ECDSA.
        """
        f2 = self.makeSSHFactory()

        p2 = f2.buildProtocol(None)

        # The list might contain other algorightm.
        # For this test just check the order for ECDSA KEX.
        self.assertIn(
            b"ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521",
            b",".join(p2.supportedKeyExchanges),
        )


class MPTests(unittest.TestCase):
    """
    Tests for L{common.getMP}.

    @cvar getMP: a method providing a MP parser.
    @type getMP: C{callable}
    """

    if not cryptography:
        skip = "can't run without cryptography"

    if cryptography:
        getMP = staticmethod(common.getMP)

    def test_getMP(self):
        """
        L{common.getMP} should parse the a multiple precision integer from a
        string: a 4-byte length followed by length bytes of the integer.
        """
        self.assertEqual(self.getMP(b"\x00\x00\x00\x04\x00\x00\x00\x01"), (1, b""))

    def test_getMPBigInteger(self):
        """
        L{common.getMP} should be able to parse a big enough integer
        (that doesn't fit on one byte).
        """
        self.assertEqual(
            self.getMP(b"\x00\x00\x00\x04\x01\x02\x03\x04"), (16909060, b"")
        )

    def test_multipleGetMP(self):
        """
        L{common.getMP} has the ability to parse multiple integer in the same
        string.
        """
        self.assertEqual(
            self.getMP(
                b"\x00\x00\x00\x04\x00\x00\x00\x01" b"\x00\x00\x00\x04\x00\x00\x00\x02",
                2,
            ),
            (1, 2, b""),
        )

    def test_getMPRemainingData(self):
        """
        When more data than needed is sent to L{common.getMP}, it should return
        the remaining data.
        """
        self.assertEqual(
            self.getMP(b"\x00\x00\x00\x04\x00\x00\x00\x01foo"), (1, b"foo")
        )

    def test_notEnoughData(self):
        """
        When the string passed to L{common.getMP} doesn't even make 5 bytes,
        it should raise a L{struct.error}.
        """
        self.assertRaises(struct.error, self.getMP, b"\x02\x00")


class GMPYInstallDeprecationTests(unittest.TestCase):
    """
    Tests for the deprecation of former GMPY accidental public API.
    """

    if not cryptography:
        skip = "cannot run without cryptography"

    def test_deprecated(self):
        """
        L{twisted.conch.ssh.common.install} is deprecated.
        """
        common.install()
        warnings = self.flushWarnings([self.test_deprecated])
        self.assertEqual(len(warnings), 1)
        self.assertEqual(
            warnings[0]["message"],
            "twisted.conch.ssh.common.install was deprecated in Twisted 16.5.0",
        )
¿Qué es la limpieza dental de perros? - Clínica veterinaria


Es la eliminación del sarro y la placa adherida a la superficie de los dientes mediante un equipo de ultrasonidos que garantiza la integridad de las piezas dentales a la vez que elimina en profundidad cualquier resto de suciedad.

A continuación se procede al pulido de los dientes mediante una fresa especial que elimina la placa bacteriana y devuelve a los dientes el aspecto sano que deben tener.

Una vez terminado todo el proceso, se mantiene al perro en observación hasta que se despierta de la anestesia, bajo la atenta supervisión de un veterinario.

¿Cada cuánto tiempo tengo que hacerle una limpieza dental a mi perro?

A partir de cierta edad, los perros pueden necesitar una limpieza dental anual o bianual. Depende de cada caso. En líneas generales, puede decirse que los perros de razas pequeñas suelen acumular más sarro y suelen necesitar una atención mayor en cuanto a higiene dental.


Riesgos de una mala higiene


Los riesgos más evidentes de una mala higiene dental en los perros son los siguientes:

  • Cuando la acumulación de sarro no se trata, se puede producir una inflamación y retracción de las encías que puede descalzar el diente y provocar caídas.
  • Mal aliento (halitosis).
  • Sarro perros
  • Puede ir a más
  • Las bacterias de la placa pueden trasladarse a través del torrente circulatorio a órganos vitales como el corazón ocasionando problemas de endocarditis en las válvulas. Las bacterias pueden incluso acantonarse en huesos (La osteomielitis es la infección ósea, tanto cortical como medular) provocando mucho dolor y una artritis séptica).

¿Cómo se forma el sarro?

El sarro es la calcificación de la placa dental. Los restos de alimentos, junto con las bacterias presentes en la boca, van a formar la placa bacteriana o placa dental. Si la placa no se retira, al mezclarse con la saliva y los minerales presentes en ella, reaccionará formando una costra. La placa se calcifica y se forma el sarro.

El sarro, cuando se forma, es de color blanquecino pero a medida que pasa el tiempo se va poniendo amarillo y luego marrón.

Síntomas de una pobre higiene dental
La señal más obvia de una mala salud dental canina es el mal aliento.

Sin embargo, a veces no es tan fácil de detectar
Y hay perros que no se dejan abrir la boca por su dueño. Por ejemplo…

Recientemente nos trajeron a la clínica a un perro que parpadeaba de un ojo y decía su dueño que le picaba un lado de la cara. Tenía molestias y dificultad para comer, lo que había llevado a sus dueños a comprarle comida blanda (que suele ser un poco más cara y llevar más contenido en grasa) durante medio año. Después de una exploración oftalmológica, nos dimos cuenta de que el ojo tenía una úlcera en la córnea probablemente de rascarse . Además, el canto lateral del ojo estaba inflamado. Tenía lo que en humanos llamamos flemón pero como era un perro de pelo largo, no se le notaba a simple vista. Al abrirle la boca nos llamó la atención el ver una muela llena de sarro. Le realizamos una radiografía y encontramos una fístula que llegaba hasta la parte inferior del ojo.

Le tuvimos que extraer la muela. Tras esto, el ojo se curó completamente con unos colirios y una lentilla protectora de úlcera. Afortunadamente, la úlcera no profundizó y no perforó el ojo. Ahora el perro come perfectamente a pesar de haber perdido una muela.

¿Cómo mantener la higiene dental de tu perro?
Hay varias maneras de prevenir problemas derivados de la salud dental de tu perro.

Limpiezas de dientes en casa
Es recomendable limpiar los dientes de tu perro semanal o diariamente si se puede. Existe una gran variedad de productos que se pueden utilizar:

Pastas de dientes.
Cepillos de dientes o dedales para el dedo índice, que hacen más fácil la limpieza.
Colutorios para echar en agua de bebida o directamente sobre el diente en líquido o en spray.

En la Clínica Tus Veterinarios enseñamos a nuestros clientes a tomar el hábito de limpiar los dientes de sus perros desde que son cachorros. Esto responde a nuestro compromiso con la prevención de enfermedades caninas.

Hoy en día tenemos muchos clientes que limpian los dientes todos los días a su mascota, y como resultado, se ahorran el dinero de hacer limpiezas dentales profesionales y consiguen una mejor salud de su perro.


Limpiezas dentales profesionales de perros y gatos

Recomendamos hacer una limpieza dental especializada anualmente. La realizamos con un aparato de ultrasonidos que utiliza agua para quitar el sarro. Después, procedemos a pulir los dientes con un cepillo de alta velocidad y una pasta especial. Hacemos esto para proteger el esmalte.

La frecuencia de limpiezas dentales necesaria varía mucho entre razas. En general, las razas grandes tienen buena calidad de esmalte, por lo que no necesitan hacerlo tan a menudo e incluso pueden pasarse la vida sin requerir una limpieza. Sin embargo, razas pequeñas como el Yorkshire o el Maltés, deben hacérselas todos los años desde cachorros si se quiere conservar sus piezas dentales.

Otro factor fundamental es la calidad del pienso. Algunas marcas han diseñado croquetas que limpian la superficie del diente y de la muela al masticarse.

Ultrasonido para perros

¿Se necesita anestesia para las limpiezas dentales de perros y gatos?

La limpieza dental en perros no es una técnica que pueda practicarse sin anestesia general , aunque hay veces que los propietarios no quieren anestesiar y si tiene poco sarro y el perro es muy bueno se puede intentar…… , pero no se va a poder pulir ni acceder a todas la zona de la boca …. Además los limpiadores dentales van a irrigar agua y hay riesgo de aspiración a vías respiratorias si no se realiza una anestesia correcta con intubación traqueal . En resumen , sin anestesia no se va hacer una correcta limpieza dental.

Tampoco sirve la sedación ya que necesitamos que el animal esté totalmente quieto, y el veterinario tenga un acceso completo a todas sus piezas dentales y encías.

Alimentos para la limpieza dental

Hay que tener cierto cuidado a la hora de comprar determinados alimentos porque no todos son saludables. Algunos tienen demasiado contenido graso, que en exceso puede causar problemas cardiovasculares y obesidad.

Los mejores alimentos para los dientes son aquellos que están elaborados por empresas farmacéuticas y llevan componentes químicos con tratamientos específicos para el diente del perro. Esto implica no solo limpieza a través de la acción mecánica de morder sino también un tratamiento antibacteriano para prevenir el sarro.

Conclusión

Si eres como la mayoría de dueños, por falta de tiempo , es probable que no estés prestando la suficiente atención a la limpieza dental de tu perro. Por eso te animamos a que comiences a limpiar los dientes de tu perro y consideres atender a su higiene bucal con frecuencia.

Estas simples medidas pueden conllevar a que tu perro tenga una vida más larga y mucho más saludable.

Si te resulta imposible introducir un cepillo de dientes a tu perro en la boca, pásate con él por clínica Tus Veterinarios y te explicamos cómo hacerlo.

Necesitas hacer una limpieza dental profesional a tu mascota?
Llámanos al 622575274 o contacta con nosotros

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

¡Hola!