pysmbについて以前書いた記事がだいぶざっくり目になっていたことと、いくつか不要な設定が混じっていたりしたので、改めて初心者向けに網羅的な内容で書き直しました。
pysmbとは
Pythonでファイル共有を利用できるモジュールで、SMB/CIFSクライアントとして動作します。
公式サイト https://miketeo.net/wp/index.php/projects/pysmb
GitHubリポジトリ https://github.com/miketeo/pysmb
公式ドキュメント http://pysmb.readthedocs.io/en/latest/index.html
インデックス
pysmbの使い方(接続・切断)
pysmbの使い方(ファイル受信)
pysmbの使い方(ファイル送信)
pysmbの使い方(ロギング)
pysmbの使い方(匿名接続)
実行環境
クライアント
- Windows 10
- Python 3.6.3
- pysmb 1.1.22
サーバー
- Windows 7
- User : IEUser
- Pass : Passw0rd!
- HostName : IEWIN7
- Domain : WORKGROUP
- IPAddress : 172.28.0.198
ちなみにサーバーはMicrosoftから無償提供されている仮想マシンを使いました。
インストール
安定バージョンはpipからインストールできます。
pip install pysmb
GitHubからインストール
GitHubからインストールすることもできます。
pip install git+https://github.com/miketeo/pysmb
pysmbバージョン1.1.22の場合、listPath()メソッドを呼んだ時UnicodeDecodeErrorになる不具合(#104 Decode of short_name in decodeQueryStruct sometimes fails)がありますが、masterリポジトリには既に修正が反映されていますので、GitHubのmasterをインストールするのがおススメです。1
最小接続サンプル
サーバーにSMBで接続してEchoするだけのサンプルです。
# main.py
import platform
from smb.SMBConnection import SMBConnection
conn = SMBConnection(
'IEUser',
'Passw0rd!',
platform.uname().node,
'IEWIN7')
conn.connect('172.28.0.198', 139)
print(conn.echo('echo success'))
conn.close()
実行すると以下のようになります。
> python main.py
echo success
内容を一つずつ見ていきましょう。
SMBConnectionkクラスのインスタンスを生成
conn = SMBConnection(
'IEUser',
'Passw0rd!',
platform.node(),
'IEWIN7')
SMBConnectionインスタンスを生成します。__init__()の引数は以下の通りです。
def __init__(self, username, password, my_name, remote_name, domain = '', use_ntlm_v2 = True, sign_options = SIGN_WHEN_REQUIRED, is_direct_tcp = False):
必須の引数
- username
サーバーのユーザー名、今回はIEUserを指定します。 - password
サーバーのパスワード、今回はPassw0rd!を指定します。 - my_name
クライアントのNetBIOS名です。platform.node()から取得すると楽です。 - remote_name
サーバーのNetBIOS名、今回はIEWIN7を指定します。
任意の引数(基本全て未設定でOK)
- domain
ネットワークドメインですが、通常空文字で問題ありませんし、デフォルトも空文字です。 - use_ntlm_v2
NTLMv2を利用するかどうか。通常Trueで問題ないと思いますが、古い環境でNTLMv1しか利用できない場合はFalseとします。 - sign_options
SMBメッセージの署名設定です。-
SIGN_WHEN_REQUIRED: サーバーが必要とする場合のみ署名する。(デフォルト) -
SIGN_WHEN_SUPPORTED: サーバーが署名を必要としていなくても、署名をサポートしていれば署名する。 -
SIGN_NEVER: 署名しない。サーバーで署名が必要な場合はアクセスエラーになる。
-
- is_direct_tcp
ダイレクトホスティングSMBを使用するかどうか。Trueの場合接続ポートが445になる。
接続
conn.connect('172.28.0.198', 139)
サーバーへ接続します。
- ip
サーバーのIPです。今回は172.28.0.198を指定。 - port
接続ポートです。通常は139、is_direct_tcp=Trueの場合は445 - sock_family
ソケットファミリです。デフォルトはAF_INETなので未指定でいいでしょう。
AF_INET6とか指定すればIPv6が使えるかもしれません。試していません。 - timeout
タイムアウト値です。デフォルト60秒
接続に失敗した場合は以下のような例外を投げます。
-
usernameやpasswordが間違っている場合はNotReadyError -
remote_nameが間違っている場合はNotConnectedError -
ipやportが間違っている場合はTimeoutError
エコー
print(conn.echo('echo success'))
SMB_COM_ECHOコマンドを実行するメソッドです。サーバーとの疎通確認に使用します。
- data
エコーする文字列を指定します。 - timeout
タイムアウト値です。デフォルト10秒
サッと見た感じ、失敗した場合はNotConnectedErrorかSMBTimeoutを投げます。
切断
conn.close()
接続を切断する。使い終わったらちゃんと切りましょう。
ちなみにこのメソッドは内部でソケットがあるかチェックしてくれるので、問答無用で呼んでOK。
コンテキストマネージャを適用
close()っていうメソッドを見るとどうしてもwithを使いたくなります。
なのでコンテキストマネージャを適用したクラスでラップしてみます。
import platform
from smb.SMBConnection import SMBConnection
class Smb():
def __init__(self, username, password, remote_name, ip):
self.conn = SMBConnection(
username, password, platform.node(), remote_name)
self.ip = ip
def __enter__(self):
self.conn.connect(self.ip, 139)
return self
def __exit__(self, exc_type, exc_value, traceback):
self.conn.close()
def echo(self, data):
return self.conn.echo(data)
こんな感じで使う
from samba import Smb
params = {
'username': 'IEUser',
'password': 'Passw0rd!',
'remote_name': 'IEWIN7',
'ip': '172.28.0.198'
}
with Smb(**params) as smb:
print(smb.echo("echo !!"))
まあ、close()忘れを気にしなくていいくらいで
SMBバージョン
サーバーがSMB2をサポートする場合はSMB2を使用し、サポートしない場合は自動的にSMB1を使って通信します。
もし明示的にSMB1を使用したい場合は、SMBConnectionのインスタンスを作成する前に以下のようにSUPPORT_SMB2フラグをFalseに設定します。
from smb import smb_structs
smb_structs.SUPPORT_SMB2 = False
インスタンスを作成した後、どっちで通信しているかはisUsingSMB2プロパティで確認できます。
print("used SMB2 Protcol" if conn.isUsingSMB2 else "used SMB1 Protcol")
読まなくてもいい話
my_name
SMBConnection#__init__のmy_name引数ですが、実はクライアントのNetBIOS名じゃなくても接続できます。空文字や適当な文字列でもOKです。
というのも適当な文字列(仮にHOGEとすると)で接続したときにパケットを盗み見たら、NetBIOS名HOGEとしてNetBIOS Sessionが正常に完了していました。
RFC1001を確認しましたが、リモートのNetBIOS名(Called NetBIOS name)は存在しなければならないと書いてありますが、クライアントのNetBIOS名(Calling NetBIOS name)については記述がありません。
そもそも本来NetBIOS名が違ったら通信できないハズですけど、pysmbではIPで接続してるから大丈夫…なのかな?
でも引数としては必須なのでなんか設定しないとダメです。
is_direct_tcp
SMBConnection#connectのis_direct_tcp引数ですが、TrueにするとダイレクトホスティングSMBを使用するので、NetBIOSを使用しなくなります。
つまりmy_nameとremote_nameを使用しなくなるということで、これらの引数に何を渡しても関係なくなります。(my_nameは元々関係ないですけど…)
その為両方'a'とかにしても接続できますが、やはり引数として必須なのでなんか設定しないとダメです。
ただし両方空文字はNGです。何かしらの文字列必要です。
-
2018/04/20現在、修正は次のバージョンに含まれる予定のようです。 ↩