Setting up WebDAV, CalDAV, and CardDAV servers

:: linux, tricks

A while back I wrote a post about paranoia in which I was considering allowing Google or Apple to manage things like my calendar and contacts. Since then, I have reequipped my paranoia hat. This week I setup my own WebDAV, CalDAV, and CardDAV servers and secured them behind an nginx proxy which provides SSL encryption and HTTP authentication.

Notes:

This blog post was written with Radicale 2. I’ve since been UNABLE to update it to Radicale 3, as I cannot get the new rights management system working.

Radicale 2 contains a bug causing it to ignore umask, so you probably want a cronjob that fixes the permissions with chown and chmod on all your calendar files instead.

Comparing servers

The most important feature I want in a server is the ability to actually serve the clients I use (shocking, I am sure). For CalDAV, my primary client is my iPhone. I access my reminders and calendar through my iPhone, so I need a CalDAV server that works with the iPhone. For CardDAV, my primary client is mutt, which I use to send most of my email. I need something I can integrate with mutt, but that will also work on my iPhone. For WebDAV, my primary client is Notability, which uses WebDAV to backup my notes. I do not really use WebDAV for much else, and I have a separate setup I use to syncing assorted files, but maybe WebDAV will soon replace it.

The secondary features that I want in a server are simplicity and low resource uses. I want something that does one thing well, because that is just how I am. My VPS has few resources—1 core, 1 GB of RAM, and 24 GB of harddrive space—so I want something the uses little additional resources.

WebDAV: nginx

I settled on nginx extended with a secondary module for my WebDAV server.

I already use nginx as my web server, so it has already passed my secondary considerations—nginx is relatively simple and makes efficient use of resources.

Unfortunately, nginx’s default WebDAV module does not pass my first requirement—that it must work with my primary client. The built-in support for WebDAV in nginx is limited, perhaps because it already subscribes to the “do one thing well” philosophy. Thankfully, nginx is modular and someone has written a module that provides the necessary extensions.

{Card,Cal}DAV: radicale

I settled on radicale for the CalDAV and CardDAV servers.

Radicale provides both a CalDAV and CardDAV server. The radicale project documents many of the client it supports, and the list includes the iPhone. After a little experimenting, I find it also support the pycarddav client, a command line CardDAV client which can provide mutt with CardDAV support.

Radicale is written in Python, which is already installed on my server. The entire server only takes up an extra .10MB of disk space. The radicale project explain it believes in the “do one thing well” philosophy, and the server is pretty simple to use and configure. It does not require complicated database back ends. Configuring the server is quite simple, and although it does provide unnecessary features like SSL and authentication support—which are unnecessary insofar as they are better provided by nginx acting as a proxy—it does so through existing Python modules and not new code.

Alternatives considered

I did not consider very many other WebDAV servers, since I already have nginx installed and respect the project a great deal. However, I did consider many other {Card,Cal}DAV servers. I will explain a little about why I did not like them.

DAViCal

DAViCal seems to better support many CalDAV clients. It is under more active development and lots of documentation compared to radicale. Unlike radicale, the project is much more concerned with faithfully implementing CalDAV, and supporting lots of fancy features. Radicale is much more concerned with simplicity and supporting clients as they act in practice, and less concerned with the CalDAV protocol and advanced features.

However, DAViCal requires PHP and PostgreSQL. I am opposed to PHP as a language, so that is one strike against it. I also do not have PHP or PostgreSQL installed, so DAViCal would increases the disk usage of my server by a lot.

Baïkal

Baïkal is a very lightweight (2MB codebase) {Cal,Card}DAV server with slick web-based configuration. It seems to be under more active development compared to radicale. It supports all the clients I care about.

However, it requires PHP and MySQL, so I had to reject it for similar reasons to DAViCal.

SabreDAV

SabreDAV is single server that provides WebDAV, CalDAV and CardDAV—among other—protocols. It seems to provide much better support for protocols than other servers. It has a plugin architecture with plugins for more advanced features. It even has a web based administration page, although less slick than Baïkal. This all makes it a great choice except…

It requires PHP. It does not even require a database, but I am still not willing to budge on this PHP thing.

ownCloud

ownCloud is a very cool project. It seems to aim to give the average computer user the ability to setup their own “cloud”, complete with WebDAV, CalDAV, CardDAV, online videos, online PDF viewing, music sharing, among about 100 other features. It has very slick web interfaces and services.

It seems to support all the right clients, but it is an incredibly complex (large) project. It has tons of features I do not need or want. Therefore, I never looked into actually installing it. I am sure it requires at least a database.

WebDAV: nginx

Setting up the server

You need nginx with two modules. The first module is included in the nginx codebase. You can build support for it by compiling nginx with --with-http_dav_module. The second module, http_dav_ext_methods, adds support for two important requests that clients seem to require. You have to build this module from source separately, and compile nginx with --add-module=<path-to-module>.

After compiling nginx, you can easily enable WebDAV in nginx.conf using the following snippet. This snippet also enables SSL support and HTTP basic authentication. All WebDAV files are stored under /www/webdav/data. The autoindex on enables viewing all files in /www/webdav/data directory even from a web browser. This snippet serves the WebDAV server only on the webdav.williamjbowman.com subdomain. This can be quite handy for remembering the server address, although this can cause problems if your server uses HSTS and your SSL certificate does not include the subdomain.

