#!/usr/libexec/platform-python

## hylafax-enterprise-appliance-console
## Copyright (C) 2010-2022 iFAX Solutions, Inc.
## Copyright (C) 2010-2022 Patrice Fournier <patrice.fournier@ifax.com>

import sys
import locale
import time
import socket
import os
import fcntl
import struct
import subprocess
import configparser
import syslog
import shutil
import select
import re
import signal
import termios
import traceback
import errno
import smtplib
import distro
import netifaces
import pwd
import types

from snack import *

APPLIANCE_NAME = "HylaFAX Enterprise Appliance"
BASE_VERSION = "6.6.23.1"
LICENSE_VERSION = 6.0
APPLIANCE_VERSION = str(BASE_VERSION)
OS_VERSION = int(distro.major_version())

#
# Default config values
#
configDefaults = {"spool": "/var/spool/hylafax",
                  "logfacility": "user",
                  "loglevel": "notice",
                  "interface": None}

class Progress:
    def __init__(self, screen, title):
        self.screen = screen
        grid = Grid(1, 2)
        self.scale = Scale(60, 100)
        self.scale.set(0)
        self.text = Textbox(60, 1, "")
        grid.setField(self.scale, 0, 0)
        grid.setField(self.text, 0, 1)
        self.screen.gridWrappedWindow(grid, title)
        self.form = Form()
        self.form.add(grid)
        self.screen.refresh()

    def update(self, value, text):
        self.scale.set(value)
        self.text.setText(text)
        self.form.draw()
        self.screen.refresh()

    def done(self):
        self.screen.popWindow()


def log_emergency(message):
    syslog.syslog(syslog.LOG_EMERG, message)

def log_alert(message):
    syslog.syslog(syslog.LOG_ALERT, message)

def log_critical(message):
    syslog.syslog(syslog.LOG_CRIT, message)

def log_error(message):
    syslog.syslog(syslog.LOG_ERR, message)

def log_warning(message):
    syslog.syslog(syslog.LOG_WARNING, message)

def log_notice(message):
    syslog.syslog(syslog.LOG_NOTICE, message)

def log_info(message):
    syslog.syslog(syslog.LOG_INFO, message)

def log_debug(message):
    syslog.syslog(syslog.LOG_DEBUG, message)

def log_exception(type, value, tb):
    log_debug("Exception occurred, traceback to follow:")
    for line in traceback.format_exception(type, value, tb):
        log_error(line)
    sys.__excepthook__(type, value, tb)
sys.excepthook = log_exception

#
# Function to get the IP address of a network interface
#
def get_ip_address(ifname):
    if not ifname:
        interfaces = netifaces.interfaces()
        for name in interfaces:
            try:
                address = netifaces.ifaddresses(name)[netifaces.AF_INET][0]['addr']
                if address and address != '127.0.0.1':
                    ifname = name
                    break
                else:
                    address = None
            except Exception:
                address = None
    else:
        try:
            address = netifaces.ifaddresses(ifname)[netifaces.AF_INET][0]['addr']
        except Exception:
            address = None

    if not address:
        if ifname:
            log_warning("Could not obtain IP address for interface " + ifname)
        else:
            log_warning("Could not find a configured network interface")
        return False
    log_debug("Interface " + ifname + " IP address is " + address)
    return address

def pause():
    input("\n\nPress [Enter] to continue...")

def parse_boolean(value):
    value = value.lower().strip()
    if len(value) < 1:
        return False
    return (value == "true" or value == "yes" or value == "y" or value == "1")

def print_boolean(value):
    if value:
        return "True"
    else:
        return "False"

def center_text(text, width):
    lines = text.split("\n")
    i = 0
    while i < len(lines):
        if len(lines[i]) < width - 2:
            lines[i] = " " * int((width - len(lines[i])) / 2) + lines[i]
        i += 1
    return "\n".join(lines)

def warning(message):
    width = 50
    title = "Warning"
    buttons = ["Ok"]

    ButtonChoiceWindow(screen, title, message, buttons, width)

def reset_child_handlers(waitForCompletion=False):
    log_debug("Resetting child signal handlers")
    signal.signal(signal.SIGHUP, signal.SIG_IGN)
    signal.signal(signal.SIGUSR2, signal.SIG_IGN)

    signal.signal(signal.SIGALRM, signal.SIG_DFL)
    signal.signal(signal.SIGTERM, signal.SIG_DFL)

    if waitForCompletion:
        signal.signal(signal.SIGUSR1, signal.SIG_IGN)
    else:
        signal.signal(signal.SIGUSR1, signal.SIG_DFL)

class AlarmSignal(Exception): pass
def sigalrm_handler(signum, frame):
    log_info("Received ALRM signal, checking license status")
    license_status()
    # Reseting alarm:
    signal.alarm(3600)
    raise AlarmSignal

def sighup_handler(signum, frame):
    if sys.stdin.isatty():
        log_info("Received HUP signal, tty still connected, ignoring")
    else:
        log_info("Received HUP signal, tty disconnected, exiting")
        terminate_children(signal.SIGHUP)
        if consolePid == os.getpid():
            sys.exit(3)
        else:
            os._exit(3)

def sigusr1_handler(signum, frame):
    log_info("Received USR1 signal, forcing console restart")
    terminate_children()
    if consolePid == os.getpid():
        console_restart()
    else:
        os._exit(1)

def sigterm_handler(signum, frame):
    log_info("Received TERM signal, forcing console termination")
    terminate_children(signal.SIGTERM)
    if consolePid == os.getpid():
        sys.exit(15)
    else:
        os._exit(15)

def sigwinch_handler(signum, frame):
    log_info("Received WINCH signal, passing down to children")
    signal_children(signal.SIGWINCH)

def signal_children(killSignal):
    global childPid
    if childPid:
        log_debug("Sending signal " + str(killSignal) + " to child: " +
                  str(childPid))
        try:
            os.kill(childPid, killSignal)
        except OSError as e:
            if e.errno != errno.ESRCH:
                # No such process
                childPid = None
                return False
            else:
                raise
        return True
    return False

def terminate_children(killSignal=signal.SIGUSR1):
    global childPid
    if childPid:
        if signal_children(killSignal):
            os.waitpid(childPid, 0)
            childPid = None
            return True
    return False

def console_restart():
    log_debug("Restarting console with: " + os.path.abspath(__file__))
    os.execl(os.path.abspath(__file__), sys.argv[0])
    log_error("Problem restarting console")

def snack_fork(function, waitForCompletion=False):
    global childPid
    pid = os.fork()
    if pid == 0:    # Child
        reset_child_handlers(waitForCompletion)
        log_debug("Snack child pid: " + str(os.getpid()))
        rc = function()
        if rc:
            os._exit(0)
        else:
            os._exit(1)
    else:
        # Just in case we are already in a child:
        prevSighup = signal.signal(signal.SIGHUP, sighup_handler)
        prevSigusr1 = signal.signal(signal.SIGUSR1, sigusr1_handler)
        prevSigterm = signal.signal(signal.SIGTERM, sigterm_handler)
        prevSigwinch = signal.signal(signal.SIGWINCH, sigwinch_handler)
        log_debug("Snack child pid: " + str(pid))
        childPid = pid
        while True:
            try:
                try:
                    pid, status = os.waitpid(pid, 0)
                except AlarmSignal:
                    continue
                except OSError as e:
                    if e.errno == errno.EINTR:
                        # Interrupted system call
                        log_debug("Restarting interrupted system call")
                        continue
                    else:
                        raise
            except:
                log_error("Unhandled exception trapped, killing children")
                terminate_children(signal.SIGTERM)
                raise
            break
        log_debug("waitpid returned pid " + str(pid) + " and status " +
                  str(status))
        signal.signal(signal.SIGHUP, prevSighup)
        signal.signal(signal.SIGUSR1, prevSigusr1)
        signal.signal(signal.SIGTERM, prevSigterm)
        signal.signal(signal.SIGWINCH, prevSigwinch)
        if childPid != pid:
            log_debug("Got pid " + str(pid) + " while waiting for pid " +
                      str(childPid))
            childPid = None
            raise Exception("Invalid pid returned from waitpid")
        else:
            childPid = None
            return status == 0

def execute(command, returnOutput=False):
    global childPid
    log_debug("Executing command: " + str(command))
    prevSigterm = signal.signal(signal.SIGTERM, sigterm_handler)
    process = subprocess.Popen(command, stdout=subprocess.PIPE,
                               stderr=subprocess.PIPE,
                               encoding="utf8")
    childPid = process.pid
    output = process.communicate()
    childPid = None
    signal.signal(signal.SIGTERM, prevSigterm)
    if process.returncode != 0:
        if not returnOutput:
            warning("The following command returned an error:\n\n" +
                    str(command) + "\n\n" +
                    "------ STDERR OUTPUT ------\n" +
                    output[1] + "\n\n" +
                    "------ STDOUT OUTPUT ------\n" +
                    output[0])
        log_warning("Command execution failed with error code: " +
                    str(process.returncode))
        if output[1]:
            log_warning("STDERR output:\n" + output[1])
        if output[0]:
            log_warning("STDOUT output:\n" + output[0])
    else:
        log_debug("Command execution succeeded")
    if returnOutput:
        return {"returncode": process.returncode, "stderr": output[1],
                "stdout": output[0]}
    else:
        return process.returncode

def service(service, command):
    return execute(["env", "BOOTUP=serial", "/usr/bin/systemctl", command, service])

#
# The hostname of the machine must resolve to an IP address of the local
# machine if we want the license manager to work. This function makes
# sure the hostname is configured for 127.0.1.1.
#
def check_hostname():
    hostname = socket.gethostname()
    file = "/etc/hosts"
    if read_item(file, "127.0.1.1", "\t") != hostname:
        log_debug("Updating hosts file for new hostname: " + str(hostname))
        update_item(file, "127.0.1.1", "\t", hostname)

def check_network():
    global ipAddress
    newAddress = get_ip_address(interface)
    while not newAddress:
        if interface:
            warning("Network device " + interface +
                    " is not configured correctly.\n\n" +
                    "Press OK to configure the network")
            log_warning("Network device " + interface +
                        " is not configured correctly")
        else:
            warning("Network is not configured correctly.\n\n" +
                    "Press OK to configure the network")
            log_warning("Network is not configured correctly")
        config_network()
        newAddress = get_ip_address(interface)
    check_hostname()
    if ipAddress != newAddress:
        if ipAddress:
            if interface:
                log_notice("Interface " + interface + " IP address changed")
            else:
                log_notice("First configured interface IP address changed")
        progress = Progress(screen, "Reconfiguring HylaFAX Enterprise")
        progress.update(1, "Checking sip gateway configuration")
        log_info("Checking sipgateway configuration")
        config = read_sip_config()
        if config["sipdevices"] > 0 and config["loopback"]:
            progress.update(10, "Updating sip gateway configuration")
            log_info("Updating sipgateway configuration")
            config["sipgateway"] = get_ip_address(interface) + ":5060"
            save_sip_config(config)
            if ipAddress:
                log_debug("Checking status of HBF")
                rc = subprocess.call(["systemctl", "status", "ifax-hbf"],
                                     stdout=devNull, stderr=devNull)
                if rc == 0:
                    progress.update(50, "Restarting HBF")
                    log_notice("Restarting HBF")
                    rc = service("ifax-hbf", "restart")
        ipAddress = newAddress
        progress.done()

def license_status():
    license = get_license()
    if license:
        if license["expiration"] and license["expiration"] > time.gmtime():
            status = 2
            text = "The evaluation of the appliance will expire on " + \
                   time.strftime("%Y-%m-%d", license["expiration"]) + "."
        elif license["expiration"]:
            status = -1
            text = "The evaluation of the appliance has expired!"
        else:
            status = 1
            text = ""
    else:
        status = 0
        text = "The appliance is unlicensed!"
    license_status.__dict__.setdefault("status", None)
    if license_status.status != status:
        # Must set static status first if we don't want to end in endless loop
        license_status.status = status
        update_status_file()
    return {"status":status, "text":text}

def update_status_file():
    info = None
    license = None
    try:
        try:
            info = open(os.path.join(spool, "status/any.info"), "w")
            license = open(os.path.join(spool, "status/any.license"), "w")
            info.write(APPLIANCE_NAME + " " + APPLIANCE_VERSION + "\n")
            status = license_status()["text"]
            if status:
                info.write(status + "\n")
                license.write(status + "\n")
            faxmaster = read_mail_config()["faxmaster"]
            if faxmaster:
                info.write("FaxMaster: " + faxmaster + "\n")
        except IOError:
            pass
    finally:
        if info:
            info.close()
        if license:
            license.close()

def config_network():
    global childPid
    screen.suspend()
    log_debug("Executing network configuration tool")
    prevSigterm = signal.signal(signal.SIGTERM, sigterm_handler)
    prog = "nmtui"
    process = subprocess.Popen(prog, encoding="utf8")
    childPid = process.pid
    log_debug("Network configuration tool started with pid " + str(childPid))
    pid, status = os.waitpid(process.pid, 0)
    log_debug("waitpid returned pid " + str(pid) + " and status " +
              str(status))
    signal.signal(signal.SIGTERM, prevSigterm)
    if childPid != pid:
        log_debug("Got pid " + str(pid) + " while waiting for pid " +
                  str(childPid))
        childPid = None
        raise Exception("Invalid pid returned from waitpid")
    else:
        childPid = None
    log_debug("Network configuration tool exited with value: " +
              str(status % 0xFFFF))
    screen.resume()
    return status == 0

def email_admin(subject, body):
    log_debug("Sending email to admin about: " + subject)
    faxmaster = read_mail_config()["faxmaster"]
    if faxmaster:
        sender = "faxmaster"
    else:
        sender = "root"
    to = sender
    message = "From: " + APPLIANCE_NAME + "<" + sender + ">\n" + \
              "To: " + to + "\n" + \
              "Subject: " + subject + "\n\n" + \
              body
    log_debug("Connecting to SMTP server")
    mail = smtplib.SMTP("localhost")
    log_debug("Sending message")
    mail.sendmail(sender, to, message)
    log_debug("Done sending message")
    mail.quit

def read_item(filename, item, separator):
    item = item.lower()

    log_debug("Checking " + filename + " for item: " + item)
    try:
        file = open(filename)
    except IOError:
        log_debug("IO Error opening " + filename)
        return False
    try:
        for line in file:
            if line.find(separator) == -1:
                continue
            var, value = line.split(separator, 1)
            var = var.lower().strip()
            value = value.strip()
            if var == item:
                log_debug('Found value of ' + item + ' to be "' + value + '"')
                return value
    finally:
        file.close()

    log_debug("Item not found in file")
    return ""

