diff --git a/Makefile.in b/Makefile.in index ec3ddfe..86fd78b 100644 --- a/Makefile.in +++ b/Makefile.in @@ -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@ @@ -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@ diff --git a/README.md b/README.md index a2db8bf..7e18e29 100644 --- a/README.md +++ b/README.md @@ -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. :) diff --git a/commands.c b/commands.c index 0b48f59..cabb0f8 100644 --- a/commands.c +++ b/commands.c @@ -1,4 +1,6 @@ +#include "includes.h" #include "commands.h" +#include "vfs.h" int python_connect(vfs_handle_struct *handle, const char *service, @@ -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) @@ -30,25 +32,25 @@ 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 { @@ -56,22 +58,31 @@ int python_mkdir(vfs_handle_struct *handle, } } -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 { @@ -81,7 +92,7 @@ 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, @@ -89,12 +100,15 @@ NTSTATUS python_create_file(struct vfs_handle_struct *handle, 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; @@ -103,15 +117,15 @@ 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, @@ -119,12 +133,15 @@ NTSTATUS python_create_file(struct vfs_handle_struct *handle, create_options, file_attributes, oplock_request, + lease, allocation_size, private_flags, sd, ea_list, result, - pinfo); + pinfo, + in_context_blobs, + out_context_blobs); } else { @@ -132,9 +149,11 @@ NTSTATUS python_create_file(struct vfs_handle_struct *handle, } } -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; @@ -146,13 +165,13 @@ 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 { @@ -160,26 +179,3 @@ int python_rename(vfs_handle_struct *handle, } } -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; - } -} diff --git a/commands.h b/commands.h index ea81aa7..643f74a 100644 --- a/commands.h +++ b/commands.h @@ -1,18 +1,21 @@ -#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, @@ -20,14 +23,17 @@ NTSTATUS python_create_file(struct vfs_handle_struct *handle, 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); diff --git a/handler.py b/handler.py index b3a7aa0..5a6ba79 100644 --- a/handler.py +++ b/handler.py @@ -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 @@ -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 @@ -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 diff --git a/helpers/reload_dev.sh b/helpers/reload_dev.sh new file mode 100644 index 0000000..b3af002 --- /dev/null +++ b/helpers/reload_dev.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# Recompiles and reloads the addon to simplify development +# Assumes that you already have Samba installed and configured + +if [ "$(id -u)" -ne 0 ]; then + echo "Please run as root" + exit 1 +fi + + +if [ -f Makefile ]; then + echo "The configure script has already been run." +else + echo "The configure script has not been run yet. Running it now..." + ./configure --with-samba-source=$HOME/samba-4.17.12/source3 +fi + +make clean +make + +cp vfs_python.so /usr/lib/x86_64-linux-gnu/samba/vfs/python.so +chown root:root /usr/lib/x86_64-linux-gnu/samba/vfs/python.so +chmod 644 /usr/lib/x86_64-linux-gnu/samba/vfs/python.so + +cp handler.py /usr/lib/x86_64-linux-gnu/samba/vfs/handler.py +chown root:root /usr/lib/x86_64-linux-gnu/samba/vfs/handler.py +chmod 644 /usr/lib/x86_64-linux-gnu/samba/vfs/handler.py + +rm -r /var/log/samba +mkdir /var/log/samba +chown root:root /var/log/samba +systemctl restart smbd \ No newline at end of file diff --git a/module_config.h b/module_config.h new file mode 100644 index 0000000..c04dfce --- /dev/null +++ b/module_config.h @@ -0,0 +1,65 @@ +/* module_config.h. Generated from module_config.h.in by configure. */ +/* module_config.h.in. Generated from configure.in by autoheader. */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_COM_ERR_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_GSSAPI_GSSAPI_GENERIC_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_GSSAPI_GSSAPI_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_GSSAPI_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_KRB5_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "" + +/* Define to the home page for this package. */ +#define PACKAGE_URL "" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "" + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 diff --git a/python_importer.c b/python_importer.c index 9a866ec..4f1af47 100644 --- a/python_importer.c +++ b/python_importer.c @@ -1,15 +1,42 @@ #include "python_importer.h" -PyObject *py_handler; +PyObject *py_handler = NULL; bool always_import = false; - -void debug(const char *text) +void debug(int level, const char *text) { - FILE *fp = fopen("/tmp/samba.log", "a"); - { - fprintf(fp, "SAMBA (%s): %s\n", getenv("USER"), text); - fclose(fp); + DEBUG(level, ("vfs_python: %s\n", text)); +} + +static PyObject* py_debug(PyObject* self, PyObject* args) { + int level = 5; + const char* text; + + if (!PyArg_ParseTuple(args, "|is", &level, &text)) { + // If parsing with an integer fails, try parsing just the string + PyErr_Clear(); + if (!PyArg_ParseTuple(args, "s", &text)) { + return NULL; + } + } + + debug(level, text); + + Py_RETURN_NONE; +} + +static PyMethodDef debug_method = { + "debug", (PyCFunction)py_debug, METH_VARARGS, "Log debug message" +}; + +void add_debug_function_to_globals() { + PyObject *module_dict = PyImport_GetModuleDict(); + PyObject *builtins = PyDict_GetItemString(module_dict, "builtins"); + + if (builtins != NULL) { + PyObject *py_func = PyCFunction_NewEx(&debug_method, NULL, NULL); + PyDict_SetItemString(PyModule_GetDict(builtins), "debug", py_func); + Py_DECREF(py_func); } } @@ -18,22 +45,23 @@ const char *get_conf(vfs_handle_struct *handle, const char *name) return lp_parm_const_string(SNUM(handle->conn), "python", name, NULL); } -struct PyObject *get_py_mod(const char *script_path) +PyObject *get_py_mod(const char *script_path) { PyObject *py_mod; PyObject *py_imp_str, *py_imp_handle, *py_imp_dict; PyObject *py_imp_load_source, *py_args_tuple; Py_Initialize(); + add_debug_function_to_globals(); - py_imp_str = PyString_FromString("imp"); + py_imp_str = PyUnicode_FromString("imp"); py_imp_handle = PyImport_Import(py_imp_str); py_imp_dict = PyModule_GetDict(py_imp_handle); py_imp_load_source = PyDict_GetItemString(py_imp_dict, "load_source"); py_args_tuple = PyTuple_New(2); - PyTuple_SetItem(py_args_tuple, 0, PyString_FromString("handler")); - PyTuple_SetItem(py_args_tuple, 1, PyString_FromString(script_path)); + PyTuple_SetItem(py_args_tuple, 0, PyUnicode_FromString("handler")); + PyTuple_SetItem(py_args_tuple, 1, PyUnicode_FromString(script_path)); py_mod = PyObject_CallObject(py_imp_load_source, py_args_tuple); @@ -46,9 +74,9 @@ struct PyObject *get_py_mod(const char *script_path) return py_mod; } -struct PyObject *get_py_func(PyObject *py_mod, const char *func_name) +PyObject *get_py_func(PyObject *py_mod, const char *func_name) { - PyObject *py_func_name = PyString_FromString(func_name); + PyObject *py_func_name = PyUnicode_FromString(func_name); PyObject *py_func = PyObject_GetAttr(py_mod, py_func_name); Py_DECREF(py_func_name); @@ -59,17 +87,16 @@ struct PyObject *get_py_func(PyObject *py_mod, const char *func_name) return py_func; } } - debug("something went wrong get_py_func."); + debug(5, "something went wrong get_py_func."); return NULL; } -struct PyObject *get_func(vfs_handle_struct *handle, - const char *func_name) +PyObject *get_func(vfs_handle_struct *handle, const char *func_name) { - debug("get_func"); + debug(5, "get_func"); if ((py_handler == NULL) || (always_import == true)) { - debug("importing module."); + debug(5, "importing module."); const char *script_path = get_conf(handle, "script"); py_handler = get_py_mod(script_path); } diff --git a/python_importer.h b/python_importer.h index 5687c6d..3f7d753 100644 --- a/python_importer.h +++ b/python_importer.h @@ -1,12 +1,12 @@ #include "include/includes.h" -#include "python2.7/Python.h" +#include "python3.11/Python.h" -PyObject *py_handler; -bool always_import; +extern PyObject *py_handler; +extern bool always_import; -void debug(const char *text); +void debug(int level, const char *text); const char *get_conf(vfs_handle_struct *handle, const char *name); -struct PyObject *get_py_mod(const char *script_path); -struct PyObject *get_py_func(PyObject *py_mod, const char *func_name); -struct PyObject *get_func(vfs_handle_struct *handle, const char *func_name); +PyObject *get_py_mod(const char *script_path); +PyObject *get_py_func(PyObject *py_mod, const char *func_name); +PyObject *get_func(vfs_handle_struct *handle, const char *func_name); diff --git a/vfs_python.c b/vfs_python.c index b7e67bd..1e588e8 100644 --- a/vfs_python.c +++ b/vfs_python.c @@ -1,19 +1,15 @@ #include "vfs_python.h" - static struct vfs_fn_pointers vfs_python_fns = { - .connect_fn = python_connect, - .disconnect_fn = python_disconnect, - .mkdir_fn = python_mkdir, - .rmdir_fn = python_rmdir, - .create_file_fn = python_create_file, - .rename_fn = python_rename, - .unlink_fn = python_unlink + .connect_fn = python_connect, + .disconnect_fn = python_disconnect, + .mkdirat_fn = python_mkdirat, + .unlinkat_fn = python_unlinkat, + .create_file_fn = python_create_file, + .renameat_fn = python_renameat }; -#define vfs_python_init samba_init_module - -NTSTATUS vfs_python_init(void) +NTSTATUS samba_init_module(TALLOC_CTX *ctx) { return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "python",