Skip to content

Commit 18ab721

Browse files
author
Pan
committed
Updated tests. Updated readme. Added helper functions module.
1 parent de7ebba commit 18ab721

File tree

5 files changed

+159
-12
lines changed

5 files changed

+159
-12
lines changed

README.rst

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,82 @@
1-
Wrapper for libssh C library.
1+
ssh-python
2+
============
3+
4+
Wrapper for libssh_ C library.
5+
6+
7+
Installation
8+
_____________
9+
10+
Currently only installation from source is provided. Binary wheels to follow.
11+
12+
To install from source, run the following after cloning the repository
13+
14+
.. code-block:: shell
15+
16+
python setup.py install
17+
18+
19+
Prerequisites
20+
--------------
21+
22+
* OpenSSL library and development headers
23+
* Optionally Zlib library and development headers for compression
24+
25+
``Libssh`` source code is embedded in this project and will be built when installation is triggered per above instructions. Versions of ``libssh`` other than the one embedded in this project are not supported.
26+
27+
28+
Quick Start
29+
_____________
30+
31+
32+
.. code-block:: python
33+
34+
from __future__ import print_function
35+
36+
import os
37+
import pwd
38+
39+
from ssh.session import Session
40+
from ssh import options
41+
42+
USERNAME = pwd.getpwuid(os.geteuid()).pw_name
43+
HOST = 'localhost'
44+
45+
s = Session()
46+
s.options_set(options.HOST, HOST)
47+
s.connect()
48+
49+
# Authenticate with agent
50+
s.userauth_agent(USERNAME)
51+
52+
chan = s.channel_new()
53+
chan.open_session()
54+
chan.request_exec('echo me')
55+
size, data = chan.read()
56+
while size > 0:
57+
print(data.strip())
58+
size, data = chan.read()
59+
chan.close()
60+
61+
Output:
62+
63+
.. code-block:: shell
64+
65+
me
66+
67+
68+
Features
69+
_________
70+
71+
The library uses `Cython`_ based native code extensions as wrappers to ``libssh``.
72+
73+
* Thread safe - GIL is released as much as possible
74+
* Very low overhead
75+
* Super fast as a consequence of the excellent C library it uses and prodigious use of native code
76+
* Object oriented - memory freed automatically and safely as objects are garbage collected by Python
77+
* Use Python semantics where applicable, such as context manager and iterator support for opening and reading from channels and SFTP file handles
78+
* Raise errors as Python exceptions
79+
80+
81+
.. _libssh: https://www.libssh.org
82+
.. _Cython: https://www.cython.org

ssh/helper.pyx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# This file is part of ssh-python.
2+
# Copyright (C) 2017-2018 Panos Kittenis
3+
#
4+
# This library is free software; you can redistribute it and/or
5+
# modify it under the terms of the GNU Lesser General Public
6+
# License as published by the Free Software Foundation, version 2.1.
7+
#
8+
# This library is distributed in the hope that it will be useful,
9+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+
# Lesser General Public License for more details.
12+
#
13+
# You should have received a copy of the GNU Lesser General Public
14+
# License along with this library; if not, write to the Free Software
15+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16+
17+
from select import select
18+
19+
from session cimport Session
20+
21+
from c_ssh cimport SSH_READ_PENDING, SSH_WRITE_PENDING, ssh_get_poll_flags
22+
23+
24+
def wait_socket(_socket not None, Session session, timeout=1):
25+
"""Helper function for testing non-blocking mode.
26+
27+
This function blocks the calling thread for <timeout> seconds -
28+
to be used only for testing purposes.
29+
"""
30+
cdef int directions = ssh_get_poll_flags(session._session)
31+
if directions == 0:
32+
return 0
33+
readfds = (_socket,) \
34+
if (directions & SSH_READ_PENDING) else ()
35+
writefds = (_socket,) \
36+
if (directions & SSH_WRITE_PENDING) else ()
37+
return select(readfds, writefds, (), timeout)

ssh/session.pyx

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ cimport c_ssh
3232
from c_sftp cimport sftp_session, sftp_new, sftp_init
3333

3434

35+
# SSH status flags
36+
SSH_CLOSED = c_ssh.SSH_CLOSED
37+
SSH_READ_PENDING = c_ssh.SSH_READ_PENDING
38+
SSH_CLOSED_ERROR = c_ssh.SSH_CLOSED_ERROR
39+
SSH_WRITE_PENDING = c_ssh.SSH_WRITE_PENDING
40+
41+
3542
cdef bint _check_connected(c_ssh.ssh_session session) nogil except -1:
3643
if not c_ssh.ssh_is_connected(session):
3744
with gil:
@@ -51,9 +58,9 @@ cdef class Session:
5158
def __dealloc__(self):
5259
if self._session is not NULL:
5360
c_ssh.ssh_free(self._session)
54-
self._session = NULL
61+
self._session = NULL
5562