#
# Update a single line of a file.
# Searches [filename] for a line that starts with [item] followed by
# optional whitespace and [separator]. The line is then replaced by
# a line containing [item], [separator], a single white space and [value]
# If block is specified, the item will need to be in a block of that name
# as delimited by lines matching "[<blockname>]"
#
def update_item(filename, item, separator, value, comment="", block=""):
    if value is not None:
        newline = item + separator + " " + str(value) + "\n"
    else:
        newline = ""
    newfilename = filename + ".new"
    try:
        oldfile = open(filename, "r")
    except IOError:
        log_debug("File not found: " + filename + ", creating new")
        oldfile = []
    newfile = open(newfilename, "w")
    try:
        step = 0
        if not block:
            step = 1
        for line in oldfile:
            if block and line.startswith("[") and line.strip().endswith("]"):
                if line.strip() == "[" + block + "]":
                    step = 1
                elif step == 1:
                    # The block is finished and the line was not replaced
                    log_debug("Item not found in block" + block)
                    log_debug("Adding item " + item + " to file: " + filename)
                    if comment:
                        newfile.write("# " + comment + "\n")
                    newfile.write(newline)
                    step = 2
            if step == 1 and line.find(separator) != -1:
                lineItem = line.split(separator, 1)[0].lower().strip()
                if lineItem == item.lower():
                    step = 2
                    log_debug("Replacing item " + item + " in file: " + filename)
                    newfile.write(newline)
                    continue
            newfile.write(line)
    except IOError:
        log_error("Error updating item. File not replaced.")
        if oldfile:
            oldfile.close()
        newfile.close()
        os.remove(newfilename)
        return False

    if step == 0:
        log_debug("Creating block: " + block + " in file: " + filename)
        newfile.write("\n")
        newfile.write("[" + block + "]\n")
        step = 1
    if step == 1:
        log_debug("Adding item " + item + " to file: " + filename)
        if not block:
            newfile.write("\n")
        if comment:
            newfile.write("# " + comment + "\n")
        newfile.write(newline)
    if oldfile:
        oldfile.close()
    newfile.close()
    os.rename(newfilename, filename)
    return True

def remove_item(filename, item, separator):
    return update_item(filename, item, separator, None)

# The create_from_template function will copy the content of template into
# filename replacing any @var@ with the value of options["var"]
def create_from_template(filename, template, options):
    head = "# This file is managed by the " + \
           "HylaFAX Enterprise Appliance Console.\n" + \
           "# This file is re-written as needed by the console and any\n" + \
           "# addition you make can be discarded without notice.\n" + \
           "# You have been warned.\n\n"
    log_info("Updating HylaFAX Appliance callctrl configuration file")

    templatefile = open(os.path.join(spool, template), "r")
    configfile = open(os.path.join(spool, filename), "w")
    try:
        log_debug("Writing HylaFAX site config file")
        configfile.write("# HylaFAX Appliance callctrl configuration file.\n")
        configfile.write(head)
        configfile.write("# Generated from " + template + " by " +
                         "hylafax-enterprise-appliance-console on " +
                         time.strftime("%Y-%m-%d %H:%M:%S") + "\n\n")
        for line in templatefile:
            if line.strip().startswith(";"):
                # Lines starting with ; are comments for the template and not
                # copied over to the configuration file
                continue
            replacedOne = False
            replacedAll = True
            for match in re.finditer(r'@([^@]+)@', line):
                var = match.group(1)
                if var in options and not options[var] in ((None, "")):
                    value = str(options[var])
                    replacedOne = True
                else:
                    value = ""
                    replacedAll = False
                line = line[:match.start()] + value + line[match.end():]
            if replacedAll:
                configfile.write(line)
    finally:
        log_debug("Closing HylaFAX Appliance callctrl configuration file")
        configfile.close()
        templatefile.close()
    return True

def get_license():
    log_debug("Get license values")
    command = ["env", "LM_LICENSE_FILE=/etc/ifax", "/usr/sbin/lmstat", "-i"]
    process = subprocess.Popen(command, stdout=subprocess.PIPE, encoding="utf8")
    values = {"version": 0.0,
              "expiration": time.gmtime(0),
              "faxgetty": 0,
              "hbf": 0,
              "sr140": 0,
              "sr140expiration": time.gmtime(0)}
    now = time.gmtime()
    for filename in os.listdir("/etc/ifax"):
        if not (filename.startswith("sr140") and filename.endswith("key")):
            continue
        filename = os.path.join("/etc/ifax", filename)
        try:
            file = open(filename, "r")
        except:
            log_debug("Unable to open file: " + filename)
            continue
        step = 0
        log_debug("Checking file '" + filename + "' for SR140 license")
        for line in file:
            if step == 0 and line.startswith("FEATURE SR140"):
                log_debug("Found 'SR140' line: " + line)
                match = re.match(r'FEATURE SR140(-DEMO)? BRKTD ([0-9.]+) ' +
                                 r'([0-9a-z-]+)', line)
                log_debug("Value 1: " + match.group(2))
                log_debug("Value 2: " + match.group(3))
                version = float(match.group(2))
                expiration = match.group(3)
                if expiration == "permanent":
                    expiration = None
                else:
                    oldlocale = locale.setlocale(locale.LC_TIME)
                    locale.setlocale(locale.LC_TIME, 'C')
                    try:
                        expiration = time.strptime(expiration, "%d-%b-%Y")
                    except ValueError:
                        log_debug("Unable to parse license expiration: " + expiration)
                        expiration = time.gmtime(0)
                    locale.setlocale(locale.LC_TIME, oldlocale)
                step = 1
            if step == 1 and line.find("VENDOR_STRING") != -1:
                log_debug("Found vendor string line: " + line)
                match = re.match(r'.*:FaxCh=([0-9]+):', line)
                log_debug("Value 1: " + match.group(1))
                channels = int(match.group(1))
                if not expiration or expiration >= now:
                    values["sr140"] += channels
                    if expiration and (values["expiration"] < now or
                                       expiration < values["sr140expiration"]):
                        values["sr140expiration"] = expiration
                step = 2
            if step == 2:
                if not line.endswith("\\\n"):
                    step = 0
        file.close()
    for line in process.communicate()[0].split("\n"):
        if line.startswith("FaxGetty"):
            log_debug("Found 'FaxGetty' line: " + line)
            match = re.match(r'^FaxGetty[ \t]+([0-9.]+)[ \t]+([0-9]+)[ \t]+' +
                             r'(?:([0-9a-z-]+)[ \t]+ifax|ifax[ \t]+([0-9a-z-]+))', line)
            log_debug("Value 1: " + match.group(1))
            log_debug("Value 2: " + match.group(2))
            if match.group(3):
                log_debug("Value 3: " + match.group(3))
                expiration = match.group(3).strip()
            else:
                log_debug("Value 3: " + match.group(4))
                expiration = match.group(4).strip()
            version = float(match.group(1))
            faxgetty = int(match.group(2))
            if expiration == "1-jan-0":
                expiration = None
            elif expiration == "permanent":
                expiration = None
            else:
                oldlocale = locale.setlocale(locale.LC_TIME)
                locale.setlocale(locale.LC_TIME, 'C')
                try:
                    expiration = time.strptime(expiration, "%d-%b-%Y")
                except ValueError:
                    log_debug("Unable to parse license expiration: " + expiration)
                    expiration = time.gmtime(0)
                locale.setlocale(locale.LC_TIME, oldlocale)
            if (values["version"] == 0.0 or
                (version > values["version"] and
                 values["version"] < LICENSE_VERSION) or
                (version >= values["version"] and
                 values["expiration"] is not None and
                 (expiration is None or expiration >= values["expiration"]))):
                values["version"] = version
                values["faxgetty"] = faxgetty
                values["expiration"] = expiration
        elif line.startswith("HBF"):
            log_debug("Found 'HBF' line: " + line)
            match = re.match(r'^HBF[ \t]+([0-9.]+)[ \t]+([0-9]+)[ \t]+' +
                             r'([0-9a-z-]+)[ \t]+ifax', line)
            log_debug("Value 1: " + match.group(1))
            log_debug("Value 2: " + match.group(2))
            log_debug("Value 3: " + match.group(3))
            values["hbf"] = int(match.group(2))
    if values["version"] < LICENSE_VERSION:
        log_info("No valid license found")
        return None
    else:
        if values["expiration"]:
            log_debug("License expiring on " +
                      time.strftime("%Y-%m-%d", values["expiration"]) +
                      " found for version " + str(values["version"]))
        else:
            log_debug("Permanent license found for version " +
                      str(values["version"]))
        log_debug("License for " + str(values["faxgetty"]) + " channels")
        log_debug("License for " + str(values["hbf"]) + " HBF devices")
        log_debug("License for " + str(values["sr140"]) + " SR140 devices")
        return values

def get_hostid():
    log_debug("Obtaining hostid")
    command = ["/usr/sbin/lmhostid", "-n"]
    process = subprocess.Popen(command, stdout=subprocess.PIPE, encoding="utf8")
    hostid = process.communicate()[0].strip()
    hostid = re.sub(r'"?([0-9a-fA-F]{12}).*', r"\1", hostid)
    return hostid

def get_serial(hostid):
    serial = None
    baseLicense = False
    hostid = hostid.lower()
    log_debug("Obtaining serial number")
    for file in os.listdir("/etc/ifax"):
        file = os.path.join("/etc/ifax", file)
        if not file.endswith(".lic"):
            continue
        license = read_item(file, "FEATURE FaxQueuer", "ifax")
        if license:
            baseLicense = True
        else:
            license = read_item(file, "UPGRADE FaxQueuer ifax", "1.0")
        if license:
            licenseVersion = float(license.split()[0])
            log_debug('License version is: "' + str(licenseVersion) + '"')
            licenseExpiration = license.split()[1]
            log_debug('License expiration is: "' + licenseExpiration + '"')
            licenseHostid = re.sub(r".*HOSTID=([0-9a-fA-F]{12}).*", r"\1",
                                   license).lower()
            log_debug('License HOSTID is: "' + licenseHostid + '"')
            if licenseHostid == hostid:
                if licenseVersion >= LICENSE_VERSION:
                    serial = read_item(file, "#IFAX EE", ":")
                    serial = re.sub(r'^.*\[(.*)\].*$', r'\1', serial)
    if baseLicense:
        return serial
    return None

def read_systemd():
    devices = []
    path = "/etc/systemd/system/hylafax.service.wants"
    if not os.path.isdir(path):
        log_debug("No systemd configured device found")
        return None

    dir = os.listdir(path)
    for filename in dir:
        device = ""
        if filename.endswith(".service"):
            if filename.startswith("hylafax-btgetty@"):
                device = filename[16:-8]
            elif filename.startswith("hylafax-faxgetty@"):
                device = filename[17:-8]
            if device:
                devices.append(device)
                log_debug("Found systemd configured device: " + device)
            continue
    rc = execute(["systemctl", "daemon-reload"])
    return devices

def save_systemd(devices, startDevices=True):
    path = "/etc/systemd/system/hylafax.service.wants"
    if not os.path.isdir(path):
        os.mkdir(path, 0o755)
    oldDevices = read_systemd()
    servicesStart = []
    servicesStop = []
    for device in list(set(devices) - set(oldDevices)):
        if device.startswith("boston"):
            deviceType = "btgetty"
        else:
            deviceType = "faxgetty"
        serviceName = "hylafax-" + deviceType + "@" + device + ".service"
        linkName = path + "/" + serviceName
        if not os.path.isfile(linkName):
            os.symlink("/usr/lib/systemd/system/hylafax-" + deviceType +
                       "@.service", linkName)
            servicesStart.append(serviceName)
    for device in list(set(oldDevices) - set(devices)):
        if device.startswith("boston"):
            deviceType = "btgetty"
        else:
            deviceType = "faxgetty"
        serviceName = "hylafax-" + deviceType + "@" + device + ".service"
        linkName = path + "/" + serviceName
        if os.path.isfile(linkName):
            os.remove(linkName)
            servicesStop.append(serviceName)
    rc = execute(["systemctl", "daemon-reload"])
    for service in servicesStop:
        rc = execute(["systemctl", "stop", service])
    if startDevices:
        for service in servicesStart:
            rc = execute(["systemctl", "start", service])
    return False

def read_init():
    return read_systemd()

def save_init(devices):
    return save_systemd(devices)

def read_config(filename, config):
    log_info("Reading configuration file: " + str(filename))
    try:
        configFile = open(filename)
    except IOError:
        log_warning("Error reading configuration file: " + str(filename))
        return config

    try:
        for line in configFile:
            if line.startswith("#") or line.startswith("\n"):
                continue
            if line.find(":") == -1:
                continue
            var, value = line.split(":", 1)
            var = var.lower().strip()
            value = value.strip()
            if value.startswith('"'):
                # This is only guaranteed to work for lines generated by us
                value = value[1:value.rindex('"')]
                value = value.replace('\\"', '"').replace('\\\\', '\\')
            if var in config:
                log_debug(var + " is set to " + value)
                config[var] = value
            else:
                log_warning("Unknown setting: " + var)
    finally:
        if configFile:
            log_debug("Closing configuration file")
            configFile.close()

    return config

def config_write(file, var, value):
    value = str(value)
    if value.find(" ") != -1:
        value = '"' + value.replace('\\', '\\\\').replace('"', '\\"') + '"'
    file.write(var + ": " + value + "\n")
    return True

def read_http_proxy_config():
    curlfile = home + "/.curlrc"
    yumfile = "/etc/yum.conf"
    config = {}
    curlProxy = read_item(curlfile, "proxy", "=") or ""
    yumProxy = read_item(yumfile, "proxy", "=") or ""
    curlItem = read_item(curlfile, "proxy-user", "=") or ""
    if curlItem.find(":") == -1:
        curlUser = curlItem
        curlPass = ""
    else:
        curlUser, curlPass = curlItem.split(":", 1)
    yumUser = read_item(yumfile, "proxy_username", "=")
    yumPass = read_item(yumfile, "proxy_password", "=")

    if curlProxy == yumProxy or curlProxy == "":
        config["proxyurl"] = yumProxy
        if curlProxy == "" or (curlUser == yumUser and curlPass == yumPass):
            config["proxyuser"] = yumUser
            config["proxypass"] = yumPass
        elif curlUser == "" and curlPass == "":
            log_warning("Authentication values were only set in Yum " +
                        "configuration. Using those values.")
            config["proxyuser"] = yumUser
            config["proxypass"] = yumPass
        elif yumUser == "" and yumPass == "":
            log_warning("Authentication values were only set in Curl " +
                        "configuration. Using those values.")
            config["proxyuser"] = curlUser
            config["proxypass"] = curlPass
        else:
            log_warning("Conflicting authentication values. " +
                        "Using Yum configuration.")
            config["proxyuser"] = yumUser
            config["proxypass"] = yumPass
    elif yumProxy == "":
        config["proxyurl"] = curlProxy
        config["proxyuser"] = curlUser
        config["proxypass"] = curlPass
    else:
        log_warning("Conflicting proxy configuration: " + yumProxy + " and " + \
                    curlProxy + ". Going with Yum configuration.")
        config["proxyurl"] = yumProxy
        config["proxyuser"] = yumUser
        config["proxypass"] = yumPass
    return config

def save_http_proxy_config(config):
    curlfile = home + "/.curlrc"
    yumfile = "/etc/yum.conf"
    if config["proxyurl"] == "":
        config["proxyurl"] = None
    update_item(curlfile, "proxy", "=", config["proxyurl"])
    update_item(yumfile, "proxy", "=", config["proxyurl"], "", "main")
    if config["proxyuser"] != "" or config["proxypass"] != "":
        arg = config["proxyuser"] + ":" + config["proxypass"]
    else:
        config["proxyuser"] = None
        config["proxypass"] = None
        arg = None
    update_item(curlfile, "proxy-user", "=", arg)
    update_item(yumfile, "proxy_username", "=", config["proxyuser"], "", "main")
    update_item(yumfile, "proxy_password", "=", config["proxypass"], "", "main")
    return True

