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')
+]