1 /* $NetBSD: smtp_sasl_auth_cache.c,v 1.1.1.2 2013/01/02 18:59:08 tron 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 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), ", \t\r\n")] != 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 hierachy. 134 */ 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) 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 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 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 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 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 232 int smtp_sasl_auth_cache_find(SMTP_SASL_AUTH_CACHE *auth_cache, 233 const SMTP_SESSION *session) 234 { 235 char *key; 236 const char *entry; 237 int valid = 0; 238 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); 247 if (auth_cache->dict->error) 248 msg_warn("SASL auth failure map %s: lookup failed for %s", 249 auth_cache->dict->name, key); 250 myfree(key); 251 return (valid); 252 } 253 254 /* smtp_sasl_auth_cache_store - update auth failure cache */ 255 256 void smtp_sasl_auth_cache_store(SMTP_SASL_AUTH_CACHE *auth_cache, 257 const SMTP_SESSION *session, 258 const SMTP_RESP *resp) 259 { 260 char *key; 261 char *value; 262 263 key = smtp_sasl_auth_cache_make_key(session->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