def config_http_proxy():
    log_debug("Entering HTTP proxy configuration menu")
    config = read_http_proxy_config()
    width = 60
    title = "Configuring HTTP proxy"
    header = "HTTP proxy is used for license registration and for updating " + \
             "packages through Yum."
    itemWidth = 30
    items = []
    items.append(("HTTP proxy URL", config["proxyurl"]))
    items.append(("HTTP proxy username", config["proxyuser"]))
    items.append(("HTTP proxy password", config["proxypass"]))
    buttons = [("Save", "save", "F12"), ("Cancel", "cancel", "ESC")]

    button, values = EntryWindow(screen, title, header, items, 1, width,
                                 itemWidth, buttons)

    if button == "cancel":
        log_debug("Exiting HTTP proxy configuration menu without " +
                  "saving changes")
        return False
    else:
        config["proxyurl"] = values[0]
        if config["proxyurl"] != "" and config["proxyurl"].find(":") == -1:
            config["proxyurl"] = "http://" + config["proxyurl"]
        config["proxyuser"] = values[1]
        config["proxypass"] = values[2]
        save_http_proxy_config(config)
        log_debug("Exiting HTTP proxy configuration menu")
        return True

def read_dialrules():
    dialrules = os.path.join(spool, "etc/dialrules")
    config = {}
    localareacodedial = read_item(dialrules, "LocalAreaCodeDial", "=")
    if localareacodedial == "${LDPrefix}${Area}":
        config["alwaysdialldcode"] = True
        config["alwaysdiallocalcode"] = True
    elif localareacodedial == "${Area}":
        config["alwaysdiallocalcode"] = True
        config["alwaysdialldcode"] = False
    else:
        config["alwaysdiallocalcode"] = False
        config["alwaysdialldcode"] = False
    config["outsideprefix"] = read_item(dialrules, "OutsidePrefix", "=")
    config["minlocalnumber"] = read_item(dialrules, "MinLocalnumber", "=")
    config["minldnumber"] = read_item(dialrules, "MinLDNumber", "=")
    config["otherlocalareacodes"] = read_item(dialrules, "OtherLocalAreaCodes",
                                              "=")
    return config

def read_hylafax_config():
    config = {"localidentifier": "",
              "faxnumber": "",
              "countrycode": "",
              "areacode": "",
              "longdistanceprefix": "",
              "internationalprefix": "",
              "include": ""}

    config = read_config(os.path.join(spool, "etc/CONFIG/site.conf"), config)
    if config["include"] == "etc/CONFIG/web.conf":
        log_debug("Web interface is set to active")
        config["enableweb"] = True
    else:
        config["enableweb"] = False
    del config["include"]

    config.update(read_dialrules())

    return config

def save_dialrules(config):
    dialrules = os.path.join(spool, "etc/dialrules")
    update_item(dialrules, "OutsidePrefix", "=", config["outsideprefix"])
    update_item(dialrules, "MinLocalNumber", "=", config["minlocalnumber"])
    update_item(dialrules, "MinLDNumber", "=", config["minldnumber"])
    update_item(dialrules, "OtherLocalAreaCodes", "=",
                config["otherlocalareacodes"])
    if config["alwaysdialldcode"]:
        update_item(dialrules, "LocalAreaCodeDial", "=", "${LDPrefix}${Area}")
    elif config["alwaysdiallocalcode"]:
        update_item(dialrules, "LocalAreaCodeDial", "=", "${Area}")
    else:
        update_item(dialrules, "LocalAreaCodeDial", "=", "")

def save_hylafax_config(config):
    head = "# This file is managed by the " + \
           "HylaFAX Enterprise Appliance Console.\n" + \
           "# This file is re-written as needed by the console and any\n" + \
           "# addition you make can be discarded without notice.\n" + \
           "# You have been warned.\n\n"
    log_info("Updating HylaFAX site config file")
    log_debug("Opening HylaFAX site config file")
    configfile = open(os.path.join(spool, "etc/CONFIG/site.conf"), "w")
    try:
        log_debug("Writing HylaFAX site config file")
        configfile.write("# HylaFAX Appliance site configuration file.\n")
        configfile.write(head)
        configfile.write("# Generated by " +
                         "hylafax-enterprise-appliance-console on " +
                         time.strftime("%Y-%m-%d %H:%M:%S") + "\n")
        config_write(configfile, "LocalIdentifier", config["localidentifier"])
        config_write(configfile, "FaxNumber", config["faxnumber"])
        config_write(configfile, "CountryCode", config["countrycode"])
        config_write(configfile, "AreaCode", config["areacode"])
        config_write(configfile, "InternationalPrefix",
                     config["internationalprefix"])
        config_write(configfile, "LongDistancePrefix",
                     config["longdistanceprefix"])
        if config["enableweb"]:
            config_write(configfile, "Include", "etc/CONFIG/web.conf")
    finally:
        log_debug("Closing HylaFAX site config file")
        configfile.close()
    return True

def config_dialrules():
    log_debug("Entering HylaFAX dialrules configuration menu")
    config = read_hylafax_config()
#    config = read_dialrules()
    width = 60
    title = "Configuring HylaFAX Enterprise dialing options"
    header = ""
    itemWidth = 30
    items = []
    items.append(("Country Code", config["countrycode"]))
    items.append(("Area Code", config["areacode"]))
    items.append(("Long Distance Prefix", config["longdistanceprefix"]))
    items.append(("International Prefix", config["internationalprefix"]))
    items.append(("Outside Dialing Prefix", config["outsideprefix"]))
    items.append(("Local number min length", config["minlocalnumber"]))
    items.append(("LD number min length", config["minldnumber"]))
    items.append(("Other local area codes (regex)",
                  config["otherlocalareacodes"]))
    items.append(("Always dial local area code",
                  Checkbox("", config["alwaysdiallocalcode"])))
    items.append(("Always dial LD code",
                  Checkbox("", config["alwaysdialldcode"])))
    buttons = [("Save", "save", "F12"), ("Cancel", "cancel", "ESC")]

    button, values = EntryWindow(screen, title, header, items, 1, width,
                                 itemWidth, buttons)

    if button == "cancel":
        log_debug("Exiting dialrules configuration menu without saving changes")
        return False
    else:
        config["countrycode"] = values[0]
        config["areacode"] = values[1]
        config["longdistanceprefix"] = values[2]
        config["internationalprefix"] = values[3]
        config["outsideprefix"] = values[4]
        config["minlocalnumber"] = values[5]
        config["minldnumber"] = values[6]
        if values[7] == "":
            # We must make sure the regexp is not empty or it will match all
            config["otherlocalareacodes"] = "NONE"
        else:
            config["otherlocalareacodes"] = values[7]
        config["alwaysdiallocalcode"] = values[8]
        config["alwaysdialldcode"] = values[9]
        save_dialrules(config)
        save_hylafax_config(config)
        log_debug("Exiting HylaFAX dialrules configuration menu")
        return True

def config_hylafax():
    log_debug("Entering HylaFAX configuration menu")
    config = read_hylafax_config()
    width = 60
    title = "Configuring HylaFAX Enterprise"
    header = ""
    itemWidth = 30
    items = []
    items.append(("Local Identifier", config["localidentifier"]))
    items.append(("Fax Number", config["faxnumber"]))
    items.append(("Enable web", Checkbox("", config["enableweb"])))
    buttons = [("Save", "save", "F12"), ("Cancel", "cancel", "ESC")]

    button, values = EntryWindow(screen, title, header, items, 1, width,
                                 itemWidth, buttons)

    if button == "cancel":
        log_debug("Exiting HylaFAX configuration menu without saving changes")
        return False
    else:
        config["localidentifier"] = values[0]
        config["faxnumber"] = values[1]
        config["enableweb"] = values[2]
        save_hylafax_config(config)
        log_debug("Exiting HylaFAX configuration menu")
        return True

def read_boston_config():
    config = {"bostondevices": "",
              "type": "",
              "protocol": "",
              "protocolfile": "",
              "defaultcid": "",
              "diddigits": "",
              "channeloffset": "",
              "gateway": "",
              "sipfrom": "",
              "sipauthuser": "",
              "sipauthpass": "",
              "sipregserver": "",
              "sipregfrom": "",
              "sipreguser": "",
              "sipregpass": "",
              "gateway2": "",
              "sipauthuser2": "",
              "sipauthpass2": "",
              "sipregserver2": "",
              "sipregfrom2": "",
              "sipreguser2": "",
              "sipregpass2": "",
              "gateway3": "",
              "sipauthuser3": "",
              "sipauthpass3": "",
              "sipregserver3": "",
              "sipregfrom3": "",
              "sipreguser3": "",
              "sipregpass3": "",
              "gateway4": "",
              "sipauthuser4": "",
              "sipauthpass4": "",
              "sipregserver4": "",
              "sipregfrom4": "",
              "sipreguser4": "",
              "sipregpass4": "",
              "rtpcodec": "",
              "t38support": "t38_only",
              "passthroughtimeoutoutbound": "4000",
              "enablev34": True,
              "loopback": False}

    devices = read_init()
    config["bostondevices"] = 0
    for device in devices:
        if device.startswith("boston"):
            config["bostondevices"] += 1
    log_debug("bostondevices set to " + str(config["bostondevices"]))
    btconfigfile = os.path.join(spool, "ifax/bt/config/btcall.cfg")
    config["enablev34"] = \
        parse_boolean(read_item(btconfigfile, "v34_enable", " "))
    config["callcontrolfile"] = read_item(btconfigfile, "call_control", " ")

    callControlFile = os.path.join(spool,
                                   "ifax/bt/config/callctrl-appliance.cfg")

    if not os.path.isfile(callControlFile):
        config["type"] = "manual"
        return config

    port_config = read_item(callControlFile, "port_config", "=")
    host_module = parse_boolean(read_item(callControlFile, "enabled", "="))

    if os.path.join(spool, config["callcontrolfile"]) != callControlFile:
        config["type"] = "manual"
    elif host_module:
        config["type"] = "manual"
        if not port_config or port_config == "INACTIVE":
            library = read_item(callControlFile, "module_library", "=")
            if library == "/usr/lib/ifax/brktsip_mt.so":
                config["type"] = "SR140_SIP"
            elif library == "/usr/lib/ifax/brkth323_mt.so":
                config["type"] = "SR140_H323"
    elif port_config == "T1_ISDN":
        config["type"] = port_config
    elif port_config == "T1_ROBBED_BIT":
        config["type"] = port_config
    elif port_config == "E1_ISDN":
        config["type"] = port_config
    elif port_config == "analog":
        config["type"] = "manual"
    elif port_config == "BRI":
        config["type"] = "manual"
    else:
        config["type"] = "manual"

    # We read all options to pre-populate fields with the older value when
    # switching between types
    config["sipfrom"] = read_item(callControlFile, "sip_from", "=")
    if config["type"] == "SR140_H323":
        config["gateway"] = read_item(callControlFile, "h323_default_gateway",
                                      "=")
    config["sipauthuser"] = read_item(callControlFile, "sip_username", "=")
#    config["sipauthpass"] = read_item(callControlFile, "sip_???")
    for i in range(1,5):
        s = str(i)
        if i == 1:
            suffix = ''
        else:
            suffix = s
        regServerItem = "sip_registration_server" + s
        if config["type"] == "SR140_SIP" or i > 1:
            config["gateway" + suffix] = read_item(callControlFile,
                                                   "sip_proxy_server" + s, "=")
        config["sipregserver" + suffix] = read_item(callControlFile,
                                                    regServerItem, "=")
        config["sipregfrom" + suffix] = read_item(callControlFile,
                                                  regServerItem + "_aor", "=")
        config["sipreguser" + suffix] = read_item(callControlFile,
                                                  regServerItem + "_username",
                                                  "=")
        config["sipregpass" + suffix] = read_item(callControlFile,
                                                  regServerItem + "_password",
                                                  "=")
    config["rtpcodec"] = read_item(callControlFile,
                                        "rtp_codec", "=").lower()
    config["t38support"] = read_item(callControlFile,
                                        "fax_transport_protocol", "=")
    config["passthroughtimeoutoutbound"] = read_item(callControlFile,
                                        "media_passthrough_timeout_outbound", "=")
    config["loopback"] = host_module and config["gateway"] == "127.0.0.1"
    config["protocol"] = read_item(callControlFile, "protocol", "=")
    config["protocolfile"] = read_item(callControlFile, "protocol_file", "=")
    config["diddigits"] = read_item(callControlFile, "max_did_digits", "=")
    config["channeloffset"] = read_item(callControlFile,
                                        "fractional_start_channel", "=")
    if config["type"] == "SR140_H323":
        config["defaultcid"] = read_item(callControlFile, "h323_e164alias", "=")
    else:
        config["defaultcid"] = read_item(callControlFile, "default_caller_id",
                                         "=")

    return config

def save_boston_config(config):
    change = False
    progress = Progress(screen, "Updating Telco configuration")
    progress.update(1, "Reading HylaFAX Telco configuration")
    oldConfig = read_boston_config()
    btconfigfile = os.path.join(spool, "ifax/bt/config/btcall.cfg")

    if (config["type"] == "manual"):
        if (oldConfig["type"] != "manual"):
            update_item(btconfigfile, "call_control", " ",
                        "ifax/bt/config/callctrl.cfg")
        progress.done()
        callControlFile = os.path.join(spool, read_item(btconfigfile,
                                                        "call_control", " "))
        warning("Manual configuration has been selected. Please manually " +
                "edit the config file at: " + callControlFile)
        return

    if (oldConfig["type"] == "manual"):
        update_item(btconfigfile, "call_control", " ",
                    "ifax/bt/config/callctrl-appliance.cfg")

    if (oldConfig["enablev34"] != config["enablev34"]):
        update_item(btconfigfile, "v34_enable", " ", int(config["enablev34"]))

    if (oldConfig["type"] != config["type"] or
        oldConfig["protocol"] != config["protocol"] or
        oldConfig["sipfrom"] != config["sipfrom"] or
        oldConfig["gateway"] != config["gateway"] or
        oldConfig["sipauthuser"] != config["sipauthuser"] or
        oldConfig["sipauthpass"] != config["sipauthpass"] or
        oldConfig["sipregserver"] != config["sipregserver"] or
        oldConfig["sipregfrom"] != config["sipregfrom"] or
        oldConfig["sipreguser"] != config["sipreguser"] or
        oldConfig["sipregpass"] != config["sipregpass"] or
        oldConfig["gateway2"] != config["gateway2"] or
        oldConfig["sipauthuser2"] != config["sipauthuser2"] or
        oldConfig["sipauthpass2"] != config["sipauthpass2"] or
        oldConfig["sipregserver2"] != config["sipregserver2"] or
        oldConfig["sipregfrom2"] != config["sipregfrom2"] or
        oldConfig["sipreguser2"] != config["sipreguser2"] or
        oldConfig["sipregpass2"] != config["sipregpass2"] or
        oldConfig["gateway3"] != config["gateway2"] or
        oldConfig["sipauthuser3"] != config["sipauthuser3"] or
        oldConfig["sipauthpass3"] != config["sipauthpass3"] or
        oldConfig["sipregserver3"] != config["sipregserver3"] or
        oldConfig["sipregfrom3"] != config["sipregfrom3"] or
        oldConfig["sipreguser3"] != config["sipreguser3"] or
        oldConfig["sipregpass3"] != config["sipregpass3"] or
        oldConfig["gateway4"] != config["gateway4"] or
        oldConfig["sipauthuser4"] != config["sipauthuser4"] or
        oldConfig["sipauthpass4"] != config["sipauthpass4"] or
        oldConfig["sipregserver4"] != config["sipregserver4"] or
        oldConfig["sipregfrom4"] != config["sipregfrom4"] or
        oldConfig["sipreguser4"] != config["sipreguser4"] or
        oldConfig["sipregpass4"] != config["sipregpass4"] or
        oldConfig["rtpcodec"] != config["rtpcodec"] or
        oldConfig["t38support"] != config["t38support"] or
        oldConfig["passthroughtimeoutoutbound"] !=
            config["passthroughtimeoutoutbound"] or
        oldConfig["defaultcid"] != config["defaultcid"] or
        oldConfig["diddigits"] != config["diddigits"] or
        oldConfig["bostondevices"] != config["bostondevices"] or
        oldConfig["channeloffset"] != config["channeloffset"]):
        change = True
        progress.update(10, "Updating HylaFAX Telco config file")
        log_info("Updating HylaFAX Telco config file")
        callControlFile = os.path.join(spool,
                             "ifax/bt/config/callctrl-appliance.cfg")

        if config["type"] == "SR140_SIP":
            template = "ifax/bt/config/templates/callctrl-sip.tmpl"
            open_firewall("sip")
        elif config["type"] == "SR140_H323":
            template = "ifax/bt/config/templates/callctrl-h323.tmpl"
            close_firewall("sip")
        else:
            template = "ifax/bt/config/templates/callctrl-tr1034.tmpl"
            close_firewall("sip")

        create_from_template(callControlFile, template, config)

    oldDevices = read_init()
    devices = []
    for device in oldDevices:
        if not device.startswith("boston"):
            devices.append(device)

    if change:
        progress.update(50, "Checking status of Boston driver")
        log_debug("Checking status of Boston driver")
        rc = subprocess.call(["systemctl", "status", "ifax-bt"],
                             stdout=devNull, stderr=devNull)
        if rc == 0:
            progress.update(45, "Restarting Boston driver")
            restart_boston()
        else:
            progress.update(45, "Starting Boston driver")
            log_notice("Starting Boston driver")
            # ifax-bt childs does not close stdout/stderr
            rc = subprocess.call(["systemctl", "start", "ifax-bt"],
                                 stdout=devNull, stderr=devNull)

    progress.update(75, "Checking existance of device configuration files")
    for i in range(config["bostondevices"]):
        devices.append("boston%02d" % i)
        filename = os.path.join(spool, "etc/config.boston%02d" % i)
        if not os.path.isfile(filename):
            progress.update(80, "Creating: " + filename)
            log_debug("Creating missing boston device file: " + filename)
            try:
                configfile = open(filename, "w")
                configfile.write("Include:\tetc/CONFIG/site\n")
                configfile.write("Include:\tetc/CONFIG/boston\n\n")
            except:
                log_error("Problem creating file: " + filename)
            if configfile:
                configfile.close()
    progress.update(85, "Saving init configuration file")
    save_init(devices)
    log_notice("Poking init")
    progress.update(90, "Pocking init")
    rc = execute(["telinit", "q"])

    if oldConfig["bostondevices"] != config["bostondevices"]:
        progress.update(95, "Updating web interface device listing")
        rc = execute("/usr/sbin/web-sync-devices.php")

    progress.done()
    return True

