Add pgp keyring management (#5)

This PR adds into the admin panel a front-end to manage PGP keys. Possibilities are many.
This commit is contained in:
David Duque 2020-10-04 16:35:59 +01:00 committed by GitHub
parent 03a1e57de6
commit 8519b7fc0e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 634 additions and 4 deletions

View file

@ -580,6 +580,63 @@ def smtp_relay_set():
except Exception as e:
return (str(e), 500)
# PGP
@app.route('/system/pgp/', methods=["GET"])
@authorized_personnel_only
def get_keys():
from pgp import get_daemon_key, get_imported_keys, key_representation
return {
"daemon": key_representation(get_daemon_key()),
"imported": list(map(key_representation, get_imported_keys()))
}
@app.route('/system/pgp/<fpr>', methods=["GET"])
@authorized_personnel_only
def get_key(fpr):
from pgp import get_key, key_representation
k = get_key(fpr)
if k is None:
abort(404)
return key_representation(k)
@app.route('/system/pgp/<fpr>', methods=["DELETE"])
@authorized_personnel_only
def delete_key(fpr):
from pgp import delete_key
try:
if delete_key(fpr) is None:
abort(404)
return "OK"
except ValueError as e:
return (str(e), 400)
@app.route('/system/pgp/<fpr>/export', methods=["GET"])
@authorized_personnel_only
def export_key(fpr):
from pgp import export_key
exp = export_key(fpr)
if exp is None:
abort(404)
return exp
@app.route('/system/pgp/import', methods=["POST"])
@authorized_personnel_only
def import_key():
from pgp import import_key
k = request.form.get('key')
try:
result = import_key(k)
return {
"keys_read": result.considered,
"keys_added": result.imported,
"keys_unchanged": result.unchanged,
"uids_added": result.new_user_ids,
"sigs_added": result.new_signatures,
"revs_added": result.new_revocations
}
except ValueError as e:
return (str(e), 400)
# MUNIN

View file

@ -21,5 +21,8 @@ management/backup.py 2>&1 | management/email_administrator.py "Backup Status"
# Provision any new certificates for new domains or domains with expiring certificates.
management/ssl_certificates.py -q 2>&1 | management/email_administrator.py "TLS Certificate Provisioning Result"
# Renew the daemon's PGP key if about to expire
management/pgp.py 2>&1 | management/email_administrator.py "PGP Key Renewal Result"
# Run status checks and email the administrator if anything changed.
management/status_checks.py --show-changes 2>&1 | management/email_administrator.py "Status Checks Change Notice"

View file

@ -7,9 +7,12 @@ import sys
import html
import smtplib
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from pgp import create_signature
# In Python 3.6:
#from email.message import Message
@ -45,6 +48,7 @@ content_html = "<html><body><pre>{}</pre></body></html>".format(html.escape(cont
msg.attach(MIMEText(content, 'plain'))
msg.attach(MIMEText(content_html, 'html'))
msg.attach(MIMEApplication(create_signature(content.encode()), Name="signed.asc"))
# In Python 3.6:
#msg.set_content(content)

125
management/pgp.py Executable file
View file

@ -0,0 +1,125 @@
#!/usr/local/lib/mailinabox/env/bin/python
# Tools to manipulate PGP keys
import gpg, utils, datetime
env = utils.load_environment()
# Import daemon's keyring - usually in /home/user-data/.gnupg/
gpghome = env['GNUPGHOME']
daemon_key_fpr = env['PGPKEY']
context = gpg.Context(armor=True, home_dir=gpghome)
# Global auxiliary lookup tables
crpyt_algos = {
0: "Unknown",
gpg.constants.PK_RSA: "RSA",
gpg.constants.PK_RSA_E: "RSA-E",
gpg.constants.PK_RSA_S: "RSA-S",
gpg.constants.PK_ELG_E: "ELG-E",
gpg.constants.PK_DSA: "DSA",
gpg.constants.PK_ECC: "ECC",
gpg.constants.PK_ELG: "ELG",
gpg.constants.PK_ECDSA: "ECDSA",
gpg.constants.PK_ECDH: "ECDH",
gpg.constants.PK_EDDSA: "EDDSA"
}
# Auxiliary function to process the key in order to be read more conveniently
def key_representation(key):
if key is None:
return None
key_rep = {
"master_fpr": key.fpr,
"revoked": key.revoked != 0,
"ids": [],
"subkeys": []
}
now = datetime.datetime.utcnow()
key_rep["ids"] = [ id.uid for id in key.uids ]
key_rep["subkeys"] = [{
"master": skey.fpr == key.fpr,
"sign": skey.can_sign == 1,
"cert": skey.can_certify == 1,
"encr": skey.can_encrypt == 1,
"auth": skey.can_authenticate == 1,
"fpr": skey.fpr,
"expires": skey.expires if skey.expires != 0 else None,
"expires_date": datetime.datetime.utcfromtimestamp(skey.expires).strftime("%x") if skey.expires != 0 else None,
"expires_days": (datetime.datetime.utcfromtimestamp(skey.expires) - now).days if skey.expires != 0 else None,
"expired": skey.expired == 1,
"algorithm": crpyt_algos[skey.pubkey_algo] if skey.pubkey_algo in crpyt_algos.keys() else crpyt_algos[0],
"bits": skey.length
} for skey in key.subkeys ]
return key_rep
# Tests an import as for whether we have any sort of private key material in our import
def contains_private_keys(imports):
import tempfile
with tempfile.TemporaryDirectory() as tmpdir:
with gpg.Context(home_dir=tmpdir, armor=True) as tmp:
result = tmp.key_import(imports)
return result.secret_read != 0
def get_key(fingerprint):
try:
return context.get_key(fingerprint, secret=False)
except KeyError:
return None
def get_daemon_key():
if daemon_key_fpr is None or daemon_key_fpr == "":
return None
return context.get_key(daemon_key_fpr, secret=True)
def get_imported_keys():
# All the keys in the keyring, except for the daemon's key
return list(
filter(
lambda k: k.fpr != daemon_key_fpr,
context.keylist(secret=False)
)
)
def import_key(key):
data = str.encode(key)
if contains_private_keys(data):
raise ValueError("Import cannot contain private keys!")
return context.key_import(data)
def export_key(fingerprint):
if get_key(fingerprint) is None:
return None
return context.key_export(pattern=fingerprint) # Key does exist, export it!
def delete_key(fingerprint):
key = get_key(fingerprint)
if fingerprint == daemon_key_fpr:
raise ValueError("You cannot delete the daemon's key!")
elif key is None:
return None
context.op_delete_ext(key, gpg.constants.DELETE_ALLOW_SECRET | gpg.constants.DELETE_FORCE)
return True
# Key usage
# Uses the daemon key to sign the provided message. If 'detached' is True, only the signature will be returned
def create_signature(data, detached=False):
signed_data, _ = context.sign(data, mode=gpg.constants.SIG_MODE_DETACH if detached else gpg.constants.SIG_MODE_CLEAR)
return signed_data
if __name__ == "__main__":
import sys, utils
# Check if we should renew the key
daemon_key = get_daemon_key()
exp = daemon_key.subkeys[0].expires
now = datetime.datetime.utcnow()
days_left = (datetime.datetime.utcfromtimestamp(exp) - now).days
if days_left > 14:
sys.exit(0)
else:
utils.shell("check_output", ["management/pgp_renew.sh"])

12
management/pgp_renew.sh Executable file
View file

@ -0,0 +1,12 @@
#!/bin/bash
# Renews the daemon's PGP key, if needed.
source /etc/mailinabox.conf # load global vars
export GNUPGHOME # Dump into the environment so that gpg uses it as homedir
gpg --batch --command-fd=0 --edit-key "${PGPKEY-}" << EOF;
key 0
expire
180d
save
EOF

View file

@ -17,6 +17,7 @@ from dns_update import get_dns_zones, build_tlsa_record, get_custom_dns_config,
from web_update import get_web_domains, get_domains_with_a_records
from ssl_certificates import get_ssl_certificates, get_domain_ssl_files, check_certificate
from mailconfig import get_mail_domains, get_mail_aliases
from pgp import get_daemon_key, get_imported_keys
from utils import shell, sort_domains, load_env_vars_from_file, load_settings
@ -59,6 +60,7 @@ def run_checks(rounded_values, env, output, pool):
shell('check_call', ["/usr/sbin/rndc", "flush"], trap=True)
run_system_checks(rounded_values, env, output)
run_pgp_checks(env, output)
# perform other checks asynchronously
@ -266,6 +268,75 @@ def check_free_memory(rounded_values, env, output):
if rounded_values: memory_msg = "System free memory is below 10%."
output.print_error(memory_msg)
def run_pgp_checks(env, output):
now = datetime.datetime.utcnow()
output.add_heading("PGP Keyring")
# Check daemon key
k = None
sk = None
try:
k = get_daemon_key()
sk = k.subkeys[0]
except KeyError:
pass
if k is None:
output.print_error("The daemon's key does not exist!")
elif sk.expired == 1:
output.print_error(f"The daemon's key ({k.fpr}) expired.")
elif k.revoked == 1:
output.print_error(f"The daemon's key ({k.fpr}) has been revoked.")
else:
exp = datetime.datetime.utcfromtimestamp(sk.expires) # Our daemon key only has one subkey
if (exp - now).days < 10 and sk.expires != 0:
output.print_warning(f"The daemon's key ({k.fpr}) will expire soon, in {(exp - now).days} days on {exp.strftime('%x')}.")
else:
output.print_ok(f"The daemon's key ({k.fpr}) is good. It expires in {(exp - now).days} days on {exp.strftime('%x')}.")
# Check imported keys
keys = get_imported_keys()
if len(keys) == 0:
output.print_warning("There are no imported keys here.")
else:
about_to_expire = []
expired = []
revoked = []
for key in keys:
if key.revoked == 1:
revoked.append(key)
continue
else:
for skey in key.subkeys:
exp = datetime.datetime.utcfromtimestamp(skey.expires)
if skey.expired == 1:
expired.append((key, skey))
elif (exp - now).days < 10 and skey.expires != 0:
about_to_expire.append((key, skey))
all_good = True
def printpair(keytuple):
key, skey = keytuple
output.print_line(f"Key {key.fpr}, subkey {skey.keyid}")
if len(about_to_expire) != 0:
all_good = False
output.print_warning(f"There {'is 1 subkey' if len(about_to_expire) == 1 else f'are {len(about_to_expire)} subkeys'} about to expire.")
list(map(printpair, about_to_expire))
if len(expired) != 0:
all_good = False
output.print_error(f"There {'is 1 expired subkey' if len(expired) == 1 else f'are {len(expired)} expired subkeys'}.")
list(map(printpair, expired))
if len(revoked) != 0:
all_good = False
output.print_error(f"There {'is 1 revoked key' if len(revoked) == 1 else f'are {len(revoked)} revoked keys'}.")
list(map(lambda k: output.print_line(k.fpr), revoked))
if all_good:
output.print_ok("All imported keys are good.")
def run_network_checks(env, output):
# Also see setup/network-checks.sh.

View file

@ -105,6 +105,8 @@
DNS</a></li>
<li class="dropdown-item"><a href="#external_dns"
onclick="return show_panel(this);">External DNS</a></li>
<li class="dropdown-item"><a href="#pgp_keyring"
onclick="return show_panel(this);">PGP Keyring Management</a></li>
<li class="dropdown-item"><a href="/admin/munin" target="_blank">Munin Monitoring</a></li>
</ul>
</li>
@ -153,6 +155,10 @@
{% include "custom-dns.html" %}
</div>
<div id="panel_pgp_keyring" class="admin_panel">
{% include "pgp-keyring.html" %}
</div>
<div id="panel_login" class="admin_panel">
{% include "login.html" %}
</div>
@ -199,7 +205,7 @@
<div id="global_modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="errorModalTitle"
aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-dialog modal-sm" style="max-width: 600px;">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="errorModalTitle"> </h4>

View file

@ -0,0 +1,279 @@
<style>
#pgp_keyring_config .status-error {
color: rgb(140, 0, 0);
}
#pgp_keyring_config .status-warning {
color: rgb(170, 120, 0);
}
#pgp_keyring_config .status-ok {
color: rgb(0, 140, 0);
}
#pgp_keyring_config .status-none {
color: rgb(190, 190, 190);
}
#pgp_keyring_config #uids {
white-space: pre-line;
}
</style>
<h2>PGP Keyring Management</h2>
<template id="pgpkey-template">
<tr>
<td>
<div id="trustlevel" style="font-size: 14pt;"><b>Trust Level:</b> Ultimate</div>
<code id="uids">
🤖 Power Mail-in-a-Box Management Daemon &lt;administrator@mailinabox.lan&gt;
</code>
<h3 style="font-size: 12pt;">Subkeys</h3>
<table id="subkeys">
<tr id="subkey-template">
<td id="ismaster">🔑</td>
<td>
<b>
<a id="sign">S</a>
<a id="cert">C</a>
<a id="encr">E</a>
<a id="auth">A</a>
</b>
</td>
<td style="width: 120pt;">
<b id="algo">RSA, 3072 bit</b>
</td>
<td>
<pre id="fpr">1756 6B81 D8A4 24C7 0098 659E 6872 2633 F692 52C6</pre>
</td>
<td id="expiration">
12/12/20 (119 days)
</td>
</tr>
</table>
</td>
<td id="options" style="width: 140pt;">
</td>
</tr>
</template>
<div id="pgp_keyring_config">
<h3>Daemon's Private Key</h3>
<table id="privatekey" class="table container">
</table>
<h3>Imported Public Keys</h3>
<table id="pubkeys" class="table container">
</table>
<h3>Import Key</h3>
<p>
You can upload your <b>public</b> key/keychain here. Keys <b>must</b> be submitted in ASCII-armored format.
<br>
If you're using <code>gpg</code>, you can export your public key by following this example:
<pre>
# Get all the keys in the ring
<b>$ gpg --list-keys</b>
/home/you/.gnupg/pubring.kbx
----------------------------
pub rsa4096 1970-01-01 [SC]
247C3553B4B36107BA0490C3CAFCCF3B4965761A
uid [ full ] Someone That I Used to Know &lt;someone@example.com&gt;
sub rsa2048 2020-01-01 [E] [expires: 2021-01-01]
pub rsa4096 2020-05-24 [SC] [expires: 2021-02-12]
52661092E5CD9EEFD7796B19E85F540C9318B69F
uid [ultimate] Me, Myself and I &lt;me@mydomain.com&gt;
sub rsa2048 2020-05-24 [E] [expires: 2021-02-12]
# Say that we want to export our own key, this is - "Me, Myself and I". That's the fingerprint "52661..."
<b>$ gpg --export --armor 52661092E5CD9EEFD7796B19E85F540C9318B69F</b> # Replace with your key's fingerprint
<b>-----BEGIN PGP PUBLIC KEY BLOCK-----
copy and paste this block in the area below
-----END PGP PUBLIC KEY BLOCK-----</b>
</pre>
</p>
<p><textarea id="pgp_paste_key" class="form-control" style="max-width: 40em; height: 8em" placeholder="-----BEGIN PGP PUBLIC KEY BLOCK-----&#xA;stuff here&#xA;-----END PGP PUBLIC KEY BLOCK-----"></textarea></p>
<button class="btn btn-primary" onclick="importkey()">Import Key</button>
</div>
<script>
function pretty_fpr(fpr) {
let pfpr = ""
for (let n = 0; n < 2; ++n) {
for (let i = 0; i < 5; ++i) {
pfpr += `${fpr.substring(n * 20 + i * 4, n * 20 + (i + 1) * 4)} `
}
pfpr += " "
}
return pfpr.substring(0, pfpr.length - 2)
}
function key_html(key, darken_bg, daemon) {
let keyrep = $("#pgpkey-template").html()
keyrep = $(keyrep)
keyrep.attr("id", key.master_fpr)
// Main key config
if (darken_bg) {
keyrep.addClass("bg-light")
}
const tlevel = keyrep.find("#trustlevel")
if (key.revoked) {
tlevel.html("<b class='status-error'>This key was revoked by it's owner.</b>")
} else {
tlevel.html("<b>Key is not revoked.</b>")
}
let uidtxt = ""
if (daemon) {
key.ids.forEach(id => {
uidtxt += "🤖 " + id + "\n"
});
} else {
key.ids.forEach(id => {
uidtxt += "🕵 " + id + "\n"
});
}
keyrep.find("#uids").text(uidtxt.substring(0, uidtxt.length - 1))
// Subkeys
const keyflags = ["sign", "cert", "encr", "auth"]
let subkeys = keyrep.find("#subkeys")
let subkeytemplate = subkeys.html()
keyrep.find("#subkey-template").remove()
key.subkeys.forEach(skey => {
let skeyrep = $(subkeytemplate)
skeyrep.attr("id", `sub${skey.fpr}`)
// Master key?
if (skey.master) {
skeyrep.find("#ismaster").html("🔑")
} else {
skeyrep.find("#ismaster").html("")
}
// Usage flags
keyflags.forEach(flag => {
if (!skey[flag]) {
skeyrep.find(`#${flag}`).addClass("status-none")
}
});
// Algorithm and fingerprint
skeyrep.find("#algo").html(`${skey.algorithm}, ${skey.bits} bits`)
skeyrep.find("#fpr").html(pretty_fpr(skey.fpr))
let expiration = skeyrep.find("#expiration")
// Expiration
if (key.revoked) {
skeyrep.addClass("status-error")
expiration.html(`Revoked`)
} else if (skey.expired) {
skeyrep.addClass("status-error")
expiration.html(`${skey.expires_date} (expired)`)
} else if (skey.expires && skey.expires_days <= 14) {
skeyrep.addClass("status-warning")
expiration.html(`${skey.expires_date} (${skey.expires_days} days)`)
} else if (skey.expires) {
skeyrep.addClass("status-ok")
expiration.html(`${skey.expires_date} (${skey.expires_days} days)`)
} else {
skeyrep.addClass("status-ok")
expiration.html("Does not expire")
}
skeyrep.appendTo(subkeys)
});
// Options
if (daemon) {
keyrep.find("#options").html(`<button class="btn btn-primary btn-block" onclick="exportkey('${key.master_fpr}')">Export Public Key</button>`)
} else {
keyrep.find("#options").html(`<button class="btn btn-secondary btn-block" onclick="exportkey('${key.master_fpr}')">Export Public Key</button><button class="btn btn-danger btn-block" onclick="rmkey('${key.master_fpr}')">Remove Key</button>`)
}
return keyrep
}
function show_pgp_keyring() {
$('#privatekey').html("<tr><td class='text-muted'>Loading...</td></tr>")
$('#pubkeys').html("<tr><td class='text-muted'>Loading...</td></tr>")
api(
"/system/pgp/",
"GET",
{},
function(r) {
$('#privatekey').html("")
$('#pubkeys').html("")
key_html(r.daemon, true, true).appendTo("#privatekey")
let pendulum = 1
r.imported.forEach(k => {
key_html(k, pendulum > 0, false).appendTo("#pubkeys")
pendulum *= -1
});
}
)
}
function exportkey(fpr) {
api(
`/system/pgp/${fpr}/export`,
"GET",
{},
function(r) {
show_modal_error("PGP Key", `Key export for <b>${fpr}</b>:<br><br><pre>${r}</pre>`)
},
function(_ ,xhr) {
if (xhr.status == 404) {
show_modal_error("Error", `The key you asked for (<b>${fpr}</b>) does not exist!`)
} else {
// Fallback to the default error modal
show_modal_error("Error", "Something went wrong, sorry.")
}
}
)
}
function rmkey(fpr) {
show_modal_confirm("Delete key", `Are you sure you wish to remove the key with the fingerprint ${pretty_fpr(fpr)}?`, "Yes, remove it", () => {
api(
`/system/pgp/${fpr}`,
"DELETE",
{},
function(r) {
show_modal_error("Delete key", r, show_pgp_keyring)
},
function(r) {
show_modal_error("Key deletion error", r)
}
)
}, ()=>{})
}
function importkey() {
api(
"/system/pgp/import",
"POST",
{
key: $("#pgp_paste_key").val()
},
function(r) {
show_modal_error("Import Results", `<ul>
<li><b>Keys read:</b> ${r.keys_read}</li>
<li><b>Keys added:</b> ${r.keys_added}</li>
<li><b>Keys not changed:</b> ${r.keys_unchanged}</li>
<li><b>User id's added:</b> ${r.uids_added}</li>
<li><b>Signatures added:</b> ${r.sigs_added}</li>
<li><b>Revocations added:</b> ${r.revs_added}</li>
</ul>`, show_pgp_keyring)
},
function(r) {
show_modal_error("Import Error", r)
}
)
}
</script>

