(merge) CardDAV, CalDAV via ownCloud and move to z-push fork fork

Merges branch 'owncloud' of github.com:jkaberg/mailinabox
which is pull request #135, closes #135

thanks @jkaberg, @fmbiete, @owncloud
This commit is contained in:
Joshua Tauberer 2014-08-17 15:31:08 -04:00
commit 04454b35c6
13 changed files with 397 additions and 27 deletions

View File

@ -27,6 +27,12 @@ In short, it's like this:
cd mailinabox
sudo setup/start.sh
Then run the post-install checklist command to see what you need to do next:
sudo management/whats_next.py
Congratulations! You should now have a working setup. Feel free to login with your mail credentials created earlier in the setup
**Status**: This is a work in progress. It works for what it is, but it is missing such things as quotas, backup/restore, etc.
The Goals

View File

@ -0,0 +1,41 @@
# ownCloud configuration.
rewrite ^/cloud$ /cloud/ redirect;
rewrite ^/cloud/$ /cloud/index.php;
rewrite ^(/cloud/core/doc/[^\/]+/)$ $1/index.html;
location /cloud/ {
alias /usr/local/lib/owncloud/;
location ~ ^/(data|config|\.ht|db_structure\.xml|README) {
deny all;
}
}
location ~ ^(/cloud)(/[^/]+\.php)(/.*)?$ {
# note: ~ has precendence over a regular location block
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /usr/local/lib/owncloud/$2;
fastcgi_param SCRIPT_NAME $1$2;
fastcgi_param PATH_INFO $3;
fastcgi_param MOD_X_ACCEL_REDIRECT_ENABLED on;
fastcgi_read_timeout 630;
fastcgi_pass php-fpm;
error_page 403 /cloud/core/templates/403.php;
error_page 404 /cloud/core/templates/404.php;
client_max_body_size 1G;
fastcgi_buffers 64 4K;
}
location ^~ /cloud/data {
# In order to support MOD_X_ACCEL_REDIRECT_ENABLED, we need to expose
# the data directory but only allow 'internal' redirects within nginx
# so that this is not exposed to the world.
internal;
alias $STORAGE_ROOT/owncloud;
}
location ~ ^/((caldav|carddav|webdav).*)$ {
# Z-Push doesn't like getting a redirect, and a plain rewrite didn't work either.
# Properly proxying like this seems to work fine.
proxy_pass https://$HOSTNAME/cloud/remote.php/$1;
}
rewrite ^/.well-known/host-meta /cloud/public.php?service=host-meta last;
rewrite ^/.well-known/host-meta.json /cloud/public.php?service=host-meta-json last;
rewrite ^/.well-known/carddav /cloud/remote.php/carddav/ redirect;
rewrite ^/.well-known/caldav /cloud/remote.php/caldav/ redirect;

View File