def config_telco_sr140_extra_proxies(config):
    log_debug("Entering Telco SR140 Extra Proxies configuration menu")
    if config["type"] == "SR140_SIP":
        title = "Configuring Extra SIP Proxies"
    else:
        return -1

    width = 60
    header = ""
    itemWidth = 30
    gateway = dict()
    user = dict()
    password = dict()
    authCalls = dict()
    register = dict()
    for i in range(2,5):
        s = str(i)
        if config["sipauthuser" + s] != "" or config["sipauthpass" + s] != "":
            user[i] = config["sipauthuser" + s]
            password[i] = config["sipauthpass" + s]
            authCalls[i] = True
        else:
            authCalls[i] = False
            user[i] = config["sipreguser" + s]
            password[i] = config["sipregpass" + s]
        register[i] = config["sipregserver" + s] != ""
    while True:
        items = []
        for i in range(2,5):
            s = str(i)
            items.append(("Gateway " + s, config["gateway" + s]))
            if config["type"] == "SR140_SIP":
#                items.append(("SIP From", config["sipfrom"]))
                items.append(("Username", user[i]))
                items.append(("Password", password[i]))
#        items.append(("Authenticate outgoing calls", Checkbox("", authCalls)))
                items.append(("Do SIP registration", Checkbox("", register[i])))
        buttons = [("Save", "save", "F12"), ("Prev", "prev"),
                   ("Cancel", "cancel", "ESC")]

        button, values = EntryWindow(screen, title, header, items, 1, width,
                                     itemWidth, buttons)

        if button == "cancel":
            log_debug("Exiting SIP configuration menu without saving changes")
            return 0
        elif button == "prev":
            return -1
        else:
            for i in range(2,5):
                s = str(i)
                block = (i - 2) * 4
                gateway[i] = values[block + 0]
                if config["type"] == "SR140_SIP":
#                    config["sipfrom"] = values[1]
                    user[i] = values[block + 1]
                    password[i] = values[block + 2]
#                    authCalls = values[4]
                    register[i] = values[block + 3]
                config["sipauthuser" + s] = ""
                config["sipauthpass" + s] = ""
                config["sipregserver" + s] = ""
                config["sipregfrom" + s] = ""
                config["sipreguser" + s] = ""
                config["sipregpass" + s] = ""
                config["gateway" + s] = gateway[i]
                if authCalls[i]:
                    config["sipauthuser" + s] = user[i]
                    config["sipauthpass" + s] = password[i]
                if register[i]:
                    config["sipregserver" + s] = gateway[i]
                    config["sipreguser" + s] = user[i]
                    config["sipregpass" + s] = password[i]
#                    if config["sipfrom"] != "":
#                        config["sipregfrom"] = config["sipfrom"]
#                    else:
#                        hostname = socket.getfqdn()
#                        config["sipregfrom"] = user + "@" + hostname
            return 1

def config_telco_sr140(config):
    log_debug("Entering Telco SR140 configuration menu")
    if config["type"] == "SR140_SIP":
        title = "Configuring Telco SIP Connections"
    elif config["type"] == "SR140_H323":
        title = "Configuring Telco H.323 Connections"
    else:
        return -1

    width = 60
    header = ""
    itemWidth = 30
    if config["sipauthuser"] != "" or config["sipauthpass"] != "":
        user = config["sipauthuser"]
        password = config["sipauthpass"]
        authCalls = True
    else:
        authCalls = False
        user = config["sipreguser"]
        password = config["sipregpass"]
    register = config["sipregserver"] != ""
    while True:
        items = []
        items.append(("Gateway", config["gateway"]))
        if config["type"] == "SR140_H323":
            items.append(("Default Caller ID", config["defaultcid"]))
        if config["type"] == "SR140_SIP":
            items.append(("SIP From", config["sipfrom"]))
            items.append(("Username", user))
            items.append(("Password", password))
#        items.append(("Authenticate outgoing calls", Checkbox("", authCalls)))
            items.append(("Do SIP registration", Checkbox("", register)))
        # rtpcodec will either be "pcmu", "pcma" or a combination of both
        # Before SDK 6.7.3, an empty value was equivalent to both, but this
        # has been changed to "pcma" in 6.7.3. Since the console used to
        # leave the value empty when both codec were wanted, that's how it
        # reads an empty value. New console always write a value in this field
        # to prevent any problems..
        items.append(("Allow U-Law",
                      Checkbox("", "pcmu" in config["rtpcodec"])))
        items.append(("Allow A-Law",
                      Checkbox("", "pcma" in config["rtpcodec"])))
        list = Listbox(1, 1, 0, 18, 0)
        # Entrywindow returns the "value()" of the item. We must thus make
        # the listbox object return the current value using that method.
        list.value = types.MethodType(Listbox.current, list)
        list.append("T.38 only", "t38_only")
        list.append("T.38 preferred", "t38_first")
        list.append("T.38 disabled", "t38_never")
        try:
            list.setCurrent(config["t38support"])
        except:
            list.setCurrent("t38_first")
        items.append(("T.38 Support", list))
        items.append(("Outbound passthrough timeout (ms)",
                      config["passthroughtimeoutoutbound"]))
        items.append(("Enable V.34",
                      Checkbox("", config["enablev34"])))
        if config["type"] == "SR140_SIP":
            buttons = [("Save", "save", "F12"), ("Prev", "prev"),
                       ("Next", "next"), ("Cancel", "cancel", "ESC")]
        else:
            buttons = [("Save", "save", "F12"), ("Prev", "prev"),
                       ("Cancel", "cancel", "ESC")]

        button, values = EntryWindow(screen, title, header, items, 1, width,
                                     itemWidth, buttons)

        if button == "next":
            proxies = config_telco_sr140_extra_proxies(config)
            if proxies == 0:
                return 0
            elif proxies == 1:
                button = "save"
        if button == "cancel":
            log_debug("Exiting SIP configuration menu without saving changes")
            return 0
        elif button == "prev":
            return -1
        else:
            gateway = values[0]
            if config["type"] == "SR140_H323":
                base = 2
                config["defaultcid"] = values[1]
            elif config["type"] == "SR140_SIP":
                base = 5
                config["sipfrom"] = values[1]
                user = values[2]
                password = values[3]
#            authCalls = values[4]
                register = values[4]
            if values[base + 0] and not values[base + 1]:
                config["rtpcodec"] = "pcmu"
            elif values[base + 1] and not values[base + 0]:
                config["rtpcodec"] = "pcma"
            else:
                config["rtpcodec"] = "pcmu pcma"
            config["t38support"] = values[base + 2]
            config["passthroughtimeoutoutbound"] = values[base + 3]
            config["enablev34"] = values[base + 4]
            config["sipauthuser"] = ""
            config["sipauthpass"] = ""
            config["sipregserver"] = ""
            config["sipregfrom"] = ""
            config["sipreguser"] = ""
            config["sipregpass"] = ""
            if gateway == "" or gateway.lower() == "loopback":
                config["gateway"] = "127.0.0.1"
            else:
                config["gateway"] = gateway
                if authCalls:
                    config["sipauthuser"] = user
                    config["sipauthpass"] = password
                if register:
                    config["sipregserver"] = gateway
                    config["sipreguser"] = user
                    config["sipregpass"] = password
                    if config["sipfrom"] != "":
                        config["sipregfrom"] = config["sipfrom"]
                    else:
                        hostname = socket.getfqdn()
                        config["sipregfrom"] = user + "@" + hostname
            if button == "save":
                return 1

def config_telco_hw(config):
    log_debug("Entering Telco hardware configuration menu")
    if config["type"] == "T1_ISDN":
        title = "Configuring Telco T1 ISDN PRI Connections"
    elif config["type"] == "T1_ROBBED_BIT":
        title = "Configuring Telco T1 Robbed bit Connections"
    elif config["type"] == "E1_ISDN":
        title = "Configuring Telco E1 ISDN Connections"
    else:
        return -1

    width = 60
    header = ""
    itemWidth = 30
    while True:
        items = []
        items.append(("Channel offset", config["channeloffset"]))
        items.append(("DID digits", config["diddigits"]))
        items.append(("Default Caller ID", config["defaultcid"]))
        list = Listbox(1, 1, 0, 28, 0)
        # Entrywindow returns the "value()" of the item. We must thus make
        # the listbox object return the current value using that method.
        list.value = types.MethodType(Listbox.current, list)
        if config["type"] == "T1_ISDN":
            list.append("National ISDN", "ISDN2")
            list.append("ATT", "ATT")
            list.append("5ESS/4ESS", "5ESS4ESS")
            try:
                list.setCurrent(config["protocol"])
            except:
                list.setCurrent("ISDN2")
        elif config["type"] == "E1_ISDN":
            list.append("EURO", "EURO")
            try:
                list.setCurrent(config["protocol"])
            except:
                list.setCurrent("EURO")
        elif config["type"] == "T1_ROBBED_BIT":
            list.append("Analog loopstart europe",
                        "ifax/bt/config/analog_loopstart_europe.lec")
            list.append("Analog loopstart US",
                        "ifax/bt/config/analog_loopstart_us.lec")
            list.append("FXO groundstart",
                        "ifax/bt/config/fxo_groundstart.lec")
            list.append("FXO loopstart",
                        "ifax/bt/config/fxo_loopstart.lec")
            list.append("FXS groundstart",
                        "ifax/bt/config/fxo_groundstart.lec")
            list.append("FXS loopstart",
                        "ifax/bt/config/fxo_loopstart.lec")
            list.append("Immediate dial",
                        "ifax/bt/config/immediatedial.lec")
            list.append("Wink start",
                        "ifax/bt/config/winkstart.lec")
            try:
                list.setCurrent(config["protocolfile"])
            except:
                list.setCurrent("ifax/bt/config/winkstart.lec")
        items.append(("Protocol", list))
        items.append(("Enable V.34",
                      Checkbox("", config["enablev34"])))
        buttons = [("Save", "save", "F12"), ("Prev", "prev"),
                   ("Cancel", "cancel", "ESC")]

        button, values = EntryWindow(screen, title, header, items, 1, width,
                                     itemWidth, buttons)

        if button == "cancel":
            log_debug("Exiting Telco hardware configuration menu " +
                      "without saving changes")
            return 0
        elif button == "prev":
            return -1
        else:
            config["channeloffset"] = values[0]
            config["diddigits"] = values[1]
            config["defaultcid"] = values[2]
            if config["type"] == "T1_ROBBED_BIT":
                config["protocolfile"] = values[3]
                config["protocol"] = None
            else:
                config["protocol"] = values[3]
                config["protocolfile"] = None
            config["enablev34"] = values[4]
            return 1
    return 1

def config_telco():
    log_debug("Entering Telco configuration menu")
    config = read_boston_config()
    width = 60
    title = "Configuring Telco Connections"
    header = ""
    itemWidth = 30
    while True:
        items = []
        items.append(("Number of boston devices", str(config["bostondevices"])))
#        items.append(("Type", config["type"]))
        list = Listbox(1, 1, 0, 16, 0)
        # Entrywindow returns the "value()" of the item. We must thus make
        # the listbox object return the current value using that method.
        list.value = types.MethodType(Listbox.current, list)
        list.append("Manual", "manual")
        list.append("SIP", "SR140_SIP")
        list.append("H.323", "SR140_H323")
        list.append("T1 ISDN PRI", "T1_ISDN")
        list.append("T1 Robbed bit", "T1_ROBBED_BIT")
        list.append("E1 ISDN PRI", "E1_ISDN")
#        list.append("ISDN BRI", "BRI")
#        list.append("Analog", "analog")
        try:
            list.setCurrent(config["type"])
        except:
            list.setCurrent("manual")
        items.append(("Type", list))
        buttons = [("Next", "next", "F12"), ("Cancel", "cancel", "ESC")]

        button, values = EntryWindow(screen, title, header, items, 1, width,
                                     itemWidth, buttons)

        if button == "cancel":
            log_debug("Exiting Telco configuration menu without saving changes")
            return False
        else:
            config["type"] = values[1]
            try:
                config["bostondevices"] = int(values[0])
                if config["bostondevices"] < 0:
                    raise Exception("Invalid value")
            except:
                log_warning("Invalid value provided for boston devices: " +
                            values[0])
                warning("Invalid number of boston devices provided: " +
                            values[0])
                config["bostondevices"] = values[0]
                continue

        if config["type"] == "manual":
            rc = 1
        elif config["type"].startswith("SR140"):
            rc = config_telco_sr140(config)
        else:
            rc = config_telco_hw(config)
        if rc == 0:
            return
        elif rc == -1:
            continue

        save_boston_config(config)
        log_debug("Exiting Telco configuration menu")
        return True

