diff --git a/actions/samba b/actions/samba new file mode 100755 index 000000000..034584671 --- /dev/null +++ b/actions/samba @@ -0,0 +1,138 @@ +#!/usr/bin/python3 +# +# This file is part of FreedomBox. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +""" +Configuration helper for samba. +""" + +import argparse +import os +import shutil +import subprocess + +import augeas +from plinth import action_utils + +SHARES_PATH = '/var/lib/samba/shares' +DEFAULT_FILE = '/etc/default/samba' + +CONF_PATH = '/etc/samba/smb-freedombox.conf' +CONF = r''' +# +# This file is managed and overwritten by Plinth. If you wish to manage +# Samba yourself, disable Samba in Plinth, remove this file and remove +# the --configfile parameter in /etc/default/samba +# +# To view configured samba shares use command `net conf list` +# + +[global] + workgroup = WORKGROUP + log file = /var/log/samba/log.%m + max log size = 1000 + logging = file + panic action = /usr/share/samba/panic-action %d + server role = standalone server + obey pam restrictions = yes + unix password sync = yes + passwd program = /usr/bin/passwd %u + passwd chat = *Enter\snew\s*\spassword:* %n\n *Retype\snew\s*\spassword:* %n\n *password\supdated\ssuccessfully* . + pam password change = yes + map to guest = bad user + # enable registry based shares + registry shares = yes +''' # noqa: E501 + + +def parse_arguments(): + """Return parsed command line arguments as dictionary.""" + parser = argparse.ArgumentParser() + subparsers = parser.add_subparsers(dest='subcommand', help='Sub command') + + subparsers.add_parser('setup', help='Configure samba after install') + + subparsers.required = True + return parser.parse_args() + + +def _share_conf(parameters, **kwargs): + """Run samba registry edit command.""" + subprocess.check_call(['net', 'conf'] + parameters, **kwargs) + + +def _create_open_share(name, path): + """Create an open samba share.""" + try: + _share_conf(['delshare', name], stderr=subprocess.DEVNULL) + except subprocess.CalledProcessError: + pass + _share_conf(['addshare', name, path, 'writeable=y', 'guest_ok=y']) + _share_conf(['setparm', name, 'force group', 'sambashare']) + _share_conf(['setparm', name, 'inherit permissions', 'yes']) + + +def _use_config_file(conf): + """Set samba configuration file location.""" + aug = augeas.Augeas( + flags=augeas.Augeas.NO_LOAD + augeas.Augeas.NO_MODL_AUTOLOAD) + aug.set('/augeas/load/Shellvars/lens', 'Shellvars.lns') + aug.set('/augeas/load/Shellvars/incl[last() + 1]', DEFAULT_FILE) + aug.load() + + aug.set('/files' + DEFAULT_FILE + '/SMBDOPTIONS', + '--configfile={0}'.format(conf)) + aug.save() + + +def subcommand_setup(_): + """Configure samba after install.""" + try: + os.mkdir(SHARES_PATH) + except FileExistsError: + pass + + open_share_path = os.path.join(SHARES_PATH, 'open_share') + try: + os.mkdir(open_share_path) + except FileExistsError: + pass + # set folder group writable, 2 turns on the setGID bit + # + # TODO: some filesystems doesn't support chown and chmod + # (and it is not needed if mounted with correct parameters) + shutil.chown(open_share_path, group='sambashare') + os.chmod(open_share_path, 0o2775) + + # use custom samba config file + with open(CONF_PATH, 'w') as file_handle: + file_handle.write(CONF) + _use_config_file(CONF_PATH) + _create_open_share('freedombox-open-share', open_share_path) + action_utils.service_restart('smbd') + + +def main(): + """Parse arguments and perform all duties.""" + arguments = parse_arguments() + + subcommand = arguments.subcommand.replace('-', '_') + subcommand_method = globals()['subcommand_' + subcommand] + subcommand_method(arguments) + + +if __name__ == '__main__': + main() diff --git a/plinth/modules/samba/__init__.py b/plinth/modules/samba/__init__.py new file mode 100644 index 000000000..4ec03b8be --- /dev/null +++ b/plinth/modules/samba/__init__.py @@ -0,0 +1,113 @@ +# +# This file is part of FreedomBox. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +""" +FreedomBox app to configure samba. +""" + +import socket + +from django.urls import reverse_lazy +from django.utils.translation import ugettext_lazy as _ + +from plinth import action_utils, actions +from plinth import app as app_module +from plinth import frontpage, menu +from plinth.daemon import Daemon +from plinth.modules.firewall.components import Firewall +from plinth.utils import format_lazy + +from .manifest import backup, clients # noqa, pylint: disable=unused-import + +version = 1 + +managed_services = ['smbd'] + +managed_packages = ['samba'] + +name = _('Samba') + +short_description = _('Samba File Sharing') + +description = [ + _('Samba file sharing allows to share files between computers in your ' + 'local network. '), + format_lazy( + _('If enabled, Samba share will be available at \\\\{hostname} on ' + 'Windows and smb://{hostname} on Linux and Mac'), + hostname=socket.gethostname()), +] + +clients = clients + +app = None + + +class SambaApp(app_module.App): + """FreedomBox app for Samba file sharing.""" + + app_id = 'samba' + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-samba', name, short_description, 'samba', + 'samba:index', parent_url_name='apps') + self.add(menu_item) + + shortcut = frontpage.Shortcut( + 'shortcut-samba', name, short_description=short_description, + icon='samba', description=description, + configure_url=reverse_lazy('samba:index'), clients=clients, + login_required=True) + self.add(shortcut) + + firewall = Firewall('firewall-samba', name, ports=['samba']) + self.add(firewall) + + daemon = Daemon('daemon-samba', managed_services[0]) + self.add(daemon) + + +def init(): + """Initialize the module.""" + global app + app = SambaApp() + + setup_helper = globals()['setup_helper'] + if setup_helper.get_state() != 'needs-setup' and app.is_enabled(): + app.set_enabled(True) + + +def setup(helper, old_version=None): + """Install and configure the module.""" + helper.install(managed_packages) + helper.call('post', actions.superuser_run, 'samba', ['setup']) + helper.call('post', app.enable) + + +def diagnose(): + """Run diagnostics and return the results.""" + results = [] + + results.append(action_utils.diagnose_port_listening(137, 'udp4')) + results.append(action_utils.diagnose_port_listening(138, 'udp4')) + results.append(action_utils.diagnose_port_listening(139, 'tcp4')) + results.append(action_utils.diagnose_port_listening(139, 'tcp6')) + results.append(action_utils.diagnose_port_listening(445, 'tcp4')) + results.append(action_utils.diagnose_port_listening(445, 'tcp6')) + + return results diff --git a/plinth/modules/samba/data/etc/plinth/modules-enabled/samba b/plinth/modules/samba/data/etc/plinth/modules-enabled/samba new file mode 100644 index 000000000..89f59abca --- /dev/null +++ b/plinth/modules/samba/data/etc/plinth/modules-enabled/samba @@ -0,0 +1 @@ +plinth.modules.samba diff --git a/plinth/modules/samba/manifest.py b/plinth/modules/samba/manifest.py new file mode 100644 index 000000000..8275de1da --- /dev/null +++ b/plinth/modules/samba/manifest.py @@ -0,0 +1,23 @@ +# +# This file is part of FreedomBox. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# + +from plinth.clients import validate +from plinth.modules.backups.api import validate as validate_backup + +clients = validate([]) + +backup = validate_backup({}) diff --git a/plinth/modules/samba/tests/__init__.py b/plinth/modules/samba/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plinth/modules/samba/urls.py b/plinth/modules/samba/urls.py new file mode 100644 index 000000000..a439d9d49 --- /dev/null +++ b/plinth/modules/samba/urls.py @@ -0,0 +1,32 @@ +# +# This file is part of FreedomBox. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +""" +URLs for the Samba module. +""" + +from django.conf.urls import url +from plinth.modules import samba +from plinth.views import AppView + +urlpatterns = [ + url( + r'^apps/samba/$', + AppView.as_view(app_id='samba', name=samba.name, + diagnostics_module_name='samba', + description=samba.description, + show_status_block=False), name='index') +]