Compare commits

...

8 Commits

Author SHA1 Message Date
Nicolas PARIS
1b89b5016c Merge branch 'docker' into 'master'
Add docker stuff

Closes #258

See merge request davical-project/davical!83
2025-01-27 23:24:40 +00:00
Andrew Ruthven
8f38332fce Set DAVResource type correctly when restoring from memcache
Assuming we're always dealing with a Principal isn't correct.
2025-01-27 23:27:45 +13:00
Andrew Ruthven
1fec8fd111 Add debug logging for ACE changes 2025-01-27 23:27:45 +13:00
Andrew Ruthven
f6547bd376 Make some lines more readable 2025-01-27 23:27:45 +13:00
Andrew Ruthven
b4bcc6cc25 Fix DAV:current-user-principal for iPhone devices
iPhone devices incorrectly implement DAV:current-user-principal from
RFC 5397. They assume that current-user-principal is the href for the
resource being queried. The RFC says it should be the current resource.

See: https://gitlab.com/davical-project/davical/-/issues/335
2025-01-27 23:27:45 +13:00
Andrew Ruthven
c1cfd8eb0d Fix typo 2025-01-27 23:02:36 +13:00
Andrew Ruthven
734d5c1f99 The public access endpoint is public.php 2025-01-22 14:48:24 +13:00
parisni
7d5867854d Add docker stuff 2022-01-09 02:39:36 +01:00
22 changed files with 1091 additions and 15 deletions

View File

@ -1,3 +1,9 @@
2025-01-22 Andrew Ruthven <andrew@etc.gen.nz>
* Using a Ticket ID requires public.php
2024-12-13 Andrew Ruthven <andrew@etc.gen.nz>
* Fix iPhone's accessing other principal's collections.
2024-04-15 Andrew Ruthven <andrew@etc.gen.nz>
* Add caching of user credential success/failure

102
docker/Dockerfile Normal file
View File

