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