Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions Makefile.in
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
CC = @CC@
CFLAGS = @CFLAGS@
CFLAGS = @CFLAGS@ -D__STDC_WANT_LIB_EXT1__=1
CPPFLAGS = @CPPFLAGS@
LDFLAGS = @LDFLAGS@
LDSHFLAGS = @LDSHFLAGS@
LDSUFFIX = -lpython2.7
LDSUFFIX = -lpython3.11
INSTALLCMD = @INSTALL@
SAMBA_SOURCE = @SAMBA_SOURCE@
SHLIBEXT = @SHLIBEXT@
Expand All @@ -20,7 +20,9 @@ FLAGS = $(CFLAGS) $(CPPFLAGS) -fPIC \
-I$(SAMBA_SOURCE)/librpc \
-I$(SAMBA_SOURCE)/../librpc \
-I$(SAMBA_SOURCE)/../ \
-I$(SAMBA_SOURCE) -I.
-I$(SAMBA_SOURCE) -I. \
-I$(SAMBA_SOURCE)/../bin/default/include \
-I$(SAMBA_SOURCE)/../bin/default


prefix = @prefix@
Expand Down
80 changes: 53 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,62 @@
vfs_python
==========

An experimental Samba module which lets you control execution of Samba actions in your own Python script.

Installation
------------
You must have the Python headers installed (`python-dev`) and the Samba sourcecode lying around.

Clone this repository, then compile as follows:

./configure --enable-debug --enable-developer --with-samba-source=/path/to/samba/source3
make

Copy / symlink the resulting `vfs_python.so` to `/usr/local/samba/lib/vfs/python.so` (or wherever your VFS modules are).

In your `smb.conf`, enable `vfs_python` per share:

[myshare]
path = /tmp
vfs objects = python
python:script = /path/to/your/handler.py
# vfs_python

An experimental Samba module which lets you control execution of Samba actions in your own Python script. The aim of this this project is to rather offer a sample vfs module for those who find Samba's documentation difficult to follow than to be fully functional production ready module. Currently I do not plan to offer constant updates nor compile it for every Samba version.

## Compiling

At the time of updating this code I used Debian 12 and Samba 4.17.12 which used Python 3.11. Both Samba and vfs_python were extracted to my home directory. You may have to adjust those commands to fit for your needs

1. Setup build environment.
```sh
wget https://gitlab.com/samba-team/samba/-/raw/master/bootstrap/generated-dists/debian12/bootstrap.sh
chmod +x bootstrap.sh
./bootstrap.sh
```

2. Make Samba
```sh
wget https://download.samba.org/pub/samba/stable/samba-4.17.12.tar.gz
tar -zxf samba-4.17.12.tar.gz
cd samba-4.17.12
./configure
make
```

3. Make this vfs_python module
```sh
git clone https://github.com/rain1/vfs_python.git
cd $HOME/vfs_python
./configure --with-samba-source=$HOME/samba-4.17.12/source3
make
```

## Installation

1. Either download already compiled `vfs_python.so` from releases page or compile it yourself.

2. Copy / symlink the resulting `vfs_python.so` to `/usr/lib/x86_64-linux-gnu/samba/vfs/python.so` (or wherever your VFS modules are).

3. In your `smb.conf`, enable `vfs_python` per share:
```ini
[global]
log level = 5 # If you want to see log that debug method in python script produces
[myshare]
path = /tmp
vfs objects = python
python:script = /path/to/your/handler.py
```

and make sure that the script path is valid.