@ -36,6 +36,7 @@ server {
return 403;
}
location ~ /mail/.*\.php {
# note: ~ has precendence over a regular location block
include fastcgi_params;
fastcgi_split_path_info ^/mail(/.*)()$;
fastcgi_index index.php;
@ -60,8 +61,9 @@ server {
# Z-Push (Microsoft Exchange ActiveSync)
location /Microsoft-Server-ActiveSync {
include /etc/nginx/fastcgi_params;
include /etc/nginx/fastcgi_params;
fastcgi_param SCRIPT_FILENAME /usr/local/lib/z-push/index.php;
fastcgi_read_timeout 630;
fastcgi_pass php-fpm;
}

View File

@ -0,0 +1,18 @@
<?php
/***********************************************
* File : config.php
* Project : Z-Push
* Descr : CalDAV backend configuration file
************************************************/
define('CALDAV_SERVER', 'https://localhost');
define('CALDAV_PORT', '443');
define('CALDAV_PATH', '/caldav/calendars/%u/');
define('CALDAV_PERSONAL', '');
// If the CalDAV server supports the sync-collection operation
// DAViCal and SOGo support it
// Setting this to false will work with most servers, but it will be slower
define('CALDAV_SUPPORTS_SYNC', false);
?>

View File

@ -0,0 +1,37 @@
<?php
/***********************************************
* File : config.php
* Project : Z-Push
* Descr : CardDAV backend configuration file
************************************************/
define('CARDDAV_PROTOCOL', 'https'); /* http or https */
define('CARDDAV_SERVER', 'localhost');
define('CARDDAV_PORT', '443');
define('CARDDAV_PATH', '/carddav/addressbooks/%u/');
define('CARDDAV_DEFAULT_PATH', '/carddav/addressbooks/%u/contacts/'); /* subdirectory of the main path */
define('CARDDAV_GAL_PATH', ''); /* readonly, searchable, not syncd */
define('CARDDAV_GAL_MIN_LENGTH', 5);
define('CARDDAV_CONTACTS_FOLDER_NAME', '%u Addressbook');
// If the CardDAV server supports the sync-collection operation
// DAViCal supports it, but SabreDav, Owncloud, SOGo don't
// Setting this to false will work with most servers, but it will be slower: 1 petition for the href of vcards, and 1 petition for each vcard
define('CARDDAV_SUPPORTS_SYNC', false);
// If the CardDAV server supports the FN attribute for searches
// DAViCal supports it, but SabreDav, Owncloud and SOGo don't
// Setting this to true will search by FN. If false will search by sn, givenName and email
// It's safe to leave it as false
define('CARDDAV_SUPPORTS_FN_SEARCH', false);
// If your carddav server needs to use file extension to recover a vcard.
// Davical needs it
// SOGo official demo online needs it, but some SOGo installation don't need it, so test it
define('CARDDAV_URL_VCARD_EXTENSION', '.vcf');
?>

View File

@ -0,0 +1,49 @@
<?php
/***********************************************
* File : backend/combined/config.php
* Project : Z-Push
* Descr : configuration file for the
* combined backend.
************************************************/
class BackendCombinedConfig {
public static function GetBackendCombinedConfig() {
return array(
'backends' => array(
'i' => array(
'name' => 'BackendIMAP',
),
'c' => array(
'name' => 'BackendCalDAV',
),
'd' => array(
'name' => 'BackendCardDAV',
),
),
'delimiter' => '/',
'folderbackend' => array(
SYNC_FOLDER_TYPE_INBOX => 'i',
SYNC_FOLDER_TYPE_DRAFTS => 'i',
SYNC_FOLDER_TYPE_WASTEBASKET => 'i',
SYNC_FOLDER_TYPE_SENTMAIL => 'i',
SYNC_FOLDER_TYPE_OUTBOX => 'i',
SYNC_FOLDER_TYPE_TASK => 'c',
SYNC_FOLDER_TYPE_APPOINTMENT => 'c',
SYNC_FOLDER_TYPE_CONTACT => 'd',
SYNC_FOLDER_TYPE_NOTE => 'c',
SYNC_FOLDER_TYPE_JOURNAL => 'c',
SYNC_FOLDER_TYPE_OTHER => 'i',
SYNC_FOLDER_TYPE_USER_MAIL => 'i',
SYNC_FOLDER_TYPE_USER_APPOINTMENT => 'c',
SYNC_FOLDER_TYPE_USER_CONTACT => 'd',
SYNC_FOLDER_TYPE_USER_TASK => 'c',
SYNC_FOLDER_TYPE_USER_JOURNAL => 'c',
SYNC_FOLDER_TYPE_USER_NOTE => 'c',
SYNC_FOLDER_TYPE_UNKNOWN => 'i',
),
'rootcreatefolderbackend' => 'i',
);
}
}
?>

View File

@ -0,0 +1,42 @@
<?php
/***********************************************
* File : config.php
* Project : Z-Push
* Descr : IMAP backend configuration file
************************************************/
define('IMAP_SERVER', 'localhost');
define('IMAP_PORT', 993);
define('IMAP_OPTIONS', '/ssl/norsh/novalidate-cert');
define('IMAP_DEFAULTFROM', '');
// not used
define('IMAP_FROM_SQL_DSN', '');
define('IMAP_FROM_SQL_USER', '');
define('IMAP_FROM_SQL_PASSWORD', '');
define('IMAP_FROM_SQL_OPTIONS', serialize(array(PDO::ATTR_PERSISTENT => true)));
define('IMAP_FROM_SQL_QUERY', "select first_name, last_name, mail_address from users where mail_address = '#username@#domain'");
define('IMAP_FROM_SQL_FIELDS', serialize(array('first_name', 'last_name', 'mail_address')));
define('IMAP_FROM_SQL_FROM', '#first_name #last_name <#mail_address>');
define('IMAP_FROM_LDAP_SERVER', '');
define('IMAP_FROM_LDAP_SERVER_PORT', '389');
define('IMAP_FROM_LDAP_USER', 'cn=zpush,ou=servers,dc=zpush,dc=org');
define('IMAP_FROM_LDAP_PASSWORD', 'password');
define('IMAP_FROM_LDAP_BASE', 'dc=zpush,dc=org');
define('IMAP_FROM_LDAP_QUERY', '(mail=#username@#domain)');
define('IMAP_FROM_LDAP_FIELDS', serialize(array('givenname', 'sn', 'mail')));
define('IMAP_FROM_LDAP_FROM', '#givenname #sn <#mail>');
// copy outgoing mail to this folder. If not set z-push will try the default folders
define('IMAP_SENTFOLDER', '');
define('IMAP_INLINE_FORWARD', true);
define('IMAP_EXCLUDED_FOLDERS', '');
define('IMAP_SMTP_METHOD', 'sendmail');
global $imap_smtp_params;
$imap_smtp_params = array('host' => 'ssl://localhost', 'port' => 587, 'auth' => true, 'username' => 'imap_username', 'password' => 'imap_password');
define('MAIL_MIMEPART_CRLF', "\r\n");
?>

View File

@ -43,9 +43,10 @@ def do_web_update(env):
nginx_conf = open(os.path.join(os.path.dirname(__file__), "../conf/nginx-top.conf")).read()
# Add configuration for each web domain.
template = open(os.path.join(os.path.dirname(__file__), "../conf/nginx.conf")).read()
template1 = open(os.path.join(os.path.dirname(__file__), "../conf/nginx.conf")).read()
template2 = open(os.path.join(os.path.dirname(__file__), "../conf/nginx-primaryonly.conf")).read()
for domain in get_web_domains(env):
nginx_conf += make_domain_config(domain, template, env)
nginx_conf += make_domain_config(domain, template1, template2, env)
# Did the file change? If not, don't bother writing & restarting nginx.
nginx_conf_fn = "/etc/nginx/conf.d/local.conf"
@ -63,7 +64,7 @@ def do_web_update(env):
return "web updated\n"
def make_domain_config(domain, template, env):
def make_domain_config(domain, template, template_for_primaryhost, env):
# How will we configure this domain.
# Where will its root directory be for static files?
@ -77,25 +78,30 @@ def make_domain_config(domain, template, env):
# available. Make a self-signed one now if one doesn't exist.
ensure_ssl_certificate_exists(domain, ssl_key, ssl_certificate, csr_path, env)
# Put pieces together.
nginx_conf_parts = re.split("\s*# ADDITIONAL DIRECTIVES HERE\s*", template)
nginx_conf = nginx_conf_parts[0] + "\n"
if domain == env['PRIMARY_HOSTNAME']:
nginx_conf += template_for_primaryhost + "\n"
# Replace substitution strings in the template & return.
nginx_conf = template
nginx_conf = nginx_conf.replace("$STORAGE_ROOT", env['STORAGE_ROOT'])
nginx_conf = nginx_conf.replace("$HOSTNAME", domain)
nginx_conf = nginx_conf.replace("$ROOT", root)
nginx_conf = nginx_conf.replace("$SSL_KEY", ssl_key)
nginx_conf = nginx_conf.replace("$SSL_CERTIFICATE", ssl_certificate)
# Add in any user customizations.
nginx_conf_parts = re.split("(# ADDITIONAL DIRECTIVES HERE\n)", nginx_conf)
nginx_conf_custom_fn = os.path.join(env["STORAGE_ROOT"], "www/custom.yaml")
if os.path.exists(nginx_conf_custom_fn):
yaml = rtyaml.load(open(nginx_conf_custom_fn))
if domain in yaml:
yaml = yaml[domain]
if "proxy" in yaml:
nginx_conf_parts[1] += "\tlocation / {\n\t\tproxy_pass %s;\n\t}\n" % yaml["proxy"]
nginx_conf += "\tlocation / {\n\t\tproxy_pass %s;\n\t}\n" % yaml["proxy"]
# Put it all together.
nginx_conf = "".join(nginx_conf_parts)
# Ending.
nginx_conf += nginx_conf_parts[1]
return nginx_conf

130
setup/owncloud.sh Executable file
View File

@ -0,0 +1,130 @@
#!/bin/bash
# Owncloud
##########################
source setup/functions.sh # load our functions
source /etc/mailinabox.conf # load global vars
apt_install \
dbconfig-common \
php5-cli php5-sqlite php5-gd php5-imap php5-curl php-pear php-apc curl libapr1 libtool libcurl4-openssl-dev php-xml-parser \
php5 php5-dev php5-gd php5-fpm memcached php5-memcache unzip
apt-get purge -qq -y owncloud*
# Install ownCloud from source if it is not already present
# TODO: Check version?
if [ ! -d /usr/local/lib/owncloud ]; then
echo installing ownCloud...
rm -f /tmp/owncloud.zip
wget -qO /tmp/owncloud.zip https://download.owncloud.org/community/owncloud-7.0.1.zip
unzip -q /tmp/owncloud.zip -d /usr/local/lib
rm -f /tmp/owncloud.zip
fi
# Setup ownCloud if the ownCloud database does not yet exist. Running setup when
# the database does exist wipes the database and user data.
if [ ! -f $STORAGE_ROOT/owncloud/owncloud.db ]; then
# Create a configuration file.
TIMEZONE=`cat /etc/timezone`
instanceid=oc$(echo $PRIMARY_HOSTNAME | sha1sum | fold -w 10 | head -n 1)
cat - > /usr/local/lib/owncloud/config/config.php <<EOF;
<?php
\$CONFIG = array (
'datadirectory' => '$STORAGE_ROOT/owncloud',
'instanceid' => '$instanceid',
'trusted_domains' =>
array (
0 => '$PRIMARY_HOSTNAME',
),
'forcessl' => true, # if unset/false, ownCloud sends a HSTS=0 header, which conflicts with nginx config
'overwritewebroot' => '/cloud',
'user_backends' => array(
array(
'class'=>'OC_User_IMAP',
'arguments'=>array('{localhost:993/imap/ssl/novalidate-cert}')
)
),
"memcached_servers" => array (
array('localhost', 11211),
),
'mail_smtpmode' => 'sendmail',
'mail_smtpsecure' => '',
'mail_smtpauthtype' => 'LOGIN',
'mail_smtpauth' => false,
'mail_smtphost' => '',
'mail_smtpport' => '',
'mail_smtpname' => '',
'mail_smtppassword' => '',
'mail_from_address' => 'owncloud',
'mail_domain' => '$PRIMARY_HOSTNAME',
'logtimezone' => '$TIMEZONE',
);
?>
EOF
# Create an auto-configuration file to fill in database settings
# when the install script is run. Make an administrator account
# here or else the install can't finish.
adminpassword=$(dd if=/dev/random bs=40 count=1 2>/dev/null | sha1sum | fold -w 30 | head -n 1)
cat - > /usr/local/lib/owncloud/config/autoconfig.php <<EOF;
<?php
\$AUTOCONFIG = array (
# storage/database
'directory' => '$STORAGE_ROOT/owncloud',
'dbtype' => 'sqlite3',
# create an administrator account with a random password so that
# the user does not have to enter anything on first load of ownCloud
'adminlogin' => 'root',
'adminpass' => '$adminpassword',
);
?>
EOF
# Create user data directory and set permissions
mkdir -p $STORAGE_ROOT/owncloud
chown -R www-data.www-data $STORAGE_ROOT/owncloud /usr/local/lib/owncloud
# Execute ownCloud's setup step, which creates the ownCloud sqlite database.
# It also wipes it if it exists. And it deletes the autoconfig.php file.
(cd /usr/local/lib/owncloud; sudo -u www-data php /usr/local/lib/owncloud/index.php;)
fi
# Enable/disable apps. Note that this must be done after the ownCloud setup.
# The firstrunwizard gave Josh all sorts of problems, so disabling that.
# user_external is what allows ownCloud to use IMAP for login.
hide_output php /usr/local/lib/owncloud/console.php app:disable firstrunwizard
hide_output php /usr/local/lib/owncloud/console.php app:enable user_external
# Set PHP FPM values to support large file uploads
# (semicolon is the comment character in this file, hashes produce deprecation warnings)
tools/editconf.py /etc/php5/fpm/php.ini -c ';' \
upload_max_filesize=16G \
post_max_size=16G \
output_buffering=16384 \
memory_limit=512M \
max_execution_time=600 \
short_open_tag=On
# Set up a cron job for owncloud.
cat > /etc/cron.hourly/mailinabox-owncloud << EOF;
#!/bin/bash
# Mail-in-a-Box
sudo -u www-data php -f /usr/local/lib/owncloud/cron.php
EOF
chmod +x /etc/cron.hourly/mailinabox-owncloud
## Ensure all system admins are ownCloud admins.
## Actually we don't do this. There's nothing much of interest that the user could
## change from the ownCloud admin, and there's a lot they could mess up.
#for user in $(tools/mail.py user admins); do
# sqlite3 $STORAGE_ROOT/owncloud/owncloud.db "INSERT OR IGNORE INTO oc_group_user VALUES ('admin', '$user')"
#done
# Finished.
php5enmod imap
restart_service php5-fpm

View File

@ -274,6 +274,7 @@ EOF
. setup/spamassassin.sh
. setup/web.sh
. setup/webmail.sh
. setup/owncloud.sh
. setup/zpush.sh
. setup/management.sh

View File

@ -14,30 +14,51 @@ source /etc/mailinabox.conf # load global vars
# Prereqs.
apt_install \
php-soap php5-imap
php-soap php5-imap libawl-php php5-xsl
php5enmod imap
# Copy Z-Push into place.
if [ ! -d /usr/local/lib/z-push ]; then
ZPUSH=z-push-2.1.3-1892
wget -qO /tmp/zpush.tgz http://download.z-push.org/final/2.1/$ZPUSH.tar.gz
tar -C /tmp -zxf /tmp/zpush.tgz
mv /tmp/$ZPUSH /usr/local/lib/z-push
needs_update=0
if [ ! -f /usr/local/lib/z-push/version ]; then
needs_update=1
elif [[ `curl -s https://api.github.com/repos/fmbiete/Z-Push-contrib/git/refs/heads/master` != `cat /usr/local/lib/z-push/version` ]]; then
# checks if the version
needs_update=1
fi
if [ $needs_update == 1 ]; then
rm -rf /usr/local/lib/z-push
rm -f /tmp/zpush.zip
echo installing z-push \(fmbiete fork\)...
wget -qO /tmp/zpush.zip https://github.com/fmbiete/Z-Push-contrib/archive/master.zip
unzip -q /tmp/zpush.zip -d /usr/local/lib/
mv /usr/local/lib/Z-Push-contrib-master /usr/local/lib/z-push
rm -f /usr/sbin/z-push-{admin,top}
ln -s /usr/local/lib/z-push/z-push-admin.php /usr/sbin/z-push-admin
ln -s /usr/local/lib/z-push/z-push-top.php /usr/sbin/z-push-top
rm /tmp/zpush.tgz;
rm /tmp/zpush.zip;
curl -s https://api.github.com/repos/fmbiete/Z-Push-contrib/git/refs/heads/master > /usr/local/lib/z-push/version
fi
# Configure. Tell is to connect to email via IMAP using SSL. Since we connect on
# localhost, the certificate won't match (it may be self-signed and invalid anyway)
# so don't check the cert.
sed -i "s/define('BACKEND_PROVIDER', .*/define('BACKEND_PROVIDER', 'BackendIMAP');/" /usr/local/lib/z-push/config.php
#sed -i "s/define('IMAP_SERVER', .*/define('IMAP_SERVER', '$PRIMARY_HOSTNAME');/" /usr/local/lib/z-push/backend/imap/config.php
sed -i "s/define('IMAP_PORT', .*/define('IMAP_PORT', 993);/" /usr/local/lib/z-push/backend/imap/config.php
sed -i "s/define('IMAP_OPTIONS', .*/define('IMAP_OPTIONS', '\/ssl\/norsh\/novalidate-cert');/" /usr/local/lib/z-push/backend/imap/config.php
# Configure default config.
sed -i "s/define('TIMEZONE', .*/define('TIMEZONE', 'Etc\/UTC');/" /usr/local/lib/z-push/config.php
sed -i "s/define('BACKEND_PROVIDER', .*/define('BACKEND_PROVIDER', 'BackendCombined');/" /usr/local/lib/z-push/config.php
# Configure BACKEND
rm -f /usr/local/lib/z-push/backend/combined/config.php
cp conf/zpush/backend_combined.php /usr/local/lib/z-push/backend/combined/config.php
# Configure IMAP
rm -f /usr/local/lib/z-push/backend/imap/config.php
cp conf/zpush/backend_imap.php /usr/local/lib/z-push/backend/imap/config.php
# Configure CardDav
rm -f /usr/local/lib/z-push/backend/carddav/config.php
cp conf/zpush/backend_carddav.php /usr/local/lib/z-push/backend/carddav/config.php
# Configure CalDav
rm -f /usr/local/lib/z-push/backend/caldav/config.php
cp conf/zpush/backend_caldav.php /usr/local/lib/z-push/backend/caldav/config.php
# Some directories it will use.

View File

@ -33,6 +33,7 @@ settings = sys.argv[2:]
delimiter = "="
delimiter_re = r"\s*=\s*"
comment_char = "#"
folded_lines = False
testing = False
while settings[0][0] == "-" and settings[0] != "--":
@ -42,7 +43,11 @@ while settings[0][0] == "-" and settings[0] != "--":
delimiter = " "
delimiter_re = r"\s+"
elif opt == "-w":
# Line folding is possible in this file.
folded_lines = True
elif opt == "-c":
# Specifies a different comment character.
comment_char = settings.pop(0)
elif opt == "-t":
testing = True
else:
@ -60,7 +65,7 @@ while len(input_lines) > 0:
# If this configuration file uses folded lines, append any folded lines
# into our input buffer.
if folded_lines and line[0] not in ("#", " ", ""):
if folded_lines and line[0] not in (comment_char, " ", ""):
while len(input_lines) > 0 and input_lines[0][0] in " \t":
line += input_lines.pop(0)
@ -68,7 +73,11 @@ while len(input_lines) > 0:
for i in range(len(settings)):
# Check that this line contain this setting from the command-line arguments.
name, val = settings[i].split("=", 1)
m = re.match("(\s*)(#\s*)?" + re.escape(name) + delimiter_re + "(.*?)\s*$", line, re.S)
m = re.match(
"(\s*)"
+ "(" + re.escape(comment_char) + "\s*)?"
+ re.escape(name) + delimiter_re + "(.*?)\s*$",
line, re.S)
if not m: continue
indent, is_comment, existing_val = m.groups()
@ -83,7 +92,7 @@ while len(input_lines) > 0:
# comment-out the existing line (also comment any folded lines)
if is_comment is None:
buf += "#" + line.rstrip().replace("\n", "\n#") + "\n"
buf += comment_char + line.rstrip().replace("\n", "\n" + comment_char) + "\n"
else:
# the line is already commented, pass it through
buf += line

View File

@ -51,6 +51,7 @@ if len(sys.argv) < 2:
print(" tools/mail.py user remove user@domain.com")
print(" tools/mail.py user make-admin user@domain.com")
print(" tools/mail.py user remove-admin user@domain.com")
print(" tools/mail.py user admins (lists admins)")
print(" tools/mail.py alias (lists aliases)")
print(" tools/mail.py alias add incoming.name@domain.com sent.to@other.domain.com")
print(" tools/mail.py alias remove incoming.name@domain.com")
@ -92,6 +93,13 @@ elif sys.argv[1] == "user" and sys.argv[2] in ("make-admin", "remove-admin") and
action = "remove"
print(mgmt("/mail/users/privileges/" + action, { "email": sys.argv[3], "privilege": "admin" }))
elif sys.argv[1] == "user" and sys.argv[2] == "admins":
# Dump a list of admin users.
users = mgmt("/mail/users?format=json", is_json=True)
for user in users:
if "admin" in user['privileges']:
print(user['email'])
elif sys.argv[1] == "alias" and len(sys.argv) == 2:
print(mgmt("/mail/aliases"))