-
Notifications
You must be signed in to change notification settings - Fork 85
Implement SMB2 LOCK operation #309
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -257,6 +257,19 @@ | |
| SMB2_WRITEFLAG_WRITE_UNBUFFERED = 0x00000002 | ||
|
|
||
|
|
||
| class LockFlags: | ||
| """ | ||
| [MS-SMB2] v53.0 2017-09-15 | ||
|
|
||
| 2.2.26.1 SMB2 LOCK Request Flags | ||
| """ | ||
|
|
||
| SMB2_LOCKFLAG_SHARED_LOCK = 0x1 | ||
| SMB2_LOCKFLAG_EXCLUSIVE_LOCK = 0x2 | ||
| SMB2_LOCKFLAG_UNLOCK = 0x4 | ||
| SMB2_LOCKFLAG_FAIL_IMMEDIATELY = 0x10 | ||
|
|
||
|
|
||
| class QueryDirectoryFlags: | ||
| """ | ||
| [MS-SMB2] v53.0 2017-09-15 | ||
|
|
@@ -766,6 +779,72 @@ | |
| return query_results | ||
|
|
||
|
|
||
| class SMB2LockElement(Structure): | ||
| """ | ||
| [MS-SMB2] v53.0 2017-09-15 | ||
|
|
||
| 2.2.26.1 SMB2 LOCK_ELEMENT Structure | ||
| """ | ||
|
|
||
| def __init__(self): | ||
| self.fields = OrderedDict( | ||
| [ | ||
| ("offset", IntField(size=8)), | ||
| ("length", IntField(size=8, unsigned=False)), | ||
| ("flags", FlagField(size=4, flag_type=LockFlags)), | ||
| ("reserved", IntField(size=4, default=0)), | ||
| ] | ||
| ) | ||
| super(SMB2LockElement, self).__init__() | ||
|
|
||
|
|
||
| class SMB2LockRequest(Structure): | ||
| """ | ||
| [MS-SMB2] v53.0 2017-09-15 | ||
|
|
||
| 2.2.26 SMB2 LOCK Request | ||
| """ | ||
|
|
||
| COMMAND = Commands.SMB2_LOCK | ||
|
|
||
| def __init__(self): | ||
| self.fields = OrderedDict( | ||
| [ | ||
| ("structure_size", IntField(size=2, default=48)), | ||
| ("lock_count", IntField(size=2, default=lambda s: len(s["locks"].get_value()))), | ||
| ("lock_sequence", IntField(size=4, default=0)), | ||
| ("file_id", BytesField(size=16)), | ||
| ( | ||
| "locks", | ||
| ListField( | ||
| list_type=StructureField(size=24, structure_type=SMB2LockElement), | ||
| list_count=lambda s: s["lock_count"].get_value(), | ||
| ), | ||
| ), | ||
| ] | ||
| ) | ||
| super(SMB2LockRequest, self).__init__() | ||
|
|
||
|
|
||
| class SMB2LockResponse(Structure): | ||
| """ | ||
| [MS-SMB2] v53.0 2017-09-15 | ||
|
|
||
| 2.2.27 SMB2 LOCK Response | ||
| """ | ||
|
|
||
| COMMAND = Commands.SMB2_LOCK | ||
|
|
||
| def __init__(self): | ||
| self.fields = OrderedDict( | ||
| [ | ||
| ("structure_size", IntField(size=2, default=4)), | ||
| ("reserved", IntField(size=2, default=0)), | ||
| ] | ||
| ) | ||
| super(SMB2LockResponse, self).__init__() | ||
|
|
||
|
|
||
| class SMB2QueryDirectoryResponse(Structure): | ||
| """ | ||
| [MS-SMB2] v53.0 2017-09-15 | ||
|
|
@@ -1523,3 +1602,50 @@ | |
| self.end_of_file = c_resp["end_of_file"].get_value() | ||
| self.file_attributes = c_resp["file_attributes"].get_value() | ||
| return c_resp | ||
|
|
||
| def lock(self, locks, lsn=0, lsi=0, wait=True, send=True): | ||
| """ | ||
| Locks or unlocks byte regions of a file. | ||
|
|
||
| Supports out of band send function, call this function with send=False | ||
| to return a tuple of (SMB2LockRequest, receive_func) instead of | ||
| sending the the request and waiting for the response. The receive_func | ||
| can be used to get the response from the server by passing in the | ||
| Request that was used to sent it out of band. | ||
|
|
||
| :param locks: (List<SMB2LockElement>) byte ranges to lock or unlock | ||
| :param lsn: LockSequenceNumber. Only used for SMB dialects > 2.0.2 | ||
| :param lsi: LockSequenceIndex. Only used for SMB dialects > 2.0.2 | ||
| :param wait: If send=True, whether to wait for a response if | ||
| STATUS_PENDING was received from the server or fail. | ||
| :param send: Whether to send the request in the same call or return the | ||
| message to the caller and the unpack function | ||
| :return: SMB2LockResponse message received from the server | ||
| """ | ||
|
|
||
| lock = SMB2LockRequest() | ||
| lock["file_id"] = self.file_id | ||
| lock["locks"] = locks | ||
|
|
||
| if self.connection.dialect > Dialects.SMB_2_0_2: | ||
| if (lsn < 0) or (lsn > 15): | ||
| raise ValueError("lsn (LockSequenceNumber) must be between 0 and 15") | ||
|
|
||
| # MS-SMB2 2.2.26 requires that 0 <= lsi <= 64 | ||
| if (lsi < 0) or (lsi > 64): | ||
| raise ValueError("lsi (LockSequenceIndex) must be between 0 and 64") | ||
|
|
||
| lock["lock_sequence"] = (lsn << 28) + lsi | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we do decide to expose this, we should ensure that
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure. I'll add those checks. |
||
|
|
||
| if not send: | ||
| return lock, self._lock_response | ||
|
|
||
| request = self.connection.send(lock, self.tree_connect.session.session_id, self.tree_connect.tree_connect_id) | ||
| return self._lock_response(request, wait) | ||
|
|
||
| def _lock_response(self, request, wait=True): | ||
| response = self.connection.receive(request, wait=wait) | ||
| lock_response = SMB2LockResponse() | ||
| lock_response.unpack(response["data"].get_value()) | ||
|
|
||
| return lock_response | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you have a particular reason for exposing the sequence and index numbers right now? The protocol docs somewhat indicate they are used for some global state that I don't fully understand and am wondering if it is just best to keep it at 0 and expose it when requested by someone.
https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/06d42500-2ead-4659-8af2-86dcaec5286e
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I exposed it because it's something that clients may use for dialects > 2.0.2. I don't fully understand it either, but I wanted to be able to reproduce any sequence of operations that a client may send to a server (the reason I implemented the LOCK operation was to reproduce an issue we detected with Samba, though it didn't use lsn/lsi).
I think it doesn't hurt to have them, and they may become useful in some cases. The protocol has them, so I don't see why we should hide them. In normal cases they won't be used, so the default value of 0 will be applied.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough, I didn't want to paint us into a corner but it's probably not going to be a problem. We can always deprecate things if we want to take away the ability to set these if we ever find out it should be calculated internally somehow but I doubt that would happen.