@ -0,0 +1,102 @@
#Version 0.4
#Davical + apache
#---------------------------------------------------------------------
#Default configuration: hostname: davical.example
# user: admin
# pass: 12345
#---------------------------------------------------------------------
FROM alpine
MAINTAINER https://github.com/datze
ARG TIME_ZONE "Europe/Rome"
ENV TIME_ZONE=$TIME_ZONE
ARG HOST_NAME "davical.example"
ENV HOST_NAME=$HOST_NAME
RUN addgroup -S apache && adduser -S apache -G apache
# apk
RUN apk --update add \
sudo \
bash \
less \
sed \
apache2 \
apache2-utils \
apache2-ssl \
php7 \
php7-apache2 \
php7-pgsql \
php7-imap \
php7-curl \
php7-cgi \
php7-xml \
php7-gettext \
php7-iconv \
php7-ldap \
php7-pdo \
php7-pdo_pgsql \
php7-calendar \
php7-session \
git \
libcap \
# git
&& git clone https://gitlab.com/davical-project/awl.git /usr/share/awl/ \
&& git clone https://gitlab.com/davical-project/davical.git /usr/share/davical/ \
&& rm -rf /usr/share/davical/.git /usr/share/awl/.git/ \
&& apk del git
# config files, shell scripts
COPY apache2_start.sh /sbin/apache2_start.sh
COPY apache.conf /config/apache.conf
COPY davical.php /config/davical.php
# Apache
RUN chown -R root:apache /usr/share/davical \
&& cd /usr/share/davical/ \
&& find ./ -type d -exec chmod u=rwx,g=rx,o=rx '{}' \; \
&& find ./ -type f -exec chmod u=rw,g=r,o=r '{}' \; \
&& find ./ -type f -name *.sh -exec chmod u=rwx,g=r,o=rx '{}' \; \
&& find ./ -type f -name *.php -exec chmod u=rwx,g=rx,o=r '{}' \; \
&& chmod o=rx /usr/share/davical \
&& chown -R root:apache /usr/share/awl \
&& cd /usr/share/awl/ \
&& find ./ -type d -exec chmod u=rwx,g=rx,o=rx '{}' \; \
&& find ./ -type f -exec chmod u=rw,g=r,o=r '{}' \; \
&& find ./ -type f -name *.sh -exec chmod u=rwx,g=rx,o=r '{}' \; \
&& find ./ -type f -name *.sh -exec chmod u=rwx,g=r,o=rx '{}' \; \
&& chmod o=rx /usr/share/awl \
&& mkdir /etc/davical \
&& sed -i /CustomLog/s/^/#/ /etc/apache2/httpd.conf \
&& sed -i /ErrorLog/s/^/#/ /etc/apache2/httpd.conf \
&& sed -i /TransferLog/s/^/#/ /etc/apache2/httpd.conf \
&& sed -i /CustomLog/s/^/#/ /etc/apache2/conf.d/ssl.conf \
&& sed -i /ErrorLog/s/^/#/ /etc/apache2/conf.d/ssl.conf \
&& sed -i /TransferLog/s/^/#/ /etc/apache2/conf.d/ssl.conf \
# permissions for shell scripts and config files
&& chmod 0755 /sbin/apache2_start.sh \
&& chown -R root:apache /etc/davical \
&& chmod -R u=rwx,g=rx,o= /etc/davical \
&& chown root:apache /config/davical.php \
&& chmod u+rwx,g+rx /config/davical.php \
&& ln -s /config/apache.conf /etc/apache2/conf.d/davical.conf \
&& ln -s /config/davical.php /etc/davical/config.php \
# clean-up etc
&& rm -rf /var/cache/apk/* \
&& mkdir -p /run/apache2 \
&& chown -R apache:apache /var/www /run/apache2 /var/log/apache2 /etc/ssl/apache2 \
&& rm /etc/apache2/conf.d/ssl.conf
#SET THE TIMEZONE
RUN apk add --update tzdata
RUN cp /usr/share/zoneinfo/$TIME_ZONE /etc/localtime
RUN echo $TIME_ZONE > /etc/timezone
RUN apk del tzdata
RUN setcap cap_net_bind_service=+epi /usr/sbin/httpd
USER apache

View File

@ -0,0 +1,61 @@
#Version 0.4
#---------------------------------------------------------------------
#Default configuration: hostname: davical.example
# user: admin
# pass: 12345
#---------------------------------------------------------------------
FROM alpine
MAINTAINER https://github.com/datze
ARG TIME_ZONE "Europe/Rome"
ENV TIME_ZONE=$TIME_ZONE
ARG HOST_NAME "davical.example"
ENV HOST_NAME=$HOST_NAME
# apk
RUN apk --update add \
sudo \
bash \
less \
sed \
postgresql \
perl \
perl-yaml \
perl-dbd-pg \
perl-dbi \
git \
# git
&& git clone https://gitlab.com/davical-project/awl.git /usr/share/awl/ \
&& git clone https://gitlab.com/davical-project/davical.git /usr/share/davical/ \
&& rm -rf /usr/share/davical/.git /usr/share/awl/.git/ \
&& apk del git
# config files, shell scripts
COPY initialize_db.sh /sbin/initialize_db.sh
RUN cd /usr/share/davical/ \
&& find ./ -type d -exec chmod u=rwx,g=rx,o=rx '{}' \; \
&& find ./ -type f -exec chmod u=rw,g=r,o=r '{}' \; \
&& find ./ -type f -name *.sh -exec chmod u=rwx,g=r,o=rx '{}' \; \
&& find ./ -type f -name *.php -exec chmod u=rwx,g=rx,o=r '{}' \; \
&& chmod o=rx /usr/share/davical/dba/update-davical-database \
&& chmod o=rx /usr/share/davical \
&& cd /usr/share/awl/ \
&& find ./ -type d -exec chmod u=rwx,g=rx,o=rx '{}' \; \
&& find ./ -type f -exec chmod u=rw,g=r,o=r '{}' \; \
&& find ./ -type f -name *.sh -exec chmod u=rwx,g=rx,o=r '{}' \; \
&& find ./ -type f -name *.sh -exec chmod u=rwx,g=r,o=rx '{}' \; \
&& chmod o=rx /usr/share/awl \
&& mkdir /etc/davical \
# permissions for shell scripts and config files
&& chmod 0755 /sbin/initialize_db.sh \
&& chmod -R u=rwx,g=rx,o= /etc/davical \
# clean-up etc
&& rm -rf /var/cache/apk/* \
&& mkdir /run/postgresql \
&& chmod a+w /run/postgresql

73
docker/README.md Normal file
View File

@ -0,0 +1,73 @@
# DAViCal Docker Container
Docker image for a complete [DAViCal](https://www.davical.org/) server (DAViCal + Apache2 + PostgreSQL) on Alpine Linux.
The repository on github.org contains example configuration files for DAViCal (as well as the Dockerfile to create the Docker image).
### About DAViCal
[DAViCal](https://www.davical.org/) is a server for shared calendars. It implements the [CalDAV protocol](https://wikipedia.org/wiki/CalDAV) and stores calendars in the [iCalendar format](https://wikipedia.org/wiki/ICalendar).
List of supported clients: Mozilla Thunderbird/Lightning, Evolution, Mulberry, Chandler, iCal, ...
**Features**
> - DAViCal is Free Software licensed under the General Public License.
> - uses an SQL database for storage of event data
> - supports backward-compatible access via WebDAV in read-only or read-write mode (not recommended)
> - is committed to inter-operation with the widest possible CalDAV client software.
>
>DAViCal supports basic delegation of read/write access among calendar users, multiple users or clients reading and writing the same calendar entries over time, and scheduling of meetings with free/busy time displayed.
(*https://www.davical.org/*)
### Settings
- Exposed Ports: TCP 80 and TCP 443
- Exposed Volumes: /config and /var/lib/postgresql/data/
- Exposed Variables: TIME_ZONE and HOST_NAME
On TCP 80 the web server listens with DAViCal on hostname davical.example (set your local host file to point davical.example to 127.0.0.1)
Exposed environment variables can be overwritten when creating the docker container (eg. docke run -e HOST_NAME='example.com').
TIME_ZONE is set by default to "Europe/Rome" but you can set as you prefer according to tzdata (for example to "Europe/Paris" or "Europe/London")
HOST_NAME is set by default to "davical.example"
/config can be mount and must contain a personalized version of all configuration files necessary:
- apache.conf
- davical.php
- rsyslog.conf
- supervisord.conf
So you can download the configuration files present in this repository, alter and copy them into a directory to be mounted on /config and the container using them!
To use HTTPS on TCP port 433, you need to uncomment the related lines in apache.conf and provide a SSL certificate for your domain in /config.
/var/lib/postgresql/data contains the database and the directory backups/. In this directory crond every day saves (thanks to /sbin/backup_db) a dump of DAViCal database to backups/davical_backup.tar
It is possible to restore the database using a backup: put the backup into the mounted /config, run the container and after the initialization use "docker exec container_name /sbin/restore_db"
The rsyslog server is configured to collect all logs from apache and postgres into /var/log/messages but you can personalize the configuration altering rsyslog.conf and put it in /config. rsyslog does NOT collect apache access.log (it is disabled)
The **default admin user** is: *admin* with password: *12345* (you can alter it from the DAViCal gui).
### Docker examples
**Simple test**
```
docker run -d --name davical-test -p 8080:80 datze/davical_https
```
Creates and runs a DAViCal Docker container which is fully operable on host TCP port 8080. Does not require any further configuration. *Attention! Only for testing! When the container is deleted, all stored calendars are lost!*
**Simple HTTP mode**
```
docker run -d --name davical -p 8080:80 -v var/davical/data:/var/lib/postgresql/data datze/davical_https
```
Creates and runs a DAViCal Docker container with default configurations on host TCP port 8080. The **database files are stored in /var/davical/data**. This is the simplest set-up without loosing calendars when the container is deleted.
**Full example**
```
docker run -d --name davical -p 8080:80 -p 8443:443 -v /var/davical/config:/config -v /var/davical/data:/var/lib/postgresql/data -e HOST_NAME='my.example.com' -e TIME_ZONE='Europe/Berlin' datze/davical_https
```
Creates and runs a DAViCal Docker container which will be accessible on the host system on TCP ports 8080 and 8443 (HTTPS).
The time zone is set to Europe/Berlin and the hostname is my.example.com.
The **config files must be created in /var/davical/config** and the **database files are stored in /var/davical/data**.
A SSL certificate must be placed in /var/davical/config and referenced in /var/davical/config/apache.conf. (Create a self-signed certificate with something like `openssl req -x509 -newkey rsa:4096 -keyout /var/davical/config/ssl/private.pem -out /var/davical/config/ssl/cert.pem -days 1000`)
### Credits
Based on https://github.com/IridiumXOR/davical and https://hub.docker.com/r/oliveria/davical (no HTTPS, older PG and PHP versions).

59
docker/apache.conf Normal file
View File

@ -0,0 +1,59 @@
AddHandler php7-script .php
AddType text/html .php
ServerName davical.example
LoadModule rewrite_module modules/mod_rewrite.so
<VirtualHost *:80 >
DocumentRoot /usr/share/davical/htdocs
DirectoryIndex index.php index.html
Alias /images/ /usr/share/davical/htdocs/images/
<Directory /usr/share/davical/htdocs/>
Require all granted
</Directory>
#Activate RewriteEngine
RewriteEngine On
# Filter all files that do not exist
RewriteCond %{LA-U:REQUEST_FILENAME} !-f
RewriteCond %{LA-U:REQUEST_FILENAME} !-d
# and redirect them to our caldav.php
RewriteRule ^(.*)$ /caldav.php/$1 [NC,L]
php_value include_path /usr/share/awl/inc
php_value magic_quotes_gpc 0
php_value magic_quotes_runtime 0
php_value register_globals 0
php_value error_reporting "E_ALL &amp; ~E_NOTICE"
php_value default_charset "utf-8"
</VirtualHost>
#<VirtualHost *:443 >
# DocumentRoot /usr/share/davical/htdocs
# DirectoryIndex index.php index.html
#
# Alias /images/ /usr/share/davical/htdocs/images/
# <Directory /usr/share/davical/htdocs/>
# Require all granted
# </Directory>
#
# #Activate RewriteEngine
# RewriteEngine On
# # Filter all files that do not exist
# RewriteCond %{LA-U:REQUEST_FILENAME} !-f
# RewriteCond %{LA-U:REQUEST_FILENAME} !-d
# # and redirect them to our caldav.php
# RewriteRule ^(.*)$ /caldav.php/$1 [NC,L]
#
# php_value include_path /usr/share/awl/inc
# php_value magic_quotes_gpc 0
# php_value magic_quotes_runtime 0
# php_value register_globals 0
# php_value error_reporting "E_ALL &amp; ~E_NOTICE"
# php_value default_charset "utf-8"
#
# SSLEngine on
# SSLCertificateFile "/config/ssl/cert.pem"
# SSLCertificateKeyFile "/config/ssl/private.pem"
#</VirtualHost>

3
docker/apache2_start.sh Normal file
View File

@ -0,0 +1,3 @@
#!/bin/bash
getcap /usr/sbin/httpd && /usr/sbin/httpd -e error -E /var/log/apache2/apache-start.log -DFOREGROUND && tail -f /var/log/apache2/*

7
docker/dav-env Normal file
View File

@ -0,0 +1,7 @@
ADMIN_PASSWORD=1234
DAVICAL_APP_PASSWORD=654321
DAVICAL_APP_USER=davical_app
DAVICAL_DBA_PASSWORD=123456
DAVICAL_DBA_USER=davical_dba
DBAOPTS="--dbhost=postgres --dbuser davical_dba --dbpass 123456 "
PSQLOPTS="-U davical_dba -h postgres -p 5432 "

467
docker/davical.php Normal file
View File

@ -0,0 +1,467 @@
<?php
/***************************************************************************
* *
* These apply everywhere and will need setting *
* *
***************************************************************************/
$c->domain_name = getenv('HOST_NAME')?:'davical.example';
$c->sysabbr = 'davical';
/****************************
********* Mandatory *********
*****************************/
/**
* Database connection: DAViCal will attempt to connect to the database by
* successively applying connection parameters from the array in
* $c->pg_connect.
*/
//$c->pg_connect[] = "dbname=davical user=davical_app";
$c->pg_connect[] = "dbname=davical user=davical_app port=5432 host=postgres password=654321";
// $c->pg_connect[] = "dbname=davical user=davical_app port=5433 host=somehost password=mypass";
/****************************
********* Desirable *********
*****************************/
/**
* "system_name" is used to specify the authentication realm of the server, as
* well as being used as a name to display in various places.
* Default: DAViCal CalDAV Server
*/
$c->system_name = "DAViCal CalDAV Server";
/**
* If "hide_TODO" is true, then VTODO requested from someone other than the
* admin or owner of a calendar will not get an answer. Often these todo are
* only relevant to the owner, but in some shared calendar situations they
* might not be in which case you should set this to false.
* Default: true
*/
// $c->hide_TODO = false;
/**
* If "readonly_webdav_collections" is true, then calendars accessed via WebDAV
* will be read-only. Any changes to them must be applied via CalDAV.
*
* You may want to set this to false during your initial setup to make it
* easier for people to PUT whole calendars as part of the conversion of
* their data. After this, it is recommended to turn it off so that clients
* which have been misconfigured are readily identifiable.
* Default: true
*/
// $c->readonly_webdav_collections = false;
/***************************************************************************
* *
* ADMIN web Interface *
* *
***************************************************************************/
/**
* Address displayed on the login page to indicate who you should ask if you
* have problems logging on. Also for the "From" header of the email sent when
* a user has lost his password and clicks on the "Help! I've forgotten my
* password" on the login page.
*/
$c->admin_email ='admin@example.com';
/**
* Set this to 'true' in order to restrict the /setup.php page (which contains
* the entire phpinfo() output) to 'Administrator' users.
* Default: false
*/
$c->restrict_setup_to_admin = true;
/**
* The "enable_row_linking" option controls whether javascript is used
* to make the entire row clickable in browse lists in the administration
* pages. Since this doesn't work in Konqueror you may want to set this
* to false if you expect people to be using Konqueror with the DAViCal
* administration pages.
* Default=true
*/
// $c->enable_row_linking = true;
/**
* These should be an array of style sheets with a path specified relative
* to the root directory. Used for overriding display styles in the admin
* interface.
* e.g. : $c->local_styles = array('/css/my.css');
*/
// $c->local_styles = array();
// $c->print_styles = array();
/***************************************************************************
* *
* Caldav Server *
* *
***************************************************************************/
/**
* The "collections_always_exist" value defines whether a MKCALENDAR
* command is needed to create a calendar collection before calendar
* resources can be stored in it. You will want to leave this to the
* default (true) if people will be using Evolution or Sunbird /
* Lightning against this because that software does not support the
* creation of calendar collections.
* Default: true
*/
// $c->collections_always_exist = false;
/**
* The name of a user's "home" calendar. This will be created for each
* new user.
* Default: 'calendar'
*/
//$c->home_calendar_name = 'calendar';
/**
* An array of groups / permissions which should be automatically added
* for each new user created. This is a crude mechanism which we
* will hopefully manage to work out some better approach for in the
* future. For now, create an array that looks something like:
* array( 9 => 'R', 4 => 'A' )
* to create a 'read' relationship to user_no 9 and an 'all' relation
* with user_no 4.
* Default: none
*/
// $c->default_relationships = array();
/**
* An array of the privileges which will be configured for a user by default
* from the possible set of real privileges:
* 'read', 'write-properties', 'write-content', 'unlock', 'read-acl', 'read-current-user-privilege-set',
* 'bind', 'unbind', 'write-acl', 'read-free-busy',
* 'schedule-deliver-invite', 'schedule-deliver-reply', 'schedule-query-freebusy',
* 'schedule-send-invite', 'schedule-send-reply', 'schedule-send-freebusy'
*
* Or also from these aggregated privileges:
* 'write', 'schedule-deliver', 'schedule-send', 'all'
*/
$c->default_privileges = array('all');
/**
* An array of fields on the usr record which should be set to specific
* values when the users are created.
* Default: none
*/
$c->template_usr = array( 'active' => true,
'locale' => 'en_EN',
'date_format_type' => 'E',
'email_ok' => date('Y-m-d')
);
/**
* If true, then remote scheduling will be enabled. There is a possibility
* of receiving spam events in calendars if enabled, you will at least know
* what domain the spam came from as domain key signatures are required for
* events to be accepted.
*
* You probably need to setup Domain Keys for your domain as well as the
* appropiate DNS SRV records.
*
* for example, if DAViCal is installed on cal.example.com you should have
* DNS SRV records like this:
* _ischedules._tcp.example.com. IN SRV 0 1 443 cal.example.com
* _ischedule._tcp.example.com. IN SRV 0 1 80 cal.example.com
*
* DNS TXT record for signing outbound requests
* example:
* cal._domainkey.example.com. 86400 IN TXT "k=rsa\; t=s\; p=PUBKEY"
* Default: false
*/
// $c->enable_scheduling = true;
/**
* Domain Key domain to use when signing outbound scheduling requests, this
* is the domain with the public key in a TXT record as shown above.
*
* TODO: enable domain/signing by per user keys, patches welcome.
* Default: none
*/
// $c->scheduling_dkim_domain = '';
/**
* Domain Key selector to use when signing outbound scheduling requests.
*
* TODO: enable selectors/signing by per user keys, patches welcome.
* Default: 'cal'
*/
// $c->scheduling_dkim_selector = 'cal';
/*
* Domain Key private key
* Required if you want to enable outbound remote server scheduling
* Default: none
*/
// $c->schedule_private_key = 'PRIVATE-KEY-BASE-64-DATA';
/*
* External subscription (BIND) minimum refresh interval
* Required if you want to enable remote binding ( webcal subscriptions )
* Default: none
*/
// $c->external_refresh = 60;
/**
* The "support_obsolete_free_busy_property" value controls whether,
* during a PROPFIND, the obsolete Scheduling property "calendar-free-busy-set"
* is returned. Set the value to true to support the property only if your
* client requires it, however note that PROPFIND performance may be
* adversely affected if you do so.
* Introduced in DAViCal version 1.1.4 in support of Issue #31 Database
* Performance Improvements.
* Default: false
*/
// $c->support_obsolete_free_busy_property = false;
/***************************************************************************
* *
* External Authentication Sources *
* *
***************************************************************************/
/**
* Allow specifying another way to control access of the user by authenticating
* him against other drivers such has LDAP (the default is the PgSQL DB)
* $c->authenticate_hook['call'] should be set to the name of the plugin and must
* be a valid function that will be call like this:
* call_user_func( $c->authenticate_hook['call'], $username, $password )
*
* The login mecanism is made in 2 places:
* - for the web interface in: index.php that calls DAViCalSession.php that extends
* Session.php (from AWL libraries)
* - for the caldav client in: caldav.php that calls BasicAuthSession.php
* Both Session.php and BasicAuthSession.php check against the
* authenticate_hook['call'], although for BasicAuthSession.php this will be for
* each page. For Session.php this will only occur during login.
*
* $c->authenticate_hook['config'] should be set up with any configuration data
* needed by the authenticate call for the moment used only in awl/inc/AuthPlugins.php
* and he used to authenticate the user should be at least 'password,user_no'
* awl/inc/AuthPlugins.php is a sample file not used by showing what could be
* a hook
*
* $c->authenticate_hook['optional'] = true; can be set to try default authentication
* as well in case the configured hook should report a failure.
*/
/********************************/
/******* Other AWL hook *********/
/********************************/
// require_once('auth-functions.php');
// $c->authenticate_hook = array(
// 'call' => 'AuthExternalAwl',
// 'config' => array(
// // A PgSQL database connection string for the database containing user records
// 'connection' => 'dbname=wrms host=otherhost port=5433 user=general',
// // Which columns should be fetched from the database
// 'columns' => "user_no, active, email_ok, joined, last_update AS updated, last_used, username, password, fullname, email",
// // a WHERE clause to limit the records returned.
// 'where' => "active AND org_code=7"
// )
// );
/********************************/
/*********** LDAP hook **********/
/********************************/
/*
* For Active Directory go down to the next example.
*/
//$c->authenticate_hook['call'] = 'LDAP_check';
//$c->authenticate_hook['config'] = array(
// 'host' => 'www.tennaxia.net', //host name of your LDAP Server
// 'port' => '389', //port
/* For the initial bind to be anonymous leave bindDN and passDN
commented out */
// DN to bind to this server enabling to perform request
// 'bindDN'=> 'cn=manager,cn=internal,dc=tennaxia,dc=net',
// Password of the previous bindDN to bind to this server enabling to perform request
// 'passDN'=> 'xxxxxxxx',
// 'protocolVersion' => '3', //Version of LDAP protocol to use
// 'baseDNUsers'=> 'dc=tennaxia,dc=net', //where to look at valid user
// 'filterUsers' => 'objectClass=kolabInetOrgPerson', //filter which must validate a user according to RFC4515, i.e. surrounded by brackets
// 'baseDNGroups' => 'ou=divisions,dc=tennaxia,dc=net', //where to look for groups
// 'filterGroups' => 'objectClass=groupOfUniqueNames', //filter with same rules as filterUsers
/** /!\ "username" should be set and "updated" must be set **/
// 'mapping_field' => array("username" => "uid",
// "updated" => "modifyTimestamp",
// "fullname" => "cn" ,
// "email" =>"mail"
// ), //used to create the user based on his ldap properties
// 'group_mapping_field' => array("username" => "cn",
// "updated" => "modifyTimestamp",
// "fullname" => "cn" ,
// "members" =>"memberUid"
// ), //used to create the group based on the ldap properties
/** used to set default value for all users, will be overcharged by ldap if defined also in mapping_field **/
// 'default_value' => array("date_format_type" => "E","locale" => "fr_FR"),
/** foreach key set start and length in the string provided by ldap
example for openLDAP timestamp : 20070503162215Z **/
// 'format_updated'=> array('Y' => array(0,4),'m' => array(4,2),'d'=> array(6,2),'H' => array(8,2),'M'=>array(10,2),'S' => array(12,2)),
// 'startTLS' => 'yes', // Require that TLS is used for LDAP?
// If ldap_start_tls is not working, it is probably
// because php wants to validate the server's
// certificate. Try adding "TLS_REQCERT never" to the
// ldap configuration file that php uses (e.g. /etc/ldap.conf
// or /etc/ldap/ldap.conf). Of course, this lessens security!
// 'scope' => 'subtree', // Search scope to use, defaults to subtree.
// // Allowed values: base, onelevel, subtree.
//
// );
//
// /* If there is some user you do not want to sync from LDAP, put their username in this list */
// $c->do_not_sync_from_ldap = array( 'admin' => true );
//
//include('drivers_ldap.php');
/*
* Use the following LDAP example if you are using Active Directory
*
* You will need to change host, passDN and DOMAIN in bindDN
* and baseDNUsers.
*/
//$c->authenticate_hook['call'] = 'LDAP_check';
//$c->authenticate_hook['config'] = array(
// 'host' => 'ldap://ldap.example.net',
// 'bindDN' => 'auth@DOMAIN',
// 'passDN' => 'secret',
// 'baseDNUsers' => 'dc=DOMAIN,dc=local',
// 'protocolVersion' => 3,
// 'optReferrals' => 0,
// 'filterUsers' => '(&(objectcategory=person)(objectclass=user)(givenname=*))',
// 'mapping_field' => array("username" => "uid",
// "fullname" => "cn" ,
// "email" => "mail"),
// 'default_value' => array("date_format_type" => "E","locale" => "en_NZ"),
// 'format_updated' => array('Y' => array(0,4),'m' => array(4,2),'d'=> array(6,2),'H' => array(8,2),'M'=>array(10,2),'S' => array(12,2))
// );
//
// /* If there is some user you do not want to sync from LDAP, put their username in this list */
// $c->do_not_sync_from_ldap = array( 'admin' => true );
//
//include('drivers_ldap.php');
/**
* Authentication against PAM using the Squid helper script.
*/
//$c->authenticate_hook = array(
// 'call' => 'SQUID_PAM_check',
// 'config' => array( 'script' => '/usr/bin/pam_auth', 'email_base' => 'example.com' )
// );
//include('drivers_squid_pam.php');
/**
* Authentication against PAM/system password database using pwauth.
*/
//$c->authenticate_hook = array('call' => 'PWAUTH_PAM_check',
// 'config' => array('path' => '/usr/sbin/pwauth',
// 'email_base' => 'example.com'));
//include('drivers_pwauth_pam.php');
/**
* The default locale will be "en_NZ";
* If you are in a non-English locale, you can set the default_locale
* configuration to one of the supported locales.
*
* Supported Locales (at present, see: "select * from supported_locales ;" for a full list)
*
* "de_DE", "en_NZ", "es_AR", "fr_FR", "nl_NL", "ru_RU"
*
* If you want locale support you probably know more about configuring it than me, but
* at this stage it should be noted that all translations are UTF-8, and pages are
* served as UTF-8, so you will need to ensure that the UTF-8 versions of these locales
* are supported on your system.
*
* People interested in providing new translations are directed to the Wiki:
* http://wiki.davical.org/w/Translating_DAViCal
*/
$c->default_locale = "it_IT";
/**
* Default will be $_SERVER['SERVER_NAME'];
* This is used to construct URLs which are passed in the answers to the client. You may
* want to force this to a specific domain in responses if your system is accessed by
* multiple names, otherwise you probably won't need to change it.
*/
// $c->domain_name;
/**
* Used as a fallback for the TZID of an event where one is not supplied as part
* of a VEVENT. The local (server) time zone will be used as a default.
*/
// $c->local_tzid;
/**
* Many people want this, but it may be a security issue for you, so it is
* disabled by default. If you enable it, then confidential / private events
* will be visible to the 'organizer' or 'attendee' lists. The reason that
* this becomes a security issue is that this identification needs to be based
* on the user's e-mail address. The user's e-mail address is generally
* something which they can set, so they could change it to be the address of
* an attendee of a meeting and then would be able to read the meeting.
*
* Without this, the only person who can view/change PRIVATE or CONFIDENTIAL
* events in a calendar is someone with full administrative rights to the calendar
* usually the owner.
*
* If the only person that devious is your sysadmin then you probably already
* enabled this option...
*/
// $c->allow_get_email_visibility = false;
/***************************************************************************
* *
* Push Notification Server *
* *
***************************************************************************/
/*
* This enable XMPP PubSub push notifications to clients that request them.
* N.B. this will publish urls for ALL updates and does NOT restrict
* subscription permissions on the jabber server! That means anyone with
* read access to the pubsub tree of your jabber server can watch for updates,
* they will only see URL's to the updated entries not the calendar data.
*
* Only tested with ejabberd 2.0.x
*/
// $c->notifications_server = array( 'host' => $_SERVER['SERVER_NAME'], // jabber server hostname
// 'jid' => 'user@example.com', // user(JID) to login/ publish as 'password' => '', // password for above account
// // 'debug_jid' => 'otheruser@example.com' // send a copy of all publishes to this jid
// );
// include ( 'pubsub.php' );
/***************************************************************************
* *
* Detailed Metrics *
* *
***************************************************************************/
/*
* This enables a /metrics.php URL containing detailed metrics about the
* operation of DAViCal. Ideally you will be running memcache if you are
* interested in keeping metrics, but there is a simple metrics collection
* available to you without running memcache.
*
* Note that there is currently no way of enabling metrics via memcache
* without memcache being enabled for all of DAViCal.
*/
$c->metrics_style = 'counters'; // Just the simple counter-based metrics
// $c->metrics_style = 'memcache'; // Only the metrics using memcache
// $c->metrics_style = 'both'; // Both styles of metrics
// $c->metrics_collectors = array('127.0.0.1'); // Restrict access to only this IP address
$c->metrics_require_user = 'admin'; // Restrict access to only connections authenticating as this user