The Python code
---------------
Take a look at [the `handler.py`](https://github.com/vortec/vfs_python/blob/master/handler.py) to see how it looks like and which Samba calls are supported at the moment (hint: not too many).
## The Python code

Take a look at [the `handler.py`](https://github.com/vortec/vfs_python/blob/master/handler.py) to see how it looks like and which Samba calls are supported at treinvent the wheel.he moment (hint: not too many).

When a user performs an action, the corresponding Python function will be called. You can either allow or deny that action by returning a boolean.

The Python script will be imported every time a user connects and is valid for the duration of the connection.

Current status
--------------
`debug` function that us used in `handler.py` is defined in `python_importer.c` which is why IDEs think it is undefined. Reason for that is that now Samba itself will take care of logging instead of python script having to reinvent the wheel.

## Current status
This is experimental, my C skills have plenty of room for improvement and if your Python code raises an exception the Samba daemon will crash. I plan to address all those things, but right now it is what it is. Any contribution is greatly appreciated. :)
98 changes: 47 additions & 51 deletions commands.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#include "includes.h"
#include "commands.h"
#include "vfs.h"

int python_connect(vfs_handle_struct *handle,
const char *service,
Expand All @@ -11,8 +13,8 @@ int python_connect(vfs_handle_struct *handle,
{
PyObject *py_ret = PyObject_CallFunction(py_func, "ss", service, user);
success = PyObject_IsTrue(py_ret);
Py_DECREF(py_ret);
Py_DECREF(py_func);
Py_XDECREF(py_ret);
Py_XDECREF(py_func);
}

if (success == 1)
Expand All @@ -30,48 +32,57 @@ void python_disconnect(vfs_handle_struct *handle)
SMB_VFS_NEXT_DISCONNECT(handle);
}


int python_mkdir(vfs_handle_struct *handle,
const char *path,
mode_t mode)
int python_mkdirat(vfs_handle_struct *handle,
struct files_struct *dirfsp,
const struct smb_filename *smb_fname,
mode_t mode)
{
int success = 1;

PyObject *py_func = get_func(handle, "mkdir");
if (py_func != NULL)
{
PyObject *py_ret = PyObject_CallFunction(py_func, "s", path);
PyObject *py_ret = PyObject_CallFunction(py_func, "s", smb_fname->base_name);
success = PyObject_IsTrue(py_ret);
Py_DECREF(py_ret);
Py_DECREF(py_func);
Py_XDECREF(py_ret);
Py_XDECREF(py_func);
}

if (success == 1)
{
return SMB_VFS_NEXT_MKDIR(handle, path, mode);
return SMB_VFS_NEXT_MKDIRAT(handle, dirfsp, smb_fname, mode);
}
else
{
return -1;
}
}

int python_rmdir(vfs_handle_struct *handle, const char *path)
int python_unlinkat(vfs_handle_struct *handle,
struct files_struct *dirfsp,
const struct smb_filename *smb_fname,
int flags)
{
int success = 1;

PyObject *py_func = get_func(handle, "rmdir");
PyObject *py_func = NULL;
if (flags & AT_REMOVEDIR) {
py_func = get_func(handle, "rmdir");
} else {
py_func = get_func(handle, "unlink");
}

if (py_func != NULL)
{
PyObject *py_ret = PyObject_CallFunction(py_func, "s", path);
PyObject *py_ret = PyObject_CallFunction(py_func, "s", smb_fname->base_name);
success = PyObject_IsTrue(py_ret);
Py_DECREF(py_ret);
Py_DECREF(py_func);
Py_XDECREF(py_ret);
Py_XDECREF(py_func);
}

if (success == 1)
{
return SMB_VFS_NEXT_RMDIR(handle, path);
return SMB_VFS_NEXT_UNLINKAT(handle, dirfsp, smb_fname, flags);
}
else
{
Expand All @@ -81,20 +92,23 @@ int python_rmdir(vfs_handle_struct *handle, const char *path)

NTSTATUS python_create_file(struct vfs_handle_struct *handle,
struct smb_request *req,
uint16_t root_dir_fid,
struct files_struct *dirfsp,
struct smb_filename *smb_fname,
uint32_t access_mask,
uint32_t share_access,
uint32_t create_disposition,
uint32_t create_options,
uint32_t file_attributes,
uint32_t oplock_request,
const struct smb2_lease *lease,
uint64_t allocation_size,
uint32_t private_flags,
struct security_descriptor *sd,
struct ea_list *ea_list,
files_struct **result,
int *pinfo)
int *pinfo,
const struct smb2_create_blobs *in_context_blobs,
struct smb2_create_blobs *out_context_blobs)
{
int success = 1;

Expand All @@ -103,38 +117,43 @@ NTSTATUS python_create_file(struct vfs_handle_struct *handle,
{
PyObject *py_ret = PyObject_CallFunction(py_func, "s", smb_fname->base_name);
success = PyObject_IsTrue(py_ret);
Py_DECREF(py_ret);
Py_DECREF(py_func);
Py_XDECREF(py_ret);
Py_XDECREF(py_func);
}

if (success == 1)
{
return SMB_VFS_NEXT_CREATE_FILE(handle,
req,
root_dir_fid,
dirfsp,
smb_fname,
access_mask,
share_access,
create_disposition,
create_options,
file_attributes,
oplock_request,
lease,
allocation_size,
private_flags,
sd,
ea_list,
result,
pinfo);
pinfo,
in_context_blobs,
out_context_blobs);
}
else
{
return NT_STATUS_UNSUCCESSFUL;
}
}

int python_rename(vfs_handle_struct *handle,
const struct smb_filename *smb_fname_src,
const struct smb_filename *smb_fname_dst)
int python_renameat(struct vfs_handle_struct *handle,
struct files_struct *oldfsp,
const struct smb_filename *smb_fname_src,
struct files_struct *newfsp,
const struct smb_filename *smb_fname_dst)
{
int success = 1;

Expand All @@ -146,40 +165,17 @@ int python_rename(vfs_handle_struct *handle,
smb_fname_src->base_name,
smb_fname_dst->base_name);
success = PyObject_IsTrue(py_ret);
Py_DECREF(py_ret);
Py_DECREF(py_func);
Py_XDECREF(py_ret);
Py_XDECREF(py_func);
}

if (success == 1)
{
return SMB_VFS_NEXT_RENAME(handle, smb_fname_src, smb_fname_dst);
return SMB_VFS_NEXT_RENAMEAT(handle, oldfsp, smb_fname_src, newfsp, smb_fname_dst);
}
else
{
return -1;
}
}

int python_unlink(vfs_handle_struct *handle,
const struct smb_filename *smb_fname)
{
int success = 1;

PyObject *py_func = get_func(handle, "unlink");
if (py_func != NULL)
{
PyObject *py_ret = PyObject_CallFunction(py_func, "s", smb_fname->base_name);
success = PyObject_IsTrue(py_ret);
Py_DECREF(py_ret);
Py_DECREF(py_func);
}

if (success == 1)
{
return SMB_VFS_NEXT_UNLINK(handle, smb_fname);
}
else
{
return -1;
}
}
32 changes: 19 additions & 13 deletions commands.h
Original file line number Diff line number Diff line change
@@ -1,33 +1,39 @@
#include "python2.7/Python.h"
#include "python3.11/Python.h"
#include "python_importer.h"

int python_connect(vfs_handle_struct *handle,
const char *service,
const char *user);
void python_disconnect(vfs_handle_struct *handle);
int python_mkdir(vfs_handle_struct *handle,
const char *path,
mode_t mode);
int python_rmdir(vfs_handle_struct *handle, const char *path);

int python_mkdirat(vfs_handle_struct *handle,
struct files_struct *dirfsp,
const struct smb_filename *smb_fname,
mode_t mode);
int python_unlinkat(vfs_handle_struct *handle,
struct files_struct *dirfsp,
const struct smb_filename *smb_fname,
int flags);
NTSTATUS python_create_file(struct vfs_handle_struct *handle,
struct smb_request *req,
uint16_t root_dir_fid,
struct files_struct *dirfsp,
struct smb_filename *smb_fname,
uint32_t access_mask,
uint32_t share_access,
uint32_t create_disposition,
uint32_t create_options,
uint32_t file_attributes,
uint32_t oplock_request,
const struct smb2_lease *lease,
uint64_t allocation_size,
uint32_t private_flags,
struct security_descriptor *sd,
struct ea_list *ea_list,
files_struct **result,
int *pinfo);
int python_rename(vfs_handle_struct *handle,
const struct smb_filename *smb_fname_src,
const struct smb_filename *smb_fname_dst);
int python_unlink(vfs_handle_struct *handle,
const struct smb_filename *smb_fname);
int *pinfo,
const struct smb2_create_blobs *in_context_blobs,
struct smb2_create_blobs *out_context_blobs);
int python_renameat(struct vfs_handle_struct *handle,
struct files_struct *oldfsp,
const struct smb_filename *smb_fname_src,
struct files_struct *newfsp,
const struct smb_filename *smb_fname_dst);
6 changes: 2 additions & 4 deletions handler.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
def debug(text):
with open('/tmp/samba.log', 'a') as fo:
fo.write('PYTHON: {}\n'.format(text))

def connect(service='default', user='default'):
debug('{} has connected to {}.'.format(user, service))
return True
Expand All @@ -13,6 +9,7 @@ def mkdir(path):
def rmdir(path):
debug('Remove dir: {}'.format(path))
if path == 'dont_touch_this':
debug(2, 'rmdir denied for: {}'.format(path))
return False
return True

Expand All @@ -31,5 +28,6 @@ def rename(source, target):
def unlink(path):
debug('Unlink: {}'.format(path))
if path == 'mc_hammer':
debug(2, 'unlink denied for: {}'.format(path))
return False
return True
Loading