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