def read_sip_config():
    config = {"sipdevices": "",
              "sipgateway": "",
              "sipauthuser": "",
              "sipauthpass": "",
              "sipregserver": "",
              "sipreguser": "",
              "sipregpass": "",
# Older releases of HylaFAX only had ulaw set to true by default. Our default
# config file now has both U-law and A-law set to true, but we don't want to
# affect running systems being upgraded, so we keep the old defaults when the
# file has no values for these settings.
              "sipinitiateulaw": "True",
              "sipinitiatealaw": "False",
              "sipinitiateudptl": "False",
              "loopback": "True"}

    devices = read_init()
    config["sipdevices"] = 0
    for device in devices:
        if device.startswith("sip"):
            config["sipdevices"] += 1
    log_debug("sipdevices set to " + str(config["sipdevices"]))
    config = read_config(os.path.join(spool, "etc/CONFIG/hbf-sip.conf"), config)
    config["sipinitiateulaw"] = parse_boolean(config["sipinitiateulaw"])
    config["sipinitiatealaw"] = parse_boolean(config["sipinitiatealaw"])
    config["sipinitiateudptl"] = parse_boolean(config["sipinitiateudptl"])
    config["loopback"] = parse_boolean(config["loopback"])
    return config

def save_sip_config(config):
    head = "# This file is managed by the " + \
           "HylaFAX Enterprise Appliance Console.\n" + \
           "# This file is re-written as needed by the console and any\n" + \
           "# addition you make can be discarded without notice.\n" + \
           "# You have been warned.\n\n"
    progress = Progress(screen, "Updating SIP configuration")
    progress.update(1, "Reading HylaFAX SIP configuration")
    oldConfig = read_sip_config()

    if (oldConfig["loopback"] != config["loopback"] or
        oldConfig["sipgateway"] != config["sipgateway"] or
        oldConfig["sipauthuser"] != config["sipauthuser"] or
        oldConfig["sipauthpass"] != config["sipauthpass"] or
        oldConfig["sipregserver"] != config["sipregserver"] or
        oldConfig["sipreguser"] != config["sipreguser"] or
        oldConfig["sipregpass"] != config["sipregpass"] or
        oldConfig["sipinitiateulaw"] != config["sipinitiateulaw"] or
        oldConfig["sipinitiatealaw"] != config["sipinitiatealaw"] or
        oldConfig["sipinitiateudptl"] != config["sipinitiateudptl"]):
        progress.update(10, "Updating HylaFAX SIP config file")
        log_info("Updating HylaFAX SIP config file")
        log_debug("Opening HylaFAX SIP config file")
        configfile = open(os.path.join(spool, "etc/CONFIG/hbf-sip.conf"), "w")
        try:
            log_debug("Writing HylaFAX SIP config file")
            configfile.write("# HylaFAX Appliance sip configuration file.\n")
            configfile.write(head)
            configfile.write("# Generated by " +
                             "hylafax-enterprise-appliance-console on " +
                             time.strftime("%Y-%m-%d %H:%M:%S") + "\n")
            config_write(configfile, "Loopback",
                         print_boolean(config["loopback"]))
            config_write(configfile, "SipGateway", config["sipgateway"])
            config_write(configfile, "SipAuthUser", config["sipauthuser"])
            config_write(configfile, "SipAuthPass", config["sipauthpass"])
            config_write(configfile, "SipRegister", "console")
            config_write(configfile, "SipRegServer", config["sipregserver"])
            config_write(configfile, "SipRegUser", config["sipreguser"])
            config_write(configfile, "SipRegPass", config["sipregpass"])
            config_write(configfile, "SipInitiateUlaw",
                         print_boolean(config["sipinitiateulaw"]))
            config_write(configfile, "SipInitiateAlaw",
                         print_boolean(config["sipinitiatealaw"]))
            config_write(configfile, "SipInitiateUdptl",
                         print_boolean(config["sipinitiateudptl"]))
        finally:
            log_debug("Closing HylaFAX SIP config file")
            configfile.close()

    if (oldConfig["sipregserver"] != config["sipregserver"] or
        oldConfig["sipreguser"] != config["sipreguser"] or
        oldConfig["sipregpass"] != config["sipregpass"]):
        progress.update(40, "Checking status of HBF")
        log_debug("Checking status of HBF")
        rc = subprocess.call(["systemctl", "status", "ifax-hbf"],
                             stdout=devNull, stderr=devNull)
        if rc == 0:
            progress.update(45, "Restarting HBF")
            log_notice("Restarting HBF")
            rc = service("ifax-hbf", "restart")

    progress.update(85, "Saving init configuration file")
    oldDevices = read_init()
    devices = []
    for i in range(config["sipdevices"]):
        devices.append("sip%02d" % i)
    for device in oldDevices:
        if not device.startswith("sip"):
            devices.append(device)
    save_init(devices)
    log_notice("Poking init")
    progress.update(95, "Pocking init")
    rc = execute(["telinit", "q"])
    progress.done()
    return True

def config_sip():
    log_debug("Entering SIP configuration menu")
    config = read_sip_config()
    width = 60
    title = "Configuring SIP Devices"
    header = ""
    itemWidth = 30
    if config["loopback"]:
        gateway = "loopback"
    else:
        gateway = config["sipgateway"]
    if config["sipauthuser"] != "" or config["sipauthpass"] != "":
        user = config["sipauthuser"]
        password = config["sipauthpass"]
        authCalls = True
    else:
        authCalls = False
        user = config["sipreguser"]
        password = config["sipregpass"]
    register = config["sipregserver"] != ""
    while True:
        items = []
        items.append(("Gateway", gateway))
        items.append(("Username", user))
        items.append(("Password", password))
        items.append(("Authenticate outgoing calls", Checkbox("", authCalls)))
        items.append(("Do SIP registration", Checkbox("", register)))
        items.append(("Initiate with U-Law",
                      Checkbox("", config["sipinitiateulaw"])))
        items.append(("Initiate with A-Law",
                      Checkbox("", config["sipinitiatealaw"])))
        items.append(("Initiate with UDPTL/T.38",
                      Checkbox("(not recommended)",
                               config["sipinitiateudptl"])))
        buttons = [("Save", "save", "F12"), ("Cancel", "cancel", "ESC")]

        button, values = EntryWindow(screen, title, header, items, 1, width,
                                     itemWidth, buttons)

        if button == "cancel":
            log_debug("Exiting SIP configuration menu without saving changes")
            return False
        else:
            gateway = values[0]
            user = values[1]
            password = values[2]
            authCalls = values[3]
            register = values[4]
            config["sipinitiateulaw"] = values[5]
            config["sipinitiatealaw"] = values[6]
            config["sipinitiateudptl"] = values[7]
            if not (config["sipinitiateulaw"] or config["sipinitiatealaw"] or
                    config["sipinitiateudptl"]):
                warning("You must enable at least one of U-law, A-law or " +
                        "T.38/UDPTL to initiate calls.")
                continue
            config["sipauthuser"] = ""
            config["sipauthpass"] = ""
            config["sipregserver"] = ""
            config["sipreguser"] = ""
            config["sipregpass"] = ""
            if gateway == "" or gateway.lower() == "loopback":
                config["loopback"] = True
                config["sipgateway"] = get_ip_address(interface) + ":5060"
            else:
                config["loopback"] = False
                config["sipgateway"] = gateway
                if authCalls:
                    config["sipauthuser"] = user
                    config["sipauthpass"] = password
                if register:
                    config["sipregserver"] = gateway
                    config["sipreguser"] = user
                    config["sipregpass"] = password
            save_sip_config(config)
            log_debug("Exiting SIP configuration menu")
            return True

def read_mail_config():
    config = dict()
    log_debug("Reading mail configuration")
    config["root"] = read_item("/etc/aliases", "root", ":")
    config["faxmaster"] = read_item("/etc/aliases", "faxmaster", ":")
    config["smarthost"] = read_item("/etc/postfix/main.cf", "relayhost", "=")
    config["faxdomain"] = read_item("/etc/postfix/main.cf",
                                    "relay_domains", "=")

    # Defaults:
    config["alpha_faxdomain_users"] = False

    filename = "/etc/postfix/virtual_alias-fax"
    try:
        file = open(filename)
    except IOError:
        log_debug("IO Error opening " + filename)
    try:
        found = False
        for line in file:
            if found:
                var, value = line.split("@", 1)
                config["alpha_faxdomain_users"] = (var == "/^(.+)")
                log_debug('Found value of alpha_faxdomain_users to be "' + str(config["alpha_faxdomain_users"]) + '"')
                break
            if line.find("# Generated by hylafax-enterprise-appliance-console") == -1:
                continue
            found = True
    finally:
        file.close()
    return config

def save_postfix_virtual_alias(faxdomain, mydomain="localdomain", alpha_faxdomain_users=False):
    head = "# This file is managed by the " + \
           "HylaFAX Enterprise Appliance Console.\n" + \
           "# This file is re-written as needed by the console and any\n" + \
           "# addition you make can be discarded without notice.\n" + \
           "# You have been warned.\n\n"
    log_info("Updating Postfix virtual alias config file")
    log_debug("Opening Postfix virtual alias config file")
    configfile = open("/etc/postfix/virtual_alias-fax", "w")
    try:
        log_debug("Writing Postfix virtual alias config file")
        configfile.write("# Postfix virtual alias table for the fax gateway.\n")
        configfile.write(head)
        configfile.write("# Generated by " +
                         "hylafax-enterprise-appliance-console on " +
                         time.strftime("%Y-%m-%d %H:%M:%S") + "\n")
        prefix = "/^([+]?[0-9*#.-]+)@"
        alias = "$1@fax.localhost"
        if not mydomain:
            mydomain = "localdomain"
        hostname = socket.getfqdn()
        if hostname.find(".") == -1:
            hostname = str(hostname) + "." + mydomain
        if faxdomain:
            if faxdomain.find(".") == -1:
                faxdomain = str(faxdomain) + "." + mydomain
            if faxdomain != hostname:
                if alpha_faxdomain_users:
                    configfile.write("/^(.+)@" + faxdomain.replace(".", "\\.") +
                                     "$/  " + alias + "\n")
                else:
                    configfile.write(prefix + faxdomain.replace(".", "\\.") +
                                     "$/  " + alias + "\n")
        configfile.write(prefix + hostname.replace(".", "\\.") +
                         "$/  " + alias + "\n")
        configfile.write(prefix + "localhost\\." +
                         mydomain.replace(".", "\\.") + "$/  " + alias + "\n")
        configfile.write(prefix + "\\[127\\.0\\.0\\.1\\]$/  " + alias + "\n")
        ipAddress = get_ip_address(interface)
        configfile.write(prefix + "\[" + ipAddress.replace(".", "\\.") +
                         "\]$/  " + alias + "\n")
    finally:
        log_debug("Closing Postfix virtual alias config file")
        configfile.close()
    return True

def save_mail_config(config):
    log_debug("Updating mail configuration")
    update_item("/etc/aliases", "root", ":", config["root"])
    oldFaxmaster = read_mail_config()["faxmaster"]
    if oldFaxmaster != config["faxmaster"]:
        update_item("/etc/aliases", "FaxMaster", ":", config["faxmaster"],
                    "alias for notification messages from HylaFAX servers")
        update_status_file()
    rc = execute("newaliases")
    update_item("/etc/postfix/main.cf", "relayhost", "=", config["smarthost"],
                "Relay mail through a smarthost")
    mydomain = read_item("/etc/postfix/main.cf", "mydomain", "=")
    if not mydomain:
        mydomain = "localdomain"
    if config["faxdomain"]:
        if config["faxdomain"].find(".") == -1:
            config["faxdomain"] = str(config["faxdomain"]) + "." + mydomain
    update_item("/etc/postfix/main.cf", "relay_domains", "=",
                config["faxdomain"])
    save_postfix_virtual_alias(config["faxdomain"], mydomain, config["alpha_faxdomain_users"])
    log_notice("Reloading postfix")
    rc = service("postfix", "reload")
    return True

def config_mail():
    log_debug("Entering mail configuration menu")
    config = read_mail_config()
    width = 60
    title = "Configuring mail"
    header = "Warning: If [Allow any char for Fax domain users] is checked, " \
             "any message sent to an address in the Fax domain will be sent " \
             "by fax. Incorrectly setting this value can cause a fax loop."
    itemWidth = 30
    while True:
        items = []
        items.append(("Root", config["root"]))
        items.append(("FaxMaster", config["faxmaster"]))
        items.append(("Smart host", config["smarthost"]))
        items.append(("Fax domain", config["faxdomain"]))
        items.append(("Allow any char for Fax domain users", Checkbox("", config["alpha_faxdomain_users"])))
        buttons = [("Save", "save", "F12"), ("Cancel", "cancel", "ESC")]

        button, values = EntryWindow(screen, title, header, items, 1, width,
                                     itemWidth, buttons)

        if button == "cancel":
            log_debug("Exiting mail configuration menu without saving changes")
            return False
        else:
            config["root"] = values[0].strip()
            config["faxmaster"] = values[1].strip()
            config["smarthost"] = values[2].strip()
            config["faxdomain"] = values[3].strip()
            config["alpha_faxdomain_users"] = values[4]
            if config["root"].endswith("@" + config["faxdomain"]) and config["alpha_faxdomain_users"]:
                warning("Sending root's mail to an address in the fax " \
                        "domain can cause a fax loop.")
                continue
            if config["faxmaster"].endswith("@" + config["faxdomain"]) and config["alpha_faxdomain_users"]:
                warning("Sending faxmaster's mail to an address in the fax " \
                        "domain can cause a fax loop.")
                continue
            save_mail_config(config)
            log_debug("Exiting mail configuration menu")
            return True

def read_services_config():
    config = {"firewall_hfaxd": False,
              "firewall_http": False,
              "firewall_smtp": False,
              "firewall_lmgrd": False,
              "bonjour_hfaxd": False,
              "bonjour_web": False,
              "bonjour_ssh": False}
    log_debug("Reading services configuration")

    command = ["firewall-cmd", "--permanent", "--list-services"]
    process = execute(command, True)
    if process["returncode"] == 0:
        for service in process["stdout"].split():
            if service == "hylafax-hfaxd":
                config["firewall_hfaxd"] = True
            elif service == "http":
                config["firewall_http"] = True
            elif service == "smtp":
                config["firewall_smtp"] = True
            elif service == "ifax-lmgrd":
                config["firewall_lmgrd"] = True
    else:
        log_warning("Error obtaining firewall configuration")

    filename = "/etc/avahi/services/hylafax.service"
    try:
        configFile = open(filename)
    except IOError:
        log_warning("Error reading configuration file: " + str(filename))
        return config

    try:
        # Manually modified files may not be read correctly as we don't
        # check for commented settings or which section they are in.
        for line in configFile:
            if line.find("_hylafax._tcp") != -1:
                log_debug("hfaxd bonjour is enabled")
                config["bonjour_hfaxd"] = True
            if line.find("_http._tcp") != -1:
                log_debug("http bonjour is enabled")
                config["bonjour_web"] = True
            if line.find("_ssh._tcp") != -1:
                log_debug("ssh bonjour is enabled")
                config["bonjour_ssh"] = True
    finally:
        if configFile:
            log_debug("Closing configuration file")
            configFile.close()

    return config

def open_firewall(service):
    log_notice("Opening firewall for " + service)
    rc = execute(["firewall-cmd", "--permanent", "--add-service=" + service])
    if service == 'hylafax-hfaxd':
        rc = execute(["firewall-cmd", "--permanent", "--helper=ftp", "--add-port=4559/tcp"])
    rc = execute(["firewall-cmd", "--reload"])

def close_firewall(service):
    log_notice("Closing firewall for " + service)
    rc = execute(["firewall-cmd", "--permanent", "--remove-service=" + service])
    rc = execute(["firewall-cmd", "--reload"])

