LoginSignup
17
15

More than 5 years have passed since last update.

pysmbの使い方(接続・切断)

Last updated at Posted at 2018-04-23

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
    接続ポートです。通常は139is_direct_tcp=Trueの場合は445
  • sock_family
    ソケットファミリです。デフォルトはAF_INETなので未指定でいいでしょう。
    AF_INET6とか指定すればIPv6が使えるかもしれません。試していません。
  • timeout
    タイムアウト値です。デフォルト60秒

接続に失敗した場合は以下のような例外を投げます。

  • usernamepasswordが間違っている場合はNotReadyError
  • remote_nameが間違っている場合はNotConnectedError
  • ipportが間違っている場合はTimeoutError

エコー

print(conn.echo('echo success'))

SMB_COM_ECHOコマンドを実行するメソッドです。サーバーとの疎通確認に使用します。

  • data
    エコーする文字列を指定します。
  • timeout
    タイムアウト値です。デフォルト10秒

サッと見た感じ、失敗した場合はNotConnectedErrorSMBTimeoutを投げます。

切断

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#connectis_direct_tcp引数ですが、TrueにするとダイレクトホスティングSMBを使用するので、NetBIOSを使用しなくなります。
つまりmy_nameremote_nameを使用しなくなるということで、これらの引数に何を渡しても関係なくなります。(my_nameは元々関係ないですけど…)
その為両方'a'とかにしても接続できますが、やはり引数として必須なのでなんか設定しないとダメです。
ただし両方空文字はNGです。何かしらの文字列必要です。


  1. 2018/04/20現在、修正は次のバージョンに含まれる予定のようです。 

17
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
15