nginx.conf
....
  # WebDAV
  server {
    listen       443 ssl spdy;
    server_name  webdav.williamjbowman.com;

    root /www/webdav;
    auth_basic "Not currently available";
    auth_basic_user_file /etc/nginx/htpasswd;

    location / {
      client_body_temp_path /webdav/tmp;

      dav_methods PUT DELETE MKCOL COPY MOVE;
      dav_ext_methods PROPFIND OPTIONS;

      create_full_put_path on;
      dav_access user:rw group:r;

      autoindex on;
    }
  }

You can also serve through a subdirectory instead of a subdomain:

nginx.conf
....
  # WebDAV
  server {
    listen       443 ssl spdy;
    server_name  williamjbowman.com www.williamjbowman.com;
    ....

    location /webdav {
      auth_basic "Not currently available";
      auth_basic_user_file /etc/nginx/htpasswd;

      client_body_temp_path /webdav/tmp;

      dav_methods PUT DELETE MKCOL COPY MOVE;
      dav_ext_methods PROPFIND OPTIONS;

      create_full_put_path on;
      dav_access user:rw group:r;

      autoindex on;
    }
  }

Setting up clients

You can easily view the files in a browser by simply going to, e.g., https://webdav.williamjbowman.com/. Of course, it requires authentication if you follow my snippet.

I also access WebDAV through thunar, my file manager, with the help of davfs2, which provides a FUSE filesystem for WebDAV. The only trick to this is thunar requires navigating to the completely intuitive URI davs://<username>@webdav.williamjbowman.com/.

Notability just requires giving the url, https://webdav.williamjbowman.com, the username, and password.

If you serve the WebDAV through a subdirectory rather than a subdomain, that works fine too. In this case, if using the example snippet above, the relevant addresses would be https://williamjbowman.com/webdav or davs://<username>@williamjbowman.com/webdav.

CalDAV and CardDAV: radicale via nginx proxy

Setting up the server

Installing radicale is quite simple. You can do so through your favorite package manage, e.g., yaourt -S radicale, or through Python’s package manager, or by unzipping the package.

The configuration file for my radicale server is stored in /etc/radicale/config and all the files for the server live in /srv/radicale/.

My radicale server is configured with no rights management, no SSL, and no authentication, but only listens on localhost. The server is publically accessible with SSL and authentication through an nginx proxy. The relevant configuration snippets are below.

/etc/radicale/config
[server]
hosts = 127.0.0.1:5232
pid = /run/radicale.pid

ssl = False

# This needs to change if served from a subdirectory instead of a
# subdomain
base_prefix = /

[encoding]
request = utf-8
stock = utf-8

[auth]
type = None

[rights]
type = None

[storage]
type = filesystem
filesystem_folder = /srv/radicale/collections

The nginx proxy is simple to setup:

nginx.conf
....
  # CalDAV and CardDAV
  server {
    listen       443 ssl spdy;
    server_name  caldav.williamjbowman.com carddav.williamjbowman.com;

    auth_basic "Not currently available";
    auth_basic_user_file /etc/nginx/caldav/htpasswd;

    location / {
      proxy_pass http://127.0.0.1:5232;
      proxy_buffering on;
    }
  }

Setting up clients

iPhone

Radicale provides instructions for setting up the iPhone, but I found that using a subdomain and a proxy simplified the procedure a bit, particularly for CardDAV. There was also one step I found necessary that is missing from the CardDAV instructions.

To setup CalDAV, simply go to the “Mail, Contacts, and Calendars” page under “Setting”, click “Add Account”, click “Other”, and click “Add CalDAV Account”. Enter the URL to the CalDAV server, followed by the username and a calendar name, then enter the username and password. For example, https://caldav.williamjbowman/user/private.ics/. The trailing slash is important, although the “https”, username, and calendar name seem less important. It seems to “just work” without them when using a subdomain, but not when using a subdirectory.

To setup CardDAV, I had to to manually create the address book on the server first: touch /srv/radicale/user/contacts.vcf. Then on your iPhone, go to “Mail, Contacts, and Calendars”, click “Add Account”, click “Other”, and click “Add CardDAV account”. Enter the URL, e.g., carddav.williamjbowman.com, the username, and the password. Things just seem to work after this, contrary to the radicale documentation.

pycarddav

pycarddav provides pretty good documentation, but I want to point out that you need the write_support option set if you actually want to modify the address book locally and sync to CardDAV server. This was not obvious to me from the documentation (despite being quite well documented) and causes some strange errors. Obviously from the value you must use, this feature is dangerous and experimental, so do not use it. I also have to disable SSL verification because my SSL certificate does not include the carddav subdomain yet. However, you should never do this because this enables man-in-the-middle attacks.

~/.config/pycard/pycard.conf
[Account wjb]
user: user

# A shell command line to read the password.
passwd_cmd: gnome-keyring-query get user@carddav.williamjbowman.com

resource: https://carddav.williamjbowman.com/user/contacts.vcf/

# If verify is set to False, no SSL Certificate checks are done at all.
verify: False

auth: basic

write_support: YesPleaseIDoHaveABackupOfMyData

Conclusion and Future Work

Now you can replace Google or Apple and manage your contacts, calendars, and reminders yourself. In the future, I need to figure out how to encrypt all these on disk in such a way that data is only decrypted when a user tries to access them, and without storing a key or password on the server. I also plan to add some scripts to enables new features for reminders, like dependencies between reminders, and enable reminders from a particular list to become due randomly.