# # This file is part of Plinth. # # 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 django import forms from django.contrib import messages from django.core import validators from django.core.urlresolvers import reverse_lazy from django.utils.translation import ugettext as _, ugettext_lazy from django.template.response import TemplateResponse import logging from plinth import actions from plinth import cfg from plinth import package logger = logging.getLogger(__name__) EMPTYSTRING = 'none' subsubmenu = [{'url': reverse_lazy('dynamicdns:index'), 'text': ugettext_lazy('About')}, {'url': reverse_lazy('dynamicdns:configure'), 'text': ugettext_lazy('Configure')}, {'url': reverse_lazy('dynamicdns:statuspage'), 'text': ugettext_lazy('Status')}] def init(): """Initialize the dynamicdns module""" menu = cfg.main_menu.get('apps:index') menu.add_urlname(ugettext_lazy('Dynamic DNS'), 'glyphicon-refresh', 'dynamicdns:index', 500) @package.required(['ez-ipupdate']) def index(request): """Serve Dynamic DNS page.""" return TemplateResponse(request, 'dynamicdns.html', {'title': _('Dynamic DNS'), 'subsubmenu': subsubmenu}) class TrimmedCharField(forms.CharField): """Trim the contents of a CharField.""" def clean(self, value): """Clean and validate the field value""" if value: value = value.strip() return super(TrimmedCharField, self).clean(value) class ConfigureForm(forms.Form): """Form to configure the Dynamic DNS client.""" help_update_url = \ ugettext_lazy('The Variables <User>, <Pass>, <Ip>, ' '<Domain> may be used within the URL. For details ' 'see the update URL templates of the example providers.') help_services = \ ugettext_lazy('Please choose an update protocol according to your ' 'provider. If your provider does not support the GnudIP ' 'protocol or your provider is not listed you may use the ' 'update URL of your provider.') help_server = \ ugettext_lazy('Please do not enter a URL here (like ' '"https://example.com/") but only the hostname of the ' 'GnuDIP server (like "example.pcom").') help_domain = \ ugettext_lazy('The public domain name you want use to reach your box.') help_disable_ssl = \ ugettext_lazy('Use this option if your provider uses self signed ' 'certificates.') help_http_auth = \ ugettext_lazy('If this option is selected, your username and password ' 'will be used for HTTP basic authentication.') help_secret = \ ugettext_lazy('Leave this field empty if you want to keep your ' 'previous configured password.') help_ip_url = \ ugettext_lazy('Optional Value. If your FreedomBox is not connected ' 'directly to the Internet (i.e. connected to a NAT ' 'router) this URL is used to figure out the real ' 'Internet IP. The URL should simply return the IP where' 'the client comes from. Example: ' 'http://myip.datasystems24.de') help_user = \ ugettext_lazy('You should have been requested to select a username ' 'when you created the account.') """ToDo: sync this list with the html template file""" provider_choices = ( ('GnuDIP', 'GnuDIP'), ('noip', 'noip.com'), ('selfhost', 'selfhost.bz'), ('freedns', 'freedns.afraid.org'), ('other', 'other update URL')) enabled = forms.BooleanField(label=ugettext_lazy('Enable Dynamic DNS'), required=False) service_type = forms.ChoiceField(label=ugettext_lazy('Service type'), help_text=help_services, choices=provider_choices) dynamicdns_server = TrimmedCharField( label=ugettext_lazy('GnudIP Server Address'), required=False, help_text=help_server, validators=[ validators.RegexValidator(r'^[\w-]{1,63}(\.[\w-]{1,63})*$', ugettext_lazy('Invalid server name'))]) dynamicdns_update_url = TrimmedCharField( label=ugettext_lazy('Update URL'), required=False, help_text=help_update_url) disable_SSL_cert_check = forms.BooleanField( label=ugettext_lazy('accept all SSL certificates'), help_text=help_disable_ssl, required=False) use_http_basic_auth = forms.BooleanField( label=ugettext_lazy('use HTTP basic authentication'), help_text=help_http_auth, required=False) dynamicdns_domain = TrimmedCharField( label=ugettext_lazy('Domain Name'), help_text=help_domain, required=False, validators=[ validators.RegexValidator(r'^[\w-]{1,63}(\.[\w-]{1,63})*$', ugettext_lazy('Invalid domain name'))]) dynamicdns_user = TrimmedCharField( label=ugettext_lazy('Username'), required=False, help_text=help_user) dynamicdns_secret = TrimmedCharField( label=ugettext_lazy('Password'), widget=forms.PasswordInput(), required=False, help_text=help_secret) showpw = forms.BooleanField(label=ugettext_lazy('show password'), required=False) dynamicdns_ipurl = TrimmedCharField( label=ugettext_lazy('IP check URL'), required=False, help_text=help_ip_url, validators=[ validators.URLValidator(schemes=['http', 'https', 'ftp'])]) def clean(self): cleaned_data = super(ConfigureForm, self).clean() dynamicdns_secret = cleaned_data.get('dynamicdns_secret') dynamicdns_update_url = cleaned_data.get('dynamicdns_update_url') dynamicdns_user = cleaned_data.get('dynamicdns_user') dynamicdns_domain = cleaned_data.get('dynamicdns_domain') dynamicdns_server = cleaned_data.get('dynamicdns_server') service_type = cleaned_data.get('service_type') old_dynamicdns_secret = self.initial['dynamicdns_secret'] # Clear the fields which are not in use if service_type == 'GnuDIP': dynamicdns_update_url = '' else: dynamicdns_server = '' if cleaned_data.get('enabled'): # Check if gnudip server or update URL is filled if not dynamicdns_update_url and not dynamicdns_server: raise forms.ValidationError( _('Please provide update URL or a GnuDIP Server')) if dynamicdns_server and not dynamicdns_user: raise forms.ValidationError(_('Please provide GnuDIP username')) if dynamicdns_server and not dynamicdns_domain: raise forms.ValidationError(_('Please provide GnuDIP domain')) # Check if a password was set before or a password is set now if dynamicdns_server and \ not dynamicdns_secret and not old_dynamicdns_secret: raise forms.ValidationError(_('Please provide a password')) @package.required(['ez-ipupdate']) def configure(request): """Serve the configuration form.""" status = get_status() form = None if request.method == 'POST': form = ConfigureForm(request.POST, initial=status) if form.is_valid(): _apply_changes(request, status, form.cleaned_data) status = get_status() form = ConfigureForm(initial=status) else: form = ConfigureForm(initial=status) return TemplateResponse(request, 'dynamicdns_configure.html', {'title': _('Configure Dynamic DNS'), 'form': form, 'subsubmenu': subsubmenu}) @package.required(['ez-ipupdate']) def statuspage(request): """Serve the status page.""" check_nat = actions.run('dynamicdns', ['get-nat']) last_update = actions.run('dynamicdns', ['get-last-success']) no_nat = check_nat.strip() == 'no' nat_unchecked = check_nat.strip() == 'unknown' timer = actions.run('dynamicdns', ['get-timer']) if no_nat: logger.info('Not behind a NAT') if nat_unchecked: logger.info('Did not check if we are behind a NAT') return TemplateResponse(request, 'dynamicdns_status.html', {'title': _('Status of Dynamic DNS'), 'no_nat': no_nat, 'nat_unchecked': nat_unchecked, 'timer': timer, 'last_update': last_update, 'subsubmenu': subsubmenu}) def get_status(): """Return the current status.""" # TODO: use key/value instead of hard coded value list status = {} output = actions.run('dynamicdns', ['status']) details = output.split() status['enabled'] = (output.split()[0] == 'enabled') if len(details) > 1: if details[1] == 'disabled': status['dynamicdns_server'] = '' else: status['dynamicdns_server'] = details[1].replace("'", "") else: status['dynamicdns_server'] = '' if len(details) > 2: if details[2] == 'disabled': status['dynamicdns_domain'] = '' else: status['dynamicdns_domain'] = details[2].replace("'", "") else: status['dynamicdns_domain'] = '' if len(details) > 3: if details[3] == 'disabled': status['dynamicdns_user'] = '' else: status['dynamicdns_user'] = details[3].replace("'", "") else: status['dynamicdns_user'] = '' if len(details) > 4: if details[4] == 'disabled': status['dynamicdns_secret'] = '' else: status['dynamicdns_secret'] = details[4].replace("'", "") else: status['dynamicdns_secret'] = '' if len(details) > 5: if details[5] == 'disabled': status['dynamicdns_ipurl'] = '' else: status['dynamicdns_ipurl'] = details[5].replace("'", "") else: status['dynamicdns_ipurl'] = '' if len(details) > 6: if details[6] == 'disabled': status['dynamicdns_update_url'] = '' else: status['dynamicdns_update_url'] = details[6].replace("'", "") else: status['dynamicdns_update_url'] = '' if len(details) > 7: status['disable_SSL_cert_check'] = (output.split()[7] == 'enabled') else: status['disable_SSL_cert_check'] = False if len(details) > 8: status['use_http_basic_auth'] = (output.split()[8] == 'enabled') else: status['use_http_basic_auth'] = False if not status['dynamicdns_server'] and not status['dynamicdns_update_url']: status['service_type'] = 'GnuDIP' elif not status['dynamicdns_server'] and status['dynamicdns_update_url']: status['service_type'] = 'other' else: status['service_type'] = 'GnuDIP' return status def _apply_changes(request, old_status, new_status): """Apply the changes to Dynamic DNS client.""" logger.info('New status is - %s', new_status) logger.info('Old status was - %s', old_status) if new_status['dynamicdns_secret'] == '': new_status['dynamicdns_secret'] = old_status['dynamicdns_secret'] if new_status['dynamicdns_ipurl'] == '': new_status['dynamicdns_ipurl'] = EMPTYSTRING if new_status['dynamicdns_update_url'] == '': new_status['dynamicdns_update_url'] = EMPTYSTRING if new_status['dynamicdns_server'] == '': new_status['dynamicdns_server'] = EMPTYSTRING if new_status['service_type'] == 'GnuDIP': new_status['dynamicdns_update_url'] = EMPTYSTRING else: new_status['dynamicdns_server'] = EMPTYSTRING if old_status != new_status: disable_ssl_check = "disabled" use_http_basic_auth = "disabled" if new_status['disable_SSL_cert_check']: disable_ssl_check = "enabled" if new_status['use_http_basic_auth']: use_http_basic_auth = "enabled" _run(['configure', '-s', new_status['dynamicdns_server'], '-d', new_status['dynamicdns_domain'], '-u', new_status['dynamicdns_user'], '-p', '-I', new_status['dynamicdns_ipurl'], '-U', new_status['dynamicdns_update_url'], '-c', disable_ssl_check, '-b', use_http_basic_auth], input=new_status['dynamicdns_secret'].encode()) if old_status['enabled']: _run(['stop']) if new_status['enabled']: _run(['start']) messages.success(request, _('Configuration updated')) else: logger.info('Nothing changed') def _run(arguments, superuser=False, input=None): """Run a given command and raise exception if there was an error.""" command = 'dynamicdns' if superuser: return actions.superuser_run(command, arguments, input=input) else: return actions.run(command, arguments, input=input)