49
docker/docker-compose.yml Normal file
View File

@ -0,0 +1,49 @@
version: '3'
services:
davical:
build:
context: .
args:
TIME_ZONE: Europe/Paris
ports:
- "8050:80"
env_file: dav-env
# volumes:
# - /var/davical/config:/config:ro
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
createdb:
condition: service_completed_successfully
entrypoint: ["/sbin/apache2_start.sh"]
createdb:
build:
context: .
dockerfile: Dockerfile.createdb
args:
TIME_ZONE: Europe/Paris
env_file: dav-env
depends_on:
postgres:
condition: service_healthy
entrypoint: ["/sbin/initialize_db.sh"]
postgres:
image: postgres
restart: always
environment:
POSTGRES_PASSWORD: eorihasdfasdf
DAVICAL_DBA_PASSWORD: 123456
DAVICAL_APP_PASSWORD: 654321
volumes:
- ./docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
healthcheck:
test: [ "CMD-SHELL", "psql -U postgres -c 'select 1'|grep -q 1" ]
interval: 5s
timeout: 1s
retries: 30

View File

@ -0,0 +1,7 @@
#!/bin/bash
set -e
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
CREATE ROLE davical_dba CREATEDB LOGIN password '${DAVICAL_DBA_PASSWORD}' ;
CREATE ROLE davical_app LOGIN password '${DAVICAL_APP_PASSWORD}';
EOSQL