56-
def set_socket(self, socket):
63+
def set_socket(self, socket not None):
5764
"""Set socket to use for session.
5865
5966
Not part of libssh API but needs to be done in C to be able to
@@ -122,6 +129,8 @@ cdef class Session:
122129
return handle_ssh_error_codes(rc, self._session)
123130

124131
def disconnect(self):
132+
if self._session is NULL:
133+
return
125134
with nogil:
126135
c_ssh.ssh_disconnect(self._session)
127136

@@ -327,16 +336,16 @@ cdef class Session:
327336
rc = c_ssh.ssh_set_agent_channel(self._session, channel._channel)
328337
return handle_ssh_error_codes(rc, self._session)
329338

330-
def set_agent_socket(self, c_ssh.socket_t fd):
339+
def set_agent_socket(self, socket not None):
331340
cdef int rc
341+
cdef c_ssh.socket_t _sock = PyObject_AsFileDescriptor(socket)
332342
with nogil:
333-
rc = c_ssh.ssh_set_agent_socket(self._session, fd)
343+
rc = c_ssh.ssh_set_agent_socket(self._session, _sock)
334344
return handle_ssh_error_codes(rc, self._session)
335345

336346
def set_blocking(self, int blocking):
337347
with nogil:
338348
c_ssh.ssh_set_blocking(self._session, blocking)
339-
return
340349

341350
def set_counters(self, scounter, rcounter):
342351
raise NotImplementedError
@@ -456,7 +465,7 @@ cdef class Session:
456465
rc = c_ssh.ssh_userauth_kbdint_getnprompts(self._session)
457466
return rc
458467

459-
def userauth_kbdint_getprompt(self, unsigned int i, bytes echo):
468+
def userauth_kbdint_getprompt(self, unsigned int i, bytes echo not None):
460469
cdef const_char *_prompt
461470
cdef bytes b_prompt
462471
cdef char *c_echo = echo
@@ -484,7 +493,7 @@ cdef class Session:
484493
b_answer = _answer
485494
return b_answer
486495

487-
def userauth_kbdint_setanswer(self, unsigned int i, bytes answer):
496+
def userauth_kbdint_setanswer(self, unsigned int i, bytes answer not None):
488497
cdef char *c_answer = answer
489498
cdef int rc
490499
with nogil:
@@ -601,3 +610,11 @@ cdef class Session:
601610
with nogil:
602611
rc = c_ssh.ssh_get_error_code(self._session)
603612
return rc
613+
614+
def scp_new(self, int mode, location not None):
615+
cdef c_ssh.ssh_scp _scp
616+
cdef bytes b_location = to_bytes(location)
617+
cdef char *c_location = b_location
618+
raise NotImplementedError
619+
with nogil:
620+
pass

tests/test_session.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,17 @@ def test_should_not_segfault(self):
5151
self.assertIsNotNone(session.get_error_code())
5252
session.connector_new()
5353

54+
def test_disconnect(self):
55+
session = Session()
56+
session.options_set(options.HOST, self.host)
57+
session.options_set_port(self.port)
58+
session.options_set(options.USER, self.user)
59+
self.assertEqual(session.connect(), 0)
60+
self.assertEqual(
61+
session.userauth_publickey(self.pkey), 0)
62+
session.disconnect()
63+
del session
64+
5465
def test_socket_connect(self):
5566
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
5667
sock.connect((self.host, self.port))

tests/test_sftp.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
from ssh.sftp_attributes import SFTPAttributes
3232
from ssh.exceptions import InvalidAPIUse, SFTPHandleError, SFTPError
3333
from ssh.error_codes import SSH_AGAIN
34+
from ssh.helper import wait_socket
35+
3436

3537
from .base_test import SSHTestCase
3638

@@ -387,21 +389,20 @@ def test_handle_open_nonblocking(self):
387389
# 'remote_test_file'])
388390
# with open(remote_filename, 'wb') as test_fh:
389391
# test_fh.write(test_file_data)
390-
# self.session.set_blocking(False)
392+
# # self.session.set_blocking(False)
391393
# try:
392394
# with sftp.open(remote_filename, os.O_RDONLY, 0) as remote_fh:
393395
# self.assertIsInstance(remote_fh, SFTPFile)
394396
# remote_fh.set_nonblocking()
395397
# remote_data = b""
396398
# async_id = remote_fh.async_read_begin()
397399
# self.assertNotEqual(async_id, 0)
398-
# # import ipdb; ipdb.set_trace()
400+
# import ipdb; ipdb.set_trace()
399401
# size, data = remote_fh.async_read(async_id)
400402
# while size > 0 or size == SSH_AGAIN:
401403
# if size == SSH_AGAIN:
402404
# print("Would try again")
403-
# select((sock,), (sock,), ())
404-
# time.sleep(1)
405+
# wait_socket(sock, self.session, timeout=1)
405406
# size, data = remote_fh.async_read(async_id)
406407
# continue
407408
# remote_data += data

0 commit comments

Comments
 (0)