$ calypso --import private/test <filenames...>
This will update any changed entries and add any new ones.
+
+Kerberos via GSSAPI support
+---------------------------
+For Kerberos authentication generate a keytab on your KDC and put the
+exported keytab on your calypso server into */etc/krb.keytab* so it
+looks like:
+
+ # ktutil -k /etc/krb5.keytab list
+ /etc/krb5.keytab:
+
+ Vno Type Principal Aliases
+ 1 aes256-cts-hmac-sha1-96 HTTP/foo.example.com@EXAMPLE.COM
+ 1 des3-cbc-sha1 HTTP/foo.example.com@EXAMPLE.COM
+ 1 arcfour-hmac-md5 HTTP/foo.example.com@EXAMPLE.COM
+
+the put the service name to use into ~/.config/calypso/config:
+
+ [server]
+ servicename=HTTP@foo.example.com
+
+and install the pykerberos module. You should then be able to authenticate
+via Kerberos using GSSAPI.
import BaseHTTPServer as server
# pylint: enable=F0401
-from . import acl, config, webdav, xmlutils, paths
+from . import acl, config, webdav, xmlutils, paths, gssapi
log = logging.getLogger()
ch = logging.StreamHandler()
formatter = logging.Formatter("%(message)s")
ch.setFormatter (formatter)
log.addHandler(ch)
+negotiate = gssapi.Negotiate(log)
VERSION = "1.5"
"""Check if user has sufficient rights for performing ``request``."""
# ``_check`` decorator can access ``request`` protected functions
# pylint: disable=W0212
+ owner = user = password = None
+ negotiate_success = False
- authorization = request.headers.get("Authorization", None)
- if authorization:
- challenge = authorization.lstrip("Basic").strip().encode("ascii")
- plain = request._decode(base64.b64decode(challenge))
- user, password = plain.split(":")
- else:
- user = password = None
-
- owner = None
if request._collection:
owner = request._collection.owner
+ authorization = request.headers.get("Authorization", None)
+ if authorization:
+ if authorization.startswith("Basic"):
+ challenge = authorization.lstrip("Basic").strip().encode("ascii")
+ plain = request._decode(base64.b64decode(challenge))
+ user, password = plain.split(":")
+ elif negotiate.enabled():
+ user, negotiate_success = negotiate.try_aaa(authorization, request, owner)
+
# Also send UNAUTHORIZED if there's no collection. Otherwise one
# could probe the server for (non-)existing collections.
- if request.server.acl.has_right(owner, user, password):
+ if request.server.acl.has_right(owner, user, password) or negotiate_success:
function(request, context={"user": user, "user-agent": request.headers.get("User-Agent", None)})
else:
request.send_calypso_response(client.UNAUTHORIZED, 0)
+ if negotiate.enabled():
+ request.send_header("WWW-Authenticate", "Negotiate")
request.send_header(
"WWW-Authenticate",
'Basic realm="Calypso CalDAV/CardDAV server - password required"')
timeout = 90
server_version = "Calypso/%s" % VERSION
+ queued_headers = {}
+
+ def queue_header(self, keyword, value):
+ self.queued_headers[keyword] = value
+
+ def end_headers(self):
+ """
+ Send out all queued headers and invoke or super classes
+ end_header.
+ """
+ if self.queued_headers:
+ for keyword, val in self.queued_headers.items():
+ self.send_header(keyword, val)
+ self.queued_headers = {}
+ return server.BaseHTTPRequestHandler.end_headers(self)
def address_string(self):
return str(self.client_address[0])
def has_right(owner, user, password):
"""Check if ``user`` is valid."""
- log.debug("owner %s user %s", owner, user)
+ log.debug("owner '%s' user '%s'", owner, user)
if user == owner or not PERSONAL:
return True
return False
--- /dev/null
+# -*- coding: utf-8 -*-
+#
+# This file is part of Calypso - CalDAV/CardDAV/WebDAV Server
+# Copyright © 2016 Guido Günther <agx@sigxcpu.org>
+#
+# This library is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Calypso. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+Gssapi module.
+
+This module handles kerberos authenticatien via gssapi
+"""
+
+import os
+
+from . import config
+from acl import nopwd
+
+# pylint: disable=F0401
+try:
+ import kerberos as krb
+except ImportError:
+ krb = None
+# pylint: disable=F0401
+
+
+class Negotiate(object):
+ _gssapi = False
+
+ def __init__(self, log):
+ self.log = log
+ try:
+ self.servicename = os.path.expanduser(config.get("server",
+ "servicename"))
+ except:
+ self.servicename = None
+
+ if self.servicename and krb:
+ self._gssapi = True
+
+ def enabled(self):
+ return self._gssapi
+
+ def try_aaa(self, authorization, request, owner):
+ """Perform authentication and authorization"""
+ user, success = self.step(authorization, request)
+ if success:
+ return user, nopwd.has_right(owner, user, None)
+ return user, False
+
+ def step(self, authorization, request):
+ """
+ Try to authenticate the client and if succesful authenticate
+ ourself to the client.
+ """
+ user = None
+
+ if not self.enabled():
+ return (None, False)
+
+ try:
+ (neg, challenge) = authorization.split()
+ if neg.lower().strip() != 'negotiate':
+ return (None, False)
+
+ self.log.debug("Negotiate header found, trying Kerberos")
+ result, context = krb.authGSSServerInit(self.servicename)
+ result = krb.authGSSServerStep(context, challenge)
+
+ if result == -1:
+ return (None, False)
+
+ response = krb.authGSSServerResponse(context)
+ # Client authenticated successfully, so authenticate to the client:
+ request.queue_header("www-authenticate",
+ "negotiate " + response)
+ user = krb.authGSSServerUserName(context)
+
+ self.log.debug("Negotiate: found user %s" % user)
+ result = krb.authGSSServerClean(context)
+ if result != 1:
+ self.log.error("Failed to cleanup gss context")
+ return (user, True)
+ except krb.GSSError as err:
+ self.log.error("gssapi error: %s", err)
+
+ return None, False