[enh] Refactor firewall_upnp action

* make sure to save final UPnP status (fix #42)
* deprecate the 'reload' action and replace it with a new 'status'
action which retrieve current UPnP status
* allow to not refresh port forwarding with the --no-refresh argument
* some other small changes, e.g. set the action argument to one
optionnal choice, rename the UPnP cron job file, add logging
This commit is contained in:
Jérôme Lebleu 2014-12-30 01:18:32 +01:00
parent eb45d39e89
commit 1c96b4f9a3
5 changed files with 120 additions and 75 deletions

View file

@ -934,17 +934,20 @@ firewall:
### firewall_upnp() ### firewall_upnp()
upnp: upnp:
action_help: Add uPnP cron and enable uPnP in firewall.yml, or the opposite. action_help: Manage port forwarding using UPnP
api: GET /firewall/upnp api: GET /firewall/upnp
arguments: arguments:
action: action:
help: enable/disable
choices: choices:
- enable - enable
- disable - disable
- status
- reload - reload
- [] nargs: "?"
nargs: "*" default: status
--no-refresh:
help: Do not refresh port forwarding
action: store_true
### firewall_stop() ### firewall_stop()
stop: stop:

View file

@ -34,6 +34,7 @@ except ImportError:
sys.exit(1) sys.exit(1)
from moulinette.core import MoulinetteError from moulinette.core import MoulinetteError
from moulinette.utils.log import getActionLogger
""" Search the ssh port in ssh config file """ Search the ssh port in ssh config file
If we don't find the ssh port we define 22""" If we don't find the ssh port we define 22"""
@ -56,6 +57,11 @@ except:
ssh_port = '22' ssh_port = '22'
ssh_port = int(ssh_port) ssh_port = int(ssh_port)
firewall_file = '/etc/yunohost/firewall.yml'
upnp_cron_job = '/etc/cron.d/yunohost-firewall-upnp'
logger = getActionLogger('yunohost.firewall')
def firewall_allow(port=None, protocol=['TCP'], ipv6=False, no_upnp=False): def firewall_allow(port=None, protocol=['TCP'], ipv6=False, no_upnp=False):
""" """
@ -94,7 +100,7 @@ def firewall_allow(port=None, protocol=['TCP'], ipv6=False, no_upnp=False):
else: else:
msignals.display(m18n.n('port_already_opened', port), 'warning') msignals.display(m18n.n('port_already_opened', port), 'warning')
with open('/etc/yunohost/firewall.yml', 'w') as f: with open(firewall_file, 'w') as f:
yaml.safe_dump(firewall, f, default_flow_style=False) yaml.safe_dump(firewall, f, default_flow_style=False)
return firewall_reload() return firewall_reload()
@ -134,7 +140,7 @@ def firewall_disallow(port=None, protocol=['TCP'], ipv6=False):
else: else:
msignals.display(m18n.n('port_already_closed', port), 'warning') msignals.display(m18n.n('port_already_closed', port), 'warning')
with open('/etc/yunohost/firewall.yml', 'w') as f: with open(firewall_file, 'w') as f:
yaml.safe_dump(firewall, f, default_flow_style=False) yaml.safe_dump(firewall, f, default_flow_style=False)
return firewall_reload() return firewall_reload()
@ -148,7 +154,7 @@ def firewall_list(raw=False):
raw -- Return the complete YAML dict raw -- Return the complete YAML dict
""" """
with open('/etc/yunohost/firewall.yml') as f: with open(firewall_file) as f:
firewall = yaml.load(f) firewall = yaml.load(f)
if raw: if raw:
@ -172,7 +178,7 @@ def firewall_reload():
if os.system("iptables -P INPUT ACCEPT") != 0: if os.system("iptables -P INPUT ACCEPT") != 0:
raise MoulinetteError(errno.ESRCH, m18n.n('iptables_unavailable')) raise MoulinetteError(errno.ESRCH, m18n.n('iptables_unavailable'))
if upnp: if upnp:
firewall_upnp(action=['reload']) firewall_upnp(no_refresh=False)
os.system("iptables -F") os.system("iptables -F")
os.system("iptables -X") os.system("iptables -X")
@ -218,75 +224,112 @@ def firewall_reload():
return firewall_list() return firewall_list()
def firewall_upnp(action=None): def firewall_upnp(action='status', no_refresh=False):
""" """
Add uPnP cron and enable uPnP in firewall.yml, or the opposite. Manage port forwarding using UPnP
Note: 'reload' action is deprecated and will be removed in the near
future. You should use 'status' instead - which retrieve UPnP status
and automatically refresh port forwarding if 'no_refresh' is False.
Keyword argument: Keyword argument:
action -- enable/disable/reload action -- Action to perform
no_refresh -- Do not refresh port forwarding
""" """
firewall = firewall_list(raw=True) firewall = firewall_list(raw=True)
enabled = firewall['uPnP']['enabled']
if action: # Compatibility with previous version
action = action[0] if action == 'reload':
logger.warning("'reload' action is deprecated and will be removed")
if action == 'enable':
firewall['uPnP']['enabled'] = True
with open('/etc/cron.d/yunohost-firewall', 'w+') as f:
f.write('PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
\n*/50 * * * * root yunohost firewall upnp reload >>/dev/null')
msignals.display(m18n.n('upnp_enabled'), 'success')
if action == 'disable':
firewall['uPnP']['enabled'] = False
try: try:
upnpc = miniupnpc.UPnP() # Remove old cron job
upnpc.discoverdelay = 3000 os.remove('/etc/cron.d/yunohost-firewall')
if upnpc.discover() == 1: except: pass
action = 'status'
no_refresh = False
if action == 'status' and no_refresh:
# Only return current state
return { 'enabled': enabled }
elif action == 'enable' or (enabled and action == 'status'):
# Add cron job
with open(upnp_cron_job, 'w+') as f:
f.write('*/50 * * * * root '
'/usr/bin/yunohost firewall upnp status >>/dev/null\n')
enabled = True
elif action == 'disable' or (not enabled and action == 'status'):
try:
# Remove cron job
os.remove(upnp_cron_job)
except: pass
enabled = False
if action == 'status':
no_refresh = True
else:
raise MoulinetteError(errno.EINVAL, m18n.n('action_invalid', action))
# Refresh port mapping using UPnP
if not no_refresh:
upnpc = miniupnpc.UPnP()
upnpc.discoverdelay = 3000
# Discover UPnP device(s)
logger.debug('discovering UPnP devices...')
nb_dev = upnpc.discover()
logger.debug('found %d UPnP device(s)', int(nb_dev))
if nb_dev < 1:
msignals.display(m18n.n('upnp_dev_not_found'), 'error')
enabled = False
else:
try:
# Select UPnP device
upnpc.selectigd() upnpc.selectigd()
except:
logger.exception('unable to select UPnP device')
enabled = False
else:
# Iterate over ports
for protocol in ['TCP', 'UDP']: for protocol in ['TCP', 'UDP']:
for port in firewall['uPnP'][protocol]: for port in firewall['uPnP'][protocol]:
# Clean the mapping of this port
if upnpc.getspecificportmapping(port, protocol): if upnpc.getspecificportmapping(port, protocol):
try: upnpc.deleteportmapping(port, protocol) try:
upnpc.deleteportmapping(port, protocol)
except: pass except: pass
except: pass if not enabled:
continue
try:
# Add new port mapping
upnpc.addportmapping(port, protocol, upnpc.lanaddr,
port, 'yunohost firewall: port %d' % port, '')
except:
logger.exception('unable to add port %d using UPnP',
port)
enabled = False
if enabled != firewall['uPnP']['enabled']:
firewall['uPnP']['enabled'] = enabled
try: os.remove('/etc/cron.d/yunohost-firewall') # Make a backup and update firewall file
except: pass os.system("cp {0} {0}.old".format(firewall_file))
with open(firewall_file, 'w') as f:
msignals.display(m18n.n('upnp_disabled'), 'success')
if action == 'reload':
upnp = firewall['uPnP']['enabled']
if upnp:
try:
upnpc = miniupnpc.UPnP()
upnpc.discoverdelay = 3000
if upnpc.discover() == 1:
upnpc.selectigd()
for protocol in ['TCP', 'UDP']:
for port in firewall['uPnP'][protocol]:
if upnpc.getspecificportmapping(port, protocol):
try: upnpc.deleteportmapping(port, protocol)
except: pass
upnpc.addportmapping(port, protocol, upnpc.lanaddr, port, 'yunohost firewall : port %d' % port, '')
else:
raise MoulinetteError(errno.ENXIO, m18n.n('upnp_dev_not_found'))
except:
msignals.display(m18n.n('upnp_port_open_failed'), 'warning')
if action:
os.system("cp /etc/yunohost/firewall.yml /etc/yunohost/firewall.yml.old")
with open('/etc/yunohost/firewall.yml', 'w') as f:
yaml.safe_dump(firewall, f, default_flow_style=False) yaml.safe_dump(firewall, f, default_flow_style=False)
return { "enabled": firewall['uPnP']['enabled'] } if not no_refresh:
# Display success message if needed
if action == 'enable' and enabled:
msignals.display(m18n.n('upnp_enabled'), 'success')
elif action == 'disable' and not enabled:
msignals.display(m18n.n('upnp_disabled'), 'success')
# Make sure to disable UPnP
elif action != 'disable' and not enabled:
firewall_upnp('disable', no_refresh=True)
if action == 'enable' and not enabled:
raise MoulinetteError(errno.ENXIO, m18n.n('upnp_port_open_failed'))
return { 'enabled': enabled }
def firewall_stop(): def firewall_stop():
@ -307,5 +350,5 @@ def firewall_stop():
os.system("ip6tables -F") os.system("ip6tables -F")
os.system("ip6tables -X") os.system("ip6tables -X")
if os.path.exists("/etc/cron.d/yunohost-firewall"): if os.path.exists(upnp_cron_job):
firewall_upnp('disable') firewall_upnp('disable')

View file

@ -6,6 +6,7 @@
"installation_complete" : "Installation complete", "installation_complete" : "Installation complete",
"installation_failed" : "Installation failed", "installation_failed" : "Installation failed",
"unexpected_error" : "An unexpected error occured", "unexpected_error" : "An unexpected error occured",
"action_invalid" : "Invalid action '{:s}'",
"license_undefined" : "undefined", "license_undefined" : "undefined",
"no_appslist_found" : "No apps list found", "no_appslist_found" : "No apps list found",
@ -72,10 +73,10 @@
"port_already_opened" : "Port {:d} is already opened", "port_already_opened" : "Port {:d} is already opened",
"port_already_closed" : "Port {:d} is already closed", "port_already_closed" : "Port {:d} is already closed",
"iptables_unavailable" : "You cannot play with iptables here. You are either in a container or your kernel does not support it.", "iptables_unavailable" : "You cannot play with iptables here. You are either in a container or your kernel does not support it.",
"upnp_dev_not_found" : "No uPnP device found", "upnp_dev_not_found" : "No UPnP device found",
"upnp_port_open_failed" : "Unable to open uPnP ports", "upnp_port_open_failed" : "Unable to open UPnP ports",
"upnp_enabled" : "uPnP successfully enabled", "upnp_enabled" : "UPnP successfully enabled",
"upnp_disabled" : "uPnP successfully disabled", "upnp_disabled" : "UPnP successfully disabled",
"firewall_reloaded" : "Firewall successfully reloaded", "firewall_reloaded" : "Firewall successfully reloaded",
"hook_list_by_invalid" : "Invalid property to list hook by", "hook_list_by_invalid" : "Invalid property to list hook by",

View file

@ -6,6 +6,7 @@
"installation_complete" : "Installation terminée", "installation_complete" : "Installation terminée",
"installation_failed" : "Échec de l'installation", "installation_failed" : "Échec de l'installation",
"unexpected_error" : "Une erreur inattendue est survenue", "unexpected_error" : "Une erreur inattendue est survenue",
"action_invalid" : "Action '{:s}' incorrecte",
"license_undefined" : "indéfinie", "license_undefined" : "indéfinie",
"no_appslist_found" : "Aucune liste d'applications trouvée", "no_appslist_found" : "Aucune liste d'applications trouvée",
@ -72,10 +73,10 @@
"port_already_opened" : "Le port {:d} est déjà ouvert", "port_already_opened" : "Le port {:d} est déjà ouvert",
"port_already_closed" : "Le port {:d} est déjà fermé", "port_already_closed" : "Le port {:d} est déjà fermé",
"iptables_unavailable" : "Vous ne pouvez pas faire joujou avec iptables ici. Vous êtes sûrement dans un conteneur, autrement votre noyau ne le supporte pas.", "iptables_unavailable" : "Vous ne pouvez pas faire joujou avec iptables ici. Vous êtes sûrement dans un conteneur, autrement votre noyau ne le supporte pas.",
"upnp_dev_not_found" : "Aucun périphérique compatible uPnP trouvé", "upnp_dev_not_found" : "Aucun périphérique compatible UPnP trouvé",
"upnp_port_open_failed" : "Impossible d'ouvrir les ports avec uPnP", "upnp_port_open_failed" : "Impossible d'ouvrir les ports avec UPnP",
"upnp_enabled" : "uPnP activé avec succès", "upnp_enabled" : "UPnP activé avec succès",
"upnp_disabled" : "uPnP désactivé avec succès", "upnp_disabled" : "UPnP désactivé avec succès",
"firewall_reloaded" : "Pare-feu rechargé avec succès", "firewall_reloaded" : "Pare-feu rechargé avec succès",
"hook_list_by_invalid" : "Propriété pour lister les scripts incorrecte", "hook_list_by_invalid" : "Propriété pour lister les scripts incorrecte",

View file

@ -318,12 +318,9 @@ def tools_postinstall(domain, password, ignore_dyndns=False):
# Change LDAP admin password # Change LDAP admin password
tools_adminpw(old_password='yunohost', new_password=password) tools_adminpw(old_password='yunohost', new_password=password)
# Enable uPnP # Enable UPnP silently and reload firewall
firewall_upnp(action=['enable']) firewall_upnp('enable', no_refresh=True)
try: firewall_reload()
firewall_reload()
except MoulinetteError:
firewall_upnp(action=['disable'])
# Enable iptables at boot time # Enable iptables at boot time
os.system('update-rc.d yunohost-firewall defaults') os.system('update-rc.d yunohost-firewall defaults')