1/*++
2/* NAME
3/*	smtp_sasl_auth_cache 3
4/* SUMMARY
5/*	Postfix SASL authentication reply cache
6/* SYNOPSIS
7/*	#include "smtp.h"
8/*	#include "smtp_sasl_auth_cache.h"
9/*
10/*	SMTP_SASL_AUTH_CACHE *smtp_sasl_auth_cache_init(map, ttl)
11/*	const char *map
12/*	int	ttl;
13/*
14/*	void	smtp_sasl_auth_cache_store(auth_cache, session, resp)
15/*	SMTP_SASL_AUTH_CACHE *auth_cache;
16/*	const SMTP_SESSION *session;
17/*	const SMTP_RESP *resp;
18/*
19/*	int	smtp_sasl_auth_cache_find(auth_cache, session)
20/*	SMTP_SASL_AUTH_CACHE *auth_cache;
21/*	const SMTP_SESSION *session;
22/*
23/*	char	*smtp_sasl_auth_cache_dsn(auth_cache)
24/*	SMTP_SASL_AUTH_CACHE *auth_cache;
25/*
26/*	char	*smtp_sasl_auth_cache_text(auth_cache)
27/*	SMTP_SASL_AUTH_CACHE *auth_cache;
28/* DESCRIPTION
29/*	This module maintains a cache of SASL authentication server replies.
30/*	This can be used to avoid repeated login failure errors.
31/*
32/*	smtp_sasl_auth_cache_init() opens or creates the named cache.
33/*
34/*	smtp_sasl_auth_cache_store() stores information about a
35/*	SASL login attempt together with the server status and
36/*	complete response.
37/*
38/*	smtp_sasl_auth_cache_find() returns non-zero when a cache
39/*	entry exists for the given host, username and password.
40/*
41/*	smtp_sasl_auth_cache_dsn() and smtp_sasl_auth_cache_text()
42/*	return the status and complete server response as found
43/*	with smtp_sasl_auth_cache_find().
44/*
45/*	Arguments:
46/* .IP map
47/*	Lookup table name. The name must be singular and must start
48/*	with "proxy:".
49/* .IP ttl
50/*	The time after which a cache entry is considered expired.
51/* .IP session
52/*	Session context.
53/* .IP resp
54/*	Remote SMTP server response, to be stored into the cache.
55/* DIAGNOSTICS
56/*	All errors are fatal.
57/* LICENSE
58/* .ad
59/* .fi
60/*	The Secure Mailer license must be distributed with this software.
61/* AUTHOR(S)
62/*	Original author:
63/*	Keean Schupke
64/*	Fry-IT Ltd.
65/*
66/*	Updated by:
67/*	Wietse Venema
68/*	IBM T.J. Watson Research
69/*	P.O. Box 704
70/*	Yorktown Heights, NY 10598, USA
71/*--*/
72
73 /*
74  * System library.
75  */
76#include <sys_defs.h>
77
78 /*
79  * Utility library
80  */
81#include <msg.h>
82#include <mymalloc.h>
83#include <stringops.h>
84#include <base64_code.h>
85#include <dict.h>
86
87 /*
88  * Global library
89  */
90#include <dsn_util.h>
91#include <dict_proxy.h>
92
93 /*
94  * Application-specific
95  */
96#include "smtp.h"
97#include "smtp_sasl_auth_cache.h"
98
99 /*
100  * XXX This feature stores passwords, so we must mask them with a strong
101  * cryptographic hash. This requires OpenSSL support.
102  *
103  * XXX It would be even better if the stored hash were salted.
104  */
105#ifdef HAVE_SASL_AUTH_CACHE
106
107/* smtp_sasl_auth_cache_init - per-process initialization (pre jail) */
108
109SMTP_SASL_AUTH_CACHE *smtp_sasl_auth_cache_init(const char *map, int ttl)
110{
111    const char *myname = "smtp_sasl_auth_cache_init";
112    SMTP_SASL_AUTH_CACHE *auth_cache;
113
114    /*
115     * Sanity checks.
116     */
117#define HAS_MULTIPLE_VALUES(s) ((s)[strcspn((s),  ", \t\r\n")] != 0)
118
119    if (*map == 0)
120	msg_panic("%s: empty SASL authentication cache name", myname);
121    if (ttl < 0)
122	msg_panic("%s: bad SASL authentication cache ttl: %d", myname, ttl);
123    if (HAS_MULTIPLE_VALUES(map))
124	msg_fatal("SASL authentication cache name \"%s\" "
125		  "contains multiple values", map);
126
127    /*
128     * XXX To avoid multiple writers the map needs to be maintained by the
129     * proxywrite service. We would like to have a DICT_FLAG_REQ_PROXY flag
130     * so that the library can enforce this, but that requires moving the
131     * dict_proxy module one level down in the build dependency hierachy.
132     */
133#define CACHE_DICT_OPEN_FLAGS \
134	(DICT_FLAG_DUP_REPLACE | DICT_FLAG_SYNC_UPDATE)
135#define PROXY_COLON	DICT_TYPE_PROXY ":"
136#define PROXY_COLON_LEN	(sizeof(PROXY_COLON) - 1)
137
138    if (strncmp(map, PROXY_COLON, PROXY_COLON_LEN) != 0)
139	msg_fatal("SASL authentication cache name \"%s\" must start with \""
140		  PROXY_COLON, map);
141
142    auth_cache = (SMTP_SASL_AUTH_CACHE *) mymalloc(sizeof(*auth_cache));
143    auth_cache->dict = dict_open(map, O_CREAT | O_RDWR, CACHE_DICT_OPEN_FLAGS);
144    auth_cache->ttl = ttl;
145    auth_cache->dsn = mystrdup("");
146    auth_cache->text = mystrdup("");
147    return (auth_cache);
148}
149
150 /*
151  * Each cache lookup key contains a server host name and user name. Each
152  * cache value contains a time stamp, a hashed password, and the server
153  * response. With this organization, we don't have to worry about cache
154  * pollution, because we can detect if a cache entry has expired, or if the
155  * password has changed.
156  */
157
158/* smtp_sasl_auth_cache_make_key - format auth failure cache lookup key */
159
160static char *smtp_sasl_auth_cache_make_key(const char *host, const char *user)
161{
162    VSTRING *buf = vstring_alloc(100);
163
164    vstring_sprintf(buf, "%s;%s", host, user);
165    return (vstring_export(buf));
166}
167
168/* smtp_sasl_auth_cache_make_pass - hash the auth failure cache password */
169
170static char *smtp_sasl_auth_cache_make_pass(const char *password)
171{
172    VSTRING *buf = vstring_alloc(2 * SHA_DIGEST_LENGTH);
173
174    base64_encode(buf, (const char *) SHA1((const unsigned char *) password,
175					   strlen(password), 0),
176		  SHA_DIGEST_LENGTH);
177    return (vstring_export(buf));
178}
179
180/* smtp_sasl_auth_cache_make_value - format auth failure cache value */
181
182static char *smtp_sasl_auth_cache_make_value(const char *password,
183					             const char *dsn,
184					             const char *rep_str)
185{
186    VSTRING *val_buf = vstring_alloc(100);
187    char   *pwd_hash;
188    unsigned long now = (unsigned long) time((time_t *) 0);
189
190    pwd_hash = smtp_sasl_auth_cache_make_pass(password);
191    vstring_sprintf(val_buf, "%lu;%s;%s;%s", now, pwd_hash, dsn, rep_str);
192    myfree(pwd_hash);
193    return (vstring_export(val_buf));
194}
195
196/* smtp_sasl_auth_cache_valid_value - validate auth failure cache value */
197
198static int smtp_sasl_auth_cache_valid_value(SMTP_SASL_AUTH_CACHE *auth_cache,
199					            const char *entry,
200					            const char *password)
201{
202    ssize_t len = strlen(entry);
203    char   *cache_hash = mymalloc(len);
204    char   *curr_hash;
205    unsigned long now = (unsigned long) time((time_t *) 0);
206    unsigned long time_stamp;
207    int     valid;
208
209    auth_cache->dsn = myrealloc(auth_cache->dsn, len);
210    auth_cache->text = myrealloc(auth_cache->text, len);
211
212    if (sscanf(entry, "%lu;%[^;];%[^;];%[^\n]", &time_stamp, cache_hash,
213	       auth_cache->dsn, auth_cache->text) != 4
214	|| !dsn_valid(auth_cache->dsn)) {
215	msg_warn("bad smtp_sasl_auth_cache entry: %.100s", entry);
216	valid = 0;
217    } else if (time_stamp + auth_cache->ttl < now) {
218	valid = 0;
219    } else {
220	curr_hash = smtp_sasl_auth_cache_make_pass(password);
221	valid = (strcmp(cache_hash, curr_hash) == 0);
222	myfree(curr_hash);
223    }
224    myfree(cache_hash);
225    return (valid);
226}
227
228/* smtp_sasl_auth_cache_find - search auth failure cache */
229
230int     smtp_sasl_auth_cache_find(SMTP_SASL_AUTH_CACHE *auth_cache,
231				          const SMTP_SESSION *session)
232{
233    SMTP_ITERATOR *iter = session->iterator;
234    char   *key;
235    const char *entry;
236    int     valid = 0;
237
238    key = smtp_sasl_auth_cache_make_key(STR(iter->host), session->sasl_username);
239    if ((entry = dict_get(auth_cache->dict, key)) != 0)
240	if ((valid = smtp_sasl_auth_cache_valid_value(auth_cache, entry,
241						session->sasl_passwd)) == 0)
242	    /* Remove expired, password changed, or malformed cache entry. */
243	    if (dict_del(auth_cache->dict, key) != 0)
244		msg_warn("SASL auth failure map %s: entry not deleted: %s",
245			 auth_cache->dict->name, key);
246    if (auth_cache->dict->error)
247	msg_warn("SASL auth failure map %s: lookup failed for %s",
248		 auth_cache->dict->name, key);
249    myfree(key);
250    return (valid);
251}
252
253/* smtp_sasl_auth_cache_store - update auth failure cache */
254
255void    smtp_sasl_auth_cache_store(SMTP_SASL_AUTH_CACHE *auth_cache,
256				           const SMTP_SESSION *session,
257				           const SMTP_RESP *resp)
258{
259    SMTP_ITERATOR *iter = session->iterator;
260    char   *key;
261    char   *value;
262
263    key = smtp_sasl_auth_cache_make_key(STR(iter->host), session->sasl_username);
264    value = smtp_sasl_auth_cache_make_value(session->sasl_passwd,
265					    resp->dsn, resp->str);
266    dict_put(auth_cache->dict, key, value);
267
268    myfree(value);
269    myfree(key);
270}
271
272#endif
273