/* * Copyright (c) 2004-2006 Sendmail, Inc. and its suppliers. * All rights reserved. * * By using this file, you agree to the terms and conditions set * forth in the LICENSE file which can be found at the top level of * the sendmail distribution. */ #include "sm/generic.h" SM_RCSID("@(#)$Id: rcpts.c,v 1.82 2007/11/14 06:03:09 ca Exp $") #include "sm/error.h" #include "sm/assert.h" #include "sm/rfc2821.h" #include "sm/ctype.h" #include "smar.h" #include "sm/smardef.h" #include "log.h" #define MAX_ALIAS_RECURSION 5u #if SM_ALIASES_LARGE /* XXX RCB size? */ # define N_ALIASES 1031u # define MAX_ALIASES 8191u # ifndef MAXALIASLEN # define MAXALIASLEN (256 * 1024) # endif /* MAXALIASLEN */ #else /* SM_ALIASES_LARGE */ # define N_ALIASES 211u # define MAX_ALIASES 1031u # ifndef MAXALIASLEN # define MAXALIASLEN (64 * 1024) # endif /* MAXALIASLEN */ #endif /* SM_ALIASES_LARGE */ /* ** SMAR_RCPTS_NEW -- Create a SMAR RCPT LIST context ** ** Parameters: ** smar_ctx -- SMAR context ** smar_clt_ctx -- SMAR client context ** psmar_rcpt -- (pointer to) SMAR RCPT context (output) ** ** Returns: ** usual sm_error code ** ** Last code review: 2004-03-22 04:46:10 ** Last code change: */ sm_ret_T smar_rcpts_new(smar_ctx_P smar_ctx, smar_clt_ctx_P smar_clt_ctx, smar_rcpts_P *psmar_rcpts) { smar_rcpts_P smar_rcpts; SM_REQUIRE(psmar_rcpts != NULL); smar_rcpts = (smar_rcpts_P) sm_zalloc(sizeof(*smar_rcpts)); if (NULL == smar_rcpts) return sm_error_temp(SM_EM_AR, ENOMEM); smar_rcpts->arrs_rcpts = bht_new(N_ALIASES, MAX_ALIASES); if (NULL == smar_rcpts->arrs_rcpts) { sm_free_size(smar_rcpts, sizeof(*smar_rcpts)); return sm_error_temp(SM_EM_AR, ENOMEM); } RCPTS_INIT(smar_rcpts); OWNER_INIT(smar_rcpts); smar_rcpts->arrs_smar_ctx = smar_ctx; smar_rcpts->arrs_smar_clt_ctx = smar_clt_ctx; *psmar_rcpts = smar_rcpts; smar_rcpts->sm_magic = SM_SMAR_RCPTS_MAGIC; return SM_SUCCESS; } /* ** SMAR_RCPT_FREE_CB -- Callback for bht_destroy ** ** Parameters: ** value -- SMAR RCPT context ** key -- ignored ** ctx -- SMAR RCPT LIST context ** ** Returns: ** usual sm_error code ** ** Last code review: 2004-03-22 04:46:31 ** Last code change: 2004-03-24 22:29:08 */ static void smar_rcpt_free_cb(void *value, void *key, void *ctx) { smar_rcpt_P smar_rcpt; smar_rcpts_P smar_rcpts; (void) key; smar_rcpt = (smar_rcpt_P) value; smar_rcpts = (smar_rcpts_P) ctx; SMAR_LEV_DPRINTF(8, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_free_cb, smar_rcpt=%p\n", smar_rcpt)); (void) smar_rcpt_free(smar_rcpt, smar_rcpts); } /* ** SMAR_RCPTS_FREE -- Free a SMAR RCPT LIST context ** including all recipients in hash table and owner-list ** ** Parameters: ** smar_rcpts -- SMAR RCPT LIST context ** ** Returns: ** usual sm_error code ** ** Last code review: 2004-03-22 04:46:50; see below ** Last code change: 2005-10-02 18:07:53 */ sm_ret_T smar_rcpts_free(smar_rcpts_P smar_rcpts) { #if 0 int r; #endif smar_rcpt_P smar_rcpt, smar_rcpt_nxt; if (NULL == smar_rcpts) return SM_SUCCESS; SM_IS_SMAR_RCPTS(smar_rcpts); SMAR_LEV_DPRINTF(8, (SMAR_DEBFP, "sev=DBG, func=smar_rcpts_free, smar_rcpts=%p\n", smar_rcpts)); bht_destroy(smar_rcpts->arrs_rcpts, smar_rcpt_free_cb, smar_rcpts); /* ** Note: arrs_rcpthd is NOT checked, list should be empty. ** arrs_pa is not free()d, it is just a pointer to ara_pa2 */ if (smar_rcpts->arrs_rcbe != NULL) { sm_rcbe_free(smar_rcpts->arrs_rcbe); smar_rcpts->arrs_rcbe = NULL; } for (smar_rcpt = OWNER_FIRST(smar_rcpts); smar_rcpt != OWNER_END(smar_rcpts); smar_rcpt = smar_rcpt_nxt) { smar_rcpt_nxt = OWNER_NEXT(smar_rcpt); smar_rcpt_free(smar_rcpt, smar_rcpts); } OWNER_INIT(smar_rcpts); /* ** This requires that the pointer is set to NULL if the recipient ** is free()d or that the flag is cleared (note: a bogus pointer ** or a pointer to a free()d value that still has the flag set ** will cause problems too!). Can that be guaranteed?? */ if (smar_rcpts->arrs_rcpt != NULL && SMARR_IS_FLAG(smar_rcpts->arrs_rcpt, SMARR_FL_ORCPT)) { smar_rcpt_free(smar_rcpts->arrs_rcpt, NULL); smar_rcpts->arrs_rcpt = NULL; } #if 0 r = pthread_mutex_destroy(&smar_rcpts->arrs_mutex); smar_rcpts->arrs_smar_ctx = NULL; smar_rcpts->arrs_smar_clt_ctx = NULL; #endif smar_rcpts->sm_magic = SM_MAGIC_NULL; sm_free_size(smar_rcpts, sizeof(*smar_rcpts)); return SM_SUCCESS; } /* ** SMAR_RCPT_T2L_CB -- Callback for bht_walk in smar_rcpts_r2l() ** ** Parameters: ** entry -- SMAR RCPT context ** ctx -- smar_rcpts context ** ** Returns: ** usual sm_error code ** ** Last code review: 2004-03-22 04:52:02 ** Last code change: */ static sm_ret_T smar_rcpt_t2l_cb(bht_entry_P entry, void *ctx) { smar_rcpts_P smar_rcpts; smar_rcpt_P smar_rcpt; smar_rcpt = (smar_rcpt_P) entry->bhe_value; smar_rcpts = (smar_rcpts_P) ctx; SM_IS_SMAR_RCPTS(smar_rcpts); SM_IS_SMAR_RCPT(smar_rcpt); SMAR_LEV_DPRINTF(7, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_t2l_cb, smar_rcpt=%p, pa=%S, flags=%#x\n", smar_rcpt, smar_rcpt->arr_pa, smar_rcpt->arr_flags)); if (SMARR_IS_FLAG(smar_rcpt, SMARR_FL_ISOWN) || SMARR_IS_FLAG(smar_rcpt, SMARR_FL_ISVERP)) { OWNER_APP(smar_rcpts, smar_rcpt); ++smar_rcpts->arrs_owners_n; SMARR_CLR_FLAG(smar_rcpt, SMARR_FL_INHT); SMARR_SET_FLAG(smar_rcpt, SMARR_FL_INOWNLST); } else if (!SMARR_IS_FLAG(smar_rcpt, SMARR_FL_EXPD)) { RCPTS_APP(smar_rcpts, smar_rcpt); ++smar_rcpts->arrs_lst_n; SMARR_CLR_FLAG(smar_rcpt, SMARR_FL_INHT); SMARR_SET_FLAG(smar_rcpt, SMARR_FL_INRLST); } else smar_rcpt_free(smar_rcpt, smar_rcpts); return SM_SUCCESS; } /* ** SMAR_RCPTS_T2L -- Convert a SMAR RCPT table into a list ** ** Parameters: ** smar_rcpts -- SMAR RCPT LIST context ** ** Returns: ** usual sm_error code ** ** Last code review: 2004-03-24 18:18:32; see comments! ** Last code change: */ sm_ret_T smar_rcpts_t2l(smar_rcpts_P smar_rcpts) { SM_IS_SMAR_RCPTS(smar_rcpts); bht_walk(smar_rcpts->arrs_rcpts, smar_rcpt_t2l_cb, smar_rcpts); /* ** Content is now in list, destroy table without free()ing elements. ** XXX why not use bht_destroy(smar_rcpts->arrs_rcpts, smar_rcpt_t2l_cb, smar_rcpts); ** and get rid of bht_walk()? */ bht_destroy(smar_rcpts->arrs_rcpts, NULL, NULL); smar_rcpts->arrs_rcpts = NULL; return SM_SUCCESS; } /* ** SMAR_RCPTS_ADD2HT -- Add a rcpt to a SMAR rcpt hash table ** ** Parameters: ** smar_rcpt -- SMAR RCPT context ** smar_rcpts -- SMAR RCPT LIST context ** ** Returns: ** usual sm_error code, ** sm_error_perm(SM_EM_AR, EEXIST) if entry exists ** ** Last code review: 2004-03-22 04:54:51; see comments! ** Last code change: */ static sm_ret_T smar_rcpts_add2ht(smar_rcpts_P smar_rcpts, smar_rcpt_P smar_rcpt) { sm_ret_T ret; void *ptr; SM_IS_SMAR_RCPT(smar_rcpt); SM_IS_SMAR_RCPTS(smar_rcpts); /* ** XXX Need "address equal" function: ** domain part: case insensitive ** local part: depends on configuration ** The entries in bht must be in "canonical" format... ** Note: if that is not the case, then it is not possible to use ** a hash table (well, because it's hashed; unless some ** "canonificaton" before hashing is done, e.g., lower case the key). */ ptr = bht_find(smar_rcpts->arrs_rcpts, (const char *) sm_str_data(smar_rcpt->arr_pa), sm_str_getlen(smar_rcpt->arr_pa)); if (NULL == ptr) { bht_entry_P entry; ret = bht_add(smar_rcpts->arrs_rcpts, (char *) sm_str_data(smar_rcpt->arr_pa), sm_str_getlen(smar_rcpt->arr_pa), smar_rcpt, &entry); if (SM_SUCCESS == ret) { ++smar_rcpts->arrs_ht_n; smar_rcpt->arr_rcpts = smar_rcpts; SMAR_LEV_DPRINTF(8, (SMAR_DEBFP, "sev=DBG, func=smar_rcpts_add2ht, smar_rcpt=%p, pa=%S, stat=inht\n", smar_rcpt, smar_rcpt->arr_pa)); SMARR_SET_FLAG(smar_rcpt, SMARR_FL_INHT); } /* else error code ret is returned to caller */ } else ret = sm_error_perm(SM_EM_AR, EEXIST); return ret; } /* ** SMAR_RCPT_PARTS -- parse recipient parts (user/detail) ** ** Parameters: ** smar_ctx -- SMAR context ** smar_rcpt -- SMAR RCPT context ** prcpt -- parsed recipient ** ** Returns: ** usual sm_error code, ** ** Last code review: ** Last code change: */ static sm_ret_T smar_rcpt_parts(smar_ctx_P smar_ctx, smar_rcpt_P smar_rcpt, sm_a2821_P prcpt) { sm_ret_T ret; smar_rcpt->arr_user_pa = sm_str_new(NULL, MAXADDRLEN, MAXADDRLEN + 2); if (NULL == smar_rcpt->arr_user_pa) goto enomem; smar_rcpt->arr_detail_pa = sm_str_new(NULL, MAXADDRLEN >> 1, MAXADDRLEN); if (NULL == smar_rcpt->arr_detail_pa) goto enomem; /* extract localpart */ ret = t2821_parts(prcpt, smar_ctx->smar_cnf.smar_cnf_addr_delim, true, smar_rcpt->arr_user_pa, smar_rcpt->arr_detail_pa, NULL, &smar_rcpt->arr_delim); if (sm_is_err(ret)) { SMAR_LEV_DPRINTF(3, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_parse, t2821_parts=%r\n", ret)); goto error; } if (ret > 0) SMARR_SET_FLAG(smar_rcpt, SMARR_FL_HASDET); return ret; enomem: ret = sm_error_temp(SM_EM_AR, ENOMEM); error: SMAR_LEV_DPRINTF(3, (SMAR_DEBFP, "sev=ERROR, func=smar_rcpt_parse, smar_rcpt=%p, flags=%#x\n", smar_rcpt, smar_rcpt == NULL ? 0xffffffff : smar_rcpt->arr_flags)); return ret; } /* ** SMAR_RCPT_EXPAND -- Expand RCPT address (add to hash table in smar_rcpts) ** ** Parameters: ** smar_rcpts -- SMAR RCPT LIST context ** smar_rcpt -- rcpt routing information ** owner_idx -- reference to owner (0: none) ** rflags -- flags ** level -- recursion level ** ** Returns: ** usual sm_error code ** ** Side Effects: ** creates and populates smar_rcpt->arr_domain_pa, ** smar_rcpt_rslv() relies on this. ** ... ** ** Note: the alias format is very restricted: it must be local-part1: ... local-part2: other-local-part ** i.e., delimiters are only whitespace, not comma. ** ** This function is called recursively. ** ** The entries are collected in a hash table, see smar_rcpts_t2l() ** how to get a list of ("valid") entries. ** ** Last code review: 2004-03-22 05:21:23; see comments! ** Last code change: */ #define SM_MAP_ENTRY_NOTFOUND(ret) \ (sm_error_perm(SM_EM_MAP, SM_E_NOTFOUND) == (ret) || \ sm_error_perm(SM_EM_MAP, SM_E_NOTIMPL) == (ret) || \ sm_error_perm(SM_EM_MAP, ENOENT) == (ret) || \ sm_error_perm(SM_EM_MAP, SM_E_UNAVAIL) == (ret) || \ sm_error_perm(SM_EM_MAP, SM_E_NOMAP) == (ret)) sm_ret_T smar_rcpt_expand(smar_rcpts_P smar_rcpts, smar_rcpt_P smar_rcpt, rcpt_idx_T owner_idx, uint rflags, uint level) { sm_ret_T ret, hasdetail, offset; uint idx, flags; uint32_t lfl; char *ipv4s; sm_a2821_T a_rcpt, a_domain; sm_a2821_P prcpt, pdomain; sm_str_P mtstr, str, rhs, owner, ownerrhs; smar_rcpt_P smar_rcpt_a; smar_ctx_P smar_ctx; bool islocaldomain; /* address is identical to one already in hash table */ #define SRE_FL_IDENTICAL 0x01 #define SRE_FL_CHK_LU 0x02 /* check local user */ #define SRE_FL_USE_MAP 0x04 /* full match using a map */ SM_IS_SMAR_RCPT(smar_rcpt); SM_IS_SMAR_RCPTS(smar_rcpts); ret = SM_SUCCESS; mtstr = str = rhs = owner = ownerrhs = NULL; smar_ctx = smar_rcpts->arrs_smar_ctx; pdomain = &a_domain; /* pdomain is just pointer to a_domain */ prcpt = &a_rcpt; /* prcpt is just pointer to a_rcpt */ A2821_INIT_RP(prcpt, NULL); A2821_INIT_RP(pdomain, NULL); smar_rcpt_a = NULL; islocaldomain = false; flags = 0; hasdetail = 0; SMAR_LEV_DPRINTF(7, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_expand, smar_rcpt=%p, level=%u\n", smar_rcpt, level)); if (level >= MAX_ALIAS_RECURSION) { ret = sm_error_perm(SM_EM_AR, SM_E_ALIAS_REC); goto error; } /* check recipient address */ ret = t2821_scan((sm_rdstr_P) smar_rcpt->arr_pa, prcpt, 0); if (sm_is_err(ret)) goto error; ret = t2821_parse(prcpt, R2821_CORRECT); if (sm_is_err(ret)) goto error; if (!SMARRS_IS_FLAG(smar_rcpts, SMARRS_FL_NOADD)) { ret = smar_rcpts_add2ht(smar_rcpts, smar_rcpt); if (ret == sm_error_perm(SM_EM_AR, EEXIST)) { if (SMARR_IS_FLAG(smar_rcpt, SMARR_FL_TAKEIT)) { smar_rcpt_free(smar_rcpt, smar_rcpts); smar_rcpt = NULL; } a2821_free(pdomain); a2821_free(prcpt); return SM_SUCCESS; } if (sm_is_err(ret)) goto error; SMARR_CLR_FLAG(smar_rcpt, SMARR_FL_TAKEIT); } /* assign next index */ smar_rcpt->arr_idx = ++smar_rcpts->arrs_idx; if (owner_idx > 0) smar_rcpt->arr_owner_idx = owner_idx; if (SM_IS_FLAG(rflags, SMARR_FL_ISVERP)) { SMARR_SET_FLAG(smar_rcpt, SMARR_FL_HASVERP); rflags &= ~SMARR_FL_ISVERP; } SM_ASSERT(NULL == smar_rcpt->arr_domain_pa); smar_rcpt->arr_domain_pa = sm_str_new(NULL, MAXDOMAINLEN, MAXDOMAINLEN + 2); if (NULL == smar_rcpt->arr_domain_pa) goto enomem; /* extract domain out of rcpt_a ... and look it up */ ret = t2821_extract_domain(NULL, prcpt, &pdomain); if (sm_is_err(ret)) goto error; ret = t2821_str(pdomain, smar_rcpt->arr_domain_pa, 0); if (sm_is_err(ret)) goto error; a2821_free(pdomain); pdomain = NULL; sm_str2lower(smar_rcpt->arr_domain_pa); /* don't expand aliases? (just parse first recipient etc) */ if (!SMARRQ_IS_FLAG(smar_rcpt, SMARRQ_FL_ALIAS)) goto done; /* XXX use different length? */ mtstr = sm_str_new(NULL, MAXADDRLEN, MAXADDRLEN + 2); if (NULL == mtstr) goto enomem; if (SMAR_MT_FULL_LOOKUP(smar_ctx) || SMARRQ_IS_FLAG(smar_rcpt, SMARRQ_FL_CONF)) { hasdetail = smar_rcpt_parts(smar_ctx, smar_rcpt, prcpt); if (sm_is_err(hasdetail)) { SMAR_LEV_DPRINTF(3, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_expand, smar_rcpt_parts=%r\n", hasdetail)); goto error; } } if (SMARRQ_IS_FLAG(smar_rcpt, SMARRQ_FL_CONF) && smar_ctx->smar_access != NULL) { lfl = smar_rcpt->arr_cnf_lfl; if (hasdetail <= 0) lfl &= ~SMMAP_LFL_DET; /* XXX use different length? */ smar_rcpt->arr_rhs_conf = sm_str_new(NULL, MAXADDRLEN, MAXADDRLEN + 2); if (NULL == smar_rcpt->arr_rhs_conf) goto enomem; /* slight abuse of mtstr... */ sm_str_scat(mtstr, SC_RCPT_CONF_TAG); smar_rcpt->arr_maprescnf = sm_map_lookup_addr(smar_ctx->smar_access, smar_rcpt->arr_user_pa, SMARR_IS_FLAG(smar_rcpt, SMARR_FL_HASDET) ? smar_rcpt->arr_detail_pa : NULL, smar_rcpt->arr_domain_pa, /* tag */ mtstr, smar_rcpt->arr_delim, lfl, smar_rcpt->arr_rhs_conf); SMAR_LEV_DPRINTF(8, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_expand, pa=%S, lfl=%#x, conf=%#T, ret=%r\n", smar_rcpt->arr_pa, lfl, smar_rcpt->arr_rhs_conf, smar_rcpt->arr_maprescnf)); sm_str_clr(mtstr); } if (SMAR_MT_FULL_LOOKUP(smar_ctx)) { lfl = smar_ctx->smar_mt_lfl; if (!SMARR_IS_FLAG(smar_rcpt, SMARR_FL_HASDET)) lfl &= ~SMMAP_LFL_DET; ret = sm_map_lookup_addr(smar_ctx->smar_mt_map, smar_rcpt->arr_user_pa, SMARR_IS_FLAG(smar_rcpt, SMARR_FL_HASDET) ? smar_rcpt->arr_detail_pa : NULL, smar_rcpt->arr_domain_pa, /* tag */ NULL, smar_rcpt->arr_delim, lfl, mtstr); } else ret = sm_map_lookup(smar_ctx->smar_mt_map, 0, smar_rcpt->arr_domain_pa, mtstr); if (SM_SUCCESS == ret) ipv4s = (char *) sm_str_getdata(mtstr); else { ret = SM_SUCCESS; ipv4s = NULL; } SMAR_LEV_DPRINTF(7, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_expand, pa=%S, domain=%S, ipv4s=%.256s, rq_flags=%#x, flags=%#x, ali_flags=%#x\n", smar_rcpt->arr_pa, smar_rcpt->arr_domain_pa, ipv4s, smar_rcpt->arr_rqflags, smar_rcpt->arr_flags, smar_ctx->smar_cnf.smar_cnf_alias_fl)); /* no entry found but domain is a literal? */ if (ipv4s == NULL && sm_str_rd_elem(smar_rcpt->arr_domain_pa, 0) == '[') { /* use it... XXX what about free()ing data? */ ipv4s = (char *) sm_str_getdata(smar_rcpt->arr_domain_pa); } /* XXX HACK ahead, see also smtpc/smtpc.c */ islocaldomain = ipv4s != NULL && (strcmp(ipv4s, LMTP_IPV4_S2) == 0 || strcmp(ipv4s, LMTP_MT) == 0); /* ** do alias expansion if ** asking for (local) alias and local address ** or ** asking for remote alias ** <=> ** do not perform alias expansion if ** !(asking for (local) alias and local address) ** and ** !(asking for remote alias) */ if (!(SM_IS_FLAG(smar_ctx->smar_cnf.smar_cnf_alias_fl, SMARCNF_FL_MAP_LP|SMARCNF_FL_MAP_LD) && islocaldomain) && !SM_IS_FLAG(smar_ctx->smar_cnf.smar_cnf_alias_fl, SMARCNF_FL_MAP_ALL)) { if (islocaldomain && SMARRQ_IS_FLAG(smar_rcpt, SMARRQ_FL_CHK_LU)) SM_SET_FLAG(flags, SRE_FL_CHK_LU); else goto done; } if (NULL == smar_rcpt->arr_user_pa) { hasdetail = smar_rcpt_parts(smar_ctx, smar_rcpt, prcpt); if (sm_is_err(hasdetail)) { SMAR_LEV_DPRINTF(3, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_expand, smar_rcpt_parts=%r\n", hasdetail)); goto error; } } str = sm_str_new(NULL, MAXADDRLEN, MAXADDRLEN + 2); if (NULL == str) goto enomem; rhs = sm_str_new(NULL, MAXADDRLEN, MAXALIASLEN); if (NULL == rhs) goto enomem; if (SMARRQ_IS_FLAG(smar_rcpt, SMARRQ_FL_OWNER)) { owner = sm_str_new(NULL, MAXADDRLEN, MAXADDRLEN); if (NULL == owner) goto enomem; ownerrhs = sm_str_new(NULL, MAXADDRLEN, MAXADDRLEN); if (NULL == ownerrhs) goto enomem; } /* ** local address but no alias expansion? ** then check whether user exists. */ if (SM_IS_FLAG(flags, SRE_FL_CHK_LU)) { uint32_t status; status = SMTP_R_OK; ret = smar_addr_lu(smar_ctx, smar_rcpt->arr_user_pa, smar_rcpt->arr_delim, (hasdetail > 0) ? smar_rcpt->arr_detail_pa : NULL, smar_rcpt->arr_domain_pa, rhs, &status); SM_CLR_FLAG(flags, SRE_FL_CHK_LU); /* how to reject an address? */ if (status == SMTP_R_TEMP || status == SMTP_R_REJECT) smar_rcpt->arr_ret = status; goto done; } lfl = smar_ctx->smar_alias_lfl; if (hasdetail <= 0) lfl &= ~SMMAP_LFL_DET; ret = sm_map_lookup_addr(smar_ctx->smar_aliases, smar_rcpt->arr_user_pa, (hasdetail > 0) ? smar_rcpt->arr_detail_pa : NULL, SM_IS_FLAG(smar_ctx->smar_cnf.smar_cnf_alias_fl, SMARCNF_FL_MAP_LD|SMARCNF_FL_MAP_ALL) ? smar_rcpt->arr_domain_pa : NULL, /* tag */ NULL, smar_rcpt->arr_delim, lfl, rhs); SMAR_LEV_DPRINTF(3, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_expand, local=%S, domain=%S, rhs=%.256S, len=%u, lfl=%#x, ret=%r\n", smar_rcpt->arr_user_pa, smar_rcpt->arr_domain_pa, rhs, sm_str_getlen(rhs), lfl, ret)); /* not found? XXX need to deal with tempfail etc! */ if (sm_is_err(ret)) { if (!SM_MAP_ENTRY_NOTFOUND(ret)) goto error; if (islocaldomain && SMARRQ_IS_FLAG(smar_rcpt, SMARRQ_FL_CHK_LU)) { lfl = smar_ctx->smar_lum_lfl; if (hasdetail == 0) lfl &= ~SMMAP_LFL_DET; ret = sm_map_lookup_addr(smar_ctx->smar_lum, smar_rcpt->arr_user_pa, (hasdetail > 0) ? smar_rcpt->arr_detail_pa : NULL, (smar_rcpt->arr_domain_pa == NULL || sm_str_getlen(smar_rcpt->arr_domain_pa) == 0) ? NULL : smar_rcpt->arr_domain_pa, /* tag */ NULL, smar_rcpt->arr_delim, lfl, rhs); SMAR_LEV_DPRINTF(3, (SMAR_DEBFP, "func=smar_rcpt_expand, local=%S, detail=%S, rhs=%.256S, local_map_lookup=%r\n", smar_rcpt->arr_user_pa, smar_rcpt->arr_detail_pa, rhs, ret)); if (SM_MAP_ENTRY_NOTFOUND(ret)) smar_rcpt->arr_ret = SMTP_R_REJECT; else if (sm_is_err(ret)) goto error; } goto done; } SMARR_SET_FLAG(smar_rcpt, SMARR_FL_ISALIAS); #define ADDR_IS_LOCAL "local:" /* don't try further if rhs is ADDR_IS_LOCAL */ if (sm_str_getlen(rhs) == sizeof(ADDR_IS_LOCAL) - 1 && strncasecmp((const char*) sm_str_data(rhs), ADDR_IS_LOCAL, sm_str_getlen(rhs)) == 0) { SMARR_SET_FLAG(smar_rcpt, SMARR_FL_LU); goto done; } /* check for error: (even before testing "NOEXP"?) */ if (sm_str_getlen(rhs) >= RHS_ERROR_LEN && strncasecmp((const char*) sm_str_data(rhs), RHS_ERROR, RHS_ERROR_LEN) == 0) { /* don't accept mail for this address */ if (sm_str_getlen(rhs) >= RHS_ERROR_4_LEN && strncasecmp((const char*) sm_str_data(rhs), RHS_ERROR_4, RHS_ERROR_4_LEN) == 0) { ret = SMTP_R_TEMP; } else ret = SMTP_R_REJECT; goto error; } /* don't rewrite address if no expand flag is set */ if (SMARRQ_IS_FLAG(smar_rcpt, SMARRQ_FL_NOEXP)) goto done; #define SM_A_OWNER "owner-" /* check for owner- */ if (SMARRQ_IS_FLAG(smar_rcpt, SMARRQ_FL_OWNER)) { sm_str_clr(owner); ret = sm_str_scat(owner, SM_A_OWNER); if (sm_is_err(ret)) goto error; ret = sm_str_cat(owner, smar_rcpt->arr_user_pa); if (sm_is_err(ret)) goto error; sm_str_clr(ownerrhs); ret = sm_map_lookup_addr(smar_ctx->smar_aliases, owner, (hasdetail > 0) ? smar_rcpt->arr_detail_pa : NULL, SM_IS_FLAG(smar_ctx->smar_cnf.smar_cnf_alias_fl, SMARCNF_FL_MAP_LD|SMARCNF_FL_MAP_ALL) ? smar_rcpt->arr_domain_pa : NULL, /* tag */ NULL, smar_rcpt->arr_delim, lfl & ~SMMAP_LFL_DOMAIN, ownerrhs); /* must match owner-, not just @domain */ SMAR_LEV_DPRINTF(3, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_expand, owner=%S, domain=%S, ownerrhs=%.256S, ret=%r\n", owner, smar_rcpt->arr_domain_pa, ownerrhs, ret)); /* XXX need to deal with tempfail etc */ if (sm_is_success(ret) && !(sm_str_getlen(ownerrhs) >= RHS_ERROR_LEN && strncasecmp((const char*) sm_str_data(ownerrhs), RHS_ERROR, RHS_ERROR_LEN) == 0)) { SMARR_SET_FLAG(smar_rcpt, SMARR_FL_ISOWN); owner_idx = smar_rcpt->arr_idx; smar_rcpt->arr_owner_pa = sm_str_new(NULL, MAXADDRLEN, MAXADDRLEN); if (NULL == smar_rcpt->arr_owner_pa) goto enomem; /* XXX cleanup? */ ret = sm_strprintf(smar_rcpt->arr_owner_pa, "<%s%S@%S>", SM_A_OWNER, smar_rcpt->arr_user_pa, smar_rcpt->arr_domain_pa); if (sm_is_err(ret) || ret >= sm_str_getmax(smar_rcpt->arr_owner_pa)) goto error; /* XXX cleanup? */ } } #define SM_A_VERP "verp-" /* check for verp- (almost identical to owner- check) */ if (SMARRQ_IS_FLAG(smar_rcpt, SMARRQ_FL_VERP) && !SMARR_IS_FLAG(smar_rcpt, SMARR_FL_ISOWN)) { sm_str_clr(owner); ret = sm_str_scat(owner, SM_A_VERP); if (sm_is_err(ret)) goto error; ret = sm_str_cat(owner, smar_rcpt->arr_user_pa); if (sm_is_err(ret)) goto error; sm_str_clr(ownerrhs); ret = sm_map_lookup_addr(smar_ctx->smar_aliases, owner, (hasdetail > 0) ? smar_rcpt->arr_detail_pa : NULL, SM_IS_FLAG(smar_ctx->smar_cnf.smar_cnf_alias_fl, SMARCNF_FL_MAP_LD|SMARCNF_FL_MAP_ALL) ? smar_rcpt->arr_domain_pa : NULL, /* tag */ NULL, smar_rcpt->arr_delim, lfl & ~SMMAP_LFL_DOMAIN, ownerrhs); /* must match owner-, not just @domain */ SMAR_LEV_DPRINTF(3, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_expand, verp=%S, domain=%S, verprhs=%.256S, ret=%r\n", owner, smar_rcpt->arr_domain_pa, ownerrhs, ret)); /* XXX need to deal with tempfail etc */ if (sm_is_success(ret) && !(sm_str_getlen(ownerrhs) >= RHS_ERROR_LEN && strncasecmp((const char*) sm_str_data(ownerrhs), RHS_ERROR, RHS_ERROR_LEN) == 0)) { SMARR_SET_FLAG(smar_rcpt, SMARR_FL_ISVERP); rflags |= SMARR_FL_ISVERP; owner_idx = smar_rcpt->arr_idx; smar_rcpt->arr_owner_pa = sm_str_new(NULL, MAXADDRLEN, MAXADDRLEN); if (NULL == smar_rcpt->arr_owner_pa) goto enomem; /* XXX cleanup? */ ret = sm_strprintf(smar_rcpt->arr_owner_pa, "<%s%S@%S>", SM_A_OWNER, smar_rcpt->arr_user_pa, smar_rcpt->arr_domain_pa); if (sm_is_err(ret) || ret >= sm_str_getmax(smar_rcpt->arr_owner_pa)) goto error; /* XXX cleanup? */ } } /* rewrite address... */ /* ** RHS must be an RFC2821 address or just a localpart. */ idx = 0; SM_CLR_FLAG(flags, SRE_FL_IDENTICAL); do { /* skip over leading spaces */ while (sm_str_getlen(rhs) > idx && ISSPACE(sm_str_rd_elem(rhs, idx))) ++idx; /* no more data? */ if (sm_str_getlen(rhs) <= idx) break; /* ** Convert localpart to ** (or use the domain of the original address) ** Note: This is broken for ** alias: local ** A real address parser should be used which supports ** unqualified addresses! */ if (sm_str_getlen(rhs) > idx && sm_str_rd_elem(rhs, idx) != (uchar) '<') { sm_str_P exchg; bool preserve_domain; preserve_domain = SM_IS_FLAG(smar_ctx->smar_cnf.smar_cnf_alias_fl, SMARCNF_FL_MAP_PD) && sm_str_getlen(smar_rcpt->arr_domain_pa) > 0; sm_str_clr(str); if (sm_str_put(str, (uchar) '<') != SM_SUCCESS || sm_str_cat(str, rhs) != SM_SUCCESS || sm_str_put(str, (uchar) '@') != SM_SUCCESS || sm_str_cat(str, preserve_domain ? smar_rcpt->arr_domain_pa : smar_ctx->smar_hostname) != SM_SUCCESS || sm_str_put(str, (uchar) '>') != SM_SUCCESS) { SMAR_LEV_DPRINTF(3, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_expand, convert_localpart=%m\n", ret)); goto error; } /* exchange rhs and str */ exchg = rhs; rhs = str; str = exchg; } a2821_free(prcpt); A2821_INIT_RP(prcpt, NULL); offset = t2821_scan((sm_rdstr_P) rhs, prcpt, idx); if (sm_is_err(offset)) { ret = offset; sm_log_write(smar_ctx->smar_lctx, AR_LCAT_RESOLVER, AR_LMOD_RESOLVER, SM_LOG_ERROR, 3, "sev=ERROR, func=smar_rcpt_expand, alias=%.256S, t2821_scan=%m" , rhs, ret); goto error; } if (sm_is_err(ret = t2821_parse(prcpt, R2821_CORRECT))) { sm_log_write(smar_ctx->smar_lctx, AR_LCAT_RESOLVER, AR_LMOD_RESOLVER, SM_LOG_ERROR, 3, "sev=ERROR, func=smar_rcpt_expand, alias=%.256S, t2821_parse=%m" , rhs, ret); goto error; } SM_ASSERT(offset >= 0); idx = offset; /* set new index to end of current address */ if (smar_rcpts->arrs_addr != NULL && SMARA_IS_LFLAG(smar_rcpts->arrs_addr, SMARA_LFL_PROTMAP) ? T2821_FL_NOANGLE : 0) SM_SET_FLAG(flags, SRE_FL_USE_MAP); sm_str_clr(str); ret = t2821_str(prcpt, str, SM_IS_FLAG(flags, SRE_FL_USE_MAP) ? T2821_FL_NOANGLE : 0); if (sm_is_err(ret)) { SMAR_LEV_DPRINTF(3, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_expand, t2821_str=%r\n", ret)); goto error; } SMAR_LEV_DPRINTF(3, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_expand, new=%.256S, len=%d, old=%.256S, len=%d, flags=%#x\n", str, sm_str_getlen(str), smar_rcpt->arr_pa, sm_str_getlen(smar_rcpt->arr_pa), flags)); /* ** Compare new address with old address? ** Stop if they are identical (instead of ** relying on recursion limit). ** XXX Need "address equal" function: ** domain part: case insensitive ** local part: depends on configuration */ if (sm_str_getlen(smar_rcpt->arr_pa) == sm_str_getlen(str) && strncmp((char *) sm_str_data(smar_rcpt->arr_pa), (char *) sm_str_data(str), sm_str_getlen(str)) == 0) { SM_SET_FLAG(flags, SRE_FL_IDENTICAL); continue; } if (SM_IS_FLAG(flags, SRE_FL_USE_MAP)) { smar_addr_P smar_addr; smar_addr = smar_rcpts->arrs_addr; sm_str_clr(smar_addr->ara_rhs2); ret = sm_map_setopt(smar_addr->ara_str_map, SMPO_STR, str, SMPO_END); if (sm_is_err(ret)) goto error; ret = sm_map_lookup_addr(smar_addr->ara_str_map, smar_addr->ara_user2, smar_addr->ara_detail2, smar_addr->ara_domain_pa2, NULL, smar_addr->ara_delim, /* Need to let caller (client) pass in other flags? */ SMMAP_LFL_ALL|SMMAP_LFL_NOAT| (SMARA_IS_LFLAG(smar_addr, SMARA_LFL_PROTIMPLDET) ? SMMAP_LFL_IMPLDET : 0), smar_addr->ara_rhs2); SMAR_LEV_DPRINTF(9, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_expand, where=lookup, user2=%S, ret=%r\n", smar_addr->ara_user2, ret)); if (sm_is_success(ret)) { SMARRS_SET_FLAG(smar_rcpts, SMARRS_FL_FOUND); goto done; } ret = SM_SUCCESS; continue; } if (SMARRS_IS_FLAG(smar_rcpts, SMARRS_FL_COMP)) { SMAR_LEV_DPRINTF(9, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_expand, where=compare, pa=%#S, str=%#S\n", smar_rcpts->arrs_pa, str)); if (sm_str_getlen(smar_rcpts->arrs_pa) == sm_str_getlen(str) && strncasecmp((char *) sm_str_data(smar_rcpts->arrs_pa), (char *) sm_str_data(str), sm_str_getlen(str)) == 0) { SMAR_LEV_DPRINTF(8, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_expand, where=compare, pa=%#S, str=%#S, status=found\n", smar_rcpts->arrs_pa, str)); SMARRS_SET_FLAG(smar_rcpts, SMARRS_FL_FOUND); goto done; } ret = SM_SUCCESS; continue; } if (SMARRS_IS_FLAG(smar_rcpts, SMARRS_FL_NOREC)) continue; ret = smar_rcpt_new(&smar_rcpt_a); if (sm_is_err(ret)) goto error; /* XXX more cleanup? */ SMAR_LEV_DPRINTF(7, (SMAR_DEBFP, "sev=DBG, func=smar_rcpt_expand, where=smar_rcpt_new, smar_rcpt_a=%p\n", smar_rcpt_a)); /* XXX fill in data... */ /* XXX some of this data should only be in rcpts, not in each rcpt that requires to rewrite the functions that access that data via rcpt */ smar_rcpt_a->arr_pa = str; str = sm_str_new(NULL, MAXADDRLEN, MAXADDRLEN + 2); if (NULL == str) goto enomem; smar_rcpt_a->arr_timeout = smar_rcpt->arr_timeout; smar_rcpt_a->arr_rqflags = smar_rcpt->arr_rqflags; RCPT_ID_COPY(smar_rcpt_a->arr_id, smar_rcpt->arr_id); SMARR_SET_FLAG(smar_rcpt_a, SMARR_FL_TAKEIT); ret = smar_rcpt_expand(smar_rcpts, smar_rcpt_a, owner_idx, rflags, level + 1); smar_rcpt_a = NULL; if (sm_is_err(ret)) goto error; } while (sm_is_success(ret) && sm_str_getlen(rhs) > idx); /* ** The recipient has only been replaced if there wasn't an identical ** address on the RHS. */ if (!SM_IS_FLAG(flags, SRE_FL_IDENTICAL)) SMARR_SET_FLAG(smar_rcpt, SMARR_FL_EXPD); done: /* * Hack: try to figure out whether smar_rcpt_parts() should be called. * Note: prcpt can change but by then smar_rcpt_parts() was called and * hence arr_user_pa is set. */ if (SMAR_MT_FULL_LOOKUP(smar_ctx) && NULL == smar_rcpt->arr_user_pa) { ret = smar_rcpt_parts(smar_ctx, smar_rcpt, prcpt); if (sm_is_err(ret)) goto error; } SM_STR_FREE(str); SM_STR_FREE(rhs); SM_STR_FREE(mtstr); SM_STR_FREE(owner); SM_STR_FREE(ownerrhs); a2821_free(prcpt); prcpt = NULL; return SM_SUCCESS; enomem: ret = sm_error_temp(SM_EM_AR, ENOMEM); error: SMAR_LEV_DPRINTF(3, (SMAR_DEBFP, "sev=ERROR, func=smar_rcpt_expand, smar_rcpt=%p, flags=%#x\n", smar_rcpt, smar_rcpt == NULL ? 0xffffffff : smar_rcpt->arr_flags)); SM_STR_FREE(str); SM_STR_FREE(rhs); SM_STR_FREE(mtstr); SM_STR_FREE(owner); SM_STR_FREE(ownerrhs); a2821_free(pdomain); pdomain = NULL; a2821_free(prcpt); prcpt = NULL; sm_log_write(smar_ctx->smar_lctx, AR_LCAT_RESOLVER, AR_LMOD_RESOLVER, SM_LOG_ERROR, 0, "sev=ERROR, func=smar_rcpt_expand, rcpt_pa=%.256S, ret=%m" , smar_rcpt == NULL ? 0 : smar_rcpt->arr_pa, ret); if (smar_rcpt_a != NULL) { smar_rcpt_free(smar_rcpt_a, smar_rcpts); smar_rcpt_a = NULL; } if (smar_rcpt != NULL && SMARR_IS_FLAG(smar_rcpt, SMARR_FL_TAKEIT)) { smar_rcpt_free(smar_rcpt, smar_rcpts); smar_rcpt = NULL; } return ret; }