def save_services_config(config):
    head = "<!-- This file is managed by the " + \
           "HylaFAX Enterprise Appliance Console.\n" + \
           "     This file is re-written as needed by the console and any\n" + \
           "     addition you make can be discarded without notice.\n" + \
           "     You have been warned. -->\n\n"
    log_debug("Updating services configuration")

    if config["firewall_hfaxd"]:
        open_firewall("hylafax-hfaxd")
    else:
        close_firewall("hylafax-hfaxd")

    if config["firewall_http"]:
        open_firewall("http")
    else:
        close_firewall("http")

    if config["firewall_smtp"]:
        open_firewall("smtp")
    else:
        close_firewall("smtp")

    if config["firewall_lmgrd"]:
        open_firewall("ifax-lmgrd")
    else:
        close_firewall("ifax-lmgrd")


    configfile = open("/etc/avahi/services/hylafax.service", "w")
    try:
        log_debug("Writing avahi hylafax service file")
        configfile.write("<?xml version=\"1.0\" standalone='no'?>" +
                         "<!--*-nxml-*-->\n" +
                         "<!DOCTYPE service-group SYSTEM" +
                         " \"avahi-service.dtd\">\n")
        configfile.write("<!-- HylaFAX avahi service configuration. -->\n")
        configfile.write(head)
        configfile.write("<!-- Generated by " +
                         "hylafax-enterprise-appliance-console on " +
                         time.strftime("%Y-%m-%d %H:%M:%S") + " -->\n")
        configfile.write("<service-group>\n")
        configfile.write("  <name replace-wildcards=\"yes\">HylaFAX" +
                         " Enterprise (%h)</name>\n")
        if config["bonjour_hfaxd"]:
            configfile.write("  <service>\n")
            configfile.write("    <type>_hylafax._tcp</type>\n")
            configfile.write("    <port>4559</port>\n")
            configfile.write("  </service>\n")
        if config["bonjour_web"]:
            configfile.write("  <service>\n")
            configfile.write("    <type>_http._tcp</type>\n")
            configfile.write("    <port>80</port>\n")
            configfile.write("  </service>\n")
        if config["bonjour_ssh"]:
            configfile.write("  <service>\n")
            configfile.write("    <type>_ssh._tcp</type>\n")
            configfile.write("    <port>22</port>\n")
            configfile.write("  </service>\n")
        configfile.write("</service-group>")

    finally:
        log_debug("Closing avahi hylafax service file")
        configfile.close()

    if config["bonjour_hfaxd"] or config["bonjour_web"] or config["bonjour_ssh"]:
        open_firewall("mdns")
    else:
        close_firewall("mdns")

    log_notice("Reloading avahi")
    rc = execute(["systemctl", "reload-or-restart", "avahi-daemon"])
    return True

def config_services():
    log_debug("Entering services configuration menu")
    config = read_services_config()
    width = 60
    title = "Configuring services"
    header = "Firewall and Bonjour configuration"
    itemWidth = 30
    while True:
        items = []
        items.append(("Open Firewall for HylaFAX Enterprise",
                      Checkbox("", config["firewall_hfaxd"])))
        items.append(("Open Firewall for Web",
                      Checkbox("", config["firewall_http"])))
        items.append(("Open Firewall for Email",
                      Checkbox("", config["firewall_smtp"])))
        items.append(("Open Firewall for License Manager",
                      Checkbox("", config["firewall_lmgrd"])))
        items.append(("Advertise HylaFAX Enterprise",
                      Checkbox("", config["bonjour_hfaxd"])))
        items.append(("Advertise HylaFAX Enterprise Web",
                      Checkbox("", config["bonjour_web"])))
        items.append(("Advertise SSH", Checkbox("", config["bonjour_ssh"])))
        buttons = [("Save", "save", "F12"), ("Cancel", "cancel", "ESC")]

        button, values = EntryWindow(screen, title, header, items, 1, width,
                                     itemWidth, buttons)

        if button == "cancel":
            log_debug("Exiting services configuration menu without saving changes")
            return False
        else:
            firewall = 0
            bonjour = 4
            config["firewall_hfaxd"] = values[firewall+0]
            config["firewall_http"] = values[firewall+1]
            config["firewall_smtp"] = values[firewall+2]
            config["firewall_lmgrd"] = values[firewall+3]
            config["bonjour_hfaxd"] = values[bonjour+0]
            config["bonjour_web"] = values[bonjour+1]
            config["bonjour_ssh"] = values[bonjour+2]
            save_services_config(config)
            log_debug("Exiting services configuration menu")
            return True

def call_register(serial, activateHbf=False):
    command = []
    command.append(os.path.join(spool, "ifax/bin/register"))
    command.append("--no-hosts")
    command.append("--no-restart")
    command.extend(["--lmgrd-port", "27000"])
    command.extend(["--vendor-port", "27059"])
    if activateHbf:
        command.append("--hbf")
    command.append(serial)
    process = execute(command, True)
    if process["returncode"] != 0:
        i = process["stdout"].find("ERROR")
        if i >= 0:
            message = process["stdout"][i+6:]
        else:
            message = process["stdout"]
        warning("The registration of serial " + serial + " failed:\n\n" +
               message)
    return process["returncode"]

def restart_boston():
    log_notice("Restarting Boston driver")
    filename = os.path.join(spool, ".restart")
    try:
        file = open(filename, "w")
        file.close()
    except:
        log_warning("Unable to create '.restart' file. Continuing.")
    log_debug("Stopping running btgetty processes")
    devices = read_init()
    for device in devices:
        if device.startswith("boston"):
            # Do not output warning if faxquit doesn't succeed...
            rc = execute(["/usr/sbin/faxquit", device], True)
    # ifax-bt childs does not close stdout/stderr
    rc = subprocess.call(["systemctl", "restart", "ifax-bt"],
                         stdout=devNull, stderr=devNull)
    # Some ifax-bt init script will take care of deleting the restart file
    if os.path.isfile(filename):
        os.remove(filename)

def register_hylafax():
    log_debug("Entering License activation menu")
    registered = False
    hbf = False
    boston = False
    sr140 = False
    serial = ""
    width = 60
    title = "Activate License(s)"
    header = "Please enter the serial number of the first license you " + \
             "would like to activate. (Hyphens are not required)"
    itemWidth = 30
    buttons = [("Activate", "activate", "F12"), ("Return", "return", "ESC")]

    while True:
        log_debug("Prompting for a serial number to activate")
        items = [("Serial Number", serial)]
        button, values = EntryWindow(screen, title, header, items, 1, width,
                                    itemWidth, buttons)

        serial = values[0].strip()
        if button == "return":
            log_debug("Return button pressed")
            break
        elif serial == "":
            log_debug("No serial number entered, exiting menu")
            break

        length = len(serial.replace("-", ""))
        if length == 20:
            serial = serial.upper().replace("-", "")
            serial = serial[0:4] + "-" + serial[4:8] + "-" + serial[8:12] + \
                     "-" + serial[12:16] + "-" + serial[16:20]
        elif length == 16:
            serial = serial.upper().replace("-", "")
            serial = serial[0:4] + "-" + serial[4:8] + "-" + serial[8:12] + \
                     "-" + serial[12:16]
        else:
            log_info("Invalid serial number entered: " + serial)
            warning("This is not a valid serial number!")
            continue

        progress = Progress(screen, "Activating license")
        progress.update(1, "Activating license for serial number " + serial)
        log_notice("Activating license for serial number: " + serial)
        rc = call_register(serial)
        if rc == 0:
            log_info("Activation succesful")
            registered = True
            config = read_boston_config()
            if config["bostondevices"] > 0:
                boston = True
                if config["type"].startswith("SR140"):
                    sr140 = True
            config = read_sip_config()
            if config["sipdevices"] > 0:
                hbf = True
            header = "The serial number " + serial + " has been " + \
                     "successfully activated. If you have another " + \
                     "serial number to activate, please enter it now " + \
                     "and select [Activate], otherwise select [Return] " + \
                     "to go back to the previous menu."
            serial = ""
        else:
            log_warning("Error activating serial number: " + serial)
            header = "The serial number " + serial + " could not be " + \
                     "activated. Please try again."
        progress.done()

    if registered:
        progress = Progress(screen, "Restarting HylaFAX Enterprise")
        if hbf:
            progress.update(1, "Activating HBF")
            log_notice("Activating HBF")
            rc = execute(["chkconfig", "ifax-hbf", "reset"])
            progress.update(10, "Restarting HBF")
            log_notice("Restarting HBF")
            rc = service("ifax-hbf", "restart")
        if boston:
            progress.update(1, "Activating Boston driver")
            log_notice("Activating Boston driver")
            rc = execute(["systemctl", "enable", "hylafax-bt"])
            progress.update(5, "Checking Boston driver status")
            log_debug("Checking Boston driver status")
            rc = subprocess.call(["systemctl", "status", "ifax-bt"],
                                 stdout=devNull, stderr=devNull)
            if rc == 0:
                progress.update(10, "Restarting Boston driver")
                restart_boston()
            else:
                progress.update(10, "Starting Boston driver")
                log_notice("Starting Boston driver")
                # ifax-bt childs does not close stdout/stderr
                rc = subprocess.call(["systemctl", "start", "ifax-bt"],
                                     stdout=devNull, stderr=devNull)

        progress.update(40, "Checking license manager")
        # We don't want to see the output warning when lmgrd is not running.
        log_debug("Checking license manager status")
        rc = subprocess.call(["systemctl", "status", "ifax-lmgrd"],
                             stdout=devNull, stderr=devNull)
        if rc == 0:
            progress.update(45, "Reloading license manager")
            log_notice("Reloading license manager")
            rc = execute("lmreread")
        else:
            progress.update(45, "Restarting license manager")
            # We can't use service() because ifax-lmgrd never closes stderr.
            log_notice("Restarting license manager")
            rc = subprocess.call(["systemctl", "restart", "ifax-lmgrd"],
                                 stdout=devNull, stderr=devNull)

        progress.update(60, "Restarting HylaFAX Enterprise")
        log_notice("Restarting HylaFAX")
        rc = service("hylafax", "restart")
        progress.update(95, "Updating HylaFAX info status file")
        update_status_file()
        progress.done()

def add_support_key(user):
    # Append iFAX support key to authorized_keys
    log_debug("Opening iFAX support key file")
    keyFile = open("/etc/hylafax-enterprise-appliance/ifax-support.pub")
    try:
        log_debug("Reading iFAX support key")
        supportKey = keyFile.read()
    finally:
        log_debug("Closing iFAX support key file")
        keyFile.close()
    filename = user.pw_dir + "/.ssh/authorized_keys"
    newfilename = filename + ".ifax"
    origfilename = filename + ".orig"
    if os.path.isfile(newfilename):
        log_warning(newfilename + " already exists. Will not install the " +
                    "support key again.")
        return True
    if os.path.isfile(filename):
        try:
            log_debug("Copying authorized_keys to work file")
            shutil.copy2(filename, newfilename)
        except:
            return False
    newfile = open(newfilename, "a", 0o600)
    try:
        log_debug("Appending support key to work file")
        newfile.write("\n")
        newfile.write(supportKey)
    except IOError:
        log_error("Error appending iFAX support key to authorized keys. " +
                  "File not replaced.")
        newfile.close()
        os.remove(newfilename)
        return False
    newfile.close()
    os.chown(newfilename, user.pw_uid, user.pw_gid)

    try:
        if os.path.isfile(filename):
            log_debug("Renaming authorized_keys to authorized_keys.orig")
            os.rename(filename, origfilename)
        log_debug("Installing new authorized_keys")
        shutil.copy2(newfilename, filename)
        os.chown(filename, user.pw_uid, user.pw_gid)
    except OSError:
        log_error("Error appending iFAX support key to authorized keys. " +
                  "File not replaced.")
        return False
    return True

def remove_support_key(user):
    filename = user.pw_dir + "/.ssh/authorized_keys"
    newfilename = filename + ".ifax"
    origfilename = filename + ".orig"
    # Remove iFAX support key from authorized_keys
    try:
        if os.path.isfile(origfilename):
            log_debug("Re-installing original authorized_keys file")
            os.rename(origfilename, filename)
        else:
            log_debug("Removing authorized_keys file")
            os.remove(filename)
        os.remove(newfilename)
    except OSError:
        warning("Error removing iFAX support key from authorized keys. " +
                "The iFAX support key is still active.")
        log_error("Error removing iFAX support key from authorized keys. " +
                  "The iFAX support key is still active.")

class TunnelDisconnectRequested(Exception): pass
class TunnelSetupProblem(Exception): pass
class TunnelProblem(Exception): pass
class ExitRequested(Exception): pass

def tunnel_sigint_handler(signum, frame):
    log_info("Received INT signal, forcing tunnel disconnection")
    raise TunnelDisconnectRequested

def tunnel_sigterm_handler(signum, frame):
    log_info("Received TERM signal")
    log_info("Forcing tunnel disconnection before exiting")
    raise ExitRequested

def tunnel_sighup_handler(signum, frame):
    log_info("Received HUP signal")
    log_info("Forcing tunnel disconnection before exiting")
    raise ExitRequested

def tunnel_sigusr1_handler(signum, frame):
    log_info("Received USR1 signal")
    log_info("Forcing tunnel disconnection before exiting")
    raise ExitRequested

def restartable_poll(poll):
    while True:
        try:
            events = poll.poll()
        except select.error as e:
            if e.args[0] == errno.EINTR:
                continue
            else:
                raise
        break
    return events

def tunnel_process_data(process):
    dataProcessed = False
    log_debug("Preparing to pass data between pipes")
    log_debug("Process stdin is fd: " + str(process.stdin.fileno()))
    log_debug("Process stdout is fd: " + str(process.stdout.fileno()))
    log_debug("Process stderr is fd: " + str(process.stderr.fileno()))
    poll = select.poll()
    poll.register(sys.stdin, select.POLLIN)
    poll.register(process.stdout, select.POLLIN)
    poll.register(process.stderr, select.POLLIN)
    fcntl.fcntl(process.stdout.fileno(), fcntl.F_SETFL, os.O_NONBLOCK)
    fcntl.fcntl(process.stderr.fileno(), fcntl.F_SETFL, os.O_NONBLOCK)
    fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, os.O_NONBLOCK)
    oldTermios = termios.tcgetattr(sys.stdin)
    newTermios = termios.tcgetattr(sys.stdin)
    newTermios[3] = newTermios[3] & ~(termios.ECHO | termios.ICANON)
    termios.tcsetattr(sys.stdin, termios.TCSADRAIN, newTermios)
    events = restartable_poll(poll)
    try:
        try:
            while process.poll() is None or events[0][1] & select.POLLIN:
                log_debug("events: " + str(events))
                for event in events:
                    type = event[1]
                    fd = event[0]
                    if type & select.POLLIN:
                        log_debug("Received data on fd " + str(fd))
                        if fd == sys.stdin.fileno():
                            data = sys.stdin.read()
                            log_debug("Data received: " + data)
                            process.stdin.write(data)
                            process.stdin.flush()
                        elif fd == process.stdout.fileno():
                            dataProcessed = True
                            data = process.stdout.read()
                            if data.find("DISCONNECT") != -1:
                                log_info("Disconnected from remote")
                                raise TunnelDisconnectRequested
                            else:
                                sys.stdout.write(data)
                                sys.stdout.flush()
                        elif fd == process.stderr.fileno():
                            data = process.stderr.read()
                            if data.find("remote port forwarding failed") != -1:
                                log_warning("Unable to setup remote port " +
                                            "forwarding")
                                raise TunnelSetupProblem
                            else:
                                log_warning("SSH: " + data)
                        else:
                            log_error("Unable to process data on this " +
                                      "descriptor")
                    if type & select.POLLHUP:
                        log_debug("Closed fd " + str(fd))
                        if fd == sys.stdin.fileno():
                            raise ExitRequested
                        else:
                            raise TunnelProblem
                    if type & select.POLLERR:
                        log_debug("Poll error on fd " + str(fd))
                        raise TunnelProblem
                    if type & select.POLLNVAL:
                        log_debug("Poll error: Invalid fd " + str(fd))
                        raise TunnelProblem
                events = restartable_poll(poll)
        finally:
            log_debug("Process poll is: " + str(process.poll()))
            if sys.stdin.isatty():
                log_debug("Restore terminal settings")
                termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldTermios)
    except TunnelProblem:
        if dataProcessed:
            raise TunnelProblem
        else:
            raise TunnelSetupProblem
    if dataProcessed:
        raise TunnelProblem
    else:
        raise TunnelSetupProblem

