5 /* smtp_sasl_auth_cache 3
7 /* Postfix SASL authentication reply cache
10 /* #include "smtp_sasl_auth_cache.h"
12 /* SMTP_SASL_AUTH_CACHE *smtp_sasl_auth_cache_init(map, ttl)
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;
21 /* int smtp_sasl_auth_cache_find(auth_cache, session)
22 /* SMTP_SASL_AUTH_CACHE *auth_cache;
23 /* const SMTP_SESSION *session;
25 /* char *smtp_sasl_auth_cache_dsn(auth_cache)
26 /* SMTP_SASL_AUTH_CACHE *auth_cache;
28 /* char *smtp_sasl_auth_cache_text(auth_cache)
29 /* SMTP_SASL_AUTH_CACHE *auth_cache;
31 /* This module maintains a cache of SASL authentication server replies.
32 /* This can be used to avoid repeated login failure errors.
34 /* smtp_sasl_auth_cache_init() opens or creates the named cache.
36 /* smtp_sasl_auth_cache_store() stores information about a
37 /* SASL login attempt together with the server status and
40 /* smtp_sasl_auth_cache_find() returns non-zero when a cache
41 /* entry exists for the given host, username and password.
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().
49 /* Lookup table name. The name must be singular and must start
52 /* The time after which a cache entry is considered expired.
56 /* Remote SMTP server response, to be stored into the cache.
58 /* All errors are fatal.
62 /* The Secure Mailer license must be distributed with this software.
70 /* IBM T.J. Watson Research
72 /* Yorktown Heights, NY 10598, USA
85 #include <stringops.h>
86 #include <base64_code.h>
93 #include <dict_proxy.h>
96 * Application-specific
99 #include "smtp_sasl_auth_cache.h"
102 * XXX This feature stores passwords, so we must mask them with a strong
103 * cryptographic hash. This requires OpenSSL support.
105 * XXX It would be even better if the stored hash were salted.
107 #ifdef HAVE_SASL_AUTH_CACHE
109 /* smtp_sasl_auth_cache_init - per-process initialization (pre jail) */
111 SMTP_SASL_AUTH_CACHE
*smtp_sasl_auth_cache_init(const char *map
, int ttl
)
113 const char *myname
= "smtp_sasl_auth_cache_init";
114 SMTP_SASL_AUTH_CACHE
*auth_cache
;
119 #define HAS_MULTIPLE_VALUES(s) ((s)[strcspn((s), ", \t\r\n")] != 0)
122 msg_panic("%s: empty SASL authentication cache name", myname
);
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
);
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 hierachy.
135 #define CACHE_DICT_OPEN_FLAGS \
136 (DICT_FLAG_DUP_REPLACE | DICT_FLAG_SYNC_UPDATE)
137 #define PROXY_COLON DICT_TYPE_PROXY ":"
138 #define PROXY_COLON_LEN (sizeof(PROXY_COLON) - 1)
140 if (strncmp(map
, PROXY_COLON
, PROXY_COLON_LEN
) != 0)
141 msg_fatal("SASL authentication cache name \"%s\" must start with \""
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("");
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.
160 /* smtp_sasl_auth_cache_make_key - format auth failure cache lookup key */
162 static char *smtp_sasl_auth_cache_make_key(const char *host
, const char *user
)
164 VSTRING
*buf
= vstring_alloc(100);
166 vstring_sprintf(buf
, "%s;%s", host
, user
);
167 return (vstring_export(buf
));
170 /* smtp_sasl_auth_cache_make_pass - hash the auth failure cache password */
172 static char *smtp_sasl_auth_cache_make_pass(const char *password
)
174 VSTRING
*buf
= vstring_alloc(2 * SHA_DIGEST_LENGTH
);
176 base64_encode(buf
, (const char *) SHA1((const unsigned char *) password
,
177 strlen(password
), 0),
179 return (vstring_export(buf
));
182 /* smtp_sasl_auth_cache_make_value - format auth failure cache value */
184 static char *smtp_sasl_auth_cache_make_value(const char *password
,
188 VSTRING
*val_buf
= vstring_alloc(100);
190 unsigned long now
= (unsigned long) time((time_t *) 0);
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
);
195 return (vstring_export(val_buf
));
198 /* smtp_sasl_auth_cache_valid_value - validate auth failure cache value */
200 static int smtp_sasl_auth_cache_valid_value(SMTP_SASL_AUTH_CACHE
*auth_cache
,
202 const char *password
)
204 ssize_t len
= strlen(entry
);
205 char *cache_hash
= mymalloc(len
);
207 unsigned long now
= (unsigned long) time((time_t *) 0);
208 unsigned long time_stamp
;
211 auth_cache
->dsn
= myrealloc(auth_cache
->dsn
, len
);
212 auth_cache
->text
= myrealloc(auth_cache
->text
, len
);
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
);
219 } else if (time_stamp
+ auth_cache
->ttl
< now
) {
222 curr_hash
= smtp_sasl_auth_cache_make_pass(password
);
223 valid
= (strcmp(cache_hash
, curr_hash
) == 0);
230 /* smtp_sasl_auth_cache_find - search auth failure cache */
232 int smtp_sasl_auth_cache_find(SMTP_SASL_AUTH_CACHE
*auth_cache
,
233 const SMTP_SESSION
*session
)
239 key
= smtp_sasl_auth_cache_make_key(session
->host
, session
->sasl_username
);
240 if ((entry
= dict_get(auth_cache
->dict
, key
)) != 0)
241 if ((valid
= smtp_sasl_auth_cache_valid_value(auth_cache
, entry
,
242 session
->sasl_passwd
)) == 0)
243 /* Remove expired, password changed, or malformed cache entry. */
244 if (dict_del(auth_cache
->dict
, key
) == 0)
245 msg_warn("SASL auth failure map %s: entry not deleted: %s",
246 auth_cache
->dict
->name
, key
);
251 /* smtp_sasl_auth_cache_store - update auth failure cache */
253 void smtp_sasl_auth_cache_store(SMTP_SASL_AUTH_CACHE
*auth_cache
,
254 const SMTP_SESSION
*session
,
255 const SMTP_RESP
*resp
)
260 key
= smtp_sasl_auth_cache_make_key(session
->host
, session
->sasl_username
);
261 value
= smtp_sasl_auth_cache_make_value(session
->sasl_passwd
,
262 resp
->dsn
, resp
->str
);
263 dict_put(auth_cache
->dict
, key
, value
);