diff options
Diffstat (limited to 'src/xsasl')
l--------- | src/xsasl/.indent.pro | 1 | ||||
-rw-r--r-- | src/xsasl/Makefile.in | 165 | ||||
-rw-r--r-- | src/xsasl/README | 109 | ||||
-rw-r--r-- | src/xsasl/xsasl.h | 146 | ||||
-rw-r--r-- | src/xsasl/xsasl_client.c | 240 | ||||
-rw-r--r-- | src/xsasl/xsasl_cyrus.h | 47 | ||||
-rw-r--r-- | src/xsasl/xsasl_cyrus_client.c | 585 | ||||
-rw-r--r-- | src/xsasl/xsasl_cyrus_common.h | 39 | ||||
-rw-r--r-- | src/xsasl/xsasl_cyrus_log.c | 104 | ||||
-rw-r--r-- | src/xsasl/xsasl_cyrus_security.c | 87 | ||||
-rw-r--r-- | src/xsasl/xsasl_cyrus_server.c | 641 | ||||
-rw-r--r-- | src/xsasl/xsasl_dovecot.h | 41 | ||||
-rw-r--r-- | src/xsasl/xsasl_dovecot_server.c | 746 | ||||
-rw-r--r-- | src/xsasl/xsasl_server.c | 270 |
14 files changed, 3221 insertions, 0 deletions
diff --git a/src/xsasl/.indent.pro b/src/xsasl/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/xsasl/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro
\ No newline at end of file diff --git a/src/xsasl/Makefile.in b/src/xsasl/Makefile.in new file mode 100644 index 0000000..ad48302 --- /dev/null +++ b/src/xsasl/Makefile.in @@ -0,0 +1,165 @@ +SHELL = /bin/sh +SRCS = xsasl_server.c xsasl_cyrus_server.c xsasl_cyrus_log.c \ + xsasl_cyrus_security.c xsasl_client.c xsasl_cyrus_client.c \ + xsasl_dovecot_server.c +OBJS = xsasl_server.o xsasl_cyrus_server.o xsasl_cyrus_log.o \ + xsasl_cyrus_security.o xsasl_client.o xsasl_cyrus_client.o \ + xsasl_dovecot_server.o +HDRS = xsasl.h +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +INCL = +LIB = libxsasl.a +TESTPROG= + +LIBS = ../../lib/lib$(LIB_PERFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PERFIX)util$(LIB_SUFFIX) +LIB_DIR = ../../lib +INC_DIR = ../../include +MAKES = + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +all: $(LIB) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: + +root_tests: + +$(LIB): $(OBJS) + $(_AR) $(ARFL) $(LIB) $? + $(_RANLIB) $(LIB) + +$(LIB_DIR)/$(LIB): $(LIB) + cp $(LIB) $(LIB_DIR) + $(_RANLIB) $(LIB_DIR)/$(LIB) + +update: $(LIB_DIR)/$(LIB) $(HDRS) + -for i in $(HDRS); \ + do \ + cmp -s $$i $(INC_DIR)/$$i 2>/dev/null || cp $$i $(INC_DIR); \ + done + cd $(INC_DIR); chmod 644 $(HDRS) + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + cp *.h printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o $(LIB) *core $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +foo: $(LIB) $(LIBS) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +xsasl_client.o: ../../include/argv.h +xsasl_client.o: ../../include/check_arg.h +xsasl_client.o: ../../include/msg.h +xsasl_client.o: ../../include/mymalloc.h +xsasl_client.o: ../../include/sys_defs.h +xsasl_client.o: ../../include/vbuf.h +xsasl_client.o: ../../include/vstream.h +xsasl_client.o: ../../include/vstring.h +xsasl_client.o: xsasl.h +xsasl_client.o: xsasl_client.c +xsasl_client.o: xsasl_cyrus.h +xsasl_cyrus_client.o: ../../include/argv.h +xsasl_cyrus_client.o: ../../include/check_arg.h +xsasl_cyrus_client.o: ../../include/mail_params.h +xsasl_cyrus_client.o: ../../include/msg.h +xsasl_cyrus_client.o: ../../include/mymalloc.h +xsasl_cyrus_client.o: ../../include/stringops.h +xsasl_cyrus_client.o: ../../include/sys_defs.h +xsasl_cyrus_client.o: ../../include/vbuf.h +xsasl_cyrus_client.o: ../../include/vstream.h +xsasl_cyrus_client.o: ../../include/vstring.h +xsasl_cyrus_client.o: xsasl.h +xsasl_cyrus_client.o: xsasl_cyrus.h +xsasl_cyrus_client.o: xsasl_cyrus_client.c +xsasl_cyrus_client.o: xsasl_cyrus_common.h +xsasl_cyrus_log.o: ../../include/msg.h +xsasl_cyrus_log.o: ../../include/sys_defs.h +xsasl_cyrus_log.o: xsasl_cyrus_common.h +xsasl_cyrus_log.o: xsasl_cyrus_log.c +xsasl_cyrus_security.o: ../../include/check_arg.h +xsasl_cyrus_security.o: ../../include/name_mask.h +xsasl_cyrus_security.o: ../../include/sys_defs.h +xsasl_cyrus_security.o: ../../include/vbuf.h +xsasl_cyrus_security.o: ../../include/vstring.h +xsasl_cyrus_security.o: xsasl_cyrus_common.h +xsasl_cyrus_security.o: xsasl_cyrus_security.c +xsasl_cyrus_server.o: ../../include/argv.h +xsasl_cyrus_server.o: ../../include/check_arg.h +xsasl_cyrus_server.o: ../../include/mail_params.h +xsasl_cyrus_server.o: ../../include/msg.h +xsasl_cyrus_server.o: ../../include/mymalloc.h +xsasl_cyrus_server.o: ../../include/name_mask.h +xsasl_cyrus_server.o: ../../include/stringops.h +xsasl_cyrus_server.o: ../../include/sys_defs.h +xsasl_cyrus_server.o: ../../include/vbuf.h +xsasl_cyrus_server.o: ../../include/vstream.h +xsasl_cyrus_server.o: ../../include/vstring.h +xsasl_cyrus_server.o: xsasl.h +xsasl_cyrus_server.o: xsasl_cyrus.h +xsasl_cyrus_server.o: xsasl_cyrus_common.h +xsasl_cyrus_server.o: xsasl_cyrus_server.c +xsasl_dovecot_server.o: ../../include/argv.h +xsasl_dovecot_server.o: ../../include/check_arg.h +xsasl_dovecot_server.o: ../../include/connect.h +xsasl_dovecot_server.o: ../../include/iostuff.h +xsasl_dovecot_server.o: ../../include/mail_params.h +xsasl_dovecot_server.o: ../../include/msg.h +xsasl_dovecot_server.o: ../../include/myaddrinfo.h +xsasl_dovecot_server.o: ../../include/mymalloc.h +xsasl_dovecot_server.o: ../../include/name_mask.h +xsasl_dovecot_server.o: ../../include/split_at.h +xsasl_dovecot_server.o: ../../include/stringops.h +xsasl_dovecot_server.o: ../../include/sys_defs.h +xsasl_dovecot_server.o: ../../include/vbuf.h +xsasl_dovecot_server.o: ../../include/vstream.h +xsasl_dovecot_server.o: ../../include/vstring.h +xsasl_dovecot_server.o: ../../include/vstring_vstream.h +xsasl_dovecot_server.o: xsasl.h +xsasl_dovecot_server.o: xsasl_dovecot.h +xsasl_dovecot_server.o: xsasl_dovecot_server.c +xsasl_server.o: ../../include/argv.h +xsasl_server.o: ../../include/check_arg.h +xsasl_server.o: ../../include/msg.h +xsasl_server.o: ../../include/mymalloc.h +xsasl_server.o: ../../include/sys_defs.h +xsasl_server.o: ../../include/vbuf.h +xsasl_server.o: ../../include/vstream.h +xsasl_server.o: ../../include/vstring.h +xsasl_server.o: xsasl.h +xsasl_server.o: xsasl_cyrus.h +xsasl_server.o: xsasl_dovecot.h +xsasl_server.o: xsasl_server.c diff --git a/src/xsasl/README b/src/xsasl/README new file mode 100644 index 0000000..d1b2f5a --- /dev/null +++ b/src/xsasl/README @@ -0,0 +1,109 @@ +Purpose of this document +======================== + +This document describes how to add your own SASL implementation to +Postfix. You don't have to provide both the server and client side. +You can provide just one and omit the other. The examples below +assume you do both. + +The plug-in API is described in cyrus_server.c and cyrus_client.c. +It was unavoidably contaminated^h^h^h^h^h^h^h^h^h^h^h^hinfluenced +by Cyrus SASL and may need revision as other implementations are +added. + +For an example of how the plug-in interface is implemented, have a +look at the xsasl/xsasl_cyrus_client.c and xsasl/xsasl_cyrus_server.c. + +Configuration features +====================== + +There are two configuration parameters that allow you to pass +information from main.cf into the plug_in: + + smtpd_sasl_path, smtpd_sasl_security_options + smtp_sasl_path, smtp_sasl_security_options + lmtp_sasl_path, lmtp_sasl_security_options + +As usual, newline characters are removed from multi-line parameter +values, and $name is expanded recursively. The parameter values +are passed to the plug-in without any further processing. The +following restrictions are imposed by the main.cf file parser: + +- parameter values never contain newlines, + +- parameter values never start or end with whitespace characters. + +The _path parameter value is passed only once during process +initialization (i.e. it is a class variable). The path typically +specifies the location of a configuration file or rendez-vous point. +The _security_options parameter value is passed each time SASL is +turned on for a connection (i.e. it is an instance variable). The +options may depend on whether or not TLS encryption is turned on. +Remember that one Postfix process may perform up to 100 mail +transactions during its life time. Things that happen in one +transaction must not affect later transactions. + +Adding Postfix support for your own SASL implementation +======================================================= + +To add your own SASL implementation, say, FOOBAR: + +- Copy xsasl/xsasl_cyrus.h to xsasl/xsasl_foobar.h and replace + CYRUS by FOOBAR: + + #if defined(USE_SASL_AUTH) && defined(USE_FOOBAR_SASL) + /* + * SASL protocol interface + */ + #define XSASL_TYPE_FOOBAR "foobar" + extern XSASL_SERVER_IMPL *xsasl_foobar_server_init(const char *, const char *); + extern XSASL_CLIENT_IMPL *xsasl_foobar_client_init(const char *, const char *); + #endif + +- Edit xsasl/xsasl_server.c, add your #include <xsasl_foobar.h> line + under #include <xsasl_cyrus.h> at the top, and add your initialization + function in the table at the bottom as shown below: + + static XSASL_SERVER_IMPL_INFO server_impl_info[] = { + #ifdef XSASL_TYPE_CYRUS + XSASL_TYPE_CYRUS, xsasl_cyrus_server_init, + #endif + #ifdef XSASL_TYPE_FOOBAR + XSASL_TYPE_FOOBAR, xsasl_foobar_server_init, + #endif + 0, + }; + +- Repeat the (almost) same procedure for xsasl/xsasl_client.c. + +- Create your own xsasl/xsasl_foobar_{client,server}.c and support + files. Perhaps it's convenient to copy the cyrus files, rip out + the function bodies, and replace CYRUS by FOOBAR. + +- List your source files in Makefile.in. Don't forget to do "make + depend" after you do "make makefiles" in the step that follows + after this one. + + SRCS = xsasl_server.c xsasl_cyrus_server.c xsasl_cyrus_log.c \ + xsasl_cyrus_security.c xsasl_client.c xsasl_cyrus_client.c \ + xsasl_foobar_client.c xsasl_foobar_server.c + OBJS = xsasl_server.o xsasl_cyrus_server.o xsasl_cyrus_log.o \ + xsasl_cyrus_security.o xsasl_client.o xsasl_cyrus_client.o \ + xsasl_foobar_client.o xsasl_foobar_server.o + +- Create the Postfix makefiles from the top-level directory: + + % make makefiles CCARGS='-DUSE_SASL_AUTH -DUSE_FOOBAR_SASL \ + -DDEF_CLIENT_SASL_TYPE=\"foobar\" -DDEF_SERVER_SASL_TYPE=\"foobar\" \ + -I/some/where/include' AUXLIBS='-L/some/where/lib -lfoobar' + + Yes, you can have different default SASL implementation types for + the client and server plug-ins. + + Of course you don't have to override the default SASL implementation + type; it is shown here as an example. + + +- Don't forget to do "make depend" in the xsasl directory. + +- Document your build and configuration with a README document. diff --git a/src/xsasl/xsasl.h b/src/xsasl/xsasl.h new file mode 100644 index 0000000..b494d7e --- /dev/null +++ b/src/xsasl/xsasl.h @@ -0,0 +1,146 @@ +#ifndef _XSASL_H_INCLUDED_ +#define _XSASL_H_INCLUDED_ + +/*++ +/* NAME +/* xsasl 3h +/* SUMMARY +/* Postfix SASL plug-in interface +/* SYNOPSIS +/* #include <xsasl.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <argv.h> +#include <vstream.h> +#include <vstring.h> + + /* + * Generic server object. Specific instances extend this with their own + * private data. + */ +typedef struct XSASL_SERVER { + void (*free) (struct XSASL_SERVER *); + int (*first) (struct XSASL_SERVER *, const char *, const char *, VSTRING *); + int (*next) (struct XSASL_SERVER *, const char *, VSTRING *); + const char *(*get_mechanism_list) (struct XSASL_SERVER *); + const char *(*get_username) (struct XSASL_SERVER *); +} XSASL_SERVER; + +#define xsasl_server_free(server) (server)->free(server) +#define xsasl_server_first(server, method, init_resp, reply) \ + (server)->first((server), (method), (init_resp), (reply)) +#define xsasl_server_next(server, request, reply) \ + (server)->next((server), (request), (reply)) +#define xsasl_server_get_mechanism_list(server) \ + (server)->get_mechanism_list((server)) +#define xsasl_server_get_username(server) \ + (server)->get_username((server)) + + /* + * Generic server implementation. Specific instances extend this with their + * own private data. + */ +typedef struct XSASL_SERVER_CREATE_ARGS { + VSTREAM *stream; + int addr_family; + const char *server_addr; + const char *server_port; + const char *client_addr; + const char *client_port; + const char *service; + const char *user_realm; + const char *security_options; + int tls_flag; +} XSASL_SERVER_CREATE_ARGS; + +typedef struct XSASL_SERVER_IMPL { + XSASL_SERVER *(*create) (struct XSASL_SERVER_IMPL *, XSASL_SERVER_CREATE_ARGS *); + void (*done) (struct XSASL_SERVER_IMPL *); +} XSASL_SERVER_IMPL; + +extern XSASL_SERVER_IMPL *xsasl_server_init(const char *, const char *); +extern ARGV *xsasl_server_types(void); + +#define xsasl_server_create(impl, args) \ + (impl)->create((impl), (args)) +#define XSASL_SERVER_CREATE(impl, args, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) \ + xsasl_server_create((impl), (((args)->a1), ((args)->a2), ((args)->a3), \ + ((args)->a4), ((args)->a5), ((args)->a6), ((args)->a7), ((args)->a8), \ + ((args)->a9), ((args)->a10), (args))) +#define xsasl_server_done(impl) (impl)->done((impl)); + + /* + * Generic client object. Specific instances extend this with their own + * private data. + */ +typedef struct XSASL_CLIENT { + void (*free) (struct XSASL_CLIENT *); + int (*first) (struct XSASL_CLIENT *, const char *, const char *, const char *, const char **, VSTRING *); + int (*next) (struct XSASL_CLIENT *, const char *, VSTRING *); +} XSASL_CLIENT; + +#define xsasl_client_free(client) (client)->free(client) +#define xsasl_client_first(client, server, method, user, pass, init_resp) \ + (client)->first((client), (server), (method), (user), (pass), (init_resp)) +#define xsasl_client_next(client, request, reply) \ + (client)->next((client), (request), (reply)) +#define xsasl_client_set_password(client, user, pass) \ + (client)->set_password((client), (user), (pass)) + + /* + * Generic client implementation. Specific instances extend this with their + * own private data. + */ +typedef struct XSASL_CLIENT_CREATE_ARGS { + VSTREAM *stream; + const char *service; + const char *server_name; + const char *security_options; +} XSASL_CLIENT_CREATE_ARGS; + +typedef struct XSASL_CLIENT_IMPL { + XSASL_CLIENT *(*create) (struct XSASL_CLIENT_IMPL *, XSASL_CLIENT_CREATE_ARGS *); + void (*done) (struct XSASL_CLIENT_IMPL *); +} XSASL_CLIENT_IMPL; + +extern XSASL_CLIENT_IMPL *xsasl_client_init(const char *, const char *); +extern ARGV *xsasl_client_types(void); + +#define xsasl_client_create(impl, args) \ + (impl)->create((impl), (args)) +#define XSASL_CLIENT_CREATE(impl, args, a1, a2, a3, a4) \ + xsasl_client_create((impl), (((args)->a1), ((args)->a2), ((args)->a3), \ + ((args)->a4), (args))) +#define xsasl_client_done(impl) (impl)->done((impl)); + + /* + * Status codes. + */ +#define XSASL_AUTH_OK 1 /* Success */ +#define XSASL_AUTH_MORE 2 /* Need another c/s protocol exchange */ +#define XSASL_AUTH_DONE 3 /* Authentication completed */ +#define XSASL_AUTH_FORM 4 /* Cannot decode response */ +#define XSASL_AUTH_FAIL 5 /* Error */ +#define XSASL_AUTH_TEMP 6 /* Temporary error condition */ + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/xsasl/xsasl_client.c b/src/xsasl/xsasl_client.c new file mode 100644 index 0000000..0bddd41 --- /dev/null +++ b/src/xsasl/xsasl_client.c @@ -0,0 +1,240 @@ +/*++ +/* NAME +/* xsasl_client 3 +/* SUMMARY +/* Postfix SASL client plug-in interface +/* SYNOPSIS +/* #include <xsasl.h> +/* +/* XSASL_CLIENT_IMPL *xsasl_client_init(client_type, path_info) +/* const char *client_type; +/* const char *path_info; +/* +/* void xsasl_client_done(implementation) +/* XSASL_CLIENT_IMPL *implementation; +/* +/* ARGV *xsasl_client_types() +/* +/* .in +4 +/* typedef struct XSASL_CLIENT_CREATE_ARGS { +/* VSTREAM *stream; +/* const char *service; +/* const char *server_name; +/* const char *security_options; +/* } XSASL_CLIENT_CREATE_ARGS; +/* .in -4 +/* +/* XSASL_CLIENT *xsasl_client_create(implementation, create_args) +/* XSASL_CLIENT_IMPL *implementation; +/* XSASL_CLIENT_CREATE_ARGS *create_args; +/* +/* XSASL_CLIENT *XSASL_CLIENT_CREATE(implementation, create_args, +/* stream = stream_val, +/* ..., +/* security_options = prop_val) +/* XSASL_CLIENT_IMPL *implementation; +/* XSASL_CLIENT_CREATE_ARGS *create_args; +/* +/* void xsasl_client_free(client) +/* XSASL_CLIENT *client; +/* +/* int xsasl_client_first(client, stream, mech_list, username, +/* password, auth_method, init_resp) +/* XSASL_CLIENT *client; +/* const char *mech_list; +/* const char *username; +/* const char *password; +/* const char **auth_method; +/* VSTRING *init_resp; +/* +/* int xsasl_client_next(client, server_reply, client_reply) +/* XSASL_CLIENT *client; +/* const char *server_reply; +/* VSTRING *client_reply; +/* DESCRIPTION +/* The XSASL_CLIENT abstraction implements a generic interface +/* to one or more SASL authentication implementations. +/* +/* xsasl_client_init() is called once during process initialization. +/* It selects a SASL implementation by name, specifies the +/* location of a configuration file or rendez-vous point, and +/* returns an implementation handle that can be used to generate +/* SASL client instances. This function is typically used to +/* initialize the underlying implementation. +/* +/* xsasl_client_done() disposes of an implementation handle, +/* and allows the underlying implementation to release resources. +/* +/* xsasl_client_types() lists the available implementation types. +/* The result should be destroyed by the caller. +/* +/* xsasl_client_create() is called at the start of an SMTP +/* session. It generates a Postfix SASL plug-in client instance +/* for the specified service and server name, with the specified +/* security properties. The stream handle is stored so that +/* encryption can be turned on after successful negotiations. +/* +/* XSASL_CLIENT_CREATE() is a macro that provides an interface +/* with named parameters. Named parameters do not have to +/* appear in a fixed order. The parameter names correspond to +/* the member names of the XSASL_CLIENT_CREATE_ARGS structure. +/* +/* xsasl_client_free() is called at the end of an SMTP session. +/* It destroys a SASL client instance, and disables further +/* read/write operations if encryption was turned on. +/* +/* xsasl_client_first() produces the client input for the AUTH +/* command. The input is an authentication method list from +/* an EHLO response, a username and a password. On return, the +/* method argument specifies the authentication method; storage +/* space is owned by the underlying implementation. The initial +/* response and client non-error replies are BASE64 encoded. +/* Client error replies are 7-bit ASCII text without control +/* characters, and without BASE64 encoding. They are meant for +/* the local application, not for transmission to the server. +/* The client may negotiate encryption of the client-server +/* connection. +/* +/* The result is one of the following: +/* .IP XSASL_AUTH_OK +/* Success. +/* .IP XSASL_AUTH_FORM +/* The server reply is incorrectly formatted. The client error +/* reply explains why. +/* .IP XSASL_AUTH_FAIL +/* Other error. The client error reply explains why. +/* .PP +/* xsasl_client_next() supports the subsequent stages of the +/* AUTH protocol. Both the client reply and client non-error +/* responses are BASE64 encoded. See xsasl_client_first() for +/* other details. +/* +/* Arguments: +/* .IP client +/* SASL plug-in client handle. +/* .IP client_reply +/* BASE64 encoded non-error client reply, or ASCII error +/* description for the user. +/* .IP client_type +/* The name of a Postfix SASL client plug_in implementation. +/* .IP client_types +/* Null-terminated array of strings with SASL client plug-in +/* implementation names. +/* .IP init_resp +/* The AUTH command initial response. +/* .IP implementation +/* Implementation handle that was obtained with xsasl_client_init(). +/* .IP mech_list +/* List of SASL mechanisms as announced by the server. +/* .IP auth_method +/* The AUTH command authentication method. +/* .IP password +/* Information from the Postfix SASL password file or equivalent. +/* .IP path_info +/* The value of the smtp_sasl_path parameter or equivalent. +/* This specifies the implementation-dependent location of a +/* configuration file, rendez-vous point, etc., and is passed +/* unchanged to the plug-in. +/* .IP security_options +/* The value of the smtp_sasl_security_options parameter or +/* equivalent. This is passed unchanged to the plug-in. +/* .IP server_name +/* The remote server fully qualified hostname. +/* .IP server_reply +/* BASE64 encoded server reply without SMTP reply code or +/* enhanced status code. +/* .IP service +/* The service that is implemented by the local client (typically, +/* "lmtp" or "smtp"). +/* .IP stream +/* The connection between client and server. +/* When SASL encryption is negotiated, the plug-in will +/* transparently intercept the socket read/write operations. +/* .IP username +/* Information from the Postfix SASL password file. +/* SECURITY +/* .ad +/* .fi +/* The caller does not sanitize the server reply. It is the +/* responsibility of the underlying SASL client implementation +/* to produce 7-bit ASCII without control characters as client +/* non-error and error replies. +/* DIAGNOSTICS +/* In case of error, xsasl_client_init() and xsasl_client_create() +/* log a warning and return a null pointer. +/* +/* Functions that normally return XSASL_AUTH_OK will log a warning +/* and return an appropriate result value. +/* +/* Panic: interface violation. +/* +/* Fatal errors: out of memory. +/* SEE ALSO +/* cyrus_security(3) Cyrus SASL security features +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this +/* software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> + +/* SASL implementations. */ + +#include <xsasl.h> +#include <xsasl_cyrus.h> + + /* + * Lookup table for available SASL client implementations. + */ +typedef struct { + char *client_type; + struct XSASL_CLIENT_IMPL *(*client_init) (const char *, const char *); +} XSASL_CLIENT_IMPL_INFO; + +static const XSASL_CLIENT_IMPL_INFO client_impl_info[] = { +#ifdef XSASL_TYPE_CYRUS + XSASL_TYPE_CYRUS, xsasl_cyrus_client_init, +#endif + 0, +}; + +/* xsasl_client_init - look up client implementation by name */ + +XSASL_CLIENT_IMPL *xsasl_client_init(const char *client_type, + const char *path_info) +{ + const XSASL_CLIENT_IMPL_INFO *xp; + + for (xp = client_impl_info; xp->client_type; xp++) + if (strcmp(client_type, xp->client_type) == 0) + return (xp->client_init(client_type, path_info)); + msg_warn("unsupported SASL client implementation: %s", client_type); + return (0); +} + +/* xsasl_client_types - report available implementation types */ + +ARGV *xsasl_client_types(void) +{ + const XSASL_CLIENT_IMPL_INFO *xp; + ARGV *argv = argv_alloc(1); + + for (xp = client_impl_info; xp->client_type; xp++) + argv_add(argv, xp->client_type, ARGV_END); + return (argv); +} diff --git a/src/xsasl/xsasl_cyrus.h b/src/xsasl/xsasl_cyrus.h new file mode 100644 index 0000000..ad8557e --- /dev/null +++ b/src/xsasl/xsasl_cyrus.h @@ -0,0 +1,47 @@ +#ifndef _XSASL_CYRUS_H_INCLUDED_ +#define _XSASL_CYRUS_H_INCLUDED_ + +/*++ +/* NAME +/* xsasl_cyrus 3h +/* SUMMARY +/* Cyrus SASL plug-in +/* SYNOPSIS +/* #include <xsasl_cyrus.h> +/* DESCRIPTION +/* .nf + + /* + * XSASL library. + */ +#include <xsasl.h> + +#if defined(USE_SASL_AUTH) && defined(USE_CYRUS_SASL) + + /* + * SASL protocol interface + */ +#define XSASL_TYPE_CYRUS "cyrus" + +extern XSASL_SERVER_IMPL *xsasl_cyrus_server_init(const char *, const char *); +extern XSASL_CLIENT_IMPL *xsasl_cyrus_client_init(const char *, const char *); + + /* + * Internal definitions for client and server module. + */ +typedef int (*XSASL_CYRUS_CB) (void); + +#endif + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/xsasl/xsasl_cyrus_client.c b/src/xsasl/xsasl_cyrus_client.c new file mode 100644 index 0000000..fc799c9 --- /dev/null +++ b/src/xsasl/xsasl_cyrus_client.c @@ -0,0 +1,585 @@ +/*++ +/* NAME +/* xsasl_cyrus_client 3 +/* SUMMARY +/* Cyrus SASL client-side plug-in +/* SYNOPSIS +/* #include <xsasl_cyrus_client.h> +/* +/* XSASL_CLIENT_IMPL *xsasl_cyrus_client_init(client_type, path_info) +/* const char *client_type; +/* DESCRIPTION +/* This module implements the Cyrus SASL client-side authentication +/* plug-in. +/* +/* xsasl_cyrus_client_init() initializes the Cyrus SASL library and +/* returns an implementation handle that can be used to generate +/* SASL client instances. +/* +/* Arguments: +/* .IP client_type +/* The plug-in SASL client type (cyrus). This argument is +/* ignored, but it could be used when one implementation +/* provides multiple variants. +/* .IP path_info +/* Implementation-specific information to specify the location +/* of a configuration file, rendez-vous point, etc. This +/* information is ignored by the Cyrus SASL client plug-in. +/* DIAGNOSTICS +/* Fatal: out of memory. +/* +/* Panic: interface violation. +/* +/* Other: the routines log a warning and return an error result +/* as specified in xsasl_client(3). +/* SEE ALSO +/* xsasl_client(3) Client API +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Original author: +/* Till Franke +/* SuSE Rhein/Main AG +/* 65760 Eschborn, Germany +/* +/* Adopted by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include <sys_defs.h> +#include <stdlib.h> +#include <string.h> + + /* + * Utility library + */ +#include <msg.h> +#include <mymalloc.h> +#include <stringops.h> + + /* + * Global library + */ +#include <mail_params.h> + + /* + * Application-specific + */ +#include <xsasl.h> +#include <xsasl_cyrus.h> +#include <xsasl_cyrus_common.h> + +#if defined(USE_SASL_AUTH) && defined(USE_CYRUS_SASL) + +#include <sasl.h> +#include <saslutil.h> + +/* + * Silly little macros. + */ +#define STR(s) vstring_str(s) + + /* + * Macros to handle API differences between SASLv1 and SASLv2. Specifics: + * + * The SASL_LOG_* constants were renamed in SASLv2. + * + * SASLv2's sasl_client_new takes two new parameters to specify local and + * remote IP addresses for auth mechs that use them. + * + * SASLv2's sasl_client_start function no longer takes the secret parameter. + * + * SASLv2's sasl_decode64 function takes an extra parameter for the length of + * the output buffer. + * + * The other major change is that SASLv2 now takes more responsibility for + * deallocating memory that it allocates internally. Thus, some of the + * function parameters are now 'const', to make sure we don't try to free + * them too. This is dealt with in the code later on. + */ +#if SASL_VERSION_MAJOR < 2 +/* SASL version 1.x */ +#define SASL_CLIENT_NEW(srv, fqdn, lport, rport, prompt, secflags, pconn) \ + sasl_client_new(srv, fqdn, prompt, secflags, pconn) +#define SASL_CLIENT_START(conn, mechlst, secret, prompt, clout, cllen, mech) \ + sasl_client_start(conn, mechlst, secret, prompt, clout, cllen, mech) +#define SASL_DECODE64(in, inlen, out, outmaxlen, outlen) \ + sasl_decode64(in, inlen, out, outlen) +typedef char *CLIENTOUT_TYPE; + +#endif + +#if SASL_VERSION_MAJOR >= 2 +/* SASL version > 2.x */ +#define SASL_CLIENT_NEW(srv, fqdn, lport, rport, prompt, secflags, pconn) \ + sasl_client_new(srv, fqdn, lport, rport, prompt, secflags, pconn) +#define SASL_CLIENT_START(conn, mechlst, secret, prompt, clout, cllen, mech) \ + sasl_client_start(conn, mechlst, prompt, clout, cllen, mech) +#define SASL_DECODE64(in, inlen, out, outmaxlen, outlen) \ + sasl_decode64(in, inlen, out, outmaxlen, outlen) +typedef const char *CLIENTOUT_TYPE; + +#endif + + /* + * The XSASL_CYRUS_CLIENT object is derived from the generic XSASL_CLIENT + * object. + */ +typedef struct { + XSASL_CLIENT xsasl; /* generic members, must be first */ + VSTREAM *stream; /* client-server connection */ + sasl_conn_t *sasl_conn; /* SASL context */ + VSTRING *decoded; /* decoded server challenge */ + sasl_callback_t *callbacks; /* user/password lookup */ + char *username; + char *password; +} XSASL_CYRUS_CLIENT; + + /* + * Forward declarations. + */ +static void xsasl_cyrus_client_done(XSASL_CLIENT_IMPL *); +static XSASL_CLIENT *xsasl_cyrus_client_create(XSASL_CLIENT_IMPL *, + XSASL_CLIENT_CREATE_ARGS *); +static int xsasl_cyrus_client_set_security(XSASL_CLIENT *, const char *); +static int xsasl_cyrus_client_first(XSASL_CLIENT *, const char *, const char *, + const char *, const char **, VSTRING *); +static int xsasl_cyrus_client_next(XSASL_CLIENT *, const char *, VSTRING *); +static void xsasl_cyrus_client_free(XSASL_CLIENT *); + +/* xsasl_cyrus_client_get_user - username lookup call-back routine */ + +static int xsasl_cyrus_client_get_user(void *context, int unused_id, + const char **result, + unsigned *len) +{ + const char *myname = "xsasl_cyrus_client_get_user"; + XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) context; + + if (msg_verbose) + msg_info("%s: %s", myname, client->username); + + /* + * Sanity check. + */ + if (client->password == 0) + msg_panic("%s: no username looked up", myname); + + *result = client->username; + if (len) + *len = strlen(client->username); + return (SASL_OK); +} + +/* xsasl_cyrus_client_get_passwd - password lookup call-back routine */ + +static int xsasl_cyrus_client_get_passwd(sasl_conn_t *conn, void *context, + int id, sasl_secret_t **psecret) +{ + const char *myname = "xsasl_cyrus_client_get_passwd"; + XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) context; + int len; + + if (msg_verbose) + msg_info("%s: %s", myname, client->password); + + /* + * Sanity check. + */ + if (!conn || !psecret || id != SASL_CB_PASS) + return (SASL_BADPARAM); + if (client->password == 0) + msg_panic("%s: no password looked up", myname); + + /* + * Convert the password into a counted string. + */ + len = strlen(client->password); + if ((*psecret = (sasl_secret_t *) malloc(sizeof(sasl_secret_t) + len)) == 0) + return (SASL_NOMEM); + (*psecret)->len = len; + memcpy((*psecret)->data, client->password, len + 1); + + return (SASL_OK); +} + +/* xsasl_cyrus_client_init - initialize Cyrus SASL library */ + +XSASL_CLIENT_IMPL *xsasl_cyrus_client_init(const char *unused_client_type, + const char *unused_path_info) +{ + XSASL_CLIENT_IMPL *xp; + int sasl_status; + + /* + * Global callbacks. These have no per-session context. + */ + static sasl_callback_t callbacks[] = { + {SASL_CB_LOG, (XSASL_CYRUS_CB) &xsasl_cyrus_log, 0}, + {SASL_CB_LIST_END, 0, 0} + }; + +#if SASL_VERSION_MAJOR >= 2 && (SASL_VERSION_MINOR >= 2 \ + || (SASL_VERSION_MINOR == 1 && SASL_VERSION_STEP >= 19)) + int sasl_major; + int sasl_minor; + int sasl_step; + + /* + * DLL hell guard. + */ + sasl_version_info((const char **) 0, (const char **) 0, + &sasl_major, &sasl_minor, + &sasl_step, (int *) 0); + if (sasl_major != SASL_VERSION_MAJOR +#if 0 + || sasl_minor != SASL_VERSION_MINOR + || sasl_step != SASL_VERSION_STEP +#endif + ) { + msg_warn("incorrect SASL library version. " + "Postfix was built with include files from version %d.%d.%d, " + "but the run-time library version is %d.%d.%d", + SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP, + sasl_major, sasl_minor, sasl_step); + return (0); + } +#endif + + if (*var_cyrus_conf_path) { +#ifdef SASL_PATH_TYPE_CONFIG /* Cyrus SASL 2.1.22 */ + if (sasl_set_path(SASL_PATH_TYPE_CONFIG, + var_cyrus_conf_path) != SASL_OK) + msg_warn("failed to set Cyrus SASL configuration path: \"%s\"", + var_cyrus_conf_path); +#else + msg_warn("%s is not empty, but setting the Cyrus SASL configuration " + "path is not supported with SASL library version %d.%d.%d", + VAR_CYRUS_CONF_PATH, SASL_VERSION_MAJOR, + SASL_VERSION_MINOR, SASL_VERSION_STEP); +#endif + } + + /* + * Initialize the SASL library. + */ + if ((sasl_status = sasl_client_init(callbacks)) != SASL_OK) { + msg_warn("SASL library initialization error: %s", + xsasl_cyrus_strerror(sasl_status)); + return (0); + } + + /* + * Return a generic XSASL_CLIENT_IMPL object. We don't need to extend it + * with our own methods or data. + */ + xp = (XSASL_CLIENT_IMPL *) mymalloc(sizeof(*xp)); + xp->create = xsasl_cyrus_client_create; + xp->done = xsasl_cyrus_client_done; + return (xp); +} + +/* xsasl_cyrus_client_done - dispose of implementation */ + +static void xsasl_cyrus_client_done(XSASL_CLIENT_IMPL *impl) +{ + myfree((void *) impl); + sasl_done(); +} + +/* xsasl_cyrus_client_create - per-session SASL initialization */ + +XSASL_CLIENT *xsasl_cyrus_client_create(XSASL_CLIENT_IMPL *unused_impl, + XSASL_CLIENT_CREATE_ARGS *args) +{ + XSASL_CYRUS_CLIENT *client = 0; + static sasl_callback_t callbacks[] = { + {SASL_CB_USER, (XSASL_CYRUS_CB) &xsasl_cyrus_client_get_user, 0}, + {SASL_CB_AUTHNAME, (XSASL_CYRUS_CB) &xsasl_cyrus_client_get_user, 0}, + {SASL_CB_PASS, (XSASL_CYRUS_CB) &xsasl_cyrus_client_get_passwd, 0}, + {SASL_CB_LIST_END, 0, 0} + }; + sasl_conn_t *sasl_conn = 0; + sasl_callback_t *custom_callbacks = 0; + sasl_callback_t *cp; + int sasl_status; + + /* + * The optimizer will eliminate code duplication and/or dead code. + */ +#define XSASL_CYRUS_CLIENT_CREATE_ERROR_RETURN(x) \ + do { \ + if (client) { \ + xsasl_cyrus_client_free(&client->xsasl); \ + } else { \ + if (custom_callbacks) \ + myfree((void *) custom_callbacks); \ + if (sasl_conn) \ + sasl_dispose(&sasl_conn); \ + } \ + return (x); \ + } while (0) + + /* + * Per-session initialization. Provide each session with its own callback + * context. + */ +#define NULL_SECFLAGS 0 + + custom_callbacks = (sasl_callback_t *) mymalloc(sizeof(callbacks)); + memcpy((void *) custom_callbacks, callbacks, sizeof(callbacks)); + +#define NULL_SERVER_ADDR ((char *) 0) +#define NULL_CLIENT_ADDR ((char *) 0) + + if ((sasl_status = SASL_CLIENT_NEW(args->service, args->server_name, + NULL_CLIENT_ADDR, NULL_SERVER_ADDR, + var_cyrus_sasl_authzid ? custom_callbacks : + custom_callbacks + 1, NULL_SECFLAGS, + &sasl_conn)) != SASL_OK) { + msg_warn("per-session SASL client initialization: %s", + xsasl_cyrus_strerror(sasl_status)); + XSASL_CYRUS_CLIENT_CREATE_ERROR_RETURN(0); + } + + /* + * Extend the XSASL_CLIENT object with our own state. We use long-lived + * conversion buffers rather than local variables to avoid memory leaks + * in case of read/write timeout or I/O error. + * + * XXX If we enable SASL encryption, there needs to be a way to inform the + * application, so that they can turn off connection caching, refuse + * STARTTLS, etc. + */ + client = (XSASL_CYRUS_CLIENT *) mymalloc(sizeof(*client)); + client->xsasl.free = xsasl_cyrus_client_free; + client->xsasl.first = xsasl_cyrus_client_first; + client->xsasl.next = xsasl_cyrus_client_next; + client->stream = args->stream; + client->sasl_conn = sasl_conn; + client->callbacks = custom_callbacks; + client->decoded = vstring_alloc(20); + client->username = 0; + client->password = 0; + + for (cp = custom_callbacks; cp->id != SASL_CB_LIST_END; cp++) + cp->context = (void *) client; + + if (xsasl_cyrus_client_set_security(&client->xsasl, + args->security_options) + != XSASL_AUTH_OK) + XSASL_CYRUS_CLIENT_CREATE_ERROR_RETURN(0); + + return (&client->xsasl); +} + +/* xsasl_cyrus_client_set_security - set security properties */ + +static int xsasl_cyrus_client_set_security(XSASL_CLIENT *xp, + const char *sasl_opts_val) +{ + XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) xp; + sasl_security_properties_t sec_props; + int sasl_status; + + /* + * Per-session security properties. XXX This routine is not sufficiently + * documented. What is the purpose of all this? + */ + memset(&sec_props, 0, sizeof(sec_props)); + sec_props.min_ssf = 0; + sec_props.max_ssf = 0; /* don't allow real SASL + * security layer */ + if (*sasl_opts_val == 0) { + sec_props.security_flags = 0; + } else { + sec_props.security_flags = + xsasl_cyrus_security_parse_opts(sasl_opts_val); + if (sec_props.security_flags == 0) { + msg_warn("bad per-session SASL security properties"); + return (XSASL_AUTH_FAIL); + } + } + sec_props.maxbufsize = 0; + sec_props.property_names = 0; + sec_props.property_values = 0; + if ((sasl_status = sasl_setprop(client->sasl_conn, SASL_SEC_PROPS, + &sec_props)) != SASL_OK) { + msg_warn("set per-session SASL security properties: %s", + xsasl_cyrus_strerror(sasl_status)); + return (XSASL_AUTH_FAIL); + } + return (XSASL_AUTH_OK); +} + +/* xsasl_cyrus_client_first - run authentication protocol */ + +static int xsasl_cyrus_client_first(XSASL_CLIENT *xp, + const char *mechanism_list, + const char *username, + const char *password, + const char **mechanism, + VSTRING *init_resp) +{ + const char *myname = "xsasl_cyrus_client_first"; + XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) xp; + unsigned enc_length; + unsigned enc_length_out; + CLIENTOUT_TYPE clientout; + unsigned clientoutlen; + int sasl_status; + +#define NO_SASL_SECRET 0 +#define NO_SASL_INTERACTION 0 + + /* + * Save the username and password for the call-backs. + */ + if (client->username) + myfree(client->username); + client->username = mystrdup(username); + if (client->password) + myfree(client->password); + client->password = mystrdup(password); + + /* + * Start the client side authentication protocol. + */ + sasl_status = SASL_CLIENT_START((sasl_conn_t *) client->sasl_conn, + mechanism_list, + NO_SASL_SECRET, NO_SASL_INTERACTION, + &clientout, &clientoutlen, mechanism); + if (sasl_status != SASL_OK && sasl_status != SASL_CONTINUE) { + vstring_strcpy(init_resp, xsasl_cyrus_strerror(sasl_status)); + return (XSASL_AUTH_FAIL); + } + + /* + * Generate the AUTH command and the optional initial client response. + * sasl_encode64() produces four bytes for each complete or incomplete + * triple of input bytes. Allocate an extra byte for string termination. + */ +#define ENCODE64_LENGTH(n) ((((n) + 2) / 3) * 4) + + if (clientoutlen > 0) { + if (msg_verbose) { + escape(client->decoded, clientout, clientoutlen); + msg_info("%s: uncoded initial reply: %s", + myname, STR(client->decoded)); + } + enc_length = ENCODE64_LENGTH(clientoutlen) + 1; + VSTRING_RESET(init_resp); /* Fix 200512 */ + VSTRING_SPACE(init_resp, enc_length); + if ((sasl_status = sasl_encode64(clientout, clientoutlen, + STR(init_resp), + vstring_avail(init_resp), + &enc_length_out)) != SASL_OK) + msg_panic("%s: sasl_encode64 botch: %s", + myname, xsasl_cyrus_strerror(sasl_status)); + vstring_set_payload_size(init_resp, enc_length_out); +#if SASL_VERSION_MAJOR < 2 + /* SASL version 1 doesn't free memory that it allocates. */ + free(clientout); +#endif + } else { + vstring_strcpy(init_resp, ""); + } + return (XSASL_AUTH_OK); +} + +/* xsasl_cyrus_client_next - continue authentication */ + +static int xsasl_cyrus_client_next(XSASL_CLIENT *xp, const char *server_reply, + VSTRING *client_reply) +{ + const char *myname = "xsasl_cyrus_client_next"; + XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) xp; + unsigned enc_length; + unsigned enc_length_out; + CLIENTOUT_TYPE clientout; + unsigned clientoutlen; + unsigned serverinlen; + int sasl_status; + + /* + * Process a server challenge. + */ + serverinlen = strlen(server_reply); + VSTRING_RESET(client->decoded); /* Fix 200512 */ + VSTRING_SPACE(client->decoded, serverinlen); + if ((sasl_status = SASL_DECODE64(server_reply, serverinlen, + STR(client->decoded), + vstring_avail(client->decoded), + &enc_length)) != SASL_OK) { + vstring_strcpy(client_reply, xsasl_cyrus_strerror(sasl_status)); + return (XSASL_AUTH_FORM); + } + if (msg_verbose) + msg_info("%s: decoded challenge: %.*s", + myname, (int) enc_length, STR(client->decoded)); + sasl_status = sasl_client_step(client->sasl_conn, STR(client->decoded), + enc_length, NO_SASL_INTERACTION, + &clientout, &clientoutlen); + if (sasl_status != SASL_OK && sasl_status != SASL_CONTINUE) { + vstring_strcpy(client_reply, xsasl_cyrus_strerror(sasl_status)); + return (XSASL_AUTH_FAIL); + } + + /* + * Send a client response. + */ + if (clientoutlen > 0) { + if (msg_verbose) + msg_info("%s: uncoded client response %.*s", + myname, (int) clientoutlen, clientout); + enc_length = ENCODE64_LENGTH(clientoutlen) + 1; + VSTRING_RESET(client_reply); /* Fix 200512 */ + VSTRING_SPACE(client_reply, enc_length); + if ((sasl_status = sasl_encode64(clientout, clientoutlen, + STR(client_reply), + vstring_avail(client_reply), + &enc_length_out)) != SASL_OK) + msg_panic("%s: sasl_encode64 botch: %s", + myname, xsasl_cyrus_strerror(sasl_status)); +#if SASL_VERSION_MAJOR < 2 + /* SASL version 1 doesn't free memory that it allocates. */ + free(clientout); +#endif + } else { + /* XXX Can't happen. */ + vstring_strcpy(client_reply, ""); + } + return (XSASL_AUTH_OK); +} + +/* xsasl_cyrus_client_free - per-session cleanup */ + +void xsasl_cyrus_client_free(XSASL_CLIENT *xp) +{ + XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) xp; + + if (client->username) + myfree(client->username); + if (client->password) + myfree(client->password); + if (client->sasl_conn) + sasl_dispose(&client->sasl_conn); + myfree((void *) client->callbacks); + vstring_free(client->decoded); + myfree((void *) client); +} + +#endif diff --git a/src/xsasl/xsasl_cyrus_common.h b/src/xsasl/xsasl_cyrus_common.h new file mode 100644 index 0000000..5447378 --- /dev/null +++ b/src/xsasl/xsasl_cyrus_common.h @@ -0,0 +1,39 @@ +#ifndef _CYRUS_COMMON_H_INCLUDED_ +#define _CYRUS_COMMON_H_INCLUDED_ + +/*++ +/* NAME +/* cyrus_common 3h +/* SUMMARY +/* Cyrus SASL plug-in helpers +/* SYNOPSIS +/* #include <cyrus_common.h> +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +#if defined(USE_SASL_AUTH) && defined(USE_CYRUS_SASL) + +#define NO_SASL_LANGLIST ((const char *) 0) +#define NO_SASL_OUTLANG ((const char **) 0) +#define xsasl_cyrus_strerror(status) \ + sasl_errstring((status), NO_SASL_LANGLIST, NO_SASL_OUTLANG) +extern int xsasl_cyrus_log(void *, int, const char *); +extern int xsasl_cyrus_security_parse_opts(const char *); + +#endif + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/xsasl/xsasl_cyrus_log.c b/src/xsasl/xsasl_cyrus_log.c new file mode 100644 index 0000000..7bf25c3 --- /dev/null +++ b/src/xsasl/xsasl_cyrus_log.c @@ -0,0 +1,104 @@ +/*++ +/* NAME +/* xsasl_cyrus_log 3 +/* SUMMARY +/* Cyrus SASL logging call-back routine +/* SYNOPSIS +/* #include <xsasl_cyrus_common.h> +/* +/* int xsasl_cyrus_log(context, priority, text) +/* void *context; +/* int priority; +/* const char *text; +/* DESCRIPTION +/* xsasl_cyrus_log() logs a Cyrus message. +/* DIAGNOSTICS: +/* Fatal: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> + +/* Utility library. */ + +#include <msg.h> + +/* Application-specific */ + +#include <xsasl_cyrus_common.h> + +#if defined(USE_SASL_AUTH) && defined(USE_CYRUS_SASL) + +#include <sasl.h> +#include <saslutil.h> + +/* xsasl_cyrus_log - logging callback */ + +int xsasl_cyrus_log(void *unused_context, int priority, + const char *message) +{ + switch (priority) { + case SASL_LOG_ERR: /* unusual errors */ +#ifdef SASL_LOG_WARN /* non-fatal warnings (Cyrus-SASL v2) */ + case SASL_LOG_WARN: +#endif +#ifdef SASL_LOG_WARNING /* non-fatal warnings (Cyrus-SASL v1) */ + case SASL_LOG_WARNING: +#endif + msg_warn("SASL authentication problem: %s", message); + break; +#ifdef SASL_LOG_INFO + case SASL_LOG_INFO: /* other info (Cyrus-SASL v1) */ + if (msg_verbose) + msg_info("SASL authentication info: %s", message); + break; +#endif +#ifdef SASL_LOG_NOTE + case SASL_LOG_NOTE: /* other info (Cyrus-SASL v2) */ + if (msg_verbose) + msg_info("SASL authentication info: %s", message); + break; +#endif +#ifdef SASL_LOG_FAIL + case SASL_LOG_FAIL: /* authentication failures + * (Cyrus-SASL v2) */ + msg_warn("SASL authentication failure: %s", message); + break; +#endif +#ifdef SASL_LOG_DEBUG + case SASL_LOG_DEBUG: /* more verbose than LOG_NOTE + * (Cyrus-SASL v2) */ + if (msg_verbose > 1) + msg_info("SASL authentication debug: %s", message); + break; +#endif +#ifdef SASL_LOG_TRACE + case SASL_LOG_TRACE: /* traces of internal + * protocols (Cyrus-SASL v2) */ + if (msg_verbose > 1) + msg_info("SASL authentication trace: %s", message); + break; +#endif +#ifdef SASL_LOG_PASS + case SASL_LOG_PASS: /* traces of internal + * protocols, including + * passwords (Cyrus-SASL v2) */ + if (msg_verbose > 1) + msg_info("SASL authentication pass: %s", message); + break; +#endif + } + return (SASL_OK); +} + +#endif diff --git a/src/xsasl/xsasl_cyrus_security.c b/src/xsasl/xsasl_cyrus_security.c new file mode 100644 index 0000000..7ca7216 --- /dev/null +++ b/src/xsasl/xsasl_cyrus_security.c @@ -0,0 +1,87 @@ +/*++ +/* NAME +/* xsasl_cyrus_security 3 +/* SUMMARY +/* convert Cyrus SASL security properties to bit mask +/* SYNOPSIS +/* #include <xsasl_cyrus_common.h> +/* +/* int xsasl_cyrus_security_parse_opts(properties) +/* const char *properties; +/* DESCRIPTION +/* xsasl_cyrus_security_parse_opts() converts a list of security +/* properties to a bit mask. The result is zero in case of error. +/* +/* Arguments: +/* .IP properties +/* A comma or space separated list of zero or more of the +/* following: +/* .RS +/* .IP noplaintext +/* Disallow authentication methods that use plaintext passwords. +/* .IP noactive +/* Disallow authentication methods that are vulnerable to +/* non-dictionary active attacks. +/* .IP nodictionary +/* Disallow authentication methods that are vulnerable to +/* passive dictionary attack. +/* .IP forward_secrecy +/* Require forward secrecy between sessions. +/* .IP noanonymous +/* Disallow anonymous logins. +/* .RE +/* DIAGNOSTICS: +/* Warning: bad input. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> + +/* Utility library. */ + +#include <name_mask.h> + +/* Application-specific. */ + +#include <xsasl_cyrus_common.h> + +#if defined(USE_SASL_AUTH) && defined(USE_CYRUS_SASL) + +#include <sasl.h> + + /* + * SASL Security options. + */ +static const NAME_MASK xsasl_cyrus_sec_mask[] = { + "noplaintext", SASL_SEC_NOPLAINTEXT, + "noactive", SASL_SEC_NOACTIVE, + "nodictionary", SASL_SEC_NODICTIONARY, +#ifdef SASL_SEC_FORWARD_SECRECY + "forward_secrecy", SASL_SEC_FORWARD_SECRECY, +#endif + "noanonymous", SASL_SEC_NOANONYMOUS, +#if SASL_VERSION_MAJOR >= 2 + "mutual_auth", SASL_SEC_MUTUAL_AUTH, +#endif + 0, +}; + +/* xsasl_cyrus_security - parse security options */ + +int xsasl_cyrus_security_parse_opts(const char *sasl_opts_val) +{ + return (name_mask_opt("SASL security options", xsasl_cyrus_sec_mask, + sasl_opts_val, NAME_MASK_RETURN)); +} + +#endif diff --git a/src/xsasl/xsasl_cyrus_server.c b/src/xsasl/xsasl_cyrus_server.c new file mode 100644 index 0000000..89e1fc9 --- /dev/null +++ b/src/xsasl/xsasl_cyrus_server.c @@ -0,0 +1,641 @@ +/*++ +/* NAME +/* xsasl_cyrus_server 3 +/* SUMMARY +/* Cyrus SASL server-side plug-in +/* SYNOPSIS +/* #include <xsasl_cyrus_server.h> +/* +/* XSASL_SERVER_IMPL *xsasl_cyrus_server_init(server_type, path_info) +/* const char *server_type; +/* const char *path_info; +/* DESCRIPTION +/* This module implements the Cyrus SASL server-side authentication +/* plug-in. +/* +/* xsasl_cyrus_server_init() initializes the Cyrus SASL library and +/* returns an implementation handle that can be used to generate +/* SASL server instances. +/* +/* Arguments: +/* .IP server_type +/* The server type (cyrus). This argument is ignored, but it +/* could be used when one implementation provides multiple +/* variants. +/* .IP path_info +/* The base name of the SASL server configuration file (example: +/* smtpd becomes /usr/lib/sasl2/smtpd.conf). +/* DIAGNOSTICS +/* Fatal: out of memory. +/* +/* Panic: interface violation. +/* +/* Other: the routines log a warning and return an error result +/* as specified in xsasl_server(3). +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Initial implementation by: +/* Till Franke +/* SuSE Rhein/Main AG +/* 65760 Eschborn, Germany +/* +/* Adopted by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <stdlib.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <name_mask.h> +#include <stringops.h> + +/* Global library. */ + +#include <mail_params.h> + +/* Application-specific. */ + +#include <xsasl.h> +#include <xsasl_cyrus.h> +#include <xsasl_cyrus_common.h> + +#if defined(USE_SASL_AUTH) && defined(USE_CYRUS_SASL) + +#include <sasl.h> +#include <saslutil.h> + +/* + * Silly little macros. + */ +#define STR(s) vstring_str(s) + + /* + * Macros to handle API differences between SASLv1 and SASLv2. Specifics: + * + * The SASL_LOG_* constants were renamed in SASLv2. + * + * SASLv2's sasl_server_new takes two new parameters to specify local and + * remote IP addresses for auth mechs that use them. + * + * SASLv2's sasl_server_start and sasl_server_step no longer have the errstr + * parameter. + * + * SASLv2's sasl_decode64 function takes an extra parameter for the length of + * the output buffer. + * + * The other major change is that SASLv2 now takes more responsibility for + * deallocating memory that it allocates internally. Thus, some of the + * function parameters are now 'const', to make sure we don't try to free + * them too. This is dealt with in the code later on. + */ + +#if SASL_VERSION_MAJOR < 2 +/* SASL version 1.x */ +#define SASL_SERVER_NEW(srv, fqdn, rlm, lport, rport, cb, secflags, pconn) \ + sasl_server_new(srv, fqdn, rlm, cb, secflags, pconn) +#define SASL_SERVER_START(conn, mech, clin, clinlen, srvout, srvoutlen, err) \ + sasl_server_start(conn, mech, clin, clinlen, srvout, srvoutlen, err) +#define SASL_SERVER_STEP(conn, clin, clinlen, srvout, srvoutlen, err) \ + sasl_server_step(conn, clin, clinlen, srvout, srvoutlen, err) +#define SASL_DECODE64(in, inlen, out, outmaxlen, outlen) \ + sasl_decode64(in, inlen, out, outlen) +typedef char *MECHANISM_TYPE; +typedef unsigned MECHANISM_COUNT_TYPE; +typedef char *SERVEROUT_TYPE; +typedef void *VOID_SERVEROUT_TYPE; + +#endif + +#if SASL_VERSION_MAJOR >= 2 +/* SASL version > 2.x */ +#define SASL_SERVER_NEW(srv, fqdn, rlm, lport, rport, cb, secflags, pconn) \ + sasl_server_new(srv, fqdn, rlm, lport, rport, cb, secflags, pconn) +#define SASL_SERVER_START(conn, mech, clin, clinlen, srvout, srvoutlen, err) \ + sasl_server_start(conn, mech, clin, clinlen, srvout, srvoutlen) +#define SASL_SERVER_STEP(conn, clin, clinlen, srvout, srvoutlen, err) \ + sasl_server_step(conn, clin, clinlen, srvout, srvoutlen) +#define SASL_DECODE64(in, inlen, out, outmaxlen, outlen) \ + sasl_decode64(in, inlen, out, outmaxlen, outlen) +typedef const char *MECHANISM_TYPE; +typedef int MECHANISM_COUNT_TYPE; +typedef const char *SERVEROUT_TYPE; +typedef const void *VOID_SERVEROUT_TYPE; + +#endif + +#ifndef NO_IP_CYRUS_SASL_AUTH +#define USE_IP_CYRUS_SASL_AUTH +#endif + + /* + * The XSASL_CYRUS_SERVER object is derived from the generic XSASL_SERVER + * object. + */ +typedef struct { + XSASL_SERVER xsasl; /* generic members, must be first */ + VSTREAM *stream; /* client-server connection */ + sasl_conn_t *sasl_conn; /* SASL context */ + VSTRING *decoded; /* decoded challenge or response */ + char *username; /* authenticated user */ + char *mechanism_list; /* applicable mechanisms */ +} XSASL_CYRUS_SERVER; + + /* + * Forward declarations. + */ +static void xsasl_cyrus_server_done(XSASL_SERVER_IMPL *); +static XSASL_SERVER *xsasl_cyrus_server_create(XSASL_SERVER_IMPL *, + XSASL_SERVER_CREATE_ARGS *); +static void xsasl_cyrus_server_free(XSASL_SERVER *); +static int xsasl_cyrus_server_first(XSASL_SERVER *, const char *, + const char *, VSTRING *); +static int xsasl_cyrus_server_next(XSASL_SERVER *, const char *, VSTRING *); +static int xsasl_cyrus_server_set_security(XSASL_SERVER *, const char *); +static const char *xsasl_cyrus_server_get_mechanism_list(XSASL_SERVER *); +static const char *xsasl_cyrus_server_get_username(XSASL_SERVER *); + + /* + * SASL callback interface structure. These call-backs have no per-session + * context. + */ +#define NO_CALLBACK_CONTEXT 0 + +static sasl_callback_t callbacks[] = { + {SASL_CB_LOG, (XSASL_CYRUS_CB) &xsasl_cyrus_log, NO_CALLBACK_CONTEXT}, + {SASL_CB_LIST_END, 0, 0} +}; + +/* xsasl_cyrus_server_init - create implementation handle */ + +XSASL_SERVER_IMPL *xsasl_cyrus_server_init(const char *unused_server_type, + const char *path_info) +{ + const char *myname = "xsasl_cyrus_server_init"; + XSASL_SERVER_IMPL *xp; + int sasl_status; + +#if SASL_VERSION_MAJOR >= 2 && (SASL_VERSION_MINOR >= 2 \ + || (SASL_VERSION_MINOR == 1 && SASL_VERSION_STEP >= 19)) + int sasl_major; + int sasl_minor; + int sasl_step; + + /* + * DLL hell guard. + */ + sasl_version_info((const char **) 0, (const char **) 0, + &sasl_major, &sasl_minor, + &sasl_step, (int *) 0); + if (sasl_major != SASL_VERSION_MAJOR +#if 0 + || sasl_minor != SASL_VERSION_MINOR + || sasl_step != SASL_VERSION_STEP +#endif + ) { + msg_warn("incorrect SASL library version. " + "Postfix was built with include files from version %d.%d.%d, " + "but the run-time library version is %d.%d.%d", + SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP, + sasl_major, sasl_minor, sasl_step); + return (0); + } +#endif + + if (*var_cyrus_conf_path) { +#ifdef SASL_PATH_TYPE_CONFIG /* Cyrus SASL 2.1.22 */ + if (sasl_set_path(SASL_PATH_TYPE_CONFIG, + var_cyrus_conf_path) != SASL_OK) + msg_warn("failed to set Cyrus SASL configuration path: \"%s\"", + var_cyrus_conf_path); +#else + msg_warn("%s is not empty, but setting the Cyrus SASL configuration " + "path is not supported with SASL library version %d.%d.%d", + VAR_CYRUS_CONF_PATH, SASL_VERSION_MAJOR, + SASL_VERSION_MINOR, SASL_VERSION_STEP); +#endif + } + + /* + * Initialize the library: load SASL plug-in routines, etc. + */ + if (msg_verbose) + msg_info("%s: SASL config file is %s.conf", myname, path_info); + if ((sasl_status = sasl_server_init(callbacks, path_info)) != SASL_OK) { + msg_warn("SASL per-process initialization failed: %s", + xsasl_cyrus_strerror(sasl_status)); + return (0); + } + + /* + * Return a generic XSASL_SERVER_IMPL object. We don't need to extend it + * with our own methods or data. + */ + xp = (XSASL_SERVER_IMPL *) mymalloc(sizeof(*xp)); + xp->create = xsasl_cyrus_server_create; + xp->done = xsasl_cyrus_server_done; + return (xp); +} + +/* xsasl_cyrus_server_done - dispose of implementation */ + +static void xsasl_cyrus_server_done(XSASL_SERVER_IMPL *impl) +{ + myfree((void *) impl); + sasl_done(); +} + +/* xsasl_cyrus_server_create - create server instance */ + +static XSASL_SERVER *xsasl_cyrus_server_create(XSASL_SERVER_IMPL *unused_impl, + XSASL_SERVER_CREATE_ARGS *args) +{ + const char *myname = "xsasl_cyrus_server_create"; + char *server_addr_port = 0; + char *client_addr_port = 0; + sasl_conn_t *sasl_conn = 0; + XSASL_CYRUS_SERVER *server = 0; + int sasl_status; + + if (msg_verbose) + msg_info("%s: SASL service=%s, realm=%s", + myname, args->service, args->user_realm ? + args->user_realm : "(null)"); + + /* + * The optimizer will eliminate code duplication and/or dead code. + */ +#define XSASL_CYRUS_SERVER_CREATE_ERROR_RETURN(x) \ + do { \ + if (server) { \ + xsasl_cyrus_server_free(&server->xsasl); \ + } else { \ + if (sasl_conn) \ + sasl_dispose(&sasl_conn); \ + } \ + XSASL_CYRUS_SERVER_CREATE_RETURN(x); \ + } while (0) + +#define XSASL_CYRUS_SERVER_CREATE_RETURN(x) \ + do { \ + if (server_addr_port) \ + myfree(server_addr_port); \ + if (client_addr_port) \ + myfree(client_addr_port); \ + return (x); \ + } while (0) + + /* + * Set up a new server context. + */ +#define NO_SECURITY_LAYERS (0) +#define NO_SESSION_CALLBACKS ((sasl_callback_t *) 0) +#define NO_AUTH_REALM ((char *) 0) + +#if SASL_VERSION_MAJOR >= 2 && defined(USE_IP_CYRUS_SASL_AUTH) + + /* + * Get IP address and port of local and remote endpoints for SASL. Some + * implementation supports "[ipv6addr]:port" and "ipv4addr:port" (e.g., + * https://illumos.org/man/3sasl/sasl_server_new), They still support the + * historical "address;port" syntax, so we stick with that for now. + */ + server_addr_port = (*args->server_addr && *args->server_port ? + concatenate(args->server_addr, ";", + args->server_port, (char *) 0) : 0); + client_addr_port = (*args->client_addr && *args->client_port ? + concatenate(args->client_addr, ";", + args->client_port, (char *) 0) : 0); +#else + + /* + * Don't give any IP address information to SASL. + */ +#endif + + if ((sasl_status = + SASL_SERVER_NEW(args->service, var_myhostname, + args->user_realm ? args->user_realm : NO_AUTH_REALM, + server_addr_port, client_addr_port, + NO_SESSION_CALLBACKS, NO_SECURITY_LAYERS, + &sasl_conn)) != SASL_OK) { + msg_warn("SASL per-connection server initialization: %s", + xsasl_cyrus_strerror(sasl_status)); + XSASL_CYRUS_SERVER_CREATE_ERROR_RETURN(0); + } + + /* + * Extend the XSASL_SERVER object with our own data. We use long-lived + * conversion buffers rather than local variables to avoid memory leaks + * in case of read/write timeout or I/O error. + */ + server = (XSASL_CYRUS_SERVER *) mymalloc(sizeof(*server)); + server->xsasl.free = xsasl_cyrus_server_free; + server->xsasl.first = xsasl_cyrus_server_first; + server->xsasl.next = xsasl_cyrus_server_next; + server->xsasl.get_mechanism_list = xsasl_cyrus_server_get_mechanism_list; + server->xsasl.get_username = xsasl_cyrus_server_get_username; + server->stream = args->stream; + server->sasl_conn = sasl_conn; + server->decoded = vstring_alloc(20); + server->username = 0; + server->mechanism_list = 0; + + if (xsasl_cyrus_server_set_security(&server->xsasl, args->security_options) + != XSASL_AUTH_OK) + XSASL_CYRUS_SERVER_CREATE_ERROR_RETURN(0); + + XSASL_CYRUS_SERVER_CREATE_RETURN(&server->xsasl); +} + +/* xsasl_cyrus_server_set_security - set security properties */ + +static int xsasl_cyrus_server_set_security(XSASL_SERVER *xp, + const char *sasl_opts_val) +{ + XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; + sasl_security_properties_t sec_props; + int sasl_status; + + /* + * Security options. Some information can be found in the sasl.h include + * file. + */ + memset(&sec_props, 0, sizeof(sec_props)); + sec_props.min_ssf = 0; + sec_props.max_ssf = 0; /* don't allow real SASL + * security layer */ + if (*sasl_opts_val == 0) { + sec_props.security_flags = 0; + } else { + sec_props.security_flags = + xsasl_cyrus_security_parse_opts(sasl_opts_val); + if (sec_props.security_flags == 0) { + msg_warn("bad per-session SASL security properties"); + return (XSASL_AUTH_FAIL); + } + } + sec_props.maxbufsize = 0; + sec_props.property_names = 0; + sec_props.property_values = 0; + + if ((sasl_status = sasl_setprop(server->sasl_conn, SASL_SEC_PROPS, + &sec_props)) != SASL_OK) { + msg_warn("SASL per-connection security setup; %s", + xsasl_cyrus_strerror(sasl_status)); + return (XSASL_AUTH_FAIL); + } + return (XSASL_AUTH_OK); +} + +/* xsasl_cyrus_server_get_mechanism_list - get available mechanisms */ + +static const char *xsasl_cyrus_server_get_mechanism_list(XSASL_SERVER *xp) +{ + const char *myname = "xsasl_cyrus_server_get_mechanism_list"; + XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; + MECHANISM_TYPE mechanism_list; + MECHANISM_COUNT_TYPE mechanism_count; + int sasl_status; + + /* + * Get the list of authentication mechanisms. + */ +#define UNSUPPORTED_USER ((char *) 0) +#define IGNORE_MECHANISM_LEN ((unsigned *) 0) + + if ((sasl_status = sasl_listmech(server->sasl_conn, UNSUPPORTED_USER, + "", " ", "", + &mechanism_list, + IGNORE_MECHANISM_LEN, + &mechanism_count)) != SASL_OK) { + msg_warn("%s: %s", myname, xsasl_cyrus_strerror(sasl_status)); + return (0); + } + if (mechanism_count <= 0) { + msg_warn("%s: no applicable SASL mechanisms", myname); + return (0); + } + server->mechanism_list = mystrdup(mechanism_list); +#if SASL_VERSION_MAJOR < 2 + /* SASL version 1 doesn't free memory that it allocates. */ + free(mechanism_list); +#endif + return (server->mechanism_list); +} + +/* xsasl_cyrus_server_free - destroy server instance */ + +static void xsasl_cyrus_server_free(XSASL_SERVER *xp) +{ + XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; + + sasl_dispose(&server->sasl_conn); + vstring_free(server->decoded); + if (server->username) + myfree(server->username); + if (server->mechanism_list) + myfree(server->mechanism_list); + myfree((void *) server); +} + +/* xsasl_cyrus_server_auth_response - encode server first/next response */ + +static int xsasl_cyrus_server_auth_response(int sasl_status, + SERVEROUT_TYPE serverout, + unsigned serveroutlen, + VSTRING *reply) +{ + const char *myname = "xsasl_cyrus_server_auth_response"; + unsigned enc_length; + unsigned enc_length_out; + + /* + * Encode the server first/next non-error response; otherwise return the + * unencoded error text that corresponds to the SASL error status. + * + * Regarding the hairy expression below: output from sasl_encode64() comes + * in multiples of four bytes for each triple of input bytes, plus four + * bytes for any incomplete last triple, plus one byte for the null + * terminator. + */ + if (sasl_status == SASL_OK) { + vstring_strcpy(reply, ""); + return (XSASL_AUTH_DONE); + } else if (sasl_status == SASL_CONTINUE) { + if (msg_verbose) + msg_info("%s: uncoded server challenge: %.*s", + myname, (int) serveroutlen, serverout); + enc_length = ((serveroutlen + 2) / 3) * 4 + 1; + VSTRING_RESET(reply); /* Fix 200512 */ + VSTRING_SPACE(reply, enc_length); + if ((sasl_status = sasl_encode64(serverout, serveroutlen, + STR(reply), vstring_avail(reply), + &enc_length_out)) != SASL_OK) + msg_panic("%s: sasl_encode64 botch: %s", + myname, xsasl_cyrus_strerror(sasl_status)); + return (XSASL_AUTH_MORE); + } else { + if (sasl_status == SASL_NOUSER) /* privacy */ + sasl_status = SASL_BADAUTH; + vstring_strcpy(reply, xsasl_cyrus_strerror(sasl_status)); + switch (sasl_status) { + case SASL_FAIL: + case SASL_NOMEM: + case SASL_TRYAGAIN: + case SASL_UNAVAIL: + return XSASL_AUTH_TEMP; + default: + return (XSASL_AUTH_FAIL); + } + } +} + +/* xsasl_cyrus_server_first - per-session authentication */ + +int xsasl_cyrus_server_first(XSASL_SERVER *xp, const char *sasl_method, + const char *init_response, VSTRING *reply) +{ + const char *myname = "xsasl_cyrus_server_first"; + XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; + char *dec_buffer; + unsigned dec_length; + unsigned reply_len; + unsigned serveroutlen; + int sasl_status; + SERVEROUT_TYPE serverout = 0; + int xsasl_status; + +#if SASL_VERSION_MAJOR < 2 + const char *errstr = 0; + +#endif + +#define IFELSE(e1,e2,e3) ((e1) ? (e2) : (e3)) + + if (msg_verbose) + msg_info("%s: sasl_method %s%s%s", myname, sasl_method, + IFELSE(init_response, ", init_response ", ""), + IFELSE(init_response, init_response, "")); + + /* + * SASL authentication protocol start-up. Process any initial client + * response that was sent along in the AUTH command. + */ + if (init_response) { + reply_len = strlen(init_response); + VSTRING_RESET(server->decoded); /* Fix 200512 */ + VSTRING_SPACE(server->decoded, reply_len); + if ((sasl_status = SASL_DECODE64(init_response, reply_len, + dec_buffer = STR(server->decoded), + vstring_avail(server->decoded), + &dec_length)) != SASL_OK) { + vstring_strcpy(reply, xsasl_cyrus_strerror(sasl_status)); + return (XSASL_AUTH_FORM); + } + if (msg_verbose) + msg_info("%s: decoded initial response %s", myname, dec_buffer); + } else { + dec_buffer = 0; + dec_length = 0; + } + sasl_status = SASL_SERVER_START(server->sasl_conn, sasl_method, dec_buffer, + dec_length, &serverout, + &serveroutlen, &errstr); + xsasl_status = xsasl_cyrus_server_auth_response(sasl_status, serverout, + serveroutlen, reply); +#if SASL_VERSION_MAJOR < 2 + /* SASL version 1 doesn't free memory that it allocates. */ + free(serverout); +#endif + return (xsasl_status); +} + +/* xsasl_cyrus_server_next - continue authentication */ + +static int xsasl_cyrus_server_next(XSASL_SERVER *xp, const char *request, + VSTRING *reply) +{ + const char *myname = "xsasl_cyrus_server_next"; + XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; + unsigned dec_length; + unsigned request_len; + unsigned serveroutlen; + int sasl_status; + SERVEROUT_TYPE serverout = 0; + int xsasl_status; + +#if SASL_VERSION_MAJOR < 2 + const char *errstr = 0; + +#endif + + request_len = strlen(request); + VSTRING_RESET(server->decoded); /* Fix 200512 */ + VSTRING_SPACE(server->decoded, request_len); + if ((sasl_status = SASL_DECODE64(request, request_len, + STR(server->decoded), + vstring_avail(server->decoded), + &dec_length)) != SASL_OK) { + vstring_strcpy(reply, xsasl_cyrus_strerror(sasl_status)); + return (XSASL_AUTH_FORM); + } + if (msg_verbose) + msg_info("%s: decoded response: %.*s", + myname, (int) dec_length, STR(server->decoded)); + sasl_status = SASL_SERVER_STEP(server->sasl_conn, STR(server->decoded), + dec_length, &serverout, + &serveroutlen, &errstr); + xsasl_status = xsasl_cyrus_server_auth_response(sasl_status, serverout, + serveroutlen, reply); +#if SASL_VERSION_MAJOR < 2 + /* SASL version 1 doesn't free memory that it allocates. */ + free(serverout); +#endif + return (xsasl_status); +} + +/* xsasl_cyrus_server_get_username - get authenticated username */ + +static const char *xsasl_cyrus_server_get_username(XSASL_SERVER *xp) +{ + const char *myname = "xsasl_cyrus_server_get_username"; + XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; + VOID_SERVEROUT_TYPE serverout = 0; + int sasl_status; + + /* + * XXX Do not free(serverout). + */ + sasl_status = sasl_getprop(server->sasl_conn, SASL_USERNAME, &serverout); + if (sasl_status != SASL_OK || serverout == 0) { + msg_warn("%s: sasl_getprop SASL_USERNAME botch: %s", + myname, xsasl_cyrus_strerror(sasl_status)); + return (0); + } + if (server->username) + myfree(server->username); + server->username = mystrdup(serverout); + printable(server->username, '?'); + return (server->username); +} + +#endif diff --git a/src/xsasl/xsasl_dovecot.h b/src/xsasl/xsasl_dovecot.h new file mode 100644 index 0000000..f99850e --- /dev/null +++ b/src/xsasl/xsasl_dovecot.h @@ -0,0 +1,41 @@ +#ifndef _XSASL_DOVECOT_H_INCLUDED_ +#define _XSASL_DOVECOT_H_INCLUDED_ + +/*++ +/* NAME +/* xsasl_dovecot 3h +/* SUMMARY +/* Dovecot SASL plug-in +/* SYNOPSIS +/* #include <xsasl_dovecot.h> +/* DESCRIPTION +/* .nf + + /* + * XSASL library. + */ +#include <xsasl.h> + +#if defined(USE_SASL_AUTH) + + /* + * SASL protocol interface + */ +#define XSASL_TYPE_DOVECOT "dovecot" + +extern XSASL_SERVER_IMPL *xsasl_dovecot_server_init(const char *, const char *); + +#endif + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/xsasl/xsasl_dovecot_server.c b/src/xsasl/xsasl_dovecot_server.c new file mode 100644 index 0000000..601f787 --- /dev/null +++ b/src/xsasl/xsasl_dovecot_server.c @@ -0,0 +1,746 @@ +/*++ +/* NAME +/* xsasl_dovecot_server 3 +/* SUMMARY +/* Dovecot SASL server-side plug-in +/* SYNOPSIS +/* XSASL_SERVER_IMPL *xsasl_dovecot_server_init(server_type, appl_name) +/* const char *server_type; +/* const char *appl_name; +/* DESCRIPTION +/* This module implements the Dovecot SASL server-side authentication +/* plug-in. +/* +/* .IP server_type +/* The plug-in type that was specified to xsasl_server_init(). +/* The argument is ignored, because the Dovecot plug-in +/* implements only one plug-in type. +/* .IP path_info +/* The location of the Dovecot authentication server's UNIX-domain +/* socket. Note: the Dovecot plug-in uses late binding, therefore +/* all connect operations are done with Postfix privileges. +/* DIAGNOSTICS +/* Fatal: out of memory. +/* +/* Panic: interface violation. +/* +/* Other: the routines log a warning and return an error result +/* as specified in xsasl_server(3). +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Initial implementation by: +/* Timo Sirainen +/* Procontrol +/* Finland +/* +/* Adopted by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <connect.h> +#include <split_at.h> +#include <stringops.h> +#include <vstream.h> +#include <vstring_vstream.h> +#include <name_mask.h> +#include <argv.h> +#include <myaddrinfo.h> + +/* Global library. */ + +#include <mail_params.h> + +/* Application-specific. */ + +#include <xsasl.h> +#include <xsasl_dovecot.h> + +#ifdef USE_SASL_AUTH + +/* Major version changes are not backwards compatible, + minor version numbers can be ignored. */ +#define AUTH_PROTOCOL_MAJOR_VERSION 1 +#define AUTH_PROTOCOL_MINOR_VERSION 0 + + /* + * Encorce read/write time limits, so that we can produce accurate + * diagnostics instead of getting killed by the watchdog timer. + */ +#define AUTH_TIMEOUT 10 + + /* + * Security property bitmasks. + */ +#define SEC_PROPS_NOPLAINTEXT (1 << 0) +#define SEC_PROPS_NOACTIVE (1 << 1) +#define SEC_PROPS_NODICTIONARY (1 << 2) +#define SEC_PROPS_NOANONYMOUS (1 << 3) +#define SEC_PROPS_FWD_SECRECY (1 << 4) +#define SEC_PROPS_MUTUAL_AUTH (1 << 5) +#define SEC_PROPS_PRIVATE (1 << 6) + +#define SEC_PROPS_POS_MASK (SEC_PROPS_MUTUAL_AUTH | SEC_PROPS_FWD_SECRECY) +#define SEC_PROPS_NEG_MASK (SEC_PROPS_NOPLAINTEXT | SEC_PROPS_NOACTIVE | \ + SEC_PROPS_NODICTIONARY | SEC_PROPS_NOANONYMOUS) + + /* + * Security properties as specified in the Postfix main.cf file. + */ +static const NAME_MASK xsasl_dovecot_conf_sec_props[] = { + "noplaintext", SEC_PROPS_NOPLAINTEXT, + "noactive", SEC_PROPS_NOACTIVE, + "nodictionary", SEC_PROPS_NODICTIONARY, + "noanonymous", SEC_PROPS_NOANONYMOUS, + "forward_secrecy", SEC_PROPS_FWD_SECRECY, + "mutual_auth", SEC_PROPS_MUTUAL_AUTH, + 0, 0, +}; + + /* + * Security properties as specified in the Dovecot protocol. See + * http://wiki.dovecot.org/Authentication_Protocol. + */ +static const NAME_MASK xsasl_dovecot_serv_sec_props[] = { + "plaintext", SEC_PROPS_NOPLAINTEXT, + "active", SEC_PROPS_NOACTIVE, + "dictionary", SEC_PROPS_NODICTIONARY, + "anonymous", SEC_PROPS_NOANONYMOUS, + "forward-secrecy", SEC_PROPS_FWD_SECRECY, + "mutual-auth", SEC_PROPS_MUTUAL_AUTH, + "private", SEC_PROPS_PRIVATE, + 0, 0, +}; + + /* + * Class variables. + */ +typedef struct XSASL_DCSRV_MECH { + char *mech_name; /* mechanism name */ + int sec_props; /* mechanism properties */ + struct XSASL_DCSRV_MECH *next; +} XSASL_DCSRV_MECH; + +typedef struct { + XSASL_SERVER_IMPL xsasl; + VSTREAM *sasl_stream; + char *socket_path; + XSASL_DCSRV_MECH *mechanism_list; /* unfiltered mechanism list */ + unsigned int request_id_counter; +} XSASL_DOVECOT_SERVER_IMPL; + + /* + * The XSASL_DOVECOT_SERVER object is derived from the generic XSASL_SERVER + * object. + */ +typedef struct { + XSASL_SERVER xsasl; /* generic members, must be first */ + XSASL_DOVECOT_SERVER_IMPL *impl; + unsigned int last_request_id; + char *service; + char *username; /* authenticated user */ + VSTRING *sasl_line; + unsigned int sec_props; /* Postfix mechanism filter */ + int tls_flag; /* TLS enabled in this session */ + char *mechanism_list; /* filtered mechanism list */ + ARGV *mechanism_argv; /* ditto */ + char *client_addr; /* remote IP address */ + char *server_addr; /* remote IP address */ +} XSASL_DOVECOT_SERVER; + + /* + * Forward declarations. + */ +static void xsasl_dovecot_server_done(XSASL_SERVER_IMPL *); +static XSASL_SERVER *xsasl_dovecot_server_create(XSASL_SERVER_IMPL *, + XSASL_SERVER_CREATE_ARGS *); +static void xsasl_dovecot_server_free(XSASL_SERVER *); +static int xsasl_dovecot_server_first(XSASL_SERVER *, const char *, + const char *, VSTRING *); +static int xsasl_dovecot_server_next(XSASL_SERVER *, const char *, VSTRING *); +static const char *xsasl_dovecot_server_get_mechanism_list(XSASL_SERVER *); +static const char *xsasl_dovecot_server_get_username(XSASL_SERVER *); + +/* xsasl_dovecot_server_mech_append - append server mechanism entry */ + +static void xsasl_dovecot_server_mech_append(XSASL_DCSRV_MECH **mech_list, + const char *mech_name, int sec_props) +{ + XSASL_DCSRV_MECH **mpp; + XSASL_DCSRV_MECH *mp; + + for (mpp = mech_list; *mpp != 0; mpp = &mpp[0]->next) + /* void */ ; + + mp = (XSASL_DCSRV_MECH *) mymalloc(sizeof(*mp)); + mp->mech_name = mystrdup(mech_name); + mp->sec_props = sec_props; + mp->next = 0; + *mpp = mp; +} + +/* xsasl_dovecot_server_mech_free - destroy server mechanism list */ + +static void xsasl_dovecot_server_mech_free(XSASL_DCSRV_MECH *mech_list) +{ + XSASL_DCSRV_MECH *mp; + XSASL_DCSRV_MECH *next; + + for (mp = mech_list; mp != 0; mp = next) { + myfree(mp->mech_name); + next = mp->next; + myfree((void *) mp); + } +} + +/* xsasl_dovecot_server_mech_filter - filter server mechanism list */ + +static char *xsasl_dovecot_server_mech_filter(ARGV *mechanism_argv, + XSASL_DCSRV_MECH *mechanism_list, + unsigned int conf_props) +{ + const char *myname = "xsasl_dovecot_server_mech_filter"; + unsigned int pos_conf_props = (conf_props & SEC_PROPS_POS_MASK); + unsigned int neg_conf_props = (conf_props & SEC_PROPS_NEG_MASK); + VSTRING *mechanisms_str = vstring_alloc(10); + XSASL_DCSRV_MECH *mp; + + /* + * Match Postfix properties against Dovecot server properties. + */ + for (mp = mechanism_list; mp != 0; mp = mp->next) { + if ((mp->sec_props & pos_conf_props) == pos_conf_props + && (mp->sec_props & neg_conf_props) == 0) { + if (VSTRING_LEN(mechanisms_str) > 0) + VSTRING_ADDCH(mechanisms_str, ' '); + vstring_strcat(mechanisms_str, mp->mech_name); + argv_add(mechanism_argv, mp->mech_name, (char *) 0); + if (msg_verbose) + msg_info("%s: keep mechanism: %s", myname, mp->mech_name); + } else { + if (msg_verbose) + msg_info("%s: skip mechanism: %s", myname, mp->mech_name); + } + } + return (vstring_export(mechanisms_str)); +} + +/* xsasl_dovecot_server_connect - initial auth server handshake */ + +static int xsasl_dovecot_server_connect(XSASL_DOVECOT_SERVER_IMPL *xp) +{ + const char *myname = "xsasl_dovecot_server_connect"; + VSTRING *line_str; + VSTREAM *sasl_stream; + char *line, *cmd, *mech_name; + unsigned int major_version, minor_version; + int fd, success, have_mech_line; + int sec_props; + const char *path; + + if (msg_verbose) + msg_info("%s: Connecting", myname); + + /* + * Not documented, but necessary for testing. + */ + path = xp->socket_path; + if (strncmp(path, "inet:", 5) == 0) { + fd = inet_connect(path + 5, BLOCKING, AUTH_TIMEOUT); + } else { + if (strncmp(path, "unix:", 5) == 0) + path += 5; + fd = unix_connect(path, BLOCKING, AUTH_TIMEOUT); + } + if (fd < 0) { + msg_warn("SASL: Connect to %s failed: %m", xp->socket_path); + return (-1); + } + sasl_stream = vstream_fdopen(fd, O_RDWR); + vstream_control(sasl_stream, + CA_VSTREAM_CTL_PATH(xp->socket_path), + CA_VSTREAM_CTL_TIMEOUT(AUTH_TIMEOUT), + CA_VSTREAM_CTL_END); + + /* XXX Encapsulate for logging. */ + vstream_fprintf(sasl_stream, + "VERSION\t%u\t%u\n" + "CPID\t%u\n", + AUTH_PROTOCOL_MAJOR_VERSION, + AUTH_PROTOCOL_MINOR_VERSION, + (unsigned int) getpid()); + if (vstream_fflush(sasl_stream) == VSTREAM_EOF) { + msg_warn("SASL: Couldn't send handshake: %m"); + return (-1); + } + success = 0; + have_mech_line = 0; + line_str = vstring_alloc(256); + /* XXX Encapsulate for logging. */ + while (vstring_get_nonl(line_str, sasl_stream) != VSTREAM_EOF) { + line = vstring_str(line_str); + + if (msg_verbose) + msg_info("%s: auth reply: %s", myname, line); + + cmd = line; + line = split_at(line, '\t'); + + if (strcmp(cmd, "VERSION") == 0) { + if (sscanf(line, "%u\t%u", &major_version, &minor_version) != 2) { + msg_warn("SASL: Protocol version error"); + break; + } + if (major_version != AUTH_PROTOCOL_MAJOR_VERSION) { + /* Major version is different from ours. */ + msg_warn("SASL: Protocol version mismatch (%d vs. %d)", + major_version, AUTH_PROTOCOL_MAJOR_VERSION); + break; + } + } else if (strcmp(cmd, "MECH") == 0 && line != NULL) { + mech_name = line; + have_mech_line = 1; + line = split_at(line, '\t'); + if (line != 0) { + sec_props = + name_mask_delim_opt(myname, + xsasl_dovecot_serv_sec_props, + line, "\t", + NAME_MASK_ANY_CASE | NAME_MASK_IGNORE); + if ((sec_props & SEC_PROPS_PRIVATE) != 0) + continue; + } else + sec_props = 0; + xsasl_dovecot_server_mech_append(&xp->mechanism_list, mech_name, + sec_props); + } else if (strcmp(cmd, "SPID") == 0) { + + /* + * Unfortunately the auth protocol handshake wasn't designed well + * to differentiate between auth-client/userdb/master. + * auth-userdb and auth-master send VERSION + SPID lines only and + * nothing afterwards, while auth-client sends VERSION + MECH + + * SPID + CUID + more. The simplest way that we can determine if + * we've connected to the correct socket is to see if MECH line + * exists or not (alternatively we'd have to have a small timeout + * after SPID to see if CUID is sent or not). + */ + if (!have_mech_line) { + msg_warn("SASL: Connected to wrong auth socket (auth-master instead of auth-client)"); + break; + } + } else if (strcmp(cmd, "DONE") == 0) { + /* Handshake finished. */ + success = 1; + break; + } else { + /* ignore any unknown commands */ + } + } + vstring_free(line_str); + + if (!success) { + /* handshake failed */ + (void) vstream_fclose(sasl_stream); + return (-1); + } + xp->sasl_stream = sasl_stream; + return (0); +} + +/* xsasl_dovecot_server_disconnect - dispose of server connection state */ + +static void xsasl_dovecot_server_disconnect(XSASL_DOVECOT_SERVER_IMPL *xp) +{ + if (xp->sasl_stream) { + (void) vstream_fclose(xp->sasl_stream); + xp->sasl_stream = 0; + } + if (xp->mechanism_list) { + xsasl_dovecot_server_mech_free(xp->mechanism_list); + xp->mechanism_list = 0; + } +} + +/* xsasl_dovecot_server_init - create implementation handle */ + +XSASL_SERVER_IMPL *xsasl_dovecot_server_init(const char *server_type, + const char *path_info) +{ + XSASL_DOVECOT_SERVER_IMPL *xp; + + xp = (XSASL_DOVECOT_SERVER_IMPL *) mymalloc(sizeof(*xp)); + xp->xsasl.create = xsasl_dovecot_server_create; + xp->xsasl.done = xsasl_dovecot_server_done; + xp->socket_path = mystrdup(path_info); + xp->sasl_stream = 0; + xp->mechanism_list = 0; + xp->request_id_counter = 0; + return (&xp->xsasl); +} + +/* xsasl_dovecot_server_done - dispose of implementation */ + +static void xsasl_dovecot_server_done(XSASL_SERVER_IMPL *impl) +{ + XSASL_DOVECOT_SERVER_IMPL *xp = (XSASL_DOVECOT_SERVER_IMPL *) impl; + + xsasl_dovecot_server_disconnect(xp); + myfree(xp->socket_path); + myfree((void *) impl); +} + +/* xsasl_dovecot_server_create - create server instance */ + +static XSASL_SERVER *xsasl_dovecot_server_create(XSASL_SERVER_IMPL *impl, + XSASL_SERVER_CREATE_ARGS *args) +{ + const char *myname = "xsasl_dovecot_server_create"; + XSASL_DOVECOT_SERVER *server; + struct sockaddr_storage ss; + struct sockaddr *sa = (struct sockaddr *) &ss; + SOCKADDR_SIZE salen; + MAI_HOSTADDR_STR server_addr; + + if (msg_verbose) + msg_info("%s: SASL service=%s, realm=%s", + myname, args->service, args->user_realm ? + args->user_realm : "(null)"); + + /* + * Extend the XSASL_SERVER_IMPL object with our own data. We use + * long-lived conversion buffers rather than local variables to avoid + * memory leaks in case of read/write timeout or I/O error. + */ + server = (XSASL_DOVECOT_SERVER *) mymalloc(sizeof(*server)); + server->xsasl.free = xsasl_dovecot_server_free; + server->xsasl.first = xsasl_dovecot_server_first; + server->xsasl.next = xsasl_dovecot_server_next; + server->xsasl.get_mechanism_list = xsasl_dovecot_server_get_mechanism_list; + server->xsasl.get_username = xsasl_dovecot_server_get_username; + server->impl = (XSASL_DOVECOT_SERVER_IMPL *) impl; + server->sasl_line = vstring_alloc(256); + server->username = 0; + server->service = mystrdup(args->service); + server->last_request_id = 0; + server->mechanism_list = 0; + server->mechanism_argv = 0; + server->tls_flag = args->tls_flag; + server->sec_props = + name_mask_opt(myname, xsasl_dovecot_conf_sec_props, + args->security_options, + NAME_MASK_ANY_CASE | NAME_MASK_FATAL); + server->client_addr = mystrdup(args->client_addr); + + /* + * XXX Temporary code until smtpd_peer.c is updated. + */ + if (args->server_addr && *args->server_addr) { + server->server_addr = mystrdup(args->server_addr); + } else { + salen = sizeof(ss); + if (getsockname(vstream_fileno(args->stream), sa, &salen) < 0 + || sockaddr_to_hostaddr(sa, salen, &server_addr, 0, 0) != 0) + server_addr.buf[0] = 0; + server->server_addr = mystrdup(server_addr.buf); + } + + return (&server->xsasl); +} + +/* xsasl_dovecot_server_get_mechanism_list - get available mechanisms */ + +static const char *xsasl_dovecot_server_get_mechanism_list(XSASL_SERVER *xp) +{ + XSASL_DOVECOT_SERVER *server = (XSASL_DOVECOT_SERVER *) xp; + + if (!server->impl->sasl_stream) { + if (xsasl_dovecot_server_connect(server->impl) < 0) + return (0); + } + if (server->mechanism_list == 0) { + server->mechanism_argv = argv_alloc(2); + server->mechanism_list = + xsasl_dovecot_server_mech_filter(server->mechanism_argv, + server->impl->mechanism_list, + server->sec_props); + } + return (server->mechanism_list[0] ? server->mechanism_list : 0); +} + +/* xsasl_dovecot_server_free - destroy server instance */ + +static void xsasl_dovecot_server_free(XSASL_SERVER *xp) +{ + XSASL_DOVECOT_SERVER *server = (XSASL_DOVECOT_SERVER *) xp; + + vstring_free(server->sasl_line); + if (server->username) + myfree(server->username); + if (server->mechanism_list) { + myfree(server->mechanism_list); + argv_free(server->mechanism_argv); + } + myfree(server->service); + myfree(server->server_addr); + myfree(server->client_addr); + myfree((void *) server); +} + +/* xsasl_dovecot_server_auth_response - encode server first/next response */ + +static int xsasl_dovecot_parse_reply(XSASL_DOVECOT_SERVER *server, char **line) +{ + char *id; + + if (*line == NULL) { + msg_warn("SASL: Protocol error"); + return -1; + } + id = *line; + *line = split_at(*line, '\t'); + + if (strtoul(id, NULL, 0) != server->last_request_id) { + /* reply to another request, shouldn't really happen.. */ + return -1; + } + return 0; +} + +static void xsasl_dovecot_parse_reply_args(XSASL_DOVECOT_SERVER *server, + char *line, VSTRING *reply, + int success) +{ + char *next; + + if (server->username) { + myfree(server->username); + server->username = 0; + } + + /* + * Note: TAB is part of the Dovecot protocol and must not appear in + * legitimate Dovecot usernames, otherwise the protocol would break. + */ + for (; line != NULL; line = next) { + next = split_at(line, '\t'); + if (strncmp(line, "user=", 5) == 0) { + server->username = mystrdup(line + 5); + printable(server->username, '?'); + } else if (strncmp(line, "reason=", 7) == 0) { + if (!success) { + printable(line + 7, '?'); + vstring_strcpy(reply, line + 7); + } + } + } +} + +/* xsasl_dovecot_handle_reply - receive and process auth reply */ + +static int xsasl_dovecot_handle_reply(XSASL_DOVECOT_SERVER *server, + VSTRING *reply) +{ + const char *myname = "xsasl_dovecot_handle_reply"; + char *line, *cmd; + + /* XXX Encapsulate for logging. */ + while (vstring_get_nonl(server->sasl_line, + server->impl->sasl_stream) != VSTREAM_EOF) { + line = vstring_str(server->sasl_line); + + if (msg_verbose) + msg_info("%s: auth reply: %s", myname, line); + + cmd = line; + line = split_at(line, '\t'); + + if (strcmp(cmd, "OK") == 0) { + if (xsasl_dovecot_parse_reply(server, &line) == 0) { + /* authentication successful */ + xsasl_dovecot_parse_reply_args(server, line, reply, 1); + if (server->username == 0) { + msg_warn("missing Dovecot server %s username field", cmd); + vstring_strcpy(reply, "Authentication backend error"); + return XSASL_AUTH_FAIL; + } + return XSASL_AUTH_DONE; + } + } else if (strcmp(cmd, "CONT") == 0) { + if (xsasl_dovecot_parse_reply(server, &line) == 0) { + if (line == 0) { + msg_warn("missing Dovecot server %s reply field", cmd); + vstring_strcpy(reply, "Authentication backend error"); + return XSASL_AUTH_FAIL; + } + vstring_strcpy(reply, line); + return XSASL_AUTH_MORE; + } + } else if (strcmp(cmd, "FAIL") == 0) { + if (xsasl_dovecot_parse_reply(server, &line) == 0) { + /* authentication failure */ + xsasl_dovecot_parse_reply_args(server, line, reply, 0); + return XSASL_AUTH_FAIL; + } + } else { + /* ignore */ + } + } + + vstring_strcpy(reply, "Connection lost to authentication server"); + return XSASL_AUTH_TEMP; +} + +/* is_valid_base64 - input sanitized */ + +static int is_valid_base64(const char *data) +{ + + /* + * XXX Maybe use ISALNUM() (isascii && isalnum, i.e. locale independent). + */ + for (; *data != '\0'; data++) { + if (!((*data >= '0' && *data <= '9') || + (*data >= 'a' && *data <= 'z') || + (*data >= 'A' && *data <= 'Z') || + *data == '+' || *data == '/' || *data == '=')) + return 0; + } + return 1; +} + +/* xsasl_dovecot_server_first - per-session authentication */ + +int xsasl_dovecot_server_first(XSASL_SERVER *xp, const char *sasl_method, + const char *init_response, VSTRING *reply) +{ + const char *myname = "xsasl_dovecot_server_first"; + XSASL_DOVECOT_SERVER *server = (XSASL_DOVECOT_SERVER *) xp; + int i; + char **cpp; + +#define IFELSE(e1,e2,e3) ((e1) ? (e2) : (e3)) + + if (msg_verbose) + msg_info("%s: sasl_method %s%s%s", myname, sasl_method, + IFELSE(init_response, ", init_response ", ""), + IFELSE(init_response, init_response, "")); + + if (server->mechanism_argv == 0) + msg_panic("%s: no mechanism list", myname); + + for (cpp = server->mechanism_argv->argv; /* see below */ ; cpp++) { + if (*cpp == 0) { + vstring_strcpy(reply, "Invalid authentication mechanism"); + return XSASL_AUTH_FAIL; + } + if (strcasecmp(sasl_method, *cpp) == 0) + break; + } + if (init_response) + if (!is_valid_base64(init_response)) { + vstring_strcpy(reply, "Invalid base64 data in initial response"); + return XSASL_AUTH_FAIL; + } + for (i = 0; i < 2; i++) { + if (!server->impl->sasl_stream) { + if (xsasl_dovecot_server_connect(server->impl) < 0) + return XSASL_AUTH_TEMP; + } + /* send the request */ + server->last_request_id = ++server->impl->request_id_counter; + /* XXX Encapsulate for logging. */ + vstream_fprintf(server->impl->sasl_stream, + "AUTH\t%u\t%s\tservice=%s\tnologin\tlip=%s\trip=%s", + server->last_request_id, sasl_method, + server->service, server->server_addr, + server->client_addr); + if (server->tls_flag) + /* XXX Encapsulate for logging. */ + vstream_fputs("\tsecured", server->impl->sasl_stream); + if (init_response) { + + /* + * initial response is already base64 encoded, so we can send it + * directly. + */ + /* XXX Encapsulate for logging. */ + vstream_fprintf(server->impl->sasl_stream, + "\tresp=%s", init_response); + } + /* XXX Encapsulate for logging. */ + VSTREAM_PUTC('\n', server->impl->sasl_stream); + + if (vstream_fflush(server->impl->sasl_stream) != VSTREAM_EOF) + break; + + if (i == 1) { + vstring_strcpy(reply, "Can't connect to authentication server"); + return XSASL_AUTH_TEMP; + } + + /* + * Reconnect and try again. + */ + xsasl_dovecot_server_disconnect(server->impl); + } + + return xsasl_dovecot_handle_reply(server, reply); +} + +/* xsasl_dovecot_server_next - continue authentication */ + +static int xsasl_dovecot_server_next(XSASL_SERVER *xp, const char *request, + VSTRING *reply) +{ + XSASL_DOVECOT_SERVER *server = (XSASL_DOVECOT_SERVER *) xp; + + if (!is_valid_base64(request)) { + vstring_strcpy(reply, "Invalid base64 data in continued response"); + return XSASL_AUTH_FAIL; + } + /* XXX Encapsulate for logging. */ + vstream_fprintf(server->impl->sasl_stream, + "CONT\t%u\t%s\n", server->last_request_id, request); + if (vstream_fflush(server->impl->sasl_stream) == VSTREAM_EOF) { + vstring_strcpy(reply, "Connection lost to authentication server"); + return XSASL_AUTH_TEMP; + } + return xsasl_dovecot_handle_reply(server, reply); +} + +/* xsasl_dovecot_server_get_username - get authenticated username */ + +static const char *xsasl_dovecot_server_get_username(XSASL_SERVER *xp) +{ + XSASL_DOVECOT_SERVER *server = (XSASL_DOVECOT_SERVER *) xp; + + return (server->username); +} + +#endif diff --git a/src/xsasl/xsasl_server.c b/src/xsasl/xsasl_server.c new file mode 100644 index 0000000..e8d7e16 --- /dev/null +++ b/src/xsasl/xsasl_server.c @@ -0,0 +1,270 @@ +/*++ +/* NAME +/* xsasl-server 3 +/* SUMMARY +/* Postfix SASL server plug-in interface +/* SYNOPSIS +/* #include <xsasl.h> +/* +/* XSASL_SERVER_IMPL *xsasl_server_init(server_type, path_info) +/* const char *server_type; +/* const char *path_info; +/* +/* void xsasl_server_done(implementation) +/* XSASL_SERVER_IMPL *implementation; +/* +/* ARGV *xsasl_server_types() +/* +/* .in +4 +/* typedef struct XSASL_SERVER_CREATE_ARGS { +/* VSTREAM *stream; +/* const char *server_addr; +/* const char *client_addr; +/* const char *service; +/* const char *user_realm; +/* const char *security_options; +/* int tls_flag; +/* } XSASL_SERVER_CREATE_ARGS; +/* .in -4 +/* +/* XSASL_SERVER *xsasl_server_create(implementation, args) +/* XSASL_SERVER_IMPL *implementation; +/* XSASL_SERVER_CREATE_ARGS *args; +/* +/* XSASL_SERVER *XSASL_SERVER_CREATE(implementation, args, +/* stream = stream_value, +/* ..., +/* tls_flag = tls_flag_value) +/* XSASL_SERVER_IMPL *implementation; +/* XSASL_SERVER_CREATE_ARGS *args; +/* +/* void xsasl_server_free(server) +/* XSASL_SERVER *server; +/* +/* int xsasl_server_first(server, auth_method, init_resp, server_reply) +/* XSASL_SERVER *server; +/* const char *auth_method; +/* const char *init_resp; +/* VSTRING *server_reply; +/* +/* int xsasl_server_next(server, client_request, server_reply) +/* XSASL_SERVER *server; +/* const char *client_request; +/* VSTRING *server_reply; +/* +/* const char *xsasl_server_get_mechanism_list(server) +/* XSASL_SERVER *server; +/* +/* const char *xsasl_server_get_username(server) +/* XSASL_SERVER *server; +/* DESCRIPTION +/* The XSASL_SERVER abstraction implements a generic interface +/* to one or more SASL authentication implementations. +/* +/* xsasl_server_init() is called once during process initialization. +/* It selects a SASL implementation by name, specifies the +/* location of a configuration file or rendez-vous point, and +/* returns an implementation handle that can be used to generate +/* SASL server instances. This function is typically used to +/* initialize the underlying implementation. +/* +/* xsasl_server_done() disposes of an implementation handle, +/* and allows the underlying implementation to release resources. +/* +/* xsasl_server_types() lists the available implementation types. +/* The result should be destroyed by the caller. +/* +/* xsasl_server_create() is called at the start of an SMTP +/* session. It generates a Postfix SASL plug-in server instance +/* for the specified service and authentication realm, and +/* with the specified security properties. Specify a null +/* pointer when no realm should be used. The stream handle is +/* stored so that encryption can be turned on after successful +/* negotiations. Specify zero-length strings when a client or +/* server address is unavailable. +/* +/* XSASL_SERVER_CREATE() is a macro that provides an interface +/* with named parameters. Named parameters do not have to +/* appear in a fixed order. The parameter names correspond to +/* the member names of the XSASL_SERVER_CREATE_ARGS structure. +/* +/* xsasl_server_free() is called at the end of an SMTP session. +/* It destroys a SASL server instance, and disables further +/* read/write operations if encryption was turned on. +/* +/* xsasl_server_first() produces the server response for the +/* client AUTH command. The client input are an authentication +/* method, and an optional initial response or null pointer. +/* The initial response and server non-error replies are BASE64 +/* encoded. Server error replies are 7-bit ASCII text without +/* control characters, without BASE64 encoding, and without +/* SMTP reply code or enhanced status code. +/* +/* The result is one of the following: +/* .IP XSASL_AUTH_MORE +/* More client input is needed. The server reply specifies +/* what. +/* .IP XSASL_AUTH_DONE +/* Authentication completed successfully. +/* .IP XSASL_AUTH_FORM +/* The client input is incorrectly formatted. The server error +/* reply explains why. +/* .IP XSASL_AUTH_FAIL +/* Authentication failed. The server error reply explains why. +/* .PP +/* xsasl_server_next() supports the subsequent stages of the +/* client-server AUTH protocol. Both the client input and +/* server non-error responses are BASE64 encoded. See +/* xsasl_server_first() for other details. +/* +/* xsasl_server_get_mechanism_list() returns the authentication +/* mechanisms that match the security properties, as a white-space +/* separated list. This is meant to be used in the SMTP EHLO +/* reply. +/* +/* xsasl_server_get_username() returns the stored username +/* after successful authentication. +/* +/* Arguments: +/* .IP addr_family +/* The network address family: AF_INET6 or AF_INET. +/* .IP auth_method +/* AUTH command authentication method. +/* .IP client_addr +/* IPv4 or IPv6 address (no surrounding [] or ipv6: prefix), +/* or zero-length string if unavailable. +/* .IP client_port +/* TCP port or zero-length string if unavailable. +/* .IP init_resp +/* AUTH command initial response or null pointer. +/* .IP implementation +/* Implementation handle that was obtained with xsasl_server_init(). +/* .IP path_info +/* The value of the smtpd_sasl_path parameter or equivalent. +/* This specifies the implementation-dependent location of a +/* configuration file, rendez-vous point, etc., and is passed +/* unchanged to the plug-in. +/* .IP security_options +/* The value of the smtpd_security_options parameter or +/* equivalent. This is passed unchanged to the plug-in. +/* .IP server +/* SASL plug-in server handle. +/* .IP server_addr +/* IPv4 or IPv6 address (no surrounding [] or ipv6: prefix), +/* or zero-length string if unavailable. +/* .IP server_port +/* TCP port or zero-length string if unavailable. +/* .IP server_reply +/* BASE64 encoded server non-error reply (without SMTP reply +/* code or enhanced status code), or ASCII error description. +/* .IP server_type +/* The name of a Postfix SASL server plug_in implementation. +/* .IP server_types +/* Null-terminated array of strings with SASL server plug-in +/* implementation names. +/* .IP service +/* The service that is implemented by the local server, typically +/* "smtp" or "lmtp". +/* .IP stream +/* The connection between client and server. When SASL +/* encryption is negotiated, the plug-in will transparently +/* intercept the socket read/write operations. +/* .IP user_realm +/* Authentication domain or null pointer. +/* SECURITY +/* .ad +/* .fi +/* The caller does not sanitize client input. It is the +/* responsibility of the underlying SASL server implementation +/* to produce 7-bit ASCII without control characters as server +/* non-error and error replies, and as the result from +/* xsasl_server_method() and xsasl_server_username(). +/* DIAGNOSTICS +/* In case of failure, xsasl_server_init(), xsasl_server_create(), +/* xsasl_server_get_mechanism_list() and xsasl_server_get_username() +/* log a warning and return a null pointer. +/* +/* Functions that normally return XSASL_AUTH_OK will log a warning +/* and return an appropriate result value. +/* +/* Fatal errors: out of memory. +/* +/* Panic: interface violations. +/* SEE ALSO +/* cyrus_security(3) Cyrus SASL security features +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this +/* software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> + +/* SASL implementations. */ + +#include <xsasl.h> +#include <xsasl_cyrus.h> +#include <xsasl_dovecot.h> + + /* + * Lookup table for available SASL server implementations. + */ +typedef struct { + char *server_type; + struct XSASL_SERVER_IMPL *(*server_init) (const char *, const char *); +} XSASL_SERVER_IMPL_INFO; + +static const XSASL_SERVER_IMPL_INFO server_impl_info[] = { +#ifdef XSASL_TYPE_CYRUS + {XSASL_TYPE_CYRUS, xsasl_cyrus_server_init}, +#endif +#ifdef XSASL_TYPE_DOVECOT + {XSASL_TYPE_DOVECOT, xsasl_dovecot_server_init}, +#endif + {0, 0} +}; + +/* xsasl_server_init - look up server implementation by name */ + +XSASL_SERVER_IMPL *xsasl_server_init(const char *server_type, + const char *path_info) +{ + const XSASL_SERVER_IMPL_INFO *xp; + + for (xp = server_impl_info; xp->server_type; xp++) + if (strcmp(server_type, xp->server_type) == 0) + return (xp->server_init(server_type, path_info)); + msg_warn("unsupported SASL server implementation: %s", server_type); + return (0); +} + +/* xsasl_server_types - report available implementation types */ + +ARGV *xsasl_server_types(void) +{ + const XSASL_SERVER_IMPL_INFO *xp; + ARGV *argv = argv_alloc(1); + + for (xp = server_impl_info; xp->server_type; xp++) + argv_add(argv, xp->server_type, ARGV_END); + return (argv); +} |