def terminate_tunnel(process):
    if process.returncode is None:
        log_info("Tunnel process still running, killing PID " +
                 str(process.pid))
        os.kill(process.pid, signal.SIGTERM)
    log_debug("Waiting for tunnel process to terminate")
    process.wait()
    rc = process.returncode
    log_debug("tunnel support command exited with value: " + str(rc))

def reenable_support():
    log_info("Trying to re-enable support tunnel")
    return enable_support(read_support_config())

def enable_support(config):
    if not config["ssh_user"]:
        config["ssh_user"] = "root"
    try:
        user = pwd.getpwnam(config["ssh_user"])
        home = user.pw_dir
    except KeyError:
        log_warning("Invalid SSH username provided: " + str(config["ssh_user"]))
        return False

    if not os.path.isdir(home):
        log_debug("User " + str(config["ssh_user"]) + " home directory does not exist")
        return False

    if not os.path.isdir(home + "/.ssh"):
        log_debug("Creating " + str(config["ssh_user"]) + " ssh configuration directory")
        os.mkdir(home + "/.ssh", 0o700)
        os.chown(home + "/.ssh", user.pw_uid, user.pw_gid)

    try:
        config["tunnel"] = int(config["tunnel"])
        if config["tunnel"] < 0 or config["tunnel"] > 999:
            raise Exception("Invalid tunnel ID")
    except:
        log_warning("Invalid tunnel ID provided: " + str(config["tunnel"]))
        return False

    if not add_support_key(user):
        return False

    hostid = get_hostid()
    if not hostid:
        hostid = "<NONE>"
    log_debug('hostid is "' + hostid + '"')
    serial = get_serial(hostid)
    if not serial:
        serial = "<NONE>"
    log_debug('serial is "' + serial + '"')
    screen.suspend()

    terminate = False
    reconnectionCount = 0
    port = 22000 + int(config["tunnel"])
    command = []
    command.append("ssh")
    command.append("-o BatchMode=yes")
    command.append("-o ServerAliveInterval=50")
# ExitOnForwardFailure requires openssh 4.4+
#    command.append("-o ExitOnForwardFailure=yes")
    command.append("-o GlobalKnownHostsFile=" +
                   "/etc/hylafax-enterprise-appliance/known_hosts")
    command.append("-i/etc/hylafax-enterprise-appliance/aplsprt.key")
    command.append("-R " + str(port) + ":localhost:22")
    command.append("aplsprt@bastion.ifax.com")
    command.append("-t")
    command.append("-t")
#    command.append(str(config["tunnel"]))
    command.append("TunnelInfo")
    while not terminate and reconnectionCount < 10:
        if reconnectionCount > 0:
            sleepTime = min(5 * reconnectionCount, 30)
            log_debug("Sleeping for " + str(sleepTime) + " seconds")
            time.sleep(sleepTime)
        reconnectionCount += 1
        log_debug("Connection attempt number " + str(reconnectionCount))
        log_debug("Executing tunnel support command: " + str(command))
        process = subprocess.Popen(command, stdin=subprocess.PIPE,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   encoding="utf8")
        try:
            # TUNNEL must be the last line sent
            log_debug("Sending host information to iFAX support")
            process.stdin.write("VERSION " + APPLIANCE_VERSION + "\n")
            process.stdin.write("HOST " + socket.getfqdn() + "\n")
            domain = read_item("/etc/resolv.conf", "domain", " ")
            if not domain:
                domain = read_item("/etc/resolv.conf", "search", " ")
            if domain:
                process.stdin.write("DOMAIN " + domain + "\n")
            process.stdin.write("SERIAL " + serial + "\n")
            process.stdin.write("HOSTID " + hostid + "\n")
            process.stdin.write("SSH_USER " + str(config["ssh_user"]) + "\n")
            process.stdin.write("TUNNEL " + str(config["tunnel"]) + "\n")
            process.stdin.flush()
        except:
            if process.poll is None:
                process.stdin.close()
                process.wait()
            screen.resume()
            log_warning("Problem establishing tunnel to iFAX Solutions")
            log_debug("Process.poll is: "+ str(process.poll))
        else:
            try:
                try:
                    prevSigterm = signal.signal(signal.SIGTERM,
                                                tunnel_sigterm_handler)
                    prevSighup = signal.signal(signal.SIGHUP,
                                                tunnel_sighup_handler)
                    prevSigusr1 = signal.signal(signal.SIGUSR1,
                                                tunnel_sigusr1_handler)
                    prevSigint = signal.signal(signal.SIGINT,
                                               tunnel_sigint_handler)
                    # tunnel_process_data only return with an exception
                    tunnel_process_data(process)
                finally:
                    signal.signal(signal.SIGTERM, prevSigterm)
                    signal.signal(signal.SIGHUP, prevSighup)
                    signal.signal(signal.SIGUSR1, prevSigusr1)
                    signal.signal(signal.SIGINT, prevSigint)
            except TunnelDisconnectRequested:
                terminate = True
            except TunnelProblem:
                # The tunnel processed data before it went down
                reconnectionCount = 0
            except TunnelSetupProblem:
                pass
            except ExitRequested:
                terminate_tunnel(process)
                sys.exit(15)
        terminate_tunnel(process)
        if not terminate and reconnectionCount < 10:
            log_debug("Disconnection not requested by user, reconnecting.")

    screen.resume()
    remove_support_key(user)
    return terminate

def read_support_config():
    config = {"tunnel": "",
              "ssh_user": "",
              "ticket": "",
              "company": "",
              "contact": "",
              "phone": ""}
    log_info("Reading Support configuration file")
    try:
        parser = configparser.RawConfigParser(config)
        parser.read("/etc/hylafax-enterprise-appliance/support.conf")
        for (item, value) in parser.items("support"):
            log_debug('Found value of ' + item + ' to be "' + value + '"')
            config[item] = value
    finally:
        return config

def save_support_config(config):
    head = "# This file is managed by the " + \
           "HylaFAX Enterprise Appliance Console.\n" + \
           "# This file is re-written as needed by the console and any\n" + \
           "# addition you make can be discarded without notice.\n" + \
           "# You have been warned.\n\n"
    log_info("Updating support config file")
    log_debug("Opening support config file")
    configfile = open("/etc/hylafax-enterprise-appliance/support.conf", "w")
    try:
        log_debug("Writing support config file")
        configfile.write("# HylaFAX Appliance support configuration file.\n")
        configfile.write(head)
        configfile.write("# Generated by " +
                         "hylafax-enterprise-appliance-console on " +
                         time.strftime("%Y-%m-%d %H:%M:%S") + "\n")
        configfile.write("[support]\n")
        configfile.write("Tunnel: " + str(config["tunnel"]) + "\n")
        configfile.write("SSH_user: " + str(config["ssh_user"]) + "\n")
        configfile.write("Ticket: " + str(config["ticket"]) + "\n")
        configfile.write("Company: " + config["company"] + "\n")
        configfile.write("Contact: " + config["contact"] + "\n")
        configfile.write("Phone: " + config["phone"] + "\n")
    finally:
        log_debug("Closing support config file")
        configfile.close()
    return True

def config_support():
    log_debug("Entering support configuration menu")
    config = read_support_config()
    width = 60
    title = "Configuring iFAX support access"
    license = get_license()
    if license and not license["expiration"]:
        address = "support@ifax.com"
    else:
        address = "eval-support@ifax.com"
    header = "To enable iFAX Support to access your appliance, you will " + \
             "need to contact " + address + " and get a Tunnel ID from " + \
             "them.  Enter that Tunnel ID here so Support can connect to " + \
             "your appliance."
    itemWidth = 30
    buttons = [("Enable", "enable", "F12"), ("Cancel", "cancel", "ESC")]

    while True:
        items = []
        items.append(("Support tunnel ID", ""))  # Require it be re-entered
#        items.append(("SSH user", config["ssh_user"]))
#        items.append(("iFAX ticket #", config["ticket"]))
#        items.append(("Company name", config["company"]))
#        items.append(("Contact name", config["contact"]))
#        items.append(("Phone number", config["phone"]))

        button, values = EntryWindow(screen, title, header, items, 1, width,
                                    itemWidth, buttons)

        if button == "cancel":
            log_debug("Exiting support configuration menu without saving " +
                      "changes")
            return False
        else:
            config["tunnel"] = values[0]
#            config["ssh_user"] = values[1]
#            config["ticket"] = values[2]
#            config["company"] = values[3]
#            config["contact"] = values[4]
#            config["phone"] = values[5]
            try:
                config["tunnel"] = int(config["tunnel"])
                if config["tunnel"] < 0 or config["tunnel"] > 999:
                    raise Exception("Invalid tunnel ID")
            except:
                log_warning("Invalid tunnel ID provided: " +
                            str(config["tunnel"]))
                warning("The tunnel ID is invalid.\n\nPlease contact " + \
                        "iFAX Solutions to obtain your tunnel ID.")
                continue
            try:
                if not config["ssh_user"]:
                    config["ssh_user"] = "root"
                user = pwd.getpwnam(config["ssh_user"])
                home = user.pw_dir
            except KeyError:
                log_warning("Invalid SSH username provided: " + str(config["ssh_user"]))
                warning("Invalid SSH username provided: " + str(config["ssh_user"]))
                continue

            if not os.path.isdir(home):
                log_warning("User " + str(config["ssh_user"]) + " home directory does not exist")
                warning("User " + str(config["ssh_user"]) + " home directory does not exist")
                continue

        save_support_config(config)
        if not enable_support(config):
            warning("Unable to open support tunnel to iFAX Solutions")
        log_debug("Exiting support configuration menu")
        return True

def check_for_updates(group=None):
    results = dict()
    log_debug("Checking for updates")
    progress = Progress(screen, "Checking for updates")
    baseCommand = []
    baseCommand.append("/usr/bin/yum")
    baseCommand.append("check-update")
    baseCommand.append("-q")

    if group is None or group == "all":
        progress.update(1, "Checking for any updates")
        command = list(baseCommand)
        process = execute(command, True)
        if process["returncode"] == 100:
            # There are updates available
            results["all"] = len(process["stdout"].split("\n")) - 2
        else:
            if process["returncode"] != 0:
                warning("A problem occured while checking for updates:\n\n" +
                       process["stdout"] + "\n\n" + process["stderr"])
            # No need to check separately
            results["all"] = 0
            results["os"] = 0
            results["appliance"] = 0
            progress.done()
            if group == "all":
                return 0
            return results

    if group is None or group == "os":
        progress.update(60, "Checking for updates to base OS")
        command = list(baseCommand)
        command.append("-x ifax*")
        command.append("-x hylafax-enterprise*")
        process = execute(command, True)
        if process["returncode"] == 100:
            # There are updates available
            results["os"] = len(process["stdout"].split("\n")) - 2
        else:
            if process["returncode"] != 0:
                warning("A problem occured while checking for updates:\n\n" +
                       process["stdout"] + "\n\n" + process["stderr"])
            results["os"] = 0

    if group is None or group == "appliance":
        progress.update(80, "Checking for updates to Appliance")
        command = list(baseCommand)
        command.append("ifax*")
        command.append("hylafax-enterprise*")
        process = execute(command, True)
        if process["returncode"] == 100:
            # There are updates available
            results["appliance"] = len(process["stdout"].split("\n")) - 2
        else:
            if process["returncode"] != 0:
                warning("A problem occured while checking for updates:\n\n" +
                       process["stdout"] + "\n\n" + process["stderr"])
            results["appliance"] = 0

    progress.done()
    if group is None:
        return results
    return results["group"]

def update_base(group):
    progress = Progress(screen, "Updating packages")
    command = []
    command.append("/usr/bin/yum")
    command.append("update")
    command.append("-y")
    if group == "os":
        log_debug("Updating OS packages")
        progress.update(1, "Updating base OS packages")
        command.append("-x ifax*")
        command.append("-x hylafax-enterprise*")
    elif group == "appliance":
        log_debug("Updating Appliance packages")
        progress.update(1, "Updating Appliance packages")
        command.append("ifax*")
        command.append("hylafax-enterprise*")
    else:
        log_debug("Updating all packages")
        progress.update(1, "Updating all packages")
    process = execute(command, True)
    if process["returncode"] != 0:
        warning("A problem occured while updating the system:\n\n" +
               process["stdout"] + "\n\n" + process["stderr"])
    progress.done()
    return process["returncode"]

def update_os():
    return update_base("os")

def update_appliance():
    return update_base("appliance")

def update_all():
    return update_base("all")

def update_menu():
    log_debug("Entering update menu")
    width = 60
    title = "Updating HylaFAX Enterprise Appliance"
    itemWidth = 30
    buttons = [("Ok", "ok", "F12"), ("Return", "return", "ESC")]

    while True:
        updates = check_for_updates()
        if updates["all"]:
            header = "There are " + str(updates["all"]) + " packages to be " + \
                     "updated.\n" + str(updates["os"]) + " OS packages and " + \
                     str(updates["appliance"]) + " Appliance packages."
        else:
            header = "There are no packages to update."
        items = []
        items.append(("Update base OS", "update_os"))
        items.append(("Update HylaFAX Enterprise", "update_appliance"))
        items.append(("Update All", "update_all"))

        button, item = ListboxChoiceWindow(screen, title, header, items,
                                           buttons, width)

        if button == "return":
            break
        else:
            globals()[item]()

    log_debug("Exiting update menu")
    return True

def shutdown():
    log_debug("Entering shutdown menu")
    # Add menu choice of Reboot, Halt or Cancel
    width = 75
    title = "Shutting down"
    header = "Are you sure you want to shutdown?"
    items = []
    items.append(("Halt System", "halt"))
    items.append(("Reboot System", "reboot"))
    buttons = [("Ok", "ok", "F12"), ("Cancel", "cancel", "ESC")]

    button, item = ListboxChoiceWindow(screen, title, header, items,
                                       buttons, width)

    if button == "cancel":
        log_debug("Exiting shutdown menu")
        return False
    else:
        if item == "halt":
            log_notice("Server halt requested")
            rc = subprocess.call("halt")
            return True
        else:
            log_notice("Server reboot requested")
            rc = subprocess.call("reboot")
            return True

