Skip to content

Commit 4016152

Browse files
authored
Add smbclient for an easy high level API to manage SMB shares (jborean93#17)
* Added higher level client API * Fixes after recent pull rebase * remove utils mention now we use conftest * Add default timeout to connection * try and reduce deadlocks in the msg queue processor * Fix deadlock issue and ensure tests are passing locally * Try and smooth out transport error handling * Fix up transport test after change * Dropped support for Python 3.4 * Added the ability to register and close a session in the pool * Fix up test and typos * More typos and minor nit changes * Set Impersonation as the default Impersonation level * Fix up listdir and read for pipes * Add various shutil methods (jborean93#16) * Add various shutil methods * reworked rmtree, review comments addressed * renamed copy2 -> copy and copytree -> copytree_copy, copying stats removed * service side copy moved to _os * fix pep8 indentation * review comments in shutil addressed * Fix up exceptions that can have unicode chars * Use open_file for copyfile * Move copyfile to top of file and add more tests * Use set size for SRV_REQUEST_RESUMT_KEY response output * Expand shutil and added more tests * Attempt to get Appveyor working again * Hopefully the last Appveyor fixes to go in * Should be last fix for appveyor * Last Appveyor fixes * Simplify stat and add ads tests * Remove ordereddict requirement
1 parent 8a4f082 commit 4016152

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+10567
-1361
lines changed

.travis.yml

+7-11
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,12 @@ language: python
44

55
dist: bionic
66

7-
matrix:
8-
include:
9-
- python: 2.7
10-
- python: 3.4
11-
dist: trusty
12-
- python: 3.5
13-
dist: trusty
14-
- python: 3.6
15-
- python: 3.7
16-
- python: 3.8
7+
python:
8+
- 2.7
9+
- 3.5
10+
- 3.6
11+
- 3.7
12+
- 3.8
1713

1814
services:
1915
- docker
@@ -36,7 +32,7 @@ install:
3632
- python ./build-scripts/check_samba.py
3733

3834
script:
39-
- py.test -v --pep8 --cov smbprotocol --cov-report term-missing
35+
- py.test -v --pep8 --cov smbclient --cov smbprotocol --cov-report term-missing
4036

4137
after_success:
4238
- coveralls

CHANGELOG.md

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
# Changelog
22

3-
## 0.3.0 - TBD
3+
## 1.0.0 - TBD
44

5-
* Drop support for Python 2.6
5+
* Dropped support for Python 2.6 and Python 3.4
6+
* Added the `smbclient` package that provides a higher level API for interactive with SMB servers
7+
* Deprecated `smbprotocol.query_info` in favour of `smbprotocol.file_info`, `query_info` will be removed in the next major release
8+
* Add automatic symlink resolver when a symlink is in the path being opened
69
* Fix issue when trying to connect to host with IPv6 address
710
* Fix response parsing for SMB2 Create Response Lease V1 and V2
811
* Added the ability to set the Oplock level when opening a file
9-
* Revamped the socket listener and message processor to run in a separate thread for better concurrency
12+
* Revamped the socket listener and message processor to run in a separate thread for faster message resolving
1013
* Added the `FileSystemWatcher` in `change_notify.py` to provider a way to watch for changes on the SMB filesystem
1114
* Added the `.cancel()` method onto a Request to cancel an SMB request on the server
1215

README.md

+53-7
Original file line numberDiff line numberDiff line change
@@ -120,13 +120,60 @@ you to set a minimum dialect version if required.
120120

121121
## Examples
122122

123-
Currently the existing classes expose a very low level interface to the SMB
124-
protocol which can make things quite complex for people starting to use this
125-
package. I do plan on making a high-level interface to make things easier for
126-
users but that's in the backlog.
123+
There are 2 different APIs you can use with this library.0
127124

128-
For now, the `examples` folder contains some examples of how this package can
129-
be used.
125+
* `smbprotocol`: Low level interface that can do whatever you want but quite verbose
126+
* `smbclient`: Higher level interface that implements the builtin `os` and `os.path` file system functions but for SMB support
127+
128+
The `examples` folder contains some examples of both the high and low level
129+
interface but for everyday user's it is recommended to use `smbclient` as it
130+
is a lot simpler.
131+
132+
### smbclient Interface
133+
134+
The higher level interface `smbclient` is designed to make this library easier
135+
for people to use for simple and common use cases. It is designed to replicate
136+
the builtin `os` and `os.path` filesystem functions like `os.open()`,
137+
`os.stat()`, and `os.path.exists()`.
138+
139+
A connection made by `smbclient` is kept in a pool and re-used for future
140+
requests to the same server until the Python process exists. This makes
141+
authentication simple and only required for the first call to the server.
142+
143+
You can specify the credentials and other connection parameters on each
144+
`smbclient` function or register a server with credentials with the following
145+
kwargs:
146+
147+
* `username`: The username used to connect to the share
148+
* `password`: The password used to connect to the share
149+
* `port`: Override the default port (`445`) to connect to
150+
* `encrypt`: Whether to force encryption on the connection, requires SMBv3 or newer on the remote server (default: `False`)
151+
* `connection_timeout`: Override the connection timeout in seconds (default: `60`)
152+
153+
If using Kerberos authentication and a Kerberos ticket has already set by
154+
`kinit` then `smbclient` will automatically use those credentials without
155+
having to be explicitly set. If no ticket has been retrieved or you wish to use
156+
different credentials then only the first request for the server in question
157+
requires the `username` and `password` kwargs.
158+
159+
For example I only need to set the credentials on the first request to create
160+
the directory and not for the subsequent file creation in that dir.
161+
162+
```
163+
import smbclient
164+
165+
# Optional - register the credentials with a server
166+
smbclient.register_session("server", username="user", password="pass")
167+
168+
smbclient.mkdir(r"\\server\share\directory", username="user", password="pass")
169+
170+
with smbclient.open_file(r"\\server\share\directory\file.txt", mode="w") as fd:
171+
fd.write(u"file contents")
172+
```
173+
174+
If you wish to reset the cache you can either start a new Python process or
175+
call `smbclient.reset_connection_cache()` to close all the connections that
176+
have been cached by the client.
130177

131178

132179
## Logging
@@ -209,5 +256,4 @@ if you want to implement them yourself;
209256

210257
* Test and support DFS mounts and not just server shares
211258
* Multiple channel support to speed up large data transfers
212-
* Create an easier API on top of the `raw` SMB calls that currently exist
213259
* Lots and lots more...

appveyor.yml

+1-3
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ environment:
1414
# https://www.appveyor.com/docs/installed-software/#python
1515
- PYTHON: Python27
1616
- PYTHON: Python27-x64
17-
- PYTHON: Python34
18-
- PYTHON: Python34-x64
1917
- PYTHON: Python35
2018
- PYTHON: Python35-x64
2119
- PYTHON: Python36
@@ -56,4 +54,4 @@ install:
5654
build: off # Do not run MSBuild, build stuff at install step
5755

5856
test_script:
59-
- cmd: py.test -v --pep8 --cov smbprotocol --cov-report term-missing
57+
- cmd: py.test -v --pep8 --cov smbclient --cov smbprotocol --cov-report term-missing

build-scripts/Vagrantfile

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# -*- mode: ruby -*-
2+
# vi: set ft=ruby :
3+
4+
Vagrant.configure("2") do |config|
5+
config.vm.box = "centos/7"
6+
config.vm.network "forwarded_port", guest: 445, host: 445
7+
config.vm.provider "virtualbox" do |vb|
8+
vb.gui = false
9+
vb.memory = "1024"
10+
end
11+
12+
config.vm.provision "ansible" do |a|
13+
a.playbook = "main.yml"
14+
end
15+
end

build-scripts/main.yml

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
---
2+
- hosts: all
3+
gather_facts: no
4+
become: yes
5+
tasks:
6+
- name: install samba
7+
package:
8+
name: samba
9+
state: present
10+
11+
- name: template out smb.conf file
12+
copy:
13+
content: |
14+
[global]
15+
workgroup = WORKGROUP
16+
valid users = @smbgroup
17+
server signing = mandatory
18+
ea support = yes
19+
store dos attributes = yes
20+
vfs objects = xattr_tdb streams_xattr
21+
22+
[share]
23+
comment = Test Samba Share
24+
path = /srv/samba/share
25+
browsable = yes
26+
guest ok = no
27+
read only = no
28+
create mask = 0755
29+
30+
[share-encrypted]
31+
command = Test Encrypted Samba Share
32+
path = /srv/samba/share-encrypted
33+
browsable = yes
34+
guest ok = no
35+
read only = no
36+
create mask = 0755
37+
smb encrypt = required
38+
dest: /etc/samba/smb.conf
39+
40+
- name: create smbgroup
41+
group:
42+
name: smbgroup
43+
state: present
44+
45+
- name: create smbuser and add to smbgroup
46+
user:
47+
name: smbuser
48+
password: "{{ 'smbpass' | password_hash('sha512', 'mysecretsalt') }}"
49+
state: present
50+
group: smbgroup
51+
52+
- name: set smbpassword
53+
shell: (echo smbpass; echo smbpass) | smbpasswd -s -a smbuser
54+
55+
- name: create share directories
56+
file:
57+
path: /srv/samba/{{ item }}
58+
state: directory
59+
group: smbgroup
60+
mode: '755'
61+
owner: smbuser
62+
loop:
63+
- share
64+
- share-encrypted
65+
66+
- name: set selinux permissions for samba dir
67+
command: chcon -R -t samba_share_t /srv/samba/
68+
69+
- name: create and start the samba services
70+
systemd:
71+
name: smb
72+
state: started
73+
enabled: yes

build-scripts/setup_samba.sh

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ cat > /etc/samba/smb.conf << EOL
77
workgroup = WORKGROUP
88
valid users = @smbgroup
99
server signing = mandatory
10+
ea support = yes
11+
store dos attributes = yes
12+
vfs objects = xattr_tdb streams_xattr
1013
1114
[$SMB_SHARE]
1215
comment = Test Samba Share
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from smbclient import (
2+
listdir,
3+
mkdir,
4+
register_session,
5+
rmdir,
6+
scandir,
7+
)
8+
9+
# Optional - register the server with explicit credentials
10+
register_session("server", username="admin", password="pass")
11+
12+
# Create a directory (only the first request needs credentials)
13+
mkdir(r"\\server\share\directory", username="user", password="pass")
14+
15+
# Remove a directory
16+
rmdir(r"\\server\share\directory")
17+
18+
# List the files/directories inside a dir
19+
for filename in listdir(r"\\server\share\directory"):
20+
print(filename)
21+
22+
# Use scandir as a more efficient directory listing as it already contains info like stat and attributes.
23+
for file_info in scandir(r"\\server\share\directory"):
24+
file_inode = file_info.inode()
25+
if file_info.is_file():
26+
print("File: %s %d" % (file_info.name, file_inode))
27+
elif file_info.is_dir():
28+
print("Dir: %s %d" % (file_info.name, file_inode))
29+
else:
30+
print("Symlink: %s %d" % (file_info.name, file_inode))
+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from smbclient import (
2+
link,
3+
open_file,
4+
remove,
5+
register_session,
6+
stat,
7+
symlink,
8+
)
9+
10+
# Optional - register the server with explicit credentials
11+
register_session("server", username="admin", password="pass")
12+
13+
# Read an existing file as text (credentials only needed for the first request to the server if not registered.)
14+
with open_file(r"\\server\share\file.txt", username="admin", password="pass") as fd:
15+
file_contents = fd.read()
16+
17+
# Read an existing file as bytes
18+
with open_file(r"\\server\share\file.txt", mode="rb") as fd:
19+
file_bytes = fd.read()
20+
21+
# Create a file and write to it
22+
with open_file(r"\\server\share\file.txt", mode="w") as fd:
23+
fd.write(u"content")
24+
25+
# Write data to the end of an existing file
26+
with open_file(r"\\server\share\file.txt", mode="a") as fd:
27+
fd.write(u"\ndata at the end")
28+
29+
# Delete a file
30+
remove(r"\\server\share\file.txt")
31+
32+
# Get info about a file
33+
stat(r"\\server\share\file.txt")
34+
35+
# Create a symbolic link
36+
symlink(r"\\server\share\directory", r"\\server\share\link")
37+
38+
# Create a hard link
39+
link(r"\\server\share\file.txt", r"\\server\share\hard-link.txt")
File renamed without changes.
File renamed without changes.

setup.cfg

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ universal = 1
44
[tool:pytest]
55
pep8maxlinelength = 119
66
pep8ignore = setup.py E501
7+
markers =
8+
pep8: Satisfy pytest warning about custom markers
79

810
[metadata]
9-
license_file = LICENSE
11+
license_file = LICENSE

setup.py

+9-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#!/usr/bin/env python
2-
# coding: utf-8
2+
# -*- coding: utf-8 -*-
3+
# Copyright: (c) 2019, Jordan Borean (@jborean93) <[email protected]>
4+
# MIT License (see LICENSE or https://opensource.org/licenses/MIT)
35

46
from setuptools import setup
57

@@ -14,26 +16,23 @@
1416

1517
setup(
1618
name='smbprotocol',
17-
version='0.3.0',
18-
packages=['smbprotocol'],
19+
version='1.0.0',
20+
packages=['smbclient', 'smbprotocol'],
1921
install_requires=[
2022
'cryptography>=2.0',
2123
'ntlm-auth>=1.2.0',
2224
'pyasn1',
2325
'six',
2426
],
2527
extras_require={
26-
':python_version<"2.7"': [
27-
'ordereddict'
28-
],
2928
'kerberos:sys_platform=="win32"': [
30-
'pywin32'
29+
'pywin32',
3130
],
3231
'kerberos:sys_platform!="win32"': [
33-
'gssapi>=1.4.1'
34-
]
32+
'gssapi>=1.4.1',
33+
],
3534
},
36-
python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*',
35+
python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*',
3736
author='Jordan Borean',
3837
author_email='[email protected]',
3938
url='https://github.com/jborean93/smbprotocol',
@@ -47,7 +46,6 @@
4746
'Programming Language :: Python :: 2',
4847
'Programming Language :: Python :: 2.7',
4948
'Programming Language :: Python :: 3',
50-
'Programming Language :: Python :: 3.4',
5149
'Programming Language :: Python :: 3.5',
5250
'Programming Language :: Python :: 3.6',
5351
'Programming Language :: Python :: 3.7',

0 commit comments

Comments
 (0)