Home | History | Annotate | Line # | Download | only in smtp
      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 
    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 
    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     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 
    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