#
# Advanced console menu
#
def advanced_menu():
    log_debug("Entering advanced menu")

    while True:
        check_network()

        width = 75
        title = APPLIANCE_NAME + " " + APPLIANCE_VERSION
        header = center_text(license_status()["text"], width - 5)
        items = []
        items.append(("Configure Network", "config_network"))
        items.append(("Configure HTTP proxy", "config_http_proxy"))
        items.append(("Activate License(s)", "register_hylafax"))
        items.append(("Configure HylaFAX Enterprise", "config_hylafax"))
        items.append(("Configure Dialing Options", "config_dialrules"))
        items.append(("Configure Telco connections", "config_telco"))
        license = get_license()
        if license and license["hbf"] > 0:
            items.append(("Configure SIP Devices", "config_sip"))
        items.append(("Configure Mail", "config_mail"))
        items.append(("Configure Services", "config_services"))
        items.append(("Enable iFAX support access", "config_support"))
        items.append(("Update Menu", "update_menu"))
        buttons = [("Ok", "ok", "F12"), ("Return", "return", "ESC"),
                   ("Shutdown", "shutdown")]

        button, item = ListboxChoiceWindow(screen, title, header, items,
                                           buttons, width)

        if button == "shutdown":
            if shutdown():
                return False
        elif button == "return":
            break
        else:
            if item == "config_network":
                snack_fork(config_network, True)
            elif item == "config_http_proxy":
                snack_fork(config_http_proxy, True)
            elif item == "register_hylafax":
                snack_fork(register_hylafax, True)
            elif item == "config_hylafax":
                snack_fork(config_hylafax, True)
            elif item == "config_dialrules":
                snack_fork(config_dialrules, True)
            elif item == "config_telco":
                snack_fork(config_telco, True)
            elif item == "config_sip":
                snack_fork(config_sip, True)
            elif item == "config_mail":
                snack_fork(config_mail, True)
            elif item == "config_services":
                snack_fork(config_services, True)
            elif item == "update_menu":
                snack_fork(update_menu, True)
            else:
                globals()[item]()

    log_debug("Exiting advanced menu")
    return True

#
# Main console menu
#
def main_menu():
    log_debug("Entering main menu")
    rc = True
    check_network()

    width = 75
    title = APPLIANCE_NAME + " " + APPLIANCE_VERSION
    header = "Your HylaFAX Enterprise Appliance is online.\n\n" + \
             "You may access the appliance via a web browser at:\n\n" + \
             "http://" + ipAddress + "/\n\n" + \
             "For additional configuration options, including activation\n" + \
             "of licenses, select [Advanced Options] below."
    header = center_text(header, width - 5)
    buttons = [("Advanced Options", "advanced_menu", "F12")]

    button = ButtonChoiceWindow(screen, title, header, buttons, width)

    if button == "advanced_menu":
        rc = advanced_menu()

    log_debug("Exiting main menu")
    return rc

def setup_client():
    log_debug("Making sure HylaFAX client configuration is correct")
    rc = execute(["/usr/sbin/faxsetup", "-client"])

def firstrun():
    config = configparser.RawConfigParser()
    try:
        log_debug("Reading firstrun configuration file")
        config.read("/etc/hylafax-enterprise-appliance/firstrun.conf")
        if not config.getboolean("console", "firstrun"):
            return False
    except (configparser.NoSectionError, configparser.NoOptionError):
        log_warning("No firstrun configuration found")

    log_notice("Starting first run setup")
    progress = Progress(screen, "First run setup")
    progress.update(1, "Enabling web interface")
    log_info("Enabling web interface")
    rc = execute(["/usr/sbin/ifax-enable-web.sh", "-d"])
    update_item(os.path.join(spool, "etc/CONFIG/web.conf"), "FaxRcvdCmd",
                ":", None)
    update_item(os.path.join(spool, "etc/FaxDispatch"), ".", " ",
                "etc/FaxDispatch.appliance")
    update_item(os.path.join(spool, "etc/FaxNotify"), ".", " ",
                "etc/FaxNotify.appliance")

    progress.update(30, "Enabling Appliance callctrl file")
    log_info("Enabling Appliance callctrl file")
    config = {"type": "SR140_SIP",
              "t38support": "t38_first",
              "rtpcodec": "pcmu",
              "passthroughtimeoutoutbound": "10000",
              "gateway": "127.0.0.1"}
    callControlFile = os.path.join(spool,
                                   "ifax/bt/config/callctrl-appliance.cfg")
    template = "ifax/bt/config/templates/callctrl-sip.tmpl"
    create_from_template(callControlFile, template, config)
    btconfigfile = os.path.join(spool, "ifax/bt/config/btcall.cfg")
    update_item(btconfigfile, "call_control", " ",
                "ifax/bt/config/callctrl-appliance.cfg")
    update_item(btconfigfile, "v34_enable", " ", 0)

    progress.update(35, "Configuring init")
    log_info("Configuring init")
    save_systemd(["boston00", "boston01"], False)
    # We leave init reloading to register

    database = "MariaDB"
    serviceName = "mariadb"
    progress.update(45, "Configuring " + database)
    log_info("Configuring " + database)
    update_item("/etc/my.cnf", "skip-networking", "\n", "",
                "Only local Web interface needs access to MySQL\n[mysqld]")
    log_notice("Restarting " + database)
    rc = service(serviceName, "restart")

    log_debug("Opening default MySQL setup file for the web interface")
    sqlFile = open(os.path.join(spool, "ifax/sql/web-mysql-default.sql"))
    try:
        log_debug("Reading file")
        sqlData = sqlFile.read()
    finally:
        log_debug("Closing default MySQL setup file for the web interface")
        sqlFile.close()
    mysqlUser = "hylafaxweb"
    mysqlPassword = "eeweb"
    mysqlDB = "hylafaxweb"
    log_debug("Executing " + serviceName)
    mysql = subprocess.Popen(["mysql",
                              "--user=" + mysqlUser,
                              "--password=" + mysqlPassword,
                              mysqlDB],
                             stdin=subprocess.PIPE,
                             encoding="utf8")
    log_debug("Sending defaults file to mysql")
    mysql.communicate(sqlData)
    log_debug("Done with mysql")

    progress.update(70, "Configuring HylaFAX Enterprise logging facilities")
    log_info("Configuring HylaFAX logging facilities")
    update_item("/etc/hylafax/hfaxd.conf", "LogFacility", ":", "local3")
    update_item("/etc/hylafax/hfaxd.conf", "ServerTracing", ":",
                "0xffffffff")
    if not os.path.isdir("/var/log/hylafax"):
        log_debug("Creating HylaFAX syslog directory")
        os.mkdir("/var/log/hylafax", 0o775)

    progress.update(75, "Configuring Email to fax gateway")
    log_info("Configuring Email to fax gateway")
    # Configure email to fax gateway
    update_item("/etc/postfix/master.cf", "fax", "      ",
                "unix   -      n        n      -       1       pipe\n" +
                "  flags= user=faxmail argv=/usr/bin/faxmail -d -n ${user}",
                "HylaFAX Email to Fax gateway")
    update_item("/etc/postfix/main.cf", "fax_destination_recipient_limit", "=",
                "1")
    update_item("/etc/postfix/main.cf", "transport_maps", "=",
                "regexp:/etc/postfix/transport-fax")
    update_item("/etc/postfix/main.cf", "virtual_alias_maps", "=",
                "regexp:/etc/postfix/virtual_alias-fax")
    update_item("/etc/postfix/main.cf", "local_recipient_maps", "=",
                "proxy:unix:passwd.byname $alias_maps " +
                "regexp:/etc/postfix/fax-regexp")
    update_item("/etc/postfix/main.cf", "inet_interfaces", "=", "all")
    update_item("/etc/postfix/main.cf", "mynetworks_style", "=", "host")
    if not os.path.isdir("/usr/sbin/faxmail"):
        os.mkdir("/usr/sbin/faxmail", 0o755)
        os.mkdir("/usr/sbin/faxmail/text", 0o755)
        os.symlink("/bin/true", "/usr/sbin/faxmail/text/html")
        os.symlink("/bin/true", "/usr/sbin/faxmail/text/vcard")
        os.symlink("/bin/true", "/usr/sbin/faxmail/text/x-vcard")
        os.symlink("/bin/true", "/usr/sbin/faxmail/text/directory")
        os.mkdir("/usr/sbin/faxmail/image", 0o755)
        os.symlink("/bin/true", "/usr/sbin/faxmail/image/gif")
        os.symlink("/bin/true", "/usr/sbin/faxmail/image/jpeg")
        os.symlink("/bin/true", "/usr/sbin/faxmail/image/png")
        os.mkdir("/usr/sbin/faxmail/application", 0o755)
        os.symlink("/bin/true", "/usr/sbin/faxmail/application/vcard")
        os.symlink("/bin/true", "/usr/sbin/faxmail/application/ms-tnef")
    log_notice("Restarting Postfix")
    rc = service("postfix", "restart")

    progress.update(89, "Configuring firewalld")
    update_item("/etc/firewalld/firewalld.conf", "AutomaticHelpers", "=", "no")
    rc = service("firewalld", "restart")

    progress.update(90, "Configuring services")
    log_info("Enabling Bonjour announcements and opening firewall")
    config = {"firewall_hfaxd": True,
              "firewall_http": True,
              "firewall_smtp": True,
              "firewall_lmgrd": True,
              "bonjour_hfaxd": True,
              "bonjour_web": True,
              "bonjour_ssh": True}
    save_services_config(config)

    progress.update(95, "Terminating First run setup")
    log_info("Saving first run configuration file")
    log_debug("Opening first run configuration file")
    configFile = open("/etc/hylafax-enterprise-appliance/firstrun.conf", "w")
    try:
        log_debug("Writing first run configuration file")
        configFile.write("[console]\n")
        configFile.write("firstrun=false\n")
    finally:
        log_debug("Closing first run configuration file")
        configFile.close()
    progress.update(100, "Done")
    progress.done()
    log_notice("First run setup done")
    return True

#
# __main__
#
if __name__ == "__main__":
    consolePid = os.getpid()
    childPid = None
    oldPid = None
    pidfilename = "/var/run/hylafax-enterprise-appliance-console.pid"

    if os.path.isfile(pidfilename):
        file = open(pidfilename)
        try:
            oldPid = int(file.read(10))
        except:
            oldPid = None
        file.close()

    # Make sure we are started from the init script.
    if not os.getenv('HYLAFAX_ENTERPRISE_APPLIANCE_INIT'):
        # If the console was previously running with the current PID and the
        # PID file hasn't been removed. There are good chances we have
        # been reloaded with USR1. We won't require for the INIT environment
        # variable to be present so that reloads from an older version
        # still work.
        if oldPid != consolePid:
            print(APPLIANCE_NAME + " " + APPLIANCE_VERSION + ".\n")
            print("The Appliance console must be run as a service.\n")
            if os.path.isfile(pidfilename):
                print("There is already an Appliance console running.\n")
            else:
                print("There is no Appliance console currently running.")
                print("Use `service hylafax-enterprise-appliance start` to " +
                      "start it.\n")
            print("You can connect to an already running console using ")
            print("`attach-console`. Once connected you can disconnect from ")
            print("it with [CTRL-\]. This will only disconnect your session ")
            print("from the console and it will continue to run.")
            sys.exit(2)

    # Ignore signals until they have a significance
    signal.signal(signal.SIGUSR2, signal.SIG_IGN)
    signal.signal(signal.SIGWINCH, signal.SIG_IGN)
    signal.signal(signal.SIGINT, signal.SIG_IGN)

    # Terminate if the control terminal dissapear:
    signal.signal(signal.SIGHUP, signal.SIG_IGN)

    # Nice restart on signal USR1.
    signal.signal(signal.SIGUSR1, sigusr1_handler)

    # Check license every hour to update license status file
    signal.signal(signal.SIGALRM, sigalrm_handler)
    signal.alarm(3600)

    # Make sure kernel messages go to tty9
    TIOCLINUX = 0x541c
    file = open("/dev/tty9")
    fcntl.ioctl(file, TIOCLINUX, struct.pack("bb", 11, 9))
    file.close()

    try:
        pidfile = open(pidfilename, "w")
        pidfile.write(str(os.getpid()) + "\n")
    finally:
        pidfile.close()

    config = configparser.RawConfigParser(configDefaults)
    config.read("/etc/hylafax-enterprise-appliance/console.conf")

    LOGLEVELS = {"emerg": syslog.LOG_EMERG,
                 "panic": syslog.LOG_EMERG,
                 "alert": syslog.LOG_ALERT,
                 "crit": syslog.LOG_CRIT,
                 "critical": syslog.LOG_CRIT,
                 "err": syslog.LOG_ERR,
                 "error": syslog.LOG_ERR,
                 "warn": syslog.LOG_WARNING,
                 "warning": syslog.LOG_WARNING,
                 "notice": syslog.LOG_NOTICE,
                 "info": syslog.LOG_INFO,
                 "debug": syslog.LOG_DEBUG}
    LOGFACILITIES = {"auth": syslog.LOG_AUTH,
                     "authpriv": syslog.LOG_AUTH,  # LOG_AUTHPRIV is undefined
                     "cron": syslog.LOG_CRON,
                     "daemon": syslog.LOG_DAEMON,
                     "kern": syslog.LOG_KERN,
                     "lpr": syslog.LOG_LPR,
                     "mail": syslog.LOG_MAIL,
                     "news": syslog.LOG_NEWS,
                     "syslog": syslog.LOG_SYSLOG,
                     "user": syslog.LOG_USER,
                     "uucp": syslog.LOG_UUCP,
                     "local0": syslog.LOG_LOCAL0,
                     "local1": syslog.LOG_LOCAL1,
                     "local2": syslog.LOG_LOCAL2,
                     "local3": syslog.LOG_LOCAL3,
                     "local4": syslog.LOG_LOCAL4,
                     "local5": syslog.LOG_LOCAL5,
                     "local6": syslog.LOG_LOCAL6,
                     "local7": syslog.LOG_LOCAL7}

    logFacility = config.get("console", "logfacility").lower()
    logFacility = LOGFACILITIES.get(logFacility, syslog.LOG_USER)
    logLevel = config.get("console", "loglevel").lower()
    logLevel = LOGLEVELS.get(logLevel, syslog.LOG_NOTICE)
    syslog.openlog("FaxConsole", syslog.LOG_PID, logFacility)
    syslog.setlogmask(syslog.LOG_UPTO(logLevel))
    log_notice("Starting " + APPLIANCE_NAME + " " + APPLIANCE_VERSION)
    spool = config.get("hylafax", "spool")
    log_info("HylaFAX spool set to: " + spool)
    interface = config.get("hylafax", "interface")
    ipAddress = ""    # To be set in check_network()

    home = "/etc/hylafax-enterprise-appliance"
    log_debug("Forcing HOME to " + home)
    os.environ["HOME"] = home

    log_debug("Preparing Snack Screen")
    screen = SnackScreen()
    log_debug("Snack Screen prepared")

    try:
        log_debug("Opening '/dev/null' device")
        devNull = open("/dev/null", "w")
    except IOError:
        log_error("Unable to open '/dev/null' device")
        warning("Unable to open '/dev/null' device")
        sys.exit(1)

    # Executing first run steps if necessary
    firstrun()

    check_network()
    update_status_file()
    setup_client()

    if not os.path.isdir(spool):
        warning("Invalid HylaFAX Enterprise spool directory: " + spool)
        log_warning("Invalid HylaFAX Enterprise spool directory: " + spool)

    # Restart the tunnel if it was never requested to terminate
    support_config = read_support_config()
    try:
        if not support_config["ssh_user"]:
            support_config["ssh_user"] = "root"
        user = pwd.getpwnam(support_config["ssh_user"])
        home = user.pw_dir
    except KeyError:
        log_warning("Invalid SSH username provided: " + str(support_config["ssh_user"]))
        support_config["ssh_user"] = "root"
        home = "/root"

    if os.path.isfile(home + "/.ssh/authorized_keys.ifax"):
        if not snack_fork(reenable_support):
            warning("Problem re-establishing the iFAX Support tunnel.")

    try:
        while True:
            rc = snack_fork(main_menu)
            if not rc:
                break

    finally:
        screen.finish()