15
docker/initialize_db.sh Normal file
View File

@ -0,0 +1,15 @@
#!/usr/bin/env bash
#CHECK IF THE DAVICAL DATABASE EXISTS, OTHERWISE INITIALIZE IT
echo "postgres:5432:*:davical_dba:${DAVICAL_DBA_PASSWORD}" > ~/.pgpass
chmod 600 ~/.pgpass
INITIALIZED_DB=$(psql -U davical_dba -h postgres -p 5432 -d template1 -c "\l" | grep -c davical)
if [[ $INITIALIZED_DB == 0 ]]; then
/usr/share/davical/dba/create-database.sh davical ${ADMIN_PASSWORD}
fi
unset INITIALIZED_DB
#UPDATE ALWAYS THE DATABASE
/usr/share/davical/dba/update-davical-database ${DBAOPTS} --dbhost postgres --dbuser "${DAVICAL_DBA_USER}" --dbname "davical" --appuser "${DAVICAL_APP_USER}"

View File

@ -363,13 +363,19 @@ class DAVResource
$this->FetchCollection();
if ( $this->_is_collection ) {
if ( $this->_is_principal || $this->collection->type == 'principal' ) $this->FetchPrincipal();
if ( $this->_is_principal || $this->collection->type == 'principal' )
$this->FetchPrincipal();
}
else {
$this->FetchResource();
}
dbg_error_log( 'DAVResource', ':FromPath: Path "%s" is%s a collection%s.',
$this->dav_name, ($this->_is_collection?' '.$this->resourcetypes:' not'), ($this->_is_principal?' and a principal':'') );
$this->dav_name,
($this->_is_collection ? ' ' . $this->resourcetypes
: ' not'),
($this->_is_principal ? ' and a principal'
: '')
);
}
@ -569,29 +575,34 @@ EOSQL;
$cache_ns = 'collection-'.preg_replace( '{/[^/]*$}', '/', $this->dav_name);
$cache_key = 'dav_resource'.$session->user_no;
$this->collection = $cache->get( $cache_ns, $cache_key );
if ( $this->collection === false ) {
$this->ReadCollectionFromDatabase();
if ( $this->collection->type != 'principal' && $this->_collection_is_cacheable ) {
$cache_ns = 'collection-'.$this->collection->dav_name;
@dbg_error_log( 'Cache', ':FetchCollection: Setting cache ns "%s" key "%s". Type: %s', $cache_ns, $cache_key, $this->collection->type );
$cache->set( $cache_ns, $cache_key, $this->collection );
}
@dbg_error_log( 'DAVResource', ':FetchCollection: Found collection named "%s" of type "%s".', $this->collection->dav_name, $this->collection->type );
}
else {
@dbg_error_log( 'Cache', ':FetchCollection: Got cache ns "%s" key "%s". Type: %s', $cache_ns, $cache_key, $this->collection->type );
if ( preg_match( '#^(/[^/]+)/?$#', $this->dav_name, $matches)
|| preg_match( '#^((/principals/[^/]+/)[^/]+)/?$#', $this->dav_name, $matches) ) {
@dbg_error_log( 'Cache', ':FetchCollection: Got cache ns "%s" key "%s". dav_name: "%s", Type: %s', $cache_ns, $cache_key, $this->collection->dav_name, $this->collection->type );
if ($this->collection->type == 'principal') {
$this->_is_principal = true;
$this->FetchPrincipal();
$this->collection->is_principal = true;
$this->collection->type = 'principal';
} else {
if ($this->collection->type == 'proxy') {
$this->_is_proxy_resource = true;
$this->proxy_type = $this->collection->proxy_type;
}
} else if ($this->collection->type == 'calendar') {
$this->_is_calendar = true;
} else if ($this->collection->type == 'proxy') {
$this->_is_proxy_resource = true;
$this->proxy_type = $this->collection->proxy_type;
}
@dbg_error_log( 'DAVResource', ':FetchCollection: Read cached collection named "%s" of type "%s".', $this->collection->dav_name, $this->collection->type );
}
@ -1881,8 +1892,15 @@ EOQRY;
$prop->NewElement( 'principal-collection-set', $reply->href( ConstructURL('/') ) );
break;
# iPhone devices incorrectly implement DAV:current-user-principal from
# RFC 5397. They assume that current-user-principal is the href for the
# resource being queried. The RFC says it should be the current resource.
# See: https://gitlab.com/davical-project/davical/-/issues/335
case 'DAV::current-user-principal':
$prop->NewElement('current-user-principal', $reply->href( ConstructURL(DeconstructURL($session->principal->url())) ) );
if ( preg_match('/iPhone/', $_SERVER['HTTP_USER_AGENT']) )
$prop->NewElement('current-user-principal', $reply->href( ConstructURL(DeconstructURL($request->principal->url())) ) );
else
$prop->NewElement('current-user-principal', $reply->href( ConstructURL(DeconstructURL($session->principal->url())) ) );
break;
case 'SOME-DENIED-PROPERTY': /** indicating the style for future expansion */

View File

@ -125,6 +125,9 @@ function process_ace( $grantor, $by_principal, $by_collection, $ace ) {
$principal_content = $principal_node->GetContent();
if ( count($principal_content) != 1 ) $request->MalformedRequest('ACL request must contain exactly one principal per ACE');
$principal_content = $principal_content[0];
dbg_error_log( 'ACE', 'NSTag: "%s", by_collection: %s, by_principal: %s', $principal_content->GetNSTag(), $by_collection ?? 'Null', $by_principal ?? 'Null');
switch( $principal_content->GetNSTag() ) {
case 'DAV::property':
$principal_property = $principal_content->GetContent();
@ -142,8 +145,10 @@ function process_ace( $grantor, $by_principal, $by_collection, $ace ) {
$principal_type = 'href';
$grantee = new DAVResource( DeconstructURL($principal_content->GetContent()) );
$grantee_id = $grantee->getProperty('principal_id');
if ( !$grantee->Exists() || !$grantee->IsPrincipal() )
$request->PreconditionFailed(403,'recognized-principal', 'Principal "' . $principal_content->GetContent() . '" not found.');
$sqlparms = array( ':to_principal' => $grantee_id);
$where = 'WHERE to_principal=:to_principal AND ';
if ( isset($by_principal) ) {
@ -154,6 +159,7 @@ function process_ace( $grantor, $by_principal, $by_collection, $ace ) {
$sqlparms[':by_collection'] = $by_collection;
$where .= 'by_collection = :by_collection';
}
$qry = new AwlQuery('SELECT privileges FROM grants '.$where, $sqlparms);
if ( $qry->Exec('ACL',__LINE__,__FILE__) && $qry->rows() == 1 && $current = $qry->Fetch() ) {
$sql = 'UPDATE grants SET privileges=:privileges::INT::BIT(24) '.$where;
@ -169,6 +175,15 @@ function process_ace( $grantor, $by_principal, $by_collection, $ace ) {
Principal::cacheDelete('dav_name',$grantee->dav_name());
Principal::cacheFlush('principal_id IN (SELECT member_id FROM group_member WHERE group_id = ?)', array($grantee_id));
}
/**
* Basically this has changed everyone's permissions now, so...
*/
$cache = getCacheInstance();
$cache->flush();
#Principal::cacheFlush('TRUE');
break;
case 'DAV::authenticated':

View File

@ -70,7 +70,7 @@ class CalDAVClient {
protected $calendar_urls;
/**
* The useragent which is send to the caldav server
* The useragent which is sent to the caldav server
*
* @var string
*/

View File

@ -26,7 +26,7 @@ class CalDAVClient {
var $base_url, $user, $pass, $calendar, $entry, $protocol, $server, $port;
/**
* The useragent which is send to the caldav server
* The useragent which is sent to the caldav server
*
* @var string
*/

View File

@ -463,7 +463,7 @@ EOTEMPLATE;
if ($can_write_collection) {
$browser->AddColumn( 'ticket_id', translate('Ticket ID'), '', '' );
}
$browser->AddColumn( 'target', translate('Target'), '', '<td style="white-space:nowrap;">%s</td>', "'".$c->base_url.'/caldav.php'."' ||COALESCE(d.dav_name,c.dav_name)" );
$browser->AddColumn( 'target', translate('Target'), '', '<td style="white-space:nowrap;">%s</td>', "'".$c->base_url.'/public.php'."' ||COALESCE(d.dav_name,c.dav_name)" );
$browser->AddColumn( 'expiry', translate('Expires'), '', '', 'TO_CHAR(expires,\'YYYYMMDD"T"HH:MI:SS\')');
$browser->AddColumn( 'privs', translate('Privileges'), '', '', "privileges_list(privileges)" );
if ($can_write_collection) {

View File

@ -0,0 +1,15 @@
HTTP/1.1 200 OK
Date: Dow, 01 Jan 2000 00:00:00 GMT
DAV: 1, 2, 3, access-control, calendar-access, calendar-schedule
DAV: extended-mkcol, bind, addressbook, calendar-auto-schedule, calendar-proxy
Content-Length: 0
Content-Type: text/plain; charset="utf-8"
SQL Query 1 Result:
by_collection: >1609<
by_principal: >NULL<
displayname: >User 4<
privileges: >000000000000000000100001<
to_principal: >1005<

View File

@ -0,0 +1,47 @@
# Test for iPhone devices which incorrectly implement
# DAV:current-user-principal from RFC 5397. They assume that
# current-user-principal is the href for the resource being queried. The
# RFC says it should be the current resource. #Sigh.
#
# See: https://gitlab.com/davical-project/davical/-/issues/335
#
# Ensure that user4 can access user1's address book.
TYPE=ACL
HEADER=User-Agent: RFC3744 Spec Tests
HEADER=Content-Type: text/xml; charset="UTF-8"
HEAD
BEGINDATA
<?xml version="1.0" encoding="utf-8" ?>
<acl xmlns="DAV:" xmlns:CalDAV="urn:ietf:params:xml:ns:caldav">
<ace>
<principal>
<href>/caldav.php/user4/</href>
</principal>
<grant>
<privilege><read/></privilege>
<privilege><read-current-user-privilege-set/></privilege>
</grant>
</ace>
<ace>
<principal><authenticated/></principal>
<grant>
<privilege/>
</grant>
</ace>
</acl>
ENDDATA
URL=http://regression.host/caldav.php/user1/
# This is by_collection, and by_principal isn't set, shouldn't it be set?
# WHERE p_by.dav_name = '/user1/'
QUERY
SELECT by_principal, by_collection, privileges, p_to.displayname, to_principal
FROM grants JOIN dav_principal p_to ON (to_principal=principal_id)
LEFT JOIN collection ON (by_collection=collection.collection_id)
LEFT JOIN dav_principal p_by ON (by_principal=p_by.principal_id)
WHERE by_collection = 1609
AND p_to.dav_name = '/user4/'
ORDER BY by_principal, to_principal
ENDQUERY

View File

@ -0,0 +1,50 @@
HTTP/1.1 207 Multi-Status
Date: Dow, 01 Jan 2000 00:00:00 GMT
Content-Location: /caldav.php/user1/addresses/
DAV: 1, 2, 3, access-control, calendar-access, calendar-schedule
DAV: extended-mkcol, bind, addressbook, calendar-auto-schedule, calendar-proxy
ETag: "43765875e20eef2d841725645b2f3c95"
Content-Length: 1129
Content-Type: text/xml; charset="utf-8"
<?xml version="1.0" encoding="utf-8" ?>
<multistatus xmlns="DAV:" xmlns:C="urn:ietf:params:xml:ns:carddav">
<response>
<href>/caldav.php/user1/addresses/</href>
<propstat>
<prop>
<getcontenttype>httpd/unix-directory</getcontenttype>
<resourcetype>
<collection/>
<C:addressbook/>
</resourcetype>
<displayname>user1 addresses</displayname>
<getlastmodified>Sun, 15 Mar 1998 12:00:00 GMT</getlastmodified>
<creationdate>19570725T120000Z</creationdate>
<getcontentlanguage/>
<supportedlock>
<lockentry>
<lockscope>
<exclusive/>
</lockscope>
<locktype>
<write/>
</locktype>
</lockentry>
</supportedlock>
<owner>
<href>/caldav.php/user1/</href>
</owner>
<current-user-principal>
<href>/caldav.php/user1/</href>
</current-user-principal>
<C:max-resource-size>6550000</C:max-resource-size>
<C:supported-address-data>
<C:address-data content-type="text/vcard" version="3.0"/>
</C:supported-address-data>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
</multistatus>

View File

@ -0,0 +1,16 @@
# Test for iPhone devices which incorrectly implement
# DAV:current-user-principal from RFC 5397. They assume that
# current-user-principal is the href for the resource being queried. The
# RFC says it should be the current resource. #Sigh.
#
# See: https://gitlab.com/davical-project/davical/-/issues/335
#
# Ensure that user4 has user1 as the current-user-principal as we're an
# 'iPhone'.
TYPE=PROPFIND
AUTH=user4:user4
HEADER=Content-Type: text/xml; charset="UTF-8"
HEADER=User-Agent: DAVKit/4.0 (728.3); iCalendar/1 (34); iPhone/3.0
HEAD
URL=http://regression.host/caldav.php/user1/addresses

View File

@ -0,0 +1,50 @@
HTTP/1.1 207 Multi-Status
Date: Dow, 01 Jan 2000 00:00:00 GMT
Content-Location: /caldav.php/user1/addresses/
DAV: 1, 2, 3, access-control, calendar-access, calendar-schedule
DAV: extended-mkcol, bind, addressbook, calendar-auto-schedule, calendar-proxy
ETag: "3f9506c10fe5b434f966d4c82f026c40"
Content-Length: 1129
Content-Type: text/xml; charset="utf-8"
<?xml version="1.0" encoding="utf-8" ?>
<multistatus xmlns="DAV:" xmlns:C="urn:ietf:params:xml:ns:carddav">
<response>
<href>/caldav.php/user1/addresses/</href>
<propstat>
<prop>
<getcontenttype>httpd/unix-directory</getcontenttype>
<resourcetype>
<collection/>
<C:addressbook/>
</resourcetype>
<displayname>user1 addresses</displayname>
<getlastmodified>Sun, 15 Mar 1998 12:00:00 GMT</getlastmodified>
<creationdate>19570725T120000Z</creationdate>
<getcontentlanguage/>
<supportedlock>
<lockentry>
<lockscope>
<exclusive/>
</lockscope>
<locktype>
<write/>
</locktype>
</lockentry>
</supportedlock>
<owner>
<href>/caldav.php/user1/</href>
</owner>
<current-user-principal>
<href>/caldav.php/user4/</href>
</current-user-principal>
<C:max-resource-size>6550000</C:max-resource-size>
<C:supported-address-data>
<C:address-data content-type="text/vcard" version="3.0"/>
</C:supported-address-data>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
</multistatus>

View File

@ -0,0 +1,16 @@
# Test for iPhone devices which incorrectly implement
# DAV:current-user-principal from RFC 5397. They assume that
# current-user-principal is the href for the resource being queried. The
# RFC says it should be the current resource. #Sigh.
#
# See: https://gitlab.com/davical-project/davical/-/issues/335
#
# Ensure that user4 has user4 as the current-user-principal as we're not an
# 'iPhone'.
TYPE=PROPFIND
AUTH=user4:user4
HEADER=Content-Type: text/xml; charset="UTF-8"
HEADER=User-Agent: Evolution/1.8.1
HEAD
URL=http://regression.host/caldav.php/user1/addresses