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 111SMTP_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 162static 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 172static 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 184static 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 200static 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 232int 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 257void 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