View file

@ -225,3 +225,7 @@ function git_clone {
function php_version {
php --version | head -n 1 | cut -d " " -f 2 | cut -c 1-3
}
function python_version {
python3 --version | cut -d " " -f 2 | cut -c 1-3
}

View file

@ -29,7 +29,7 @@ done
#
# certbot installs EFF's certbot which we use to
# provision free TLS certificates.
apt_install duplicity python3-pip virtualenv certbot
apt_install duplicity python3-pip python3-gpg virtualenv certbot
hide_output pip3 install --upgrade boto
# Create a virtualenv for the installation of Python 3 packages
@ -52,6 +52,11 @@ hide_output $venv/bin/pip install --upgrade \
flask dnspython python-dateutil \
"idna>=2.0.0" "cryptography==2.2.2" boto psutil postfix-mta-sts-resolver
# Make the venv use the packaged gpgme bindings (the ones pip provides are severely out-of-date)
if [ ! -d $venv/lib/python$(python_version)/site-packages/gpg/ ]; then
ln -s /usr/lib/python3/dist-packages/gpg/ $venv/lib/python$(python_version)/site-packages/
fi
# CONFIGURATION
# Create a backup directory and a random key for encrypting backups.

32
setup/pgp.sh Executable file
View file

@ -0,0 +1,32 @@
#!/bin/bash
# Daemon PGP Keyring
# ------------------
#
# Initializes the PGP keyring at /home/user-data/.gnupg
# For this, we will generate a new PGP keypair (if one isn't already present)
source setup/functions.sh # load our functions
source /etc/mailinabox.conf # load global vars
export GNUPGHOME # Dump into the environment so that gpg uses it as homedir
# Install gnupg
apt_install gnupg
if [ "$(gpg --list-secret-keys 2> /dev/null)" = "" -o "${PGPKEY-}" = "" ]; then
echo "No keypair found. Generating daemon's PGP keypair..."
gpg --generate-key --batch << EOF;
%no-protection
Key-Type: RSA
Key-Length: 4096
Key-Usage: sign,encrypt,auth
Name-Real: Power Mail-in-a-Box Management Daemon
Name-Email: administrator@${PRIMARY_HOSTNAME}
Expire-Date: 180d
%commit
EOF
chown -R root:root $GNUPGHOME
# Remove the old key fingerprint if it exists, and add the new one
echo "$(cat /etc/mailinabox.conf | grep -v "PGPKEY")" > /etc/mailinabox.conf
echo "PGPKEY=$(gpg --list-secret-keys --with-colons | grep fpr | head -n 1 | sed 's/fpr//g' | sed 's/://g')" >> /etc/mailinabox.conf
fi

View file

@ -100,12 +100,15 @@ PUBLIC_IPV6=$PUBLIC_IPV6
PRIVATE_IP=$PRIVATE_IP
PRIVATE_IPV6=$PRIVATE_IPV6
MTA_STS_MODE=${MTA_STS_MODE-}
GNUPGHOME=${STORAGE_ROOT}/.gnupg/
PGPKEY=${DEFAULT_PGPKEY-}
EOF
# Start service configuration.
source setup/system.sh
source setup/ssl.sh
source setup/dns.sh
source setup/pgp.sh
source setup/mail-postfix.sh
source setup/mail-dovecot.sh
source setup/mail-users.sh

View file

@ -23,7 +23,7 @@ echo "Installing Roundcube (webmail)..."
apt_install \
dbconfig-common \
php-cli php-sqlite3 php-intl php-json php-common php-curl php-ldap \
php-gd php-pspell tinymce libjs-jquery libjs-jquery-mousewheel libmagic1 php-mbstring
php-gd php-pspell tinymce libjs-jquery libjs-jquery-mousewheel libmagic1 php-mbstring php-gnupg
# Install Roundcube from source if it is not already present or if it is out of date.
# Combine the Roundcube version number with the commit hash of plugins to track
@ -126,7 +126,7 @@ cat > $RCM_CONFIG <<EOF;
\$config['support_url'] = 'https://mailinabox.email/';
\$config['product_name'] = '$PRIMARY_HOSTNAME Webmail';
\$config['des_key'] = '$SECRET_KEY';
\$config['plugins'] = array('html5_notifier', 'archive', 'zipdownload', 'password', 'managesieve', 'jqueryui', 'persistent_login', 'carddav');
\$config['plugins'] = array('html5_notifier', 'archive', 'zipdownload', 'password', 'managesieve', 'jqueryui', 'persistent_login', 'carddav', 'enigma');
\$config['skin'] = 'elastic';
\$config['login_autocomplete'] = 2;
\$config['password_charset'] = 'UTF-8';
@ -134,6 +134,35 @@ cat > $RCM_CONFIG <<EOF;
?>
EOF
mkdir -p ${STORAGE_ROOT}/userkeys/
chmod 700 ${STORAGE_ROOT}/userkeys/
chown www-data:www-data ${STORAGE_ROOT}/userkeys/
# Configure Enigma
cat > ${RCM_PLUGIN_DIR}/enigma/config.inc.php <<EOF;
<?php
/* Do not edit. Written by Mail-in-a-Box. Regenerated on updates. */
\$config['enigma_pgp_driver'] = 'gnupg';
\$config['enigma_smime_driver'] = 'phpssl';
\$config['enigma_debug'] = false;
\$config['enigma_pgp_homedir'] = '${STORAGE_ROOT}/.enigma/';
\$config['enigma_pgp_binary'] = '';
\$config['enigma_pgp_agent'] = '';
\$config['enigma_pgp_gpgconf'] = '';
\$config['enigma_pgp_cipher_algo'] = null;
\$config['enigma_pgp_digest_algo'] = null;
\$config['enigma_multihost'] = false;
\$config['enigma_signatures'] = true;
\$config['enigma_decryption'] = true;
\$config['enigma_encryption'] = true;
\$config['enigma_sign_all'] = false;
\$config['enigma_encrypt_all'] = false;
\$config['enigma_attach_pubkey'] = false;
\$config['enigma_password_time'] = 5;
\$config['enigma_options_lock'] = array();
?>
EOF
# Configure CardDav
cat > ${RCM_PLUGIN_DIR}/carddav/config.inc.php <<EOF;
<?php