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現在、修正は次のバージョンに含まれる予定のようです。 ↩