/* Compare a domain name with a possibly-wildcarded name. Wildcards are restricted to a single one, as the first element of patterns having at least three dot-separated elements. Case-independent. Return TRUE for a match */ static BOOL is_name_match(const uschar * name, const uschar * pat) { uschar * cp; return *pat == '*' /* possible wildcard match */ ? *++pat == '.' /* starts star, dot */ && !Ustrchr(++pat, '*') /* has no more stars */ && Ustrchr(pat, '.') /* and has another dot. */ && (cp = Ustrchr(name, '.'))/* The name has at least one dot */ && strcmpic(++cp, pat) == 0 /* and we only compare after it. */ : !Ustrchr(pat+1, '*') && strcmpic(name, pat) == 0; }
static pcre_list * compile(const uschar * list) { int sep = 0; uschar *regex_string; const char *pcre_error; int pcre_erroffset; pcre_list *re_list_head = NULL; pcre_list *ri; /* precompile our regexes */ while ((regex_string = string_nextinlist(&list, &sep, NULL, 0))) if (strcmpic(regex_string, US"false") != 0 && Ustrcmp(regex_string, "0") != 0) { pcre *re; /* compile our regular expression */ if (!(re = pcre_compile( CS regex_string, 0, &pcre_error, &pcre_erroffset, NULL ))) { log_write(0, LOG_MAIN, "regex acl condition warning - error in regex '%s': %s at offset %d, skipped.", regex_string, pcre_error, pcre_erroffset); continue; } ri = store_get(sizeof(pcre_list)); ri->re = re; ri->pcre_text = regex_string; ri->next = re_list_head; re_list_head = ri; } return re_list_head; }
void dkim_exim_acl_setup(uschar *id) { pdkim_signature *sig = dkim_signatures; dkim_cur_sig = NULL; dkim_cur_signer = id; if (dkim_disable_verify || !id || !dkim_verify_ctx) return; /* Find signature to run ACL on */ while (sig != NULL) { uschar *cmp_val = NULL; if (Ustrchr(id,'@') != NULL) cmp_val = (uschar *)sig->identity; else cmp_val = (uschar *)sig->domain; if (cmp_val && (strcmpic(cmp_val,id) == 0)) { dkim_cur_sig = sig; /* The "dkim_domain" and "dkim_selector" expansion variables have related globals, since they are used in the signing code too. Instead of inventing separate names for verification, we set them here. This is easy since a domain and selector is guaranteed to be in a signature. The other dkim_* expansion items are dynamically fetched from dkim_cur_sig at expansion time (see function below). */ dkim_signing_domain = (uschar *)sig->domain; dkim_signing_selector = (uschar *)sig->selector; return; } sig = sig->next; } }
/* * Return 1 iff the string is "UUCP" (ignore case). */ int isuucp(char *str) { if(strcmpic(str, "UUCP") == 0) { return(1); } else { return(0); } }
/* * The user name "postmaster" must be accepted regardless of what * combination of upper and lower case is used. This function is * used to convert all case variants of "postmaster" to all lower * case. If the user name passed in is not "postmaster", it is * returned unchanged. */ char *postmaster(char *user) { static char *pm = "postmaster"; if(strcmpic(user, pm) == 0) { return(pm); } else { return(user); } }
void sort_rank::sort_snippets(std::vector<search_snippet*> &snippets, const hash_map<const char*, const char*, hash<const char*>, eqstr> *parameters) { // sort by rank, date or activiy. const char *order = miscutil::lookup(parameters,"order"); if (!order || strcmpic(order,"rank")==0) std::stable_sort(snippets.begin(),snippets.end(), search_snippet::max_seeks_rank); else if (strcmpic(order,"new-date")==0) { std::stable_sort(snippets.begin(),snippets.end(), search_snippet::new_date); } else if (strcmpic(order,"old-date")==0) { std::stable_sort(snippets.begin(),snippets.end(), search_snippet::old_date); } else if (strcmpic(order,"new-activity")==0) { std::stable_sort(snippets.begin(),snippets.end(), search_snippet::new_activity); } else if (strcmpic(order,"old-activity")==0) { std::stable_sort(snippets.begin(),snippets.end(), search_snippet::old_activity); } else { // log error, and default to max seeks rank ordering. errlog::log_error(LOG_LEVEL_ERROR,"wrong search result order parameter %s, ordering by seeks rank as default", order); std::stable_sort(snippets.begin(),snippets.end(), search_snippet::max_seeks_rank); } }
static int header_name_match(const uschar * header, uschar * tick) { uschar * hname; uschar * lcopy; uschar * p; uschar * q; uschar * hcolon = Ustrchr(header, ':'); /* Get header name */ if (!hcolon) return PDKIM_FAIL; /* This isn't a header */ /* if we had strncmpic() we wouldn't need this copy */ hname = string_copyn(header, hcolon-header); /* Copy tick-off list locally, so we can punch zeroes into it */ p = lcopy = string_copy(tick); for (q = Ustrchr(p, ':'); q; q = Ustrchr(p, ':')) { *q = '\0'; if (strcmpic(p, hname) == 0) goto found; p = q+1; } if (strcmpic(p, hname) == 0) goto found; return PDKIM_FAIL; found: /* Invalidate header name instance in tick-off list */ tick[p-lcopy] = '_'; return PDKIM_OK; }
/********************************************************************* * * Function : unknown_method * * Description : Checks whether a method is unknown. * * Parameters : * 1 : method = points to a http method * * Returns : TRUE if it's unknown, FALSE otherwise. * *********************************************************************/ static int unknown_method(const char *method) { static const char * const known_http_methods[] = { /* Basic HTTP request type */ "GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS", "TRACE", "CONNECT", /* webDAV extensions (RFC2518) */ "PROPFIND", "PROPPATCH", "MOVE", "COPY", "MKCOL", "LOCK", "UNLOCK", /* * Microsoft webDAV extension for Exchange 2000. See: * http://lists.w3.org/Archives/Public/w3c-dist-auth/2002JanMar/0001.html * http://msdn.microsoft.com/library/en-us/wss/wss/_webdav_methods.asp */ "BCOPY", "BMOVE", "BDELETE", "BPROPFIND", "BPROPPATCH", /* * Another Microsoft webDAV extension for Exchange 2000. See: * http://systems.cs.colorado.edu/grunwald/MobileComputing/Papers/draft-cohen-gena-p-base-00.txt * http://lists.w3.org/Archives/Public/w3c-dist-auth/2002JanMar/0001.html * http://msdn.microsoft.com/library/en-us/wss/wss/_webdav_methods.asp */ "SUBSCRIBE", "UNSUBSCRIBE", "NOTIFY", "POLL", /* * Yet another WebDAV extension, this time for * Web Distributed Authoring and Versioning (RFC3253) */ "VERSION-CONTROL", "REPORT", "CHECKOUT", "CHECKIN", "UNCHECKOUT", "MKWORKSPACE", "UPDATE", "LABEL", "MERGE", "BASELINE-CONTROL", "MKACTIVITY", /* * The PATCH method is defined by RFC5789, the format of the * actual patch in the body depends on the application, but from * Privoxy's point of view it doesn't matter. */ "PATCH", }; int i; for (i = 0; i < SZ(known_http_methods); i++) { if (0 == strcmpic(method, known_http_methods[i])) { return FALSE; } } return TRUE; }
int dnslookup_router_entry( router_instance *rblock, /* data for this instantiation */ address_item *addr, /* address we are working on */ struct passwd *pw, /* passwd entry after check_local_user */ int verify, /* v_none/v_recipient/v_sender/v_expn */ address_item **addr_local, /* add it to this if it's local */ address_item **addr_remote, /* add it to this if it's remote */ address_item **addr_new, /* put new addresses on here */ address_item **addr_succeed) /* put old address here on success */ { host_item h; int rc; int widen_sep = 0; int whichrrs = HOST_FIND_BY_MX | HOST_FIND_BY_A; dnslookup_router_options_block *ob = (dnslookup_router_options_block *)(rblock->options_block); uschar *srv_service = NULL; uschar *widen = NULL; uschar *pre_widen = addr->domain; uschar *post_widen = NULL; uschar *fully_qualified_name; uschar *listptr; uschar widen_buffer[256]; addr_new = addr_new; /* Keep picky compilers happy */ addr_succeed = addr_succeed; DEBUG(D_route) debug_printf("%s router called for %s\n domain = %s\n", rblock->name, addr->address, addr->domain); /* If an SRV check is required, expand the service name */ if (ob->check_srv != NULL) { srv_service = expand_string(ob->check_srv); if (srv_service == NULL && !expand_string_forcedfail) { addr->message = string_sprintf("%s router: failed to expand \"%s\": %s", rblock->name, ob->check_srv, expand_string_message); return DEFER; } else whichrrs |= HOST_FIND_BY_SRV; } /* Set up the first of any widening domains. The code further down copes with either pre- or post-widening, but at present there is no way to turn on pre-widening, as actually doing so seems like a rather bad idea, and nobody has requested it. Pre-widening would cause local abbreviated names to take precedence over global names. For example, if the domain is "xxx.ch" it might be something in the "ch" toplevel domain, but it also might be xxx.ch.xyz.com. The choice of pre- or post-widening affects which takes precedence. If ever somebody comes up with some kind of requirement for pre-widening, presumably with some conditions under which it is done, it can be selected here. The rewrite_headers option works only when routing an address at transport time, because the alterations to the headers are not persistent so must be worked out immediately before they are used. Sender addresses are routed for verification purposes, but never at transport time, so any header changes that you might expect as a result of sender domain widening do not occur. Therefore we do not perform widening when verifying sender addresses; however, widening sender addresses is OK if we do not have to rewrite the headers. A corollary of this is that if the current address is not the original address, then it does not appear in the message header so it is also OK to widen. The suppression of widening for sender addresses is silent because it is the normal desirable behaviour. */ if (ob->widen_domains != NULL && (verify != v_sender || !ob->rewrite_headers || addr->parent != NULL)) { listptr = ob->widen_domains; widen = string_nextinlist(&listptr, &widen_sep, widen_buffer, sizeof(widen_buffer)); /**** if (some condition requiring pre-widening) { post_widen = pre_widen; pre_widen = NULL; } ****/ } /* Loop to cope with explicit widening of domains as configured. This code copes with widening that may happen before or after the original name. The decision as to which is taken above. */ for (;;) { int flags = whichrrs; BOOL removed = FALSE; if (pre_widen != NULL) { h.name = pre_widen; pre_widen = NULL; } else if (widen != NULL) { h.name = string_sprintf("%s.%s", addr->domain, widen); widen = string_nextinlist(&listptr, &widen_sep, widen_buffer, sizeof(widen_buffer)); DEBUG(D_route) debug_printf("%s router widened %s to %s\n", rblock->name, addr->domain, h.name); } else if (post_widen != NULL) { h.name = post_widen; post_widen = NULL; DEBUG(D_route) debug_printf("%s router trying %s after widening failed\n", rblock->name, h.name); } else return DECLINE; /* Set up the rest of the initial host item. Others may get chained on if there is more than one IP address. We set it up here instead of outside the loop so as to re-initialize if a previous try succeeded but was rejected because of not having an MX record. */ h.next = NULL; h.address = NULL; h.port = PORT_NONE; h.mx = MX_NONE; h.status = hstatus_unknown; h.why = hwhy_unknown; h.last_try = 0; /* Unfortunately, we cannot set the mx_only option in advance, because the DNS lookup may extend an unqualified name. Therefore, we must do the test subsequently. We use the same logic as that for widen_domains above to avoid requesting a header rewrite that cannot work. */ if (verify != v_sender || !ob->rewrite_headers || addr->parent != NULL) { if (ob->qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE; if (ob->search_parents) flags |= HOST_FIND_SEARCH_PARENTS; } rc = host_find_bydns(&h, rblock->ignore_target_hosts, flags, srv_service, ob->srv_fail_domains, ob->mx_fail_domains, &fully_qualified_name, &removed); if (removed) setflag(addr, af_local_host_removed); /* If host found with only address records, test for the domain's being in the mx_domains list. Note that this applies also to SRV records; the name of the option is historical. */ if ((rc == HOST_FOUND || rc == HOST_FOUND_LOCAL) && h.mx < 0 && ob->mx_domains != NULL) { switch(match_isinlist(fully_qualified_name, &(ob->mx_domains), 0, &domainlist_anchor, addr->domain_cache, MCL_DOMAIN, TRUE, NULL)) { case DEFER: addr->message = US"lookup defer for mx_domains"; return DEFER; case OK: DEBUG(D_route) debug_printf("%s router rejected %s: no MX record(s)\n", rblock->name, fully_qualified_name); continue; } } /* Deferral returns forthwith, and anything other than failure breaks the loop. */ if (rc == HOST_FIND_AGAIN) { if (rblock->pass_on_timeout) { DEBUG(D_route) debug_printf("%s router timed out, and pass_on_timeout is set\n", rblock->name); return PASS; } addr->message = US"host lookup did not complete"; return DEFER; } if (rc != HOST_FIND_FAILED) break; /* Check to see if the failure is the result of MX records pointing to non-existent domains, and if so, set an appropriate error message; the case of an MX or SRV record pointing to "." is another special case that we can detect. Otherwise "unknown mail domain" is used, which is confusing. Also, in this case don't do the widening. We need check only the first host to see if its MX has been filled in, but there is no address, because if there were any usable addresses returned, we would not have had HOST_FIND_FAILED. As a common cause of this problem is MX records with IP addresses on the RHS, give a special message in this case. */ if (h.mx >= 0 && h.address == NULL) { setflag(addr, af_pass_message); /* This is not a security risk */ if (h.name[0] == 0) addr->message = US"an MX or SRV record indicated no SMTP service"; else { addr->message = US"all relevant MX records point to non-existent hosts"; if (!allow_mx_to_ip && string_is_ip_address(h.name, NULL) != 0) { addr->user_message = string_sprintf("It appears that the DNS operator for %s\n" "has installed an invalid MX record with an IP address\n" "instead of a domain name on the right hand side.", addr->domain); addr->message = string_sprintf("%s or (invalidly) to IP addresses", addr->message); } } return DECLINE; } /* If there's a syntax error, do not continue with any widening, and note the error. */ if (host_find_failed_syntax) { addr->message = string_sprintf("mail domain \"%s\" is syntactically " "invalid", h.name); return DECLINE; } } /* If the original domain name has been changed as a result of the host lookup, set up a child address for rerouting and request header rewrites if so configured. Then yield REROUTED. However, if the only change is a change of case in the domain name, which some resolvers yield (others don't), just change the domain name in the original address so that the official version is used in RCPT commands. */ if (Ustrcmp(addr->domain, fully_qualified_name) != 0) { if (strcmpic(addr->domain, fully_qualified_name) == 0) { uschar *at = Ustrrchr(addr->address, '@'); memcpy(at+1, fully_qualified_name, Ustrlen(at+1)); } else { rf_change_domain(addr, fully_qualified_name, ob->rewrite_headers, addr_new); return REROUTED; } } /* If the yield is HOST_FOUND_LOCAL, the remote domain name either found MX records with the lowest numbered one pointing to a host with an IP address that is set on one of the interfaces of this machine, or found A records or got addresses from gethostbyname() that contain one for this machine. This can happen quite legitimately if the original name was a shortened form of a domain, but we will have picked that up already via the name change test above. Otherwise, the action to be taken can be configured by the self option, the handling of which is in a separate function, as it is also required for other routers. */ if (rc == HOST_FOUND_LOCAL) { rc = rf_self_action(addr, &h, rblock->self_code, rblock->self_rewrite, rblock->self, addr_new); if (rc != OK) return rc; } /* Otherwise, insist on being a secondary MX if so configured */ else if (ob->check_secondary_mx && !testflag(addr, af_local_host_removed)) { DEBUG(D_route) debug_printf("check_secondary_mx set and local host not secondary\n"); return DECLINE; } /* Set up the errors address, if any. */ rc = rf_get_errors_address(addr, rblock, verify, &(addr->p.errors_address)); if (rc != OK) return rc; /* Set up the additional and removeable headers for this address. */ rc = rf_get_munge_headers(addr, rblock, &(addr->p.extra_headers), &(addr->p.remove_headers)); if (rc != OK) return rc; /* Get store in which to preserve the original host item, chained on to the address. */ addr->host_list = store_get(sizeof(host_item)); addr->host_list[0] = h; /* Fill in the transport and queue the address for delivery. */ if (!rf_get_transport(rblock->transport_name, &(rblock->transport), addr, rblock->name, NULL)) return DEFER; addr->transport = rblock->transport; return rf_queue_add(addr, addr_local, addr_remote, rblock, pw)? OK : DEFER; }
int dns_special_lookup(dns_answer *dnsa, const uschar *name, int type, const uschar **fully_qualified_name) { switch (type) { /* The "mx hosts only" type doesn't require any special action here */ case T_MXH: return dns_lookup(dnsa, name, T_MX, fully_qualified_name); /* Find nameservers for the domain or the nearest enclosing zone, excluding the root servers. */ case T_ZNS: type = T_NS; /* FALLTHROUGH */ case T_SOA: { const uschar *d = name; while (d != 0) { int rc = dns_lookup(dnsa, d, type, fully_qualified_name); if (rc != DNS_NOMATCH && rc != DNS_NODATA) return rc; while (*d != 0 && *d != '.') d++; if (*d++ == 0) break; } return DNS_NOMATCH; } /* Try to look up the Client SMTP Authorization SRV record for the name. If there isn't one, search from the top downwards for a CSA record in a parent domain, which might be making assertions about subdomains. If we find a record we set fully_qualified_name to whichever lookup succeeded, so that the caller can tell whether to look at the explicit authorization field or the subdomain assertion field. */ case T_CSA: { uschar *srvname, *namesuff, *tld, *p; int priority, weight, port; int limit, rc, i; BOOL ipv6; dns_record *rr; dns_scan dnss; DEBUG(D_dns) debug_printf("CSA lookup of %s\n", name); srvname = string_sprintf("_client._smtp.%s", name); rc = dns_lookup(dnsa, srvname, T_SRV, NULL); if (rc == DNS_SUCCEED || rc == DNS_AGAIN) { if (rc == DNS_SUCCEED) *fully_qualified_name = string_copy(name); return rc; } /* Search for CSA subdomain assertion SRV records from the top downwards, starting with the 2nd level domain. This order maximizes cache-friendliness. We skip the top level domains to avoid loading their nameservers and because we know they'll never have CSA SRV records. */ namesuff = Ustrrchr(name, '.'); if (namesuff == NULL) return DNS_NOMATCH; tld = namesuff + 1; ipv6 = FALSE; limit = dns_csa_search_limit; /* Use more appropriate search parameters if we are in the reverse DNS. */ if (strcmpic(namesuff, US".arpa") == 0) if (namesuff - 8 > name && strcmpic(namesuff - 8, US".in-addr.arpa") == 0) { namesuff -= 8; tld = namesuff + 1; limit = 3; } else if (namesuff - 4 > name && strcmpic(namesuff - 4, US".ip6.arpa") == 0) { namesuff -= 4; tld = namesuff + 1; ipv6 = TRUE; limit = 3; } DEBUG(D_dns) debug_printf("CSA TLD %s\n", tld); /* Do not perform the search if the top level or 2nd level domains do not exist. This is quite common, and when it occurs all the search queries would go to the root or TLD name servers, which is not friendly. So we check the AUTHORITY section; if it contains the root's SOA record or the TLD's SOA then the TLD or the 2LD (respectively) doesn't exist and we can skip the search. If the TLD and the 2LD exist but the explicit CSA record lookup failed, then the AUTHORITY SOA will be the 2LD's or a subdomain thereof. */ if (rc == DNS_NOMATCH) { /* This is really gross. The successful return value from res_search() is the packet length, which is stored in dnsa->answerlen. If we get a negative DNS reply then res_search() returns -1, which causes the bounds checks for name decompression to fail when it is treated as a packet length, which in turn causes the authority search to fail. The correct packet length has been lost inside libresolv, so we have to guess a replacement value. (The only way to fix this properly would be to re-implement res_search() and res_query() so that they don't muddle their success and packet length return values.) For added safety we only reset the packet length if the packet header looks plausible. */ HEADER *h = (HEADER *)dnsa->answer; if (h->qr == 1 && h->opcode == QUERY && h->tc == 0 && (h->rcode == NOERROR || h->rcode == NXDOMAIN) && ntohs(h->qdcount) == 1 && ntohs(h->ancount) == 0 && ntohs(h->nscount) >= 1) dnsa->answerlen = MAXPACKET; for (rr = dns_next_rr(dnsa, &dnss, RESET_AUTHORITY); rr; rr = dns_next_rr(dnsa, &dnss, RESET_NEXT) ) if (rr->type != T_SOA) continue; else if (strcmpic(rr->name, US"") == 0 || strcmpic(rr->name, tld) == 0) return DNS_NOMATCH; else break; } for (i = 0; i < limit; i++) { if (ipv6) { /* Scan through the IPv6 reverse DNS in chunks of 16 bits worth of IP address, i.e. 4 hex chars and 4 dots, i.e. 8 chars. */ namesuff -= 8; if (namesuff <= name) return DNS_NOMATCH; } else /* Find the start of the preceding domain name label. */ do if (--namesuff <= name) return DNS_NOMATCH; while (*namesuff != '.'); DEBUG(D_dns) debug_printf("CSA parent search at %s\n", namesuff + 1); srvname = string_sprintf("_client._smtp.%s", namesuff + 1); rc = dns_lookup(dnsa, srvname, T_SRV, NULL); if (rc == DNS_AGAIN) return rc; if (rc != DNS_SUCCEED) continue; /* Check that the SRV record we have found is worth returning. We don't just return the first one we find, because some lower level SRV record might make stricter assertions than its parent domain. */ for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr; rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) { if (rr->type != T_SRV) continue; /* Extract the numerical SRV fields (p is incremented) */ p = rr->data; GETSHORT(priority, p); GETSHORT(weight, p); weight = weight; /* compiler quietening */ GETSHORT(port, p); /* Check the CSA version number */ if (priority != 1) continue; /* If it's making an interesting assertion, return this response. */ if (port & 1) { *fully_qualified_name = namesuff + 1; return DNS_SUCCEED; } } } return DNS_NOMATCH; } default: if (type >= 0) return dns_lookup(dnsa, name, type, fully_qualified_name); } /* Control should never reach here */ return DNS_FAIL; }
static void add_generated(router_instance *rblock, address_item **addr_new, address_item *addr, address_item *generated, address_item_propagated *addr_prop, ugid_block *ugidptr, struct passwd *pw) { redirect_router_options_block *ob = (redirect_router_options_block *)(rblock->options_block); while (generated != NULL) { address_item *parent; address_item *next = generated; uschar *errors_address = next->prop.errors_address; generated = next->next; next->parent = addr; orflag(next, addr, af_ignore_error); next->start_router = rblock->redirect_router; if (addr->child_count == SHRT_MAX) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s router generated more than %d " "child addresses for <%s>", rblock->name, SHRT_MAX, addr->address); addr->child_count++; next->next = *addr_new; *addr_new = next; /* Don't do the "one_time" thing for the first pass of a 2-stage queue run. */ if (ob->one_time && !queue_2stage) { for (parent = addr; parent->parent != NULL; parent = parent->parent); next->onetime_parent = parent->address; } if (ob->hide_child_in_errmsg) setflag(next, af_hide_child); /* If check_ancestor is set, we want to know if any ancestor of this address is the address we are about to generate. The check must be done caselessly unless the ancestor was routed by a case-sensitive router. */ if (ob->check_ancestor) { for (parent = addr; parent != NULL; parent = parent->parent) { if (((parent->router != NULL && parent->router->caseful_local_part)? Ustrcmp(next->address, parent->address) : strcmpic(next->address, parent->address) ) == 0) { DEBUG(D_route) debug_printf("generated parent replaced by child\n"); next->address = string_copy(addr->address); break; } } } /* A user filter may, under some circumstances, set up an errors address. If so, we must take care to re-instate it when we copy in the propagated data so that it overrides any errors_to setting on the router. */ next->prop = *addr_prop; if (errors_address != NULL) next->prop.errors_address = errors_address; /* For pipes, files, and autoreplies, record this router as handling them, because they don't go through the routing process again. Then set up uid, gid, home and current directories for transporting. */ if (testflag(next, af_pfr)) { next->router = rblock; rf_set_ugid(next, ugidptr); /* Will contain pw values if not overridden */ /* When getting the home directory out of the password information, wrap it in \N...\N to avoid expansion later. In Cygwin, home directories can contain $ characters. */ if (rblock->home_directory != NULL) next->home_dir = rblock->home_directory; else if (rblock->check_local_user) next->home_dir = string_sprintf("\\N%s\\N", pw->pw_dir); else if (rblock->router_home_directory != NULL && testflag(addr, af_home_expanded)) { next->home_dir = deliver_home; setflag(next, af_home_expanded); } next->current_dir = rblock->current_directory; /* Permission options */ if (!ob->forbid_pipe) setflag(next, af_allow_pipe); if (!ob->forbid_file) setflag(next, af_allow_file); if (!ob->forbid_filter_reply) setflag(next, af_allow_reply); /* If the transport setting fails, the error gets picked up at the outer level from the setting of basic_errno in the address. */ if (next->address[0] == '|') { address_pipe = next->address; if (rf_get_transport(ob->pipe_transport_name, &(ob->pipe_transport), next, rblock->name, US"pipe_transport")) next->transport = ob->pipe_transport; address_pipe = NULL; } else if (next->address[0] == '>') { if (rf_get_transport(ob->reply_transport_name, &(ob->reply_transport), next, rblock->name, US"reply_transport")) next->transport = ob->reply_transport; } else /* must be file or directory */ { int len = Ustrlen(next->address); address_file = next->address; if (next->address[len-1] == '/') { if (rf_get_transport(ob->directory_transport_name, &(ob->directory_transport), next, rblock->name, US"directory_transport")) next->transport = ob->directory_transport; } else { if (rf_get_transport(ob->file_transport_name, &(ob->file_transport), next, rblock->name, US"file_transport")) next->transport = ob->file_transport; } address_file = NULL; } } #ifdef SUPPORT_I18N next->prop.utf8_msg = string_is_utf8(next->address) || (sender_address && string_is_utf8(sender_address)); #endif DEBUG(D_route) { debug_printf("%s router generated %s\n %serrors_to=%s transport=%s\n", rblock->name, next->address, testflag(next, af_pfr)? "pipe, file, or autoreply\n " : "", next->prop.errors_address, (next->transport == NULL)? US"NULL" : next->transport->name); if (testflag(next, af_uid_set)) debug_printf(" uid=%ld ", (long int)(next->uid)); else debug_printf(" uid=unset "); if (testflag(next, af_gid_set)) debug_printf("gid=%ld ", (long int)(next->gid)); else debug_printf("gid=unset "); #ifdef SUPPORT_I18N if (next->prop.utf8_msg) debug_printf("utf8 "); #endif debug_printf("home=%s\n", next->home_dir); } } }
uschar * rfc2047_decode2(uschar *string, BOOL lencheck, uschar *target, int zeroval, int *lenptr, int *sizeptr, uschar **error) { int size = Ustrlen(string); size_t dlen; uschar *dptr; gstring *yield; uschar *mimeword, *q1, *q2, *endword; *error = NULL; mimeword = decode_mimeword(string, lencheck, &q1, &q2, &endword, &dlen, &dptr); if (!mimeword) { if (lenptr) *lenptr = size; return string; } /* Scan through the string, decoding MIME words and copying intermediate text, building the result as we go. The result may be longer than the input if it is translated into a multibyte code such as UTF-8. That's why we use the dynamic string building code. */ yield = store_get(sizeof(gstring) + ++size); yield->size = size; yield->ptr = 0; yield->s = US(yield + 1); while (mimeword) { #if HAVE_ICONV iconv_t icd = (iconv_t)(-1); #endif if (mimeword != string) yield = string_catn(yield, string, mimeword - string); /* Do a charset translation if required. This is supported only on hosts that have the iconv() function. Translation errors set error, but carry on, using the untranslated data. If there is more than one error, the message passed back refers to the final one. We use a loop to cater for the case of long strings - the RFC puts limits on the length, but it's best to be robust. */ #if HAVE_ICONV *q1 = 0; if (target != NULL && strcmpic(target, mimeword+2) != 0) { icd = iconv_open(CS target, CS(mimeword+2)); if (icd == (iconv_t)(-1)) { *error = string_sprintf("iconv_open(\"%s\", \"%s\") failed: %s%s", target, mimeword+2, strerror(errno), (errno == EINVAL)? " (maybe unsupported conversion)" : ""); } } *q1 = '?'; #endif while (dlen > 0) { uschar *tptr = NULL; /* Stops compiler warning */ int tlen = -1; #if HAVE_ICONV uschar tbuffer[256]; uschar *outptr = tbuffer; size_t outleft = sizeof(tbuffer); /* If translation is required, go for it. */ if (icd != (iconv_t)(-1)) { (void)iconv(icd, (ICONV_ARG2_TYPE)(&dptr), &dlen, CSS &outptr, &outleft); /* If outptr has been adjusted, there is some output. Set up to add it to the output buffer. The function will have adjusted dptr and dlen. If iconv() stopped because of an error, we'll pick it up next time when there's no output. If there is no output, we expect there to have been a translation error, because we know there was at least one input byte. We leave the value of tlen as -1, which causes the rest of the input to be copied verbatim. */ if (outptr > tbuffer) { tptr = tbuffer; tlen = outptr - tbuffer; } else { DEBUG(D_any) debug_printf("iconv error translating \"%.*s\" to %s: " "%s\n", (int)(endword + 2 - mimeword), mimeword, target, strerror(errno)); } } #endif /* No charset translation is happening or there was a translation error; just set up the original as the string to be added, and mark it all used. */ if (tlen == -1) { tptr = dptr; tlen = dlen; dlen = 0; } /* Deal with zero values; convert them if requested. */ if (zeroval != 0) for (int i = 0; i < tlen; i++) if (tptr[i] == 0) tptr[i] = zeroval; /* Add the new string onto the result */ yield = string_catn(yield, tptr, tlen); } #if HAVE_ICONV if (icd != (iconv_t)(-1)) iconv_close(icd); #endif /* Update string past the MIME word; skip any white space if the next thing is another MIME word. */ string = endword + 2; mimeword = decode_mimeword(string, lencheck, &q1, &q2, &endword, &dlen, &dptr); if (mimeword) { uschar *s = string; while (isspace(*s)) s++; if (s == mimeword) string = s; } } /* Copy the remaining characters of the string, zero-terminate it, and return the length as well if requested. */ yield = string_cat(yield, string); if (lenptr) *lenptr = yield->ptr; if (sizeptr) *sizeptr = yield->size; return string_from_gstring(yield); }
/********************************************************************* * * Function : block_url * * Description : Called from `chat'. Check to see if we need to block this. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) * * Returns : NULL => unblocked, else HTTP block response * *********************************************************************/ struct http_response *block_url(struct client_state *csp) { #ifdef FEATURE_IMAGE_BLOCKING char *p; #endif /* def FEATURE_IMAGE_BLOCKING */ struct http_response *rsp; /* * If it's not blocked, don't block it ;-) */ if ((csp->action->flags & ACTION_BLOCK) == 0) { return NULL; } /* * Else, prepare a response */ if (NULL == (rsp = alloc_http_response())) { return cgi_error_memory(); } /* * If it's an image-url, send back an image or redirect * as specified by the relevant +image action */ #ifdef FEATURE_IMAGE_BLOCKING if (((csp->action->flags & ACTION_IMAGE_BLOCKER) != 0) && is_imageurl(csp)) { /* determine HOW images should be blocked */ p = csp->action->string[ACTION_STRING_IMAGE_BLOCKER]; #if 1 /* Two alternative strategies, use this one for now: */ /* and handle accordingly: */ if ((p == NULL) || (0 == strcmpic(p, "pattern"))) { rsp->body = bindup(image_pattern_data, image_pattern_length); if (rsp->body == NULL) { free_http_response(rsp); return cgi_error_memory(); } rsp->content_length = image_pattern_length; if (enlist_unique_header(rsp->headers, "Content-Type", BUILTIN_IMAGE_MIMETYPE)) { free_http_response(rsp); return cgi_error_memory(); } } else if (0 == strcmpic(p, "blank")) { rsp->body = bindup(image_blank_data, image_blank_length); if (rsp->body == NULL) { free_http_response(rsp); return cgi_error_memory(); } rsp->content_length = image_blank_length; if (enlist_unique_header(rsp->headers, "Content-Type", BUILTIN_IMAGE_MIMETYPE)) { free_http_response(rsp); return cgi_error_memory(); } } else { rsp->status = strdup("302 Local Redirect from Privoxy"); if (rsp->status == NULL) { free_http_response(rsp); return cgi_error_memory(); } if (enlist_unique_header(rsp->headers, "Location", p)) { free_http_response(rsp); return cgi_error_memory(); } } #else /* Following code is disabled for now */ /* and handle accordingly: */ if ((p == NULL) || (0 == strcmpic(p, "pattern"))) { p = CGI_PREFIX "send-banner?type=pattern"; } else if (0 == strcmpic(p, "blank")) { p = CGI_PREFIX "send-banner?type=blank"; } rsp->status = strdup("302 Local Redirect from Privoxy"); if (rsp->status == NULL) { free_http_response(rsp); return cgi_error_memory(); } if (enlist_unique_header(rsp->headers, "Location", p)) { free_http_response(rsp); return cgi_error_memory(); } #endif /* Preceeding code is disabled for now */ } else #endif /* def FEATURE_IMAGE_BLOCKING */ /* * Else, generate an HTML "blocked" message: */ { jb_err err; struct map * exports; /* * Workaround for stupid Netscape bug which prevents * pages from being displayed if loading a referenced * JavaScript or style sheet fails. So make it appear * as if it succeeded. */ if ( NULL != (p = get_header_value(csp->headers, "User-Agent:")) && !strncmpic(p, "mozilla", 7) /* Catch Netscape but */ && !strstr(p, "Gecko") /* save Mozilla, */ && !strstr(p, "compatible") /* MSIE */ && !strstr(p, "Opera")) /* and Opera. */ { rsp->status = strdup("200 Request for blocked URL"); } else { rsp->status = strdup("404 Request for blocked URL"); } if (rsp->status == NULL) { free_http_response(rsp); return cgi_error_memory(); } exports = default_exports(csp, NULL); if (exports == NULL) { free_http_response(rsp); return cgi_error_memory(); } #ifdef FEATURE_FORCE_LOAD err = map(exports, "force-prefix", 1, FORCE_PREFIX, 1); if (csp->http->ssl != 0) #endif /* ndef FEATURE_FORCE_LOAD */ { err = map_block_killer(exports, "force-support"); } if (!err) err = map(exports, "protocol", 1, csp->http->ssl ? "https://" : "http://", 1); if (!err) err = map(exports, "hostport", 1, html_encode(csp->http->hostport), 0); if (!err) err = map(exports, "path", 1, html_encode(csp->http->path), 0); if (!err) err = map(exports, "path-ue", 1, url_encode(csp->http->path), 0); if (err) { free_map(exports); free_http_response(rsp); return cgi_error_memory(); } err = template_fill_for_cgi(csp, "blocked", exports, rsp); if (err) { free_http_response(rsp); return cgi_error_memory(); } } return finish_http_response(rsp); }
static int perform_ldap_search(uschar *ldap_url, uschar *server, int s_port, int search_type, uschar **res, uschar **errmsg, BOOL *defer_break, uschar *user, uschar *password, int sizelimit, int timelimit, int tcplimit, int dereference, void *referrals) { LDAPURLDesc *ludp = NULL; LDAPMessage *result = NULL; BerElement *ber; LDAP_CONNECTION *lcp; struct timeval timeout; struct timeval *timeoutptr = NULL; uschar *attr; uschar **attrp; uschar *data = NULL; uschar *dn = NULL; uschar *host; uschar **values; uschar **firstval; uschar porttext[16]; uschar *error1 = NULL; /* string representation of errcode (static) */ uschar *error2 = NULL; /* error message from the server */ uschar *matched = NULL; /* partially matched DN */ int attr_count = 0; int error_yield = DEFER; int msgid; int rc, ldap_rc, ldap_parse_rc; int port; int ptr = 0; int rescount = 0; int size = 0; BOOL attribute_found = FALSE; BOOL ldapi = FALSE; DEBUG(D_lookup) debug_printf("perform_ldap_search: ldap%s URL = \"%s\" server=%s port=%d " "sizelimit=%d timelimit=%d tcplimit=%d\n", (search_type == SEARCH_LDAP_MULTIPLE)? "m" : (search_type == SEARCH_LDAP_DN)? "dn" : (search_type == SEARCH_LDAP_AUTH)? "auth" : "", ldap_url, server, s_port, sizelimit, timelimit, tcplimit); /* Check if LDAP thinks the URL is a valid LDAP URL. We assume that if the LDAP library that is in use doesn't recognize, say, "ldapi", it will barf here. */ if (!ldap_is_ldap_url(CS ldap_url)) { *errmsg = string_sprintf("ldap_is_ldap_url: not an LDAP url \"%s\"\n", ldap_url); goto RETURN_ERROR_BREAK; } /* Parse the URL */ if ((rc = ldap_url_parse(CS ldap_url, &ludp)) != 0) { *errmsg = string_sprintf("ldap_url_parse: (error %d) parsing \"%s\"\n", rc, ldap_url); goto RETURN_ERROR_BREAK; } /* If the host name is empty, take it from the separate argument, if one is given. OpenLDAP 2.0.6 sets an unset hostname to "" rather than empty, but expects NULL later in ldap_init() to mean "default", annoyingly. In OpenLDAP 2.0.11 this has changed (it uses NULL). */ if ((ludp->lud_host == NULL || ludp->lud_host[0] == 0) && server != NULL) { host = server; port = s_port; } else { host = US ludp->lud_host; if (host != NULL && host[0] == 0) host = NULL; port = ludp->lud_port; } DEBUG(D_lookup) debug_printf("after ldap_url_parse: host=%s port=%d\n", host, port); if (port == 0) port = LDAP_PORT; /* Default if none given */ sprintf(CS porttext, ":%d", port); /* For messages */ /* If the "host name" is actually a path, we are going to connect using a Unix socket, regardless of whether "ldapi" was actually specified or not. This means that a Unix socket can be declared in eldap_default_servers, and "traditional" LDAP queries using just "ldap" can be used ("ldaps" is similarly overridden). The path may start with "/" or it may already be escaped as "%2F" if it was actually declared that way in eldap_default_servers. (I did it that way the first time.) If the host name is not a path, the use of "ldapi" causes an error, except in the default case. (But lud_scheme doesn't seem to exist in older libraries.) */ if (host != NULL) { if ((host[0] == '/' || Ustrncmp(host, "%2F", 3) == 0)) { ldapi = TRUE; porttext[0] = 0; /* Remove port from messages */ } #if defined LDAP_LIB_OPENLDAP2 else if (strncmp(ludp->lud_scheme, "ldapi", 5) == 0) { *errmsg = string_sprintf("ldapi requires an absolute path (\"%s\" given)", host); goto RETURN_ERROR; } #endif } /* Count the attributes; we need this later to tell us how to format results */ for (attrp = USS ludp->lud_attrs; attrp != NULL && *attrp != NULL; attrp++) attr_count++; /* See if we can find a cached connection to this host. The port is not relevant for ldapi. The host name pointer is set to NULL if no host was given (implying the library default), rather than to the empty string. Note that in this case, there is no difference between ldap and ldapi. */ for (lcp = ldap_connections; lcp != NULL; lcp = lcp->next) { if ((host == NULL) != (lcp->host == NULL) || (host != NULL && strcmpic(lcp->host, host) != 0)) continue; if (ldapi || port == lcp->port) break; } /* Use this network timeout in any requests. */ if (tcplimit > 0) { timeout.tv_sec = tcplimit; timeout.tv_usec = 0; timeoutptr = &timeout; } /* If no cached connection found, we must open a connection to the server. If the server name is actually an absolute path, we set ldapi=TRUE above. This requests connection via a Unix socket. However, as far as I know, only OpenLDAP supports the use of sockets, and the use of ldap_initialize(). */ if (lcp == NULL) { LDAP *ld; /* --------------------------- OpenLDAP ------------------------ */ /* There seems to be a preference under OpenLDAP for ldap_initialize() instead of ldap_init(), though I have as yet been unable to find documentation that says this. (OpenLDAP documentation is sparse to non-existent). So we handle OpenLDAP differently here. Also, support for ldapi seems to be OpenLDAP-only at present. */ #ifdef LDAP_LIB_OPENLDAP2 /* We now need an empty string for the default host. Get some store in which to build a URL for ldap_initialize(). In the ldapi case, it can't be bigger than (9 + 3*Ustrlen(shost)), whereas in the other cases it can't be bigger than the host name + "ldaps:///" plus : and a port number, say 20 + the length of the host name. What we get should accommodate both, easily. */ uschar *shost = (host == NULL)? US"" : host; uschar *init_url = store_get(20 + 3 * Ustrlen(shost)); uschar *init_ptr; /* Handle connection via Unix socket ("ldapi"). We build a basic LDAP URI to contain the path name, with slashes escaped as %2F. */ if (ldapi) { int ch; init_ptr = init_url + 8; Ustrcpy(init_url, "ldapi://"); while ((ch = *shost++) != 0) { if (ch == '/') { Ustrncpy(init_ptr, "%2F", 3); init_ptr += 3; } else *init_ptr++ = ch; } *init_ptr = 0; } /* This is not an ldapi call. Just build a URI with the protocol type, host name, and port. */ else { init_ptr = Ustrchr(ldap_url, '/'); Ustrncpy(init_url, ldap_url, init_ptr - ldap_url); init_ptr = init_url + (init_ptr - ldap_url); sprintf(CS init_ptr, "//%s:%d/", shost, port); } /* Call ldap_initialize() and check the result */ DEBUG(D_lookup) debug_printf("ldap_initialize with URL %s\n", init_url); rc = ldap_initialize(&ld, CS init_url); if (rc != LDAP_SUCCESS) { *errmsg = string_sprintf("ldap_initialize: (error %d) URL \"%s\"\n", rc, init_url); goto RETURN_ERROR; } store_reset(init_url); /* Might as well save memory when we can */ /* ------------------------- Not OpenLDAP ---------------------- */ /* For libraries other than OpenLDAP, use ldap_init(). */ #else /* LDAP_LIB_OPENLDAP2 */ ld = ldap_init(CS host, port); #endif /* LDAP_LIB_OPENLDAP2 */ /* -------------------------------------------------------------- */ /* Handle failure to initialize */ if (ld == NULL) { *errmsg = string_sprintf("failed to initialize for LDAP server %s%s - %s", host, porttext, strerror(errno)); goto RETURN_ERROR; } /* Set the TCP connect time limit if available. This is something that is in Netscape SDK v4.1; I don't know about other libraries. */ #ifdef LDAP_X_OPT_CONNECT_TIMEOUT if (tcplimit > 0) { int timeout1000 = tcplimit*1000; ldap_set_option(ld, LDAP_X_OPT_CONNECT_TIMEOUT, (void *)&timeout1000); } else { int notimeout = LDAP_X_IO_TIMEOUT_NO_TIMEOUT; ldap_set_option(ld, LDAP_X_OPT_CONNECT_TIMEOUT, (void *)¬imeout); } #endif /* Set the TCP connect timeout. This works with OpenLDAP 2.2.14. */ #ifdef LDAP_OPT_NETWORK_TIMEOUT if (tcplimit > 0) ldap_set_option(ld, LDAP_OPT_NETWORK_TIMEOUT, (void *)timeoutptr); #endif /* I could not get TLS to work until I set the version to 3. That version seems to be the default nowadays. The RFC is dated 1997, so I would hope that all the LDAP libraries support it. Therefore, if eldap_version hasn't been set, go for v3 if we can. */ if (eldap_version < 0) { #ifdef LDAP_VERSION3 eldap_version = LDAP_VERSION3; #else eldap_version = 2; #endif } #ifdef LDAP_OPT_PROTOCOL_VERSION ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, (void *)&eldap_version); #endif DEBUG(D_lookup) debug_printf("initialized for LDAP (v%d) server %s%s\n", eldap_version, host, porttext); /* If not using ldapi and TLS is available, set appropriate TLS options: hard for "ldaps" and soft otherwise. */ #ifdef LDAP_OPT_X_TLS if (!ldapi) { int tls_option; if (strncmp(ludp->lud_scheme, "ldaps", 5) == 0) { tls_option = LDAP_OPT_X_TLS_HARD; DEBUG(D_lookup) debug_printf("LDAP_OPT_X_TLS_HARD set\n"); } else { tls_option = LDAP_OPT_X_TLS_TRY; DEBUG(D_lookup) debug_printf("LDAP_OPT_X_TLS_TRY set\n"); } ldap_set_option(ld, LDAP_OPT_X_TLS, (void *)&tls_option); } #endif /* LDAP_OPT_X_TLS */ #ifdef LDAP_OPT_X_TLS_CACERTFILE if (eldap_ca_cert_file != NULL) { ldap_set_option(ld, LDAP_OPT_X_TLS_CACERTFILE, eldap_ca_cert_file); } #endif #ifdef LDAP_OPT_X_TLS_CACERTDIR if (eldap_ca_cert_dir != NULL) { ldap_set_option(ld, LDAP_OPT_X_TLS_CACERTDIR, eldap_ca_cert_dir); } #endif #ifdef LDAP_OPT_X_TLS_CERTFILE if (eldap_cert_file != NULL) { ldap_set_option(ld, LDAP_OPT_X_TLS_CERTFILE, eldap_cert_file); } #endif #ifdef LDAP_OPT_X_TLS_KEYFILE if (eldap_cert_key != NULL) { ldap_set_option(ld, LDAP_OPT_X_TLS_KEYFILE, eldap_cert_key); } #endif #ifdef LDAP_OPT_X_TLS_CIPHER_SUITE if (eldap_cipher_suite != NULL) { ldap_set_option(ld, LDAP_OPT_X_TLS_CIPHER_SUITE, eldap_cipher_suite); } #endif #ifdef LDAP_OPT_X_TLS_REQUIRE_CERT if (eldap_require_cert != NULL) { int cert_option = LDAP_OPT_X_TLS_NEVER; if (Ustrcmp(eldap_require_cert, "hard") == 0) { cert_option = LDAP_OPT_X_TLS_HARD; } else if (Ustrcmp(eldap_require_cert, "demand") == 0) { cert_option = LDAP_OPT_X_TLS_DEMAND; } else if (Ustrcmp(eldap_require_cert, "allow") == 0) { cert_option = LDAP_OPT_X_TLS_ALLOW; } else if (Ustrcmp(eldap_require_cert, "try") == 0) { cert_option = LDAP_OPT_X_TLS_TRY; } ldap_set_option(ld, LDAP_OPT_X_TLS_REQUIRE_CERT, &cert_option); } #endif /* Now add this connection to the chain of cached connections */ lcp = store_get(sizeof(LDAP_CONNECTION)); lcp->host = (host == NULL)? NULL : string_copy(host); lcp->bound = FALSE; lcp->user = NULL; lcp->password = NULL; lcp->port = port; lcp->ld = ld; lcp->next = ldap_connections; ldap_connections = lcp; } /* Found cached connection */ else { DEBUG(D_lookup) debug_printf("re-using cached connection to LDAP server %s%s\n", host, porttext); } /* Bind with the user/password supplied, or an anonymous bind if these values are NULL, unless a cached connection is already bound with the same values. */ if (!lcp->bound || (lcp->user == NULL && user != NULL) || (lcp->user != NULL && user == NULL) || (lcp->user != NULL && user != NULL && Ustrcmp(lcp->user, user) != 0) || (lcp->password == NULL && password != NULL) || (lcp->password != NULL && password == NULL) || (lcp->password != NULL && password != NULL && Ustrcmp(lcp->password, password) != 0)) { DEBUG(D_lookup) debug_printf("%sbinding with user=%s password=%s\n", (lcp->bound)? "re-" : "", user, password); #ifdef LDAP_OPT_X_TLS /* The Oracle LDAP libraries (LDAP_LIB_TYPE=SOLARIS) don't support this: */ if (eldap_start_tls) { ldap_start_tls_s(lcp->ld, NULL, NULL); } #endif if ((msgid = ldap_bind(lcp->ld, CS user, CS password, LDAP_AUTH_SIMPLE)) == -1) { *errmsg = string_sprintf("failed to bind the LDAP connection to server " "%s%s - ldap_bind() returned -1", host, porttext); goto RETURN_ERROR; } if ((rc = ldap_result( lcp->ld, msgid, 1, timeoutptr, &result )) <= 0) { *errmsg = string_sprintf("failed to bind the LDAP connection to server " "%s%s - LDAP error: %s", host, porttext, rc == -1 ? "result retrieval failed" : "timeout" ); result = NULL; goto RETURN_ERROR; } rc = ldap_result2error( lcp->ld, result, 0 ); /* Invalid credentials when just checking credentials returns FAIL. This stops any further servers being tried. */ if (search_type == SEARCH_LDAP_AUTH && rc == LDAP_INVALID_CREDENTIALS) { DEBUG(D_lookup) debug_printf("Invalid credentials: ldapauth returns FAIL\n"); error_yield = FAIL; goto RETURN_ERROR_NOMSG; } /* Otherwise we have a problem that doesn't stop further servers from being tried. */ if (rc != LDAP_SUCCESS) { *errmsg = string_sprintf("failed to bind the LDAP connection to server " "%s%s - LDAP error %d: %s", host, porttext, rc, ldap_err2string(rc)); goto RETURN_ERROR; } /* Successful bind */ lcp->bound = TRUE; lcp->user = (user == NULL)? NULL : string_copy(user); lcp->password = (password == NULL)? NULL : string_copy(password); ldap_msgfree(result); result = NULL; } /* If we are just checking credentials, return OK. */ if (search_type == SEARCH_LDAP_AUTH) { DEBUG(D_lookup) debug_printf("Bind succeeded: ldapauth returns OK\n"); goto RETURN_OK; } /* Before doing the search, set the time and size limits (if given). Here again the different implementations of LDAP have chosen to do things differently. */ #if defined(LDAP_OPT_SIZELIMIT) ldap_set_option(lcp->ld, LDAP_OPT_SIZELIMIT, (void *)&sizelimit); ldap_set_option(lcp->ld, LDAP_OPT_TIMELIMIT, (void *)&timelimit); #else lcp->ld->ld_sizelimit = sizelimit; lcp->ld->ld_timelimit = timelimit; #endif /* Similarly for dereferencing aliases. Don't know if this is possible on an LDAP library without LDAP_OPT_DEREF. */ #if defined(LDAP_OPT_DEREF) ldap_set_option(lcp->ld, LDAP_OPT_DEREF, (void *)&dereference); #endif /* Similarly for the referral setting; should the library follow referrals that the LDAP server returns? The conditional is just in case someone uses a library without it. */ #if defined(LDAP_OPT_REFERRALS) ldap_set_option(lcp->ld, LDAP_OPT_REFERRALS, referrals); #endif /* Start the search on the server. */ DEBUG(D_lookup) debug_printf("Start search\n"); msgid = ldap_search(lcp->ld, ludp->lud_dn, ludp->lud_scope, ludp->lud_filter, ludp->lud_attrs, 0); if (msgid == -1) { #if defined LDAP_LIB_SOLARIS || defined LDAP_LIB_OPENLDAP2 int err; ldap_get_option(lcp->ld, LDAP_OPT_ERROR_NUMBER, &err); *errmsg = string_sprintf("ldap_search failed: %d, %s", err, ldap_err2string(err)); #else *errmsg = string_sprintf("ldap_search failed"); #endif goto RETURN_ERROR; } /* Loop to pick up results as they come in, setting a timeout if one was given. */ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) == LDAP_RES_SEARCH_ENTRY) { LDAPMessage *e; DEBUG(D_lookup) debug_printf("ldap_result loop\n"); for(e = ldap_first_entry(lcp->ld, result); e != NULL; e = ldap_next_entry(lcp->ld, e)) { uschar *new_dn; BOOL insert_space = FALSE; DEBUG(D_lookup) debug_printf("LDAP entry loop\n"); rescount++; /* Count results */ /* Results for multiple entries values are separated by newlines. */ if (data != NULL) data = string_cat(data, &size, &ptr, US"\n", 1); /* Get the DN from the last result. */ new_dn = US ldap_get_dn(lcp->ld, e); if (new_dn != NULL) { if (dn != NULL) { #if defined LDAP_LIB_NETSCAPE || defined LDAP_LIB_OPENLDAP2 ldap_memfree(dn); #else /* OPENLDAP 1, UMich, Solaris */ free(dn); #endif } /* Save for later */ dn = new_dn; } /* If the data we want is actually the DN rather than any attribute values, (an "ldapdn" search) add it to the data string. If there are multiple entries, the DNs will be concatenated, but we test for this case below, as for SEARCH_LDAP_SINGLE, and give an error. */ if (search_type == SEARCH_LDAP_DN) /* Do not amalgamate these into one */ { /* condition, because of the else */ if (new_dn != NULL) /* below, that's for the first only */ { data = string_cat(data, &size, &ptr, new_dn, Ustrlen(new_dn)); data[ptr] = 0; attribute_found = TRUE; } } /* Otherwise, loop through the entry, grabbing attribute values. If there's only one attribute being retrieved, no attribute name is given, and the result is not quoted. Multiple values are separated by (comma, space). If more than one attribute is being retrieved, the data is given as a sequence of name=value pairs, with the value always in quotes. If there are multiple values, they are given within the quotes, comma separated. */ else for (attr = US ldap_first_attribute(lcp->ld, e, &ber); attr != NULL; attr = US ldap_next_attribute(lcp->ld, e, ber)) { if (attr[0] != 0) { /* Get array of values for this attribute. */ if ((firstval = values = USS ldap_get_values(lcp->ld, e, CS attr)) != NULL) { if (attr_count != 1) { if (insert_space) data = string_cat(data, &size, &ptr, US" ", 1); else insert_space = TRUE; data = string_cat(data, &size, &ptr, attr, Ustrlen(attr)); data = string_cat(data, &size, &ptr, US"=\"", 2); } while (*values != NULL) { uschar *value = *values; int len = Ustrlen(value); DEBUG(D_lookup) debug_printf("LDAP attr loop %s:%s\n", attr, value); if (values != firstval) data = string_cat(data, &size, &ptr, US", ", 2); /* For multiple attributes, the data is in quotes. We must escape internal quotes, backslashes, newlines. */ if (attr_count != 1) { int j; for (j = 0; j < len; j++) { if (value[j] == '\n') data = string_cat(data, &size, &ptr, US"\\n", 2); else { if (value[j] == '\"' || value[j] == '\\') data = string_cat(data, &size, &ptr, US"\\", 1); data = string_cat(data, &size, &ptr, value+j, 1); } } } /* For single attributes, copy the value verbatim */ else data = string_cat(data, &size, &ptr, value, len); /* Move on to the next value */ values++; attribute_found = TRUE; } /* Closing quote at the end of the data for a named attribute. */ if (attr_count != 1) data = string_cat(data, &size, &ptr, US"\"", 1); /* Free the values */ ldap_value_free(CSS firstval); } } #if defined LDAP_LIB_NETSCAPE || defined LDAP_LIB_OPENLDAP2 /* Netscape and OpenLDAP2 LDAP's attrs are dynamically allocated and need to be freed. UMich LDAP stores them in static storage and does not require this. */ ldap_memfree(attr); #endif } /* End "for" loop for extracting attributes from an entry */ } /* End "for" loop for extracting entries from a result */ /* Free the result */ ldap_msgfree(result); result = NULL; } /* End "while" loop for multiple results */ /* Terminate the dynamic string that we have built and reclaim unused store */ if (data != NULL) { data[ptr] = 0; store_reset(data + ptr + 1); } /* Copy the last dn into eldap_dn */ if (dn != NULL) { eldap_dn = string_copy(dn); #if defined LDAP_LIB_NETSCAPE || defined LDAP_LIB_OPENLDAP2 ldap_memfree(dn); #else /* OPENLDAP 1, UMich, Solaris */ free(dn); #endif } DEBUG(D_lookup) debug_printf("search ended by ldap_result yielding %d\n",rc); if (rc == 0) { *errmsg = US"ldap_result timed out"; goto RETURN_ERROR; } /* A return code of -1 seems to mean "ldap_result failed internally or couldn't provide you with a message". Other error states seem to exist where ldap_result() didn't give us any message from the server at all, leaving result set to NULL. Apparently, "the error parameters of the LDAP session handle will be set accordingly". That's the best we can do to retrieve an error status; we can't use functions like ldap_result2error because they parse a message from the server, which we didn't get. Annoyingly, the different implementations of LDAP have gone for different methods of handling error codes and generating error messages. */ if (rc == -1 || result == NULL) { int err; DEBUG(D_lookup) debug_printf("ldap_result failed\n"); #if defined LDAP_LIB_SOLARIS || defined LDAP_LIB_OPENLDAP2 ldap_get_option(lcp->ld, LDAP_OPT_ERROR_NUMBER, &err); *errmsg = string_sprintf("ldap_result failed: %d, %s", err, ldap_err2string(err)); #elif defined LDAP_LIB_NETSCAPE /* Dubious (surely 'matched' is spurious here?) */ (void)ldap_get_lderrno(lcp->ld, &matched, &error1); *errmsg = string_sprintf("ldap_result failed: %s (%s)", error1, matched); #else /* UMich LDAP aka OpenLDAP 1.x */ *errmsg = string_sprintf("ldap_result failed: %d, %s", lcp->ld->ld_errno, ldap_err2string(lcp->ld->ld_errno)); #endif goto RETURN_ERROR; } /* A return code that isn't -1 doesn't necessarily mean there were no problems with the search. The message must be an LDAP_RES_SEARCH_RESULT or LDAP_RES_SEARCH_REFERENCE or else it's something we can't handle. Some versions of LDAP do not define LDAP_RES_SEARCH_REFERENCE (LDAP v1 is one, it seems). So we don't provide that functionality when we can't. :-) */ if (rc != LDAP_RES_SEARCH_RESULT #ifdef LDAP_RES_SEARCH_REFERENCE && rc != LDAP_RES_SEARCH_REFERENCE #endif ) { *errmsg = string_sprintf("ldap_result returned unexpected code %d", rc); goto RETURN_ERROR; } /* We have a result message from the server. This doesn't yet mean all is well. We need to parse the message to find out exactly what's happened. */ #if defined LDAP_LIB_SOLARIS || defined LDAP_LIB_OPENLDAP2 ldap_rc = rc; ldap_parse_rc = ldap_parse_result(lcp->ld, result, &rc, CSS &matched, CSS &error2, NULL, NULL, 0); DEBUG(D_lookup) debug_printf("ldap_parse_result: %d\n", ldap_parse_rc); if (ldap_parse_rc < 0 && (ldap_parse_rc != LDAP_NO_RESULTS_RETURNED #ifdef LDAP_RES_SEARCH_REFERENCE || ldap_rc != LDAP_RES_SEARCH_REFERENCE #endif )) { *errmsg = string_sprintf("ldap_parse_result failed %d", ldap_parse_rc); goto RETURN_ERROR; } error1 = US ldap_err2string(rc); #elif defined LDAP_LIB_NETSCAPE /* Dubious (it doesn't reference 'result' at all!) */ rc = ldap_get_lderrno(lcp->ld, &matched, &error1); #else /* UMich LDAP aka OpenLDAP 1.x */ rc = ldap_result2error(lcp->ld, result, 0); error1 = ldap_err2string(rc); error2 = lcp->ld->ld_error; matched = lcp->ld->ld_matched; #endif /* Process the status as follows: (1) If we get LDAP_SIZELIMIT_EXCEEDED, just carry on, to return the truncated result list. (2) If we get LDAP_RES_SEARCH_REFERENCE, also just carry on. This was a submitted patch that is reported to "do the right thing" with Solaris LDAP libraries. (The problem it addresses apparently does not occur with Open LDAP.) (3) The range of errors defined by LDAP_NAME_ERROR generally mean "that object does not, or cannot, exist in the database". For those cases we fail the lookup. (4) All other non-successes here are treated as some kind of problem with the lookup, so return DEFER (which is the default in error_yield). */ DEBUG(D_lookup) debug_printf("ldap_parse_result yielded %d: %s\n", rc, ldap_err2string(rc)); if (rc != LDAP_SUCCESS && rc != LDAP_SIZELIMIT_EXCEEDED #ifdef LDAP_RES_SEARCH_REFERENCE && rc != LDAP_RES_SEARCH_REFERENCE #endif ) { *errmsg = string_sprintf("LDAP search failed - error %d: %s%s%s%s%s", rc, (error1 != NULL)? error1 : US"", (error2 != NULL && error2[0] != 0)? US"/" : US"", (error2 != NULL)? error2 : US"", (matched != NULL && matched[0] != 0)? US"/" : US"", (matched != NULL)? matched : US""); #if defined LDAP_NAME_ERROR if (LDAP_NAME_ERROR(rc)) #elif defined NAME_ERROR /* OPENLDAP1 calls it this */ if (NAME_ERROR(rc)) #else if (rc == LDAP_NO_SUCH_OBJECT) #endif { DEBUG(D_lookup) debug_printf("lookup failure forced\n"); error_yield = FAIL; } goto RETURN_ERROR; } /* The search succeeded. Check if we have too many results */ if (search_type != SEARCH_LDAP_MULTIPLE && rescount > 1) { *errmsg = string_sprintf("LDAP search: more than one entry (%d) was returned " "(filter not specific enough?)", rescount); goto RETURN_ERROR_BREAK; } /* Check if we have too few (zero) entries */ if (rescount < 1) { *errmsg = string_sprintf("LDAP search: no results"); error_yield = FAIL; goto RETURN_ERROR_BREAK; } /* If an entry was found, but it had no attributes, we behave as if no entries were found, that is, the lookup failed. */ if (!attribute_found) { *errmsg = US"LDAP search: found no attributes"; error_yield = FAIL; goto RETURN_ERROR; } /* Otherwise, it's all worked */ DEBUG(D_lookup) debug_printf("LDAP search: returning: %s\n", data); *res = data; RETURN_OK: if (result != NULL) ldap_msgfree(result); ldap_free_urldesc(ludp); return OK; /* Error returns */ RETURN_ERROR_BREAK: *defer_break = TRUE; RETURN_ERROR: DEBUG(D_lookup) debug_printf("%s\n", *errmsg); RETURN_ERROR_NOMSG: if (result != NULL) ldap_msgfree(result); if (ludp != NULL) ldap_free_urldesc(ludp); #if defined LDAP_LIB_OPENLDAP2 if (error2 != NULL) ldap_memfree(error2); if (matched != NULL) ldap_memfree(matched); #endif return error_yield; }
static int control_ldap_search(uschar *ldap_url, int search_type, uschar **res, uschar **errmsg) { BOOL defer_break = FALSE; int timelimit = LDAP_NO_LIMIT; int sizelimit = LDAP_NO_LIMIT; int tcplimit = 0; int sep = 0; int dereference = LDAP_DEREF_NEVER; void* referrals = LDAP_OPT_ON; uschar *url = ldap_url; uschar *p; uschar *user = NULL; uschar *password = NULL; uschar *server, *list; uschar buffer[512]; while (isspace(*url)) url++; /* Until the string begins "ldap", search for the other parameter settings that are recognized. They are of the form NAME=VALUE, with the value being optionally double-quoted. There must still be a space after it, however. No NAME has the value "ldap". */ while (strncmpic(url, US"ldap", 4) != 0) { uschar *name = url; while (*url != 0 && *url != '=') url++; if (*url == '=') { int namelen; uschar *value; namelen = ++url - name; value = string_dequote(&url); if (isspace(*url)) { if (strncmpic(name, US"USER="******"PASS="******"SIZE=", namelen) == 0) sizelimit = Uatoi(value); else if (strncmpic(name, US"TIME=", namelen) == 0) timelimit = Uatoi(value); else if (strncmpic(name, US"CONNECT=", namelen) == 0) tcplimit = Uatoi(value); else if (strncmpic(name, US"NETTIME=", namelen) == 0) tcplimit = Uatoi(value); /* Don't know if all LDAP libraries have LDAP_OPT_DEREF */ #ifdef LDAP_OPT_DEREF else if (strncmpic(name, US"DEREFERENCE=", namelen) == 0) { if (strcmpic(value, US"never") == 0) dereference = LDAP_DEREF_NEVER; else if (strcmpic(value, US"searching") == 0) dereference = LDAP_DEREF_SEARCHING; else if (strcmpic(value, US"finding") == 0) dereference = LDAP_DEREF_FINDING; if (strcmpic(value, US"always") == 0) dereference = LDAP_DEREF_ALWAYS; } #else else if (strncmpic(name, US"DEREFERENCE=", namelen) == 0) { *errmsg = string_sprintf("LDAP_OP_DEREF not defined in this LDAP " "library - cannot use \"dereference\""); DEBUG(D_lookup) debug_printf("%s\n", *errmsg); return DEFER; } #endif #ifdef LDAP_OPT_REFERRALS else if (strncmpic(name, US"REFERRALS=", namelen) == 0) { if (strcmpic(value, US"follow") == 0) referrals = LDAP_OPT_ON; else if (strcmpic(value, US"nofollow") == 0) referrals = LDAP_OPT_OFF; else { *errmsg = string_sprintf("LDAP option REFERRALS is not \"follow\" " "or \"nofollow\""); DEBUG(D_lookup) debug_printf("%s\n", *errmsg); return DEFER; } } #else else if (strncmpic(name, US"REFERRALS=", namelen) == 0) { *errmsg = string_sprintf("LDAP_OP_REFERRALS not defined in this LDAP " "library - cannot use \"referrals\""); DEBUG(D_lookup) debug_printf("%s\n", *errmsg); return DEFER; } #endif else { *errmsg = string_sprintf("unknown parameter \"%.*s\" precedes LDAP URL", namelen, name); DEBUG(D_lookup) debug_printf("LDAP query error: %s\n", *errmsg); return DEFER; } while (isspace(*url)) url++; continue; } } *errmsg = US"malformed parameter setting precedes LDAP URL"; DEBUG(D_lookup) debug_printf("LDAP query error: %s\n", *errmsg); return DEFER; } /* If user is set, de-URL-quote it. Some LDAP libraries do this for themselves, but it seems that not all behave like this. The DN for the user is often the result of ${quote_ldap_dn:...} quoting, which does apply URL quoting, because that is needed when the DN is used as a base DN in a query. Sigh. This is all far too complicated. */ if (user != NULL) { uschar *s; uschar *t = user; for (s = user; *s != 0; s++) { int c, d; if (*s == '%' && isxdigit(c=s[1]) && isxdigit(d=s[2])) { c = tolower(c); d = tolower(d); *t++ = (((c >= 'a')? (10 + c - 'a') : c - '0') << 4) | ((d >= 'a')? (10 + d - 'a') : d - '0'); s += 2; } else *t++ = *s; } *t = 0; } DEBUG(D_lookup) debug_printf("LDAP parameters: user=%s pass=%s size=%d time=%d connect=%d " "dereference=%d referrals=%s\n", user, password, sizelimit, timelimit, tcplimit, dereference, (referrals == LDAP_OPT_ON)? "on" : "off"); /* If the request is just to check authentication, some credentials must be given. The password must not be empty because LDAP binds with an empty password are considered anonymous, and will succeed on most installations. */ if (search_type == SEARCH_LDAP_AUTH) { if (user == NULL || password == NULL) { *errmsg = US"ldapauth lookups must specify the username and password"; return DEFER; } if (password[0] == 0) { DEBUG(D_lookup) debug_printf("Empty password: ldapauth returns FAIL\n"); return FAIL; } } /* Check for valid ldap url starters */ p = url + 4; if (tolower(*p) == 's' || tolower(*p) == 'i') p++; if (Ustrncmp(p, "://", 3) != 0) { *errmsg = string_sprintf("LDAP URL does not start with \"ldap://\", " "\"ldaps://\", or \"ldapi://\" (it starts with \"%.16s...\")", url); DEBUG(D_lookup) debug_printf("LDAP query error: %s\n", *errmsg); return DEFER; } /* No default servers, or URL contains a server name: just one attempt */ if (eldap_default_servers == NULL || p[3] != '/') { return perform_ldap_search(url, NULL, 0, search_type, res, errmsg, &defer_break, user, password, sizelimit, timelimit, tcplimit, dereference, referrals); } /* Loop through the default servers until OK or FAIL */ list = eldap_default_servers; while ((server = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL) { int rc; int port = 0; uschar *colon = Ustrchr(server, ':'); if (colon != NULL) { *colon = 0; port = Uatoi(colon+1); } rc = perform_ldap_search(url, server, port, search_type, res, errmsg, &defer_break, user, password, sizelimit, timelimit, tcplimit, dereference, referrals); if (rc != DEFER || defer_break) return rc; } return DEFER; }
/********************************************************************* * * Function : parse_http_request * * Description : Parse out the host and port from the URL. Find the * hostname & path, port (if ':'), and/or password (if '@') * * Parameters : * 1 : req = HTTP request line to break down * 2 : http = pointer to the http structure to hold elements * * Returns : JB_ERR_OK on success * JB_ERR_CGI_PARAMS on malformed command/URL * or >100 domains deep. * *********************************************************************/ jb_err parse_http_request(const char *req, struct http_request *http) { char *buf; char *v[3]; int n; jb_err err; memset(http, '\0', sizeof(*http)); buf = strdup_or_die(req); n = ssplit(buf, " \r\n", v, SZ(v)); if (n != 3) { freez(buf); return JB_ERR_PARSE; } /* * Fail in case of unknown methods * which we might not handle correctly. * * XXX: There should be a config option * to forward requests with unknown methods * anyway. Most of them don't need special * steps. */ if (unknown_method(v[0])) { log_error(LOG_LEVEL_ERROR, "Unknown HTTP method detected: %s", v[0]); freez(buf); return JB_ERR_PARSE; } if (JB_ERR_OK != normalize_http_version(v[2])) { freez(buf); return JB_ERR_PARSE; } http->ssl = !strcmpic(v[0], "CONNECT"); err = parse_http_url(v[1], http, !http->ssl); if (err) { freez(buf); return err; } /* * Copy the details into the structure */ http->cmd = strdup_or_die(req); http->gpc = strdup_or_die(v[0]); http->ver = strdup_or_die(v[2]); http->ocmd = strdup_or_die(http->cmd); freez(buf); return JB_ERR_OK; }
int rf_lookup_hostlist(router_instance *rblock, address_item *addr, uschar *ignore_target_hosts, int lookup_type, int hff_code, address_item **addr_new) { BOOL self_send = FALSE; /* Look up each host address. A lookup may add additional items into the chain if there are multiple addresses. Hence the use of next_h to start each cycle of the loop at the next original host. If any host is identified as being the local host, omit it and any subsequent hosts - i.e. treat the list like an ordered list of MX hosts. If the first host is the local host, act according to the "self" option in the configuration. */ for (host_item * prev = NULL, * h = addr->host_list, *next_h; h; h = next_h) { const uschar *canonical_name; int rc, len, port, mx, sort_key; next_h = h->next; if (h->address) { prev = h; continue; } DEBUG(D_route|D_host_lookup) debug_printf("finding IP address for %s\n", h->name); /* Handle any port setting that may be on the name; it will be removed from the end of the name. */ port = host_item_get_port(h); /* Store the previous mx and sort_key values, which were assigned in host_build_hostlist and will be overwritten by host_find_bydns. */ mx = h->mx; sort_key = h->sort_key; /* If the name ends with "/MX", we interpret it to mean "the list of hosts pointed to by MX records with this name", and the MX record values override the ordering from host_build_hostlist. */ len = Ustrlen(h->name); if (len > 3 && strcmpic(h->name + len - 3, US"/mx") == 0) { int whichrrs = lookup_type & LK_IPV4_ONLY ? HOST_FIND_BY_MX | HOST_FIND_IPV4_ONLY : lookup_type & LK_IPV4_PREFER ? HOST_FIND_BY_MX | HOST_FIND_IPV4_FIRST : HOST_FIND_BY_MX; DEBUG(D_route|D_host_lookup) debug_printf("doing DNS MX lookup for %s\n", h->name); mx = MX_NONE; h->name = string_copyn(h->name, len - 3); rc = host_find_bydns(h, ignore_target_hosts, whichrrs, /* look only for MX records */ NULL, /* SRV service not relevant */ NULL, /* failing srv domains not relevant */ NULL, /* no special mx failing domains */ &rblock->dnssec, /* dnssec request/require */ NULL, /* fully_qualified_name */ NULL); /* indicate local host removed */ } /* If explicitly configured to look up by name, or if the "host name" is actually an IP address, do a byname lookup. */ else if (lookup_type & LK_BYNAME || string_is_ip_address(h->name, NULL) != 0) { DEBUG(D_route|D_host_lookup) debug_printf("calling host_find_byname\n"); rc = host_find_byname(h, ignore_target_hosts, HOST_FIND_QUALIFY_SINGLE, &canonical_name, TRUE); } /* Otherwise, do a DNS lookup. If that yields "host not found", and the lookup type is the default (i.e. "bydns" is not explicitly configured), follow up with a byname lookup, just in case. */ else { BOOL removed; int whichrrs = lookup_type & LK_IPV4_ONLY ? HOST_FIND_BY_A : lookup_type & LK_IPV4_PREFER ? HOST_FIND_BY_A | HOST_FIND_BY_AAAA | HOST_FIND_IPV4_FIRST : HOST_FIND_BY_A | HOST_FIND_BY_AAAA; DEBUG(D_route|D_host_lookup) debug_printf("doing DNS lookup\n"); switch (rc = host_find_bydns(h, ignore_target_hosts, whichrrs, NULL, NULL, NULL, &rblock->dnssec, /* domains for request/require */ &canonical_name, &removed)) { case HOST_FOUND: if (removed) setflag(addr, af_local_host_removed); break; case HOST_FIND_FAILED: if (lookup_type & LK_DEFAULT) { DEBUG(D_route|D_host_lookup) debug_printf("DNS lookup failed: trying getipnodebyname\n"); rc = host_find_byname(h, ignore_target_hosts, HOST_FIND_QUALIFY_SINGLE, &canonical_name, TRUE); } break; } } /* Temporary failure defers, unless pass_on_timeout is set */ if (rc == HOST_FIND_SECURITY) { addr->message = string_sprintf("host lookup for %s done insecurely" , h->name); addr->basic_errno = ERRNO_DNSDEFER; return DEFER; } if (rc == HOST_FIND_AGAIN) { if (rblock->pass_on_timeout) { DEBUG(D_route) debug_printf("%s router timed out and pass_on_timeout set\n", rblock->name); return PASS; } addr->message = string_sprintf("host lookup for %s did not complete " "(DNS timeout?)", h->name); addr->basic_errno = ERRNO_DNSDEFER; return DEFER; } /* Permanent failure is controlled by host_find_failed */ if (rc == HOST_FIND_FAILED) { if (hff_code == hff_ignore) { if (prev == NULL) addr->host_list = next_h; else prev->next = next_h; continue; /* With the next host, leave prev unchanged */ } if (hff_code == hff_pass) return PASS; if (hff_code == hff_decline) return DECLINE; addr->basic_errno = ERRNO_UNKNOWNHOST; addr->message = string_sprintf("lookup of host \"%s\" failed in %s router%s", h->name, rblock->name, f.host_find_failed_syntax? ": syntax error in name" : ""); if (hff_code == hff_defer) return DEFER; if (hff_code == hff_fail) return FAIL; addr->special_action = SPECIAL_FREEZE; return DEFER; } /* Deal with the settings that were previously cleared: port, mx and sort_key. */ if (port != PORT_NONE) for (host_item * hh = h; hh != next_h; hh = hh->next) hh->port = port; if (mx != MX_NONE) for (host_item * hh = h; hh != next_h; hh = hh->next) { hh->mx = mx; hh->sort_key = sort_key; } /* A local host gets chopped, with its successors, if there are previous hosts. Otherwise the self option is used. If it is set to "send", any subsequent hosts that are also the local host do NOT get chopped. */ if (rc == HOST_FOUND_LOCAL && !self_send) { if (prev) { DEBUG(D_route) { debug_printf("Removed from host list:\n"); for (; h; h = h->next) debug_printf(" %s\n", h->name); } prev->next = NULL; setflag(addr, af_local_host_removed); break; } rc = rf_self_action(addr, h, rblock->self_code, rblock->self_rewrite, rblock->self, addr_new); if (rc != OK) { addr->host_list = NULL; /* Kill the host list for */ return rc; /* anything other than "send" */ } self_send = TRUE; } /* Ensure that prev is the host before next_h; this will not be h if a lookup found multiple addresses or multiple MX records. */ prev = h; while (prev->next != next_h) prev = prev->next; } return OK; }
int auth_check_some_cond(auth_instance *ablock, uschar *label, uschar *condition, int unset) { uschar *cond; HDEBUG(D_auth) { int i; debug_printf("%s authenticator %s:\n", ablock->name, label); for (i = 0; i < AUTH_VARS; i++) { if (auth_vars[i] != NULL) debug_printf(" $auth%d = %s\n", i + 1, auth_vars[i]); } for (i = 1; i <= expand_nmax; i++) debug_printf(" $%d = %.*s\n", i, expand_nlength[i], expand_nstring[i]); debug_print_string(ablock->server_debug_string); /* customized debug */ } /* For the plaintext authenticator, server_condition is never NULL. For the rest, an unset condition lets everything through. */ /* For server_condition, an unset condition lets everything through. For plaintext/gsasl authenticators, it will have been pre-checked to prevent this. We return the unset scenario value given to us, which for server_condition will be OK and otherwise will typically be FAIL. */ if (condition == NULL) return unset; cond = expand_string(condition); HDEBUG(D_auth) { if (cond == NULL) debug_printf("expansion failed: %s\n", expand_string_message); else debug_printf("expanded string: %s\n", cond); } /* A forced expansion failure causes authentication to fail. Other expansion failures yield DEFER, which will cause a temporary error code to be returned to the AUTH command. The problem is at the server end, so the client should try again later. */ if (cond == NULL) { if (expand_string_forcedfail) return FAIL; auth_defer_msg = expand_string_message; return DEFER; } /* Return FAIL for empty string, "0", "no", and "false"; return OK for "1", "yes", and "true"; return DEFER for anything else, with the string available as an error text for the user. */ if (*cond == 0 || Ustrcmp(cond, "0") == 0 || strcmpic(cond, US"no") == 0 || strcmpic(cond, US"false") == 0) return FAIL; if (Ustrcmp(cond, "1") == 0 || strcmpic(cond, US"yes") == 0 || strcmpic(cond, US"true") == 0) return OK; auth_defer_msg = cond; auth_defer_user_msg = string_sprintf(": %s", cond); return DEFER; }
uschar *bmi_get_base64_verdict(uschar *bmi_local_part, uschar *bmi_domain) { BmiError err; BmiErrorLocation err_loc; BmiErrorType err_type; BmiVerdict *verdict = NULL; const BmiRecipient *recipient = NULL; const char *verdict_str = NULL; uschar *verdict_ptr; uschar *verdict_buffer = NULL; int sep = 0; /* return nothing if there are no verdicts available */ if (bmi_verdicts == NULL) return NULL; /* allocate room for the b64 verdict string */ verdict_buffer = store_get(Ustrlen(bmi_verdicts)+1); /* loop through verdicts */ verdict_ptr = bmi_verdicts; while ((verdict_str = (const char *)string_nextinlist(&verdict_ptr, &sep, verdict_buffer, Ustrlen(bmi_verdicts)+1)) != NULL) { /* create verdict from base64 string */ err = bmiCreateVerdictFromStr(verdict_str, &verdict); if (bmiErrorIsFatal(err) == BMI_TRUE) { err_loc = bmiErrorGetLocation(err); err_type = bmiErrorGetType(err); log_write(0, LOG_PANIC, "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, verdict_str); return NULL; }; /* loop through rcpts for this verdict */ for ( recipient = bmiVerdictAccessFirstRecipient(verdict); recipient != NULL; recipient = bmiVerdictAccessNextRecipient(verdict, recipient)) { uschar *rcpt_local_part; uschar *rcpt_domain; /* compare address against our subject */ rcpt_local_part = (unsigned char *)bmiRecipientAccessAddress(recipient); rcpt_domain = Ustrchr(rcpt_local_part,'@'); if (rcpt_domain == NULL) { rcpt_domain = US""; } else { *rcpt_domain = '\0'; rcpt_domain++; }; if ( (strcmpic(rcpt_local_part, bmi_local_part) == 0) && (strcmpic(rcpt_domain, bmi_domain) == 0) ) { /* found verdict */ bmiFreeVerdict(verdict); return (uschar *)verdict_str; }; }; bmiFreeVerdict(verdict); }; return NULL; }
int spam(const uschar **listptr) { int sep = 0; const uschar *list = *listptr; uschar *user_name; uschar user_name_buffer[128]; unsigned long mbox_size; FILE *mbox_file; int spamd_sock = -1; uschar spamd_buffer[32600]; int i, j, offset, result; uschar spamd_version[8]; uschar spamd_short_result[8]; uschar spamd_score_char; double spamd_threshold, spamd_score, spamd_reject_score; int spamd_report_offset; uschar *p,*q; int override = 0; time_t start; size_t read, wrote; #ifndef NO_POLL_H struct pollfd pollfd; #else /* Patch posted by Erik ? for OS X */ struct timeval select_tv; /* and applied by PH */ fd_set select_fd; #endif uschar *spamd_address_work; spamd_address_container * sd; /* stop compiler warning */ result = 0; /* find the username from the option list */ if ((user_name = string_nextinlist(&list, &sep, user_name_buffer, sizeof(user_name_buffer))) == NULL) { /* no username given, this means no scanning should be done */ return FAIL; } /* if username is "0" or "false", do not scan */ if ( (Ustrcmp(user_name,"0") == 0) || (strcmpic(user_name,US"false") == 0) ) return FAIL; /* if there is an additional option, check if it is "true" */ if (strcmpic(list,US"true") == 0) /* in that case, always return true later */ override = 1; /* expand spamd_address if needed */ if (*spamd_address == '$') { spamd_address_work = expand_string(spamd_address); if (spamd_address_work == NULL) { log_write(0, LOG_MAIN|LOG_PANIC, "%s spamd_address starts with $, but expansion failed: %s", loglabel, expand_string_message); return DEFER; } } else spamd_address_work = spamd_address; DEBUG(D_acl) debug_printf("spamd: addrlist '%s'\n", spamd_address_work); /* check if previous spamd_address was expanded and has changed. dump cached results if so */ if ( spam_ok && prev_spamd_address_work != NULL && Ustrcmp(prev_spamd_address_work, spamd_address_work) != 0 ) spam_ok = 0; /* if we scanned for this username last time, just return */ if (spam_ok && Ustrcmp(prev_user_name, user_name) == 0) return override ? OK : spam_rc; /* make sure the eml mbox file is spooled up */ mbox_file = spool_mbox(&mbox_size, NULL); if (mbox_file == NULL) { /* error while spooling */ log_write(0, LOG_MAIN|LOG_PANIC, "%s error while creating mbox spool file", loglabel); return DEFER; } start = time(NULL); { int num_servers = 0; int current_server; uschar * address; const uschar * spamd_address_list_ptr = spamd_address_work; spamd_address_container * spamd_address_vector[32]; /* Check how many spamd servers we have and register their addresses */ sep = 0; /* default colon-sep */ while ((address = string_nextinlist(&spamd_address_list_ptr, &sep, NULL, 0)) != NULL) { const uschar * sublist; int sublist_sep = -(int)' '; /* default space-sep */ unsigned args; uschar * s; DEBUG(D_acl) debug_printf("spamd: addr entry '%s'\n", address); sd = (spamd_address_container *)store_get(sizeof(spamd_address_container)); for (sublist = address, args = 0, spamd_param_init(sd); (s = string_nextinlist(&sublist, &sublist_sep, NULL, 0)); args++ ) { DEBUG(D_acl) debug_printf("spamd: addr parm '%s'\n", s); switch (args) { case 0: sd->hostspec = s; if (*s == '/') args++; /* local; no port */ break; case 1: sd->hostspec = string_sprintf("%s %s", sd->hostspec, s); break; default: spamd_param(s, sd); break; } } if (args < 2) { log_write(0, LOG_MAIN, "%s warning - invalid spamd address: '%s'", loglabel, address); continue; } spamd_address_vector[num_servers] = sd; if (++num_servers > 31) break; } /* check if we have at least one server */ if (!num_servers) { log_write(0, LOG_MAIN|LOG_PANIC, "%s no useable spamd server addresses in spamd_address configuration option.", loglabel); goto defer; } current_server = spamd_get_server(spamd_address_vector, num_servers); sd = spamd_address_vector[current_server]; for(;;) { uschar * errstr; DEBUG(D_acl) debug_printf("spamd: trying server %s\n", sd->hostspec); for (;;) { if ( (spamd_sock = ip_streamsocket(sd->hostspec, &errstr, 5)) >= 0 || sd->retry <= 0 ) break; DEBUG(D_acl) debug_printf("spamd: server %s: retry conn\n", sd->hostspec); while (sd->retry > 0) sd->retry = sleep(sd->retry); } if (spamd_sock >= 0) break; log_write(0, LOG_MAIN, "%s spamd: %s", loglabel, errstr); sd->is_failed = TRUE; current_server = spamd_get_server(spamd_address_vector, num_servers); if (current_server < 0) { log_write(0, LOG_MAIN|LOG_PANIC, "%s all spamd servers failed", loglabel); goto defer; } sd = spamd_address_vector[current_server]; } } (void)fcntl(spamd_sock, F_SETFL, O_NONBLOCK); /* now we are connected to spamd on spamd_sock */ if (sd->is_rspamd) { /* rspamd variant */ uschar *req_str; const uschar * helo; const uschar * fcrdns; const uschar * authid; req_str = string_sprintf("CHECK RSPAMC/1.3\r\nContent-length: %lu\r\n" "Queue-Id: %s\r\nFrom: <%s>\r\nRecipient-Number: %d\r\n", mbox_size, message_id, sender_address, recipients_count); for (i = 0; i < recipients_count; i ++) req_str = string_sprintf("%sRcpt: <%s>\r\n", req_str, recipients_list[i].address); if ((helo = expand_string(US"$sender_helo_name")) != NULL && *helo != '\0') req_str = string_sprintf("%sHelo: %s\r\n", req_str, helo); if ((fcrdns = expand_string(US"$sender_host_name")) != NULL && *fcrdns != '\0') req_str = string_sprintf("%sHostname: %s\r\n", req_str, fcrdns); if (sender_host_address != NULL) req_str = string_sprintf("%sIP: %s\r\n", req_str, sender_host_address); if ((authid = expand_string(US"$authenticated_id")) != NULL && *authid != '\0') req_str = string_sprintf("%sUser: %s\r\n", req_str, authid); req_str = string_sprintf("%s\r\n", req_str); wrote = send(spamd_sock, req_str, Ustrlen(req_str), 0); } else { /* spamassassin variant */ (void)string_format(spamd_buffer, sizeof(spamd_buffer), "REPORT SPAMC/1.2\r\nUser: %s\r\nContent-length: %ld\r\n\r\n", user_name, mbox_size); /* send our request */ wrote = send(spamd_sock, spamd_buffer, Ustrlen(spamd_buffer), 0); } if (wrote == -1) { (void)close(spamd_sock); log_write(0, LOG_MAIN|LOG_PANIC, "%s spamd %s send failed: %s", loglabel, callout_address, strerror(errno)); goto defer; } /* now send the file */ /* spamd sometimes accepts conections but doesn't read data off * the connection. We make the file descriptor non-blocking so * that the write will only write sufficient data without blocking * and we poll the desciptor to make sure that we can write without * blocking. Short writes are gracefully handled and if the whole * trasaction takes too long it is aborted. * Note: poll() is not supported in OSX 10.2 and is reported to be * broken in more recent versions (up to 10.4). */ #ifndef NO_POLL_H pollfd.fd = spamd_sock; pollfd.events = POLLOUT; #endif (void)fcntl(spamd_sock, F_SETFL, O_NONBLOCK); do { read = fread(spamd_buffer,1,sizeof(spamd_buffer),mbox_file); if (read > 0) { offset = 0; again: #ifndef NO_POLL_H result = poll(&pollfd, 1, 1000); /* Patch posted by Erik ? for OS X and applied by PH */ #else select_tv.tv_sec = 1; select_tv.tv_usec = 0; FD_ZERO(&select_fd); FD_SET(spamd_sock, &select_fd); result = select(spamd_sock+1, NULL, &select_fd, NULL, &select_tv); #endif /* End Erik's patch */ if (result == -1 && errno == EINTR) goto again; else if (result < 1) { if (result == -1) log_write(0, LOG_MAIN|LOG_PANIC, "%s %s on spamd %s socket", loglabel, callout_address, strerror(errno)); else { if (time(NULL) - start < sd->timeout) goto again; log_write(0, LOG_MAIN|LOG_PANIC, "%s timed out writing spamd %s, socket", loglabel, callout_address); } (void)close(spamd_sock); goto defer; } wrote = send(spamd_sock,spamd_buffer + offset,read - offset,0); if (wrote == -1) { log_write(0, LOG_MAIN|LOG_PANIC, "%s %s on spamd %s socket", loglabel, callout_address, strerror(errno)); (void)close(spamd_sock); goto defer; } if (offset + wrote != read) { offset += wrote; goto again; } } } while (!feof(mbox_file) && !ferror(mbox_file)); if (ferror(mbox_file)) { log_write(0, LOG_MAIN|LOG_PANIC, "%s error reading spool file: %s", loglabel, strerror(errno)); (void)close(spamd_sock); goto defer; } (void)fclose(mbox_file); /* we're done sending, close socket for writing */ shutdown(spamd_sock,SHUT_WR); /* read spamd response using what's left of the timeout. */ memset(spamd_buffer, 0, sizeof(spamd_buffer)); offset = 0; while ((i = ip_recv(spamd_sock, spamd_buffer + offset, sizeof(spamd_buffer) - offset - 1, sd->timeout - time(NULL) + start)) > 0) offset += i; spamd_buffer[offset] = '\0'; /* guard byte */ /* error handling */ if (i <= 0 && errno != 0) { log_write(0, LOG_MAIN|LOG_PANIC, "%s error reading from spamd %s, socket: %s", loglabel, callout_address, strerror(errno)); (void)close(spamd_sock); return DEFER; } /* reading done */ (void)close(spamd_sock); if (sd->is_rspamd) { /* rspamd variant of reply */ int r; if ( (r = sscanf(CS spamd_buffer, "RSPAMD/%7s 0 EX_OK\r\nMetric: default; %7s %lf / %lf / %lf\r\n%n", spamd_version, spamd_short_result, &spamd_score, &spamd_threshold, &spamd_reject_score, &spamd_report_offset)) != 5 || spamd_report_offset >= offset /* verify within buffer */ ) { log_write(0, LOG_MAIN|LOG_PANIC, "%s cannot parse spamd %s, output: %d", loglabel, callout_address, r); return DEFER; } /* now parse action */ p = &spamd_buffer[spamd_report_offset]; if (Ustrncmp(p, "Action: ", sizeof("Action: ") - 1) == 0) { p += sizeof("Action: ") - 1; q = &spam_action_buffer[0]; while (*p && *p != '\r' && (q - spam_action_buffer) < sizeof(spam_action_buffer) - 1) *q++ = *p++; *q = '\0'; } } else { /* spamassassin */ /* dig in the spamd output and put the report in a multiline header, if requested */ if (sscanf(CS spamd_buffer, "SPAMD/%7s 0 EX_OK\r\nContent-length: %*u\r\n\r\n%lf/%lf\r\n%n", spamd_version,&spamd_score,&spamd_threshold,&spamd_report_offset) != 3) { /* try to fall back to pre-2.50 spamd output */ if (sscanf(CS spamd_buffer, "SPAMD/%7s 0 EX_OK\r\nSpam: %*s ; %lf / %lf\r\n\r\n%n", spamd_version,&spamd_score,&spamd_threshold,&spamd_report_offset) != 3) { log_write(0, LOG_MAIN|LOG_PANIC, "%s cannot parse spamd %s output", loglabel, callout_address); return DEFER; } } Ustrcpy(spam_action_buffer, spamd_score >= spamd_threshold ? "reject" : "no action"); } /* Create report. Since this is a multiline string, we must hack it into shape first */ p = &spamd_buffer[spamd_report_offset]; q = spam_report_buffer; while (*p != '\0') { /* skip \r */ if (*p == '\r') { p++; continue; } *q++ = *p; if (*p++ == '\n') { /* add an extra space after the newline to ensure that it is treated as a header continuation line */ *q++ = ' '; } } /* NULL-terminate */ *q-- = '\0'; /* cut off trailing leftovers */ while (*q <= ' ') *q-- = '\0'; spam_report = spam_report_buffer; spam_action = spam_action_buffer; /* create spam bar */ spamd_score_char = spamd_score > 0 ? '+' : '-'; j = abs((int)(spamd_score)); i = 0; if (j != 0) while ((i < j) && (i <= MAX_SPAM_BAR_CHARS)) spam_bar_buffer[i++] = spamd_score_char; else { spam_bar_buffer[0] = '/'; i = 1; } spam_bar_buffer[i] = '\0'; spam_bar = spam_bar_buffer; /* create "float" spam score */ (void)string_format(spam_score_buffer, sizeof(spam_score_buffer), "%.1f", spamd_score); spam_score = spam_score_buffer; /* create "int" spam score */ j = (int)((spamd_score + 0.001)*10); (void)string_format(spam_score_int_buffer, sizeof(spam_score_int_buffer), "%d", j); spam_score_int = spam_score_int_buffer; /* compare threshold against score */ spam_rc = spamd_score >= spamd_threshold ? OK /* spam as determined by user's threshold */ : FAIL; /* not spam */ /* remember expanded spamd_address if needed */ if (spamd_address_work != spamd_address) prev_spamd_address_work = string_copy(spamd_address_work); /* remember user name and "been here" for it */ Ustrcpy(prev_user_name, user_name); spam_ok = 1; return override ? OK /* always return OK, no matter what the score */ : spam_rc; defer: (void)fclose(mbox_file); return DEFER; }
int auth_dovecot_server(auth_instance * ablock, uschar * data) { auth_dovecot_options_block *ob = (auth_dovecot_options_block *) ablock->options_block; struct sockaddr_un sa; uschar buffer[DOVECOT_AUTH_MAXLINELEN]; uschar *args[DOVECOT_AUTH_MAXFIELDCOUNT]; uschar *auth_command; uschar *auth_extra_data = US""; uschar *p; int nargs, tmp; int crequid = 1, cont = 1, fd = -1, ret = DEFER; BOOL found = FALSE, have_mech_line = FALSE; HDEBUG(D_auth) debug_printf("dovecot authentication\n"); if (!data) { ret = FAIL; goto out; } memset(&sa, 0, sizeof(sa)); sa.sun_family = AF_UNIX; /* This was the original code here: it is nonsense because strncpy() does not return an integer. I have converted this to use the function that formats and checks length. PH */ /* if (strncpy(sa.sun_path, ob->server_socket, sizeof(sa.sun_path)) < 0) { } */ if (!string_format(US sa.sun_path, sizeof(sa.sun_path), "%s", ob->server_socket)) { auth_defer_msg = US"authentication socket path too long"; return DEFER; } auth_defer_msg = US"authentication socket connection error"; if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) return DEFER; if (connect(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) goto out; auth_defer_msg = US"authentication socket protocol error"; socket_buffer_left = 0; /* Global, used to read more than a line but return by line */ while (cont) { if (dc_gets(buffer, sizeof(buffer), fd) == NULL) OUT("authentication socket read error or premature eof"); p = buffer + Ustrlen(buffer) - 1; if (*p != '\n') OUT("authentication socket protocol line too long"); *p = '\0'; HDEBUG(D_auth) debug_printf("received: %s\n", buffer); nargs = strcut(buffer, args, sizeof(args) / sizeof(args[0])); /* HDEBUG(D_auth) debug_strcut(args, nargs, sizeof(args) / sizeof(args[0])); */ /* Code below rewritten by Kirill Miazine ([email protected]). Only check commands that Exim will need. Original code also failed if Dovecot server sent unknown command. E.g. COOKIE in version 1.1 of the protocol would cause troubles. */ /* pdp: note that CUID is a per-connection identifier sent by the server, which increments at server discretion. By contrast, the "id" field of the protocol is a connection-specific request identifier, which needs to be unique per request from the client and is not connected to the CUID value, so we ignore CUID from server. It's purely for diagnostics. */ if (Ustrcmp(args[0], US"VERSION") == 0) { CHECK_COMMAND("VERSION", 2, 2); if (Uatoi(args[1]) != VERSION_MAJOR) OUT("authentication socket protocol version mismatch"); } else if (Ustrcmp(args[0], US"MECH") == 0) { CHECK_COMMAND("MECH", 1, INT_MAX); have_mech_line = TRUE; if (strcmpic(US args[1], ablock->public_name) == 0) found = TRUE; } else if (Ustrcmp(args[0], US"SPID") == 0) { /* Unfortunately the auth protocol handshake wasn't designed well to differentiate between auth-client/userdb/master. auth-userdb and auth-master send VERSION + SPID lines only and nothing afterwards, while auth-client sends VERSION + MECH + SPID + CUID + more. The simplest way that we can determine if we've connected to the correct socket is to see if MECH line exists or not (alternatively we'd have to have a small timeout after SPID to see if CUID is sent or not). */ if (!have_mech_line) OUT("authentication socket type mismatch" " (connected to auth-master instead of auth-client)"); } else if (Ustrcmp(args[0], US"DONE") == 0) { CHECK_COMMAND("DONE", 0, 0); cont = 0; } } if (!found) { auth_defer_msg = string_sprintf( "Dovecot did not advertise mechanism \"%s\" to us", ablock->public_name); goto out; } /* Added by PH: data must not contain tab (as it is b64 it shouldn't, but check for safety). */ if (Ustrchr(data, '\t') != NULL) { ret = FAIL; goto out; } /* Added by PH: extra fields when TLS is in use or if the TCP/IP connection is local. */ if (tls_in.cipher != NULL) auth_extra_data = string_sprintf("secured\t%s%s", tls_in.certificate_verified? "valid-client-cert" : "", tls_in.certificate_verified? "\t" : ""); else if ( interface_address != NULL && Ustrcmp(sender_host_address, interface_address) == 0) auth_extra_data = US"secured\t"; /**************************************************************************** The code below was the original code here. It didn't work. A reading of the file auth-protocol.txt.gz that came with Dovecot 1.0_beta8 indicated that this was not right. Maybe something changed. I changed it to move the service indication into the AUTH command, and it seems to be better. PH fprintf(f, "VERSION\t%d\t%d\r\nSERVICE\tSMTP\r\nCPID\t%d\r\n" "AUTH\t%d\t%s\trip=%s\tlip=%s\tresp=%s\r\n", VERSION_MAJOR, VERSION_MINOR, getpid(), cuid, ablock->public_name, sender_host_address, interface_address, data ? CS data : ""); Subsequently, the command was modified to add "secured" and "valid-client- cert" when relevant. ****************************************************************************/ auth_command = string_sprintf("VERSION\t%d\t%d\nCPID\t%d\n" "AUTH\t%d\t%s\tservice=smtp\t%srip=%s\tlip=%s\tnologin\tresp=%s\n", VERSION_MAJOR, VERSION_MINOR, getpid(), crequid, ablock->public_name, auth_extra_data, sender_host_address, interface_address, data); if (write(fd, auth_command, Ustrlen(auth_command)) < 0) HDEBUG(D_auth) debug_printf("error sending auth_command: %s\n", strerror(errno)); HDEBUG(D_auth) debug_printf("sent: %s", auth_command); while (1) { uschar *temp; uschar *auth_id_pre = NULL; int i; if (dc_gets(buffer, sizeof(buffer), fd) == NULL) { auth_defer_msg = US"authentication socket read error or premature eof"; goto out; } buffer[Ustrlen(buffer) - 1] = 0; HDEBUG(D_auth) debug_printf("received: %s\n", buffer); nargs = strcut(buffer, args, sizeof(args) / sizeof(args[0])); if (Uatoi(args[1]) != crequid) OUT("authentication socket connection id mismatch"); switch (toupper(*args[0])) { case 'C': CHECK_COMMAND("CONT", 1, 2); if ((tmp = auth_get_no64_data(&data, US args[2])) != OK) { ret = tmp; goto out; } /* Added by PH: data must not contain tab (as it is b64 it shouldn't, but check for safety). */ if (Ustrchr(data, '\t') != NULL) { ret = FAIL; goto out; } temp = string_sprintf("CONT\t%d\t%s\n", crequid, data); if (write(fd, temp, Ustrlen(temp)) < 0) OUT("authentication socket write error"); break; case 'F': CHECK_COMMAND("FAIL", 1, -1); for (i=2; (i<nargs) && (auth_id_pre == NULL); i++) { if ( Ustrncmp(args[i], US"user="******"OK", 2, -1); /* Search for the "user=$USER" string in the args array and return the proper value. */ for (i=2; (i<nargs) && (auth_id_pre == NULL); i++) { if ( Ustrncmp(args[i], US"user="******"authentication socket protocol error, username missing"); ret = OK; /* fallthrough */ default: goto out; } } out: /* close the socket used by dovecot */ if (fd >= 0) close(fd); /* Expand server_condition as an authorization check */ return ret == OK ? auth_check_serv_cond(ablock) : ret; }
int mime_regex(uschar **listptr) { int sep = 0; uschar *list = *listptr; uschar *regex_string; uschar regex_string_buffer[1024]; pcre *re; pcre_list *re_list_head = NULL; pcre_list *re_list_item; const char *pcre_error; int pcre_erroffset; FILE *f; uschar *mime_subject = NULL; int mime_subject_len = 0; /* reset expansion variable */ regex_match_string = NULL; /* precompile our regexes */ while ((regex_string = string_nextinlist(&list, &sep, regex_string_buffer, sizeof(regex_string_buffer))) != NULL) { /* parse option */ if ( (strcmpic(regex_string,US"false") == 0) || (Ustrcmp(regex_string,"0") == 0) ) { /* explicitly no matching */ continue; }; /* compile our regular expression */ re = pcre_compile( CS regex_string, 0, &pcre_error, &pcre_erroffset, NULL ); if (re == NULL) { log_write(0, LOG_MAIN, "regex acl condition warning - error in regex '%s': %s at offset %d, skipped.", regex_string, pcre_error, pcre_erroffset); continue; } else { re_list_item = store_get(sizeof(pcre_list)); re_list_item->re = re; re_list_item->pcre_text = string_copy(regex_string); re_list_item->next = re_list_head; re_list_head = re_list_item; }; }; /* no regexes -> nothing to do */ if (re_list_head == NULL) { return FAIL; }; /* check if the file is already decoded */ if (mime_decoded_filename == NULL) { uschar *empty = US""; /* no, decode it first */ mime_decode(&empty); if (mime_decoded_filename == NULL) { /* decoding failed */ log_write(0, LOG_MAIN, "mime_regex acl condition warning - could not decode MIME part to file."); return DEFER; }; }; /* open file */ f = fopen(CS mime_decoded_filename, "rb"); if (f == NULL) { /* open failed */ log_write(0, LOG_MAIN, "mime_regex acl condition warning - can't open '%s' for reading.", mime_decoded_filename); return DEFER; }; /* get 32k memory */ mime_subject = (uschar *)store_get(32767); /* read max 32k chars from file */ mime_subject_len = fread(mime_subject, 1, 32766, f); re_list_item = re_list_head; do { /* try matcher on the mmapped file */ debug_printf("Matching '%s'\n", re_list_item->pcre_text); if (pcre_exec(re_list_item->re, NULL, CS mime_subject, mime_subject_len, 0, 0, NULL, 0) >= 0) { Ustrncpy(regex_match_string_buffer, re_list_item->pcre_text, 1023); regex_match_string = regex_match_string_buffer; (void)fclose(f); return OK; }; re_list_item = re_list_item->next; } while (re_list_item != NULL); (void)fclose(f); /* no matches ... */ return FAIL; }
int dcc_process(uschar **listptr) { int sep = 0; uschar *list = *listptr; FILE *data_file; uschar *dcc_daemon_ip = US""; uschar *dcc_default_ip_option = US"127.0.0.1"; uschar *dcc_ip_option = US""; uschar *dcc_helo_option = US"localhost"; uschar *dcc_reject_message = US"Rejected by DCC"; uschar *xtra_hdrs = NULL; /* from local_scan */ int i, j, k, c, retval, sockfd, resp, line; unsigned int portnr; struct sockaddr_un serv_addr; struct sockaddr_in serv_addr_in; struct hostent *ipaddress; uschar sockpath[128]; uschar sockip[40], client_ip[40]; uschar opts[128]; uschar rcpt[128], from[128]; uschar sendbuf[4096]; uschar recvbuf[4096]; uschar dcc_return_text[1024]; uschar mbox_path[1024]; uschar message_subdir[2]; struct header_line *dcchdr; uschar *dcc_acl_options; uschar dcc_acl_options_buffer[10]; uschar dcc_xtra_hdrs[1024]; /* grep 1st option */ if ((dcc_acl_options = string_nextinlist(&list, &sep, dcc_acl_options_buffer, sizeof(dcc_acl_options_buffer))) != NULL) { /* parse 1st option */ if ( (strcmpic(dcc_acl_options,US"false") == 0) || (Ustrcmp(dcc_acl_options,"0") == 0) ) { /* explicitly no matching */ return FAIL; }; /* special cases (match anything except empty) */ if ( (strcmpic(dcc_acl_options,US"true") == 0) || (Ustrcmp(dcc_acl_options,"*") == 0) || (Ustrcmp(dcc_acl_options,"1") == 0) ) { dcc_acl_options = dcc_acl_options; }; } else { /* empty means "don't match anything" */ return FAIL; }; sep = 0; /* if we scanned this message last time, just return */ if ( dcc_ok ) return dcc_rc; /* open the spooled body */ message_subdir[1] = '\0'; for (i = 0; i < 2; i++) { message_subdir[0] = (split_spool_directory == (i == 0))? message_id[5] : 0; sprintf(CS mbox_path, "%s/input/%s/%s-D", spool_directory, message_subdir, message_id); data_file = Ufopen(mbox_path,"rb"); if (data_file != NULL) break; }; if (data_file == NULL) { /* error while spooling */ log_write(0, LOG_MAIN|LOG_PANIC, "dcc acl condition: error while opening spool file"); return DEFER; }; /* Initialize the variables */ bzero(sockip,sizeof(sockip)); if (dccifd_address) { if (dccifd_address[0] == '/') Ustrncpy(sockpath, dccifd_address, sizeof(sockpath)); else if( sscanf(CS dccifd_address, "%s %u", sockip, &portnr) != 2) { log_write(0, LOG_MAIN, "dcc acl condition: warning - invalid dccifd address: '%s'", dccifd_address); (void)fclose(data_file); return DEFER; } } /* opts is what we send as dccifd options - see man dccifd */ /* We don't support any other option than 'header' so just copy that */ bzero(opts,sizeof(opts)); Ustrncpy(opts, "header", sizeof(opts)-1); Ustrncpy(client_ip, dcc_ip_option, sizeof(client_ip)-1); /* If the dcc_client_ip is not provided use the * sender_host_address or 127.0.0.1 if it is NULL */ DEBUG(D_acl) debug_printf("my_ip_option = %s - client_ip = %s - sender_host_address = %s\n", dcc_ip_option, client_ip, sender_host_address); if(!(Ustrcmp(client_ip, ""))){ /* Do we have a sender_host_address or is it NULL? */ if(sender_host_address){ Ustrncpy(client_ip, sender_host_address, sizeof(client_ip)-1); } else { /* sender_host_address is NULL which means it comes from localhost */ Ustrncpy(client_ip, dcc_default_ip_option, sizeof(client_ip)-1); } } DEBUG(D_acl) debug_printf("Client IP: %s\n", client_ip); Ustrncpy(sockip, dcc_daemon_ip, sizeof(sockip)-1); /* strncat(opts, my_request, strlen(my_request)); */ Ustrcat(opts, "\n"); Ustrncat(opts, client_ip, sizeof(opts)-Ustrlen(opts)-1); Ustrncat(opts, "\nHELO ", sizeof(opts)-Ustrlen(opts)-1); Ustrncat(opts, dcc_helo_option, sizeof(opts)-Ustrlen(opts)-2); Ustrcat(opts, "\n"); /* initialize the other variables */ dcchdr = header_list; /* we set the default return value to DEFER */ retval = DEFER; bzero(sendbuf,sizeof(sendbuf)); bzero(dcc_header_str,sizeof(dcc_header_str)); bzero(rcpt,sizeof(rcpt)); bzero(from,sizeof(from)); /* send a null return path as "<>". */ if (Ustrlen(sender_address) > 0) Ustrncpy(from, sender_address, sizeof(from)); else Ustrncpy(from, "<>", sizeof(from)); Ustrncat(from, "\n", sizeof(from)-Ustrlen(from)-1); /************************************** * Now creating the socket connection * **************************************/ /* If there is a dcc_daemon_ip, we use a tcp socket, otherwise a UNIX socket */ if(Ustrcmp(sockip, "")){ ipaddress = gethostbyname((char *)sockip); bzero((char *) &serv_addr_in, sizeof(serv_addr_in)); serv_addr_in.sin_family = AF_INET; bcopy((char *)ipaddress->h_addr, (char *)&serv_addr_in.sin_addr.s_addr, ipaddress->h_length); serv_addr_in.sin_port = htons(portnr); if ((sockfd = socket(AF_INET, SOCK_STREAM,0)) < 0){ DEBUG(D_acl) debug_printf("Creating socket failed: %s\n", strerror(errno)); log_write(0,LOG_REJECT,"Creating socket failed: %s\n", strerror(errno)); /* if we cannot create the socket, defer the mail */ (void)fclose(data_file); return retval; } /* Now connecting the socket (INET) */ if (connect(sockfd, (struct sockaddr *)&serv_addr_in, sizeof(serv_addr_in)) < 0){ DEBUG(D_acl) debug_printf("Connecting socket failed: %s\n", strerror(errno)); log_write(0,LOG_REJECT,"Connecting socket failed: %s\n", strerror(errno)); /* if we cannot contact the socket, defer the mail */ (void)fclose(data_file); return retval; } } else { /* connecting to the dccifd UNIX socket */ bzero((char *)&serv_addr,sizeof(serv_addr)); serv_addr.sun_family = AF_UNIX; Ustrcpy(serv_addr.sun_path, sockpath); if ((sockfd = socket(AF_UNIX, SOCK_STREAM,0)) < 0){ DEBUG(D_acl) debug_printf("Creating socket failed: %s\n", strerror(errno)); log_write(0,LOG_REJECT,"Creating socket failed: %s\n", strerror(errno)); /* if we cannot create the socket, defer the mail */ (void)fclose(data_file); return retval; } /* Now connecting the socket (UNIX) */ if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0){ DEBUG(D_acl) debug_printf("Connecting socket failed: %s\n", strerror(errno)); log_write(0,LOG_REJECT,"Connecting socket failed: %s\n", strerror(errno)); /* if we cannot contact the socket, defer the mail */ (void)fclose(data_file); return retval; } } /* the socket is open, now send the options to dccifd*/ DEBUG(D_acl) debug_printf("\n---------------------------\nSocket opened; now sending input\n-----------------\n"); /* First, fill in the input buffer */ Ustrncpy(sendbuf, opts, sizeof(sendbuf)); Ustrncat(sendbuf, from, sizeof(sendbuf)-Ustrlen(sendbuf)-1); DEBUG(D_acl) { debug_printf("opts = %s\nsender = %s\nrcpt count = %d\n", opts, from, recipients_count); debug_printf("Sending options:\n****************************\n"); } /* let's send each of the recipients to dccifd */ for (i = 0; i < recipients_count; i++){ DEBUG(D_acl) debug_printf("recipient = %s\n",recipients_list[i].address); if(Ustrlen(sendbuf) + Ustrlen(recipients_list[i].address) > sizeof(sendbuf)) { DEBUG(D_acl) debug_printf("Writing buffer: %s\n", sendbuf); flushbuffer(sockfd, sendbuf); bzero(sendbuf, sizeof(sendbuf)); } Ustrncat(sendbuf, recipients_list[i].address, sizeof(sendbuf)-Ustrlen(sendbuf)-1); Ustrncat(sendbuf, "\r\n", sizeof(sendbuf)-Ustrlen(sendbuf)-1); } /* send a blank line between options and message */ Ustrncat(sendbuf, "\n", sizeof(sendbuf)-Ustrlen(sendbuf)-1); /* Now we send the input buffer */ DEBUG(D_acl) debug_printf("%s\n****************************\n", sendbuf); flushbuffer(sockfd, sendbuf); /* now send the message */ /* Clear the input buffer */ bzero(sendbuf, sizeof(sendbuf)); /* First send the headers */ /* Now send the headers */ DEBUG(D_acl) debug_printf("Sending headers:\n****************************\n"); Ustrncpy(sendbuf, dcchdr->text, sizeof(sendbuf)-2); while((dcchdr=dcchdr->next)) { if(dcchdr->slen > sizeof(sendbuf)-2) { /* The size of the header is bigger than the size of * the input buffer, so split it up in smaller parts. */ flushbuffer(sockfd, sendbuf); bzero(sendbuf, sizeof(sendbuf)); j = 0; while(j < dcchdr->slen) { for(i = 0; i < sizeof(sendbuf)-2; i++) { sendbuf[i] = dcchdr->text[j]; j++; } flushbuffer(sockfd, sendbuf); bzero(sendbuf, sizeof(sendbuf)); } } else if(Ustrlen(sendbuf) + dcchdr->slen > sizeof(sendbuf)-2) { flushbuffer(sockfd, sendbuf); bzero(sendbuf, sizeof(sendbuf)); Ustrncpy(sendbuf, dcchdr->text, sizeof(sendbuf)-2); } else { Ustrncat(sendbuf, dcchdr->text, sizeof(sendbuf)-Ustrlen(sendbuf)-2); } } /* a blank line seperates header from body */ Ustrncat(sendbuf, "\n", sizeof(sendbuf)-Ustrlen(sendbuf)-1); flushbuffer(sockfd, sendbuf); DEBUG(D_acl) debug_printf("\n****************************\n%s", sendbuf); /* Clear the input buffer */ bzero(sendbuf, sizeof(sendbuf)); /* now send the body */ DEBUG(D_acl) debug_printf("Writing body:\n****************************\n"); (void)fseek(data_file, SPOOL_DATA_START_OFFSET, SEEK_SET); while((fread(sendbuf, 1, sizeof(sendbuf)-1, data_file)) > 0) { flushbuffer(sockfd, sendbuf); bzero(sendbuf, sizeof(sendbuf)); } DEBUG(D_acl) debug_printf("\n****************************\n"); /* shutdown() the socket */ if(shutdown(sockfd, 1) < 0){ DEBUG(D_acl) debug_printf("Couldn't shutdown socket: %s\n", strerror(errno)); log_write(0,LOG_MAIN,"Couldn't shutdown socket: %s\n", strerror(errno)); /* If there is a problem with the shutdown() * defer the mail. */ (void)fclose(data_file); return retval; } DEBUG(D_acl) debug_printf("\n-------------------------\nInput sent.\n-------------------------\n"); /******************************** * receiving output from dccifd * ********************************/ DEBUG(D_acl) debug_printf("\n-------------------------------------\nNow receiving output from server\n-----------------------------------\n"); /****************************************************************** * We should get 3 lines: * * 1/ First line is overall result: either 'A' for Accept, * * 'R' for Reject, 'S' for accept Some recipients or * * 'T' for a Temporary error. * * 2/ Second line contains the list of Accepted/Rejected * * recipients in the form AARRA (A = accepted, R = rejected). * * 3/ Third line contains the X-DCC header. * ******************************************************************/ line = 1; /* we start at the first line of the output */ j = 0; /* will be used as index for the recipients list */ k = 0; /* initializing the index of the X-DCC header: dcc_header_str[k] */ /* Let's read from the socket until there's nothing left to read */ bzero(recvbuf, sizeof(recvbuf)); while((resp = read(sockfd, recvbuf, sizeof(recvbuf)-1)) > 0) { /* How much did we get from the socket */ c = Ustrlen(recvbuf) + 1; DEBUG(D_acl) debug_printf("Length of the output buffer is: %d\nOutput buffer is:\n------------\n%s\n-----------\n", c, recvbuf); /* Now let's read each character and see what we've got */ for(i = 0; i < c; i++) { /* First check if we reached the end of the line and * then increment the line counter */ if(recvbuf[i] == '\n') { line++; } else { /* The first character of the first line is the * overall response. If there's another character * on that line it is not correct. */ if(line == 1) { if(i == 0) { /* Now get the value and set the * return value accordingly */ if(recvbuf[i] == 'A') { DEBUG(D_acl) debug_printf("Overall result = A\treturning OK\n"); Ustrcpy(dcc_return_text, "Mail accepted by DCC"); dcc_result = US"A"; retval = OK; } else if(recvbuf[i] == 'R') { DEBUG(D_acl) debug_printf("Overall result = R\treturning FAIL\n"); dcc_result = US"R"; retval = FAIL; if(sender_host_name) { log_write(0, LOG_MAIN, "H=%s [%s] F=<%s>: rejected by DCC", sender_host_name, sender_host_address, sender_address); } else { log_write(0, LOG_MAIN, "H=[%s] F=<%s>: rejected by DCC", sender_host_address, sender_address); } Ustrncpy(dcc_return_text, dcc_reject_message, Ustrlen(dcc_reject_message) + 1); } else if(recvbuf[i] == 'S') { DEBUG(D_acl) debug_printf("Overall result = S\treturning OK\n"); Ustrcpy(dcc_return_text, "Not all recipients accepted by DCC"); /* Since we're in an ACL we want a global result * so we accept for all */ dcc_result = US"A"; retval = OK; } else if(recvbuf[i] == 'G') { DEBUG(D_acl) debug_printf("Overall result = G\treturning FAIL\n"); Ustrcpy(dcc_return_text, "Greylisted by DCC"); dcc_result = US"G"; retval = FAIL; } else if(recvbuf[i] == 'T') { DEBUG(D_acl) debug_printf("Overall result = T\treturning DEFER\n"); retval = DEFER; log_write(0,LOG_MAIN,"Temporary error with DCC: %s\n", recvbuf); Ustrcpy(dcc_return_text, "Temporary error with DCC"); dcc_result = US"T"; } else { DEBUG(D_acl) debug_printf("Overall result = something else\treturning DEFER\n"); retval = DEFER; log_write(0,LOG_MAIN,"Unknown DCC response: %s\n", recvbuf); Ustrcpy(dcc_return_text, "Unknown DCC response"); dcc_result = US"T"; } } else { /* We're on the first line but not on the first character, * there must be something wrong. */ DEBUG(D_acl) debug_printf("Line = %d but i = %d != 0 character is %c - This is wrong!\n", line, i, recvbuf[i]); log_write(0,LOG_MAIN,"Wrong header from DCC, output is %s\n", recvbuf); } } else if(line == 2) { /* On the second line we get a list of * answers for each recipient. We don't care about * it because we're in an acl and take the * global result. */ } else if(line > 2) { /* The third and following lines are the X-DCC header, * so we store it in dcc_header_str. */ /* check if we don't get more than we can handle */ if(k < sizeof(dcc_header_str)) { dcc_header_str[k] = recvbuf[i]; k++; } else { DEBUG(D_acl) debug_printf("We got more output than we can store in the X-DCC header. Truncating at 120 characters.\n"); } } else { /* Wrong line number. There must be a problem with the output. */ DEBUG(D_acl) debug_printf("Wrong line number in output. Line number is %d\n", line); } } } /* we reinitialize the output buffer before we read again */ bzero(recvbuf,sizeof(recvbuf)); } /* We have read everything from the socket */ /* We need to terminate the X-DCC header with a '\n' character. This needs to be k-1 * since dcc_header_str[k] contains '\0'. */ dcc_header_str[k-1] = '\n'; /* Now let's sum up what we've got. */ DEBUG(D_acl) debug_printf("\n--------------------------\nOverall result = %d\nX-DCC header: %sReturn message: %s\ndcc_result: %s\n", retval, dcc_header_str, dcc_return_text, dcc_result); /* We only add the X-DCC header if it starts with X-DCC */ if(!(Ustrncmp(dcc_header_str, "X-DCC", 5))){ dcc_header = dcc_header_str; if(dcc_direct_add_header) { header_add(' ' , "%s", dcc_header_str); /* since the MIME ACL already writes the .eml file to disk without DCC Header we've to erase it */ unspool_mbox(); } } else { DEBUG(D_acl) debug_printf("Wrong format of the X-DCC header: %s\n", dcc_header_str); } /* check if we should add additional headers passed in acl_m_dcc_add_header */ if(dcc_direct_add_header) { if (((xtra_hdrs = expand_string(US"$acl_m_dcc_add_header")) != NULL) && (xtra_hdrs[0] != '\0')) { Ustrncpy(dcc_xtra_hdrs, xtra_hdrs, sizeof(dcc_xtra_hdrs) - 2); if (dcc_xtra_hdrs[Ustrlen(dcc_xtra_hdrs)-1] != '\n') Ustrcat(dcc_xtra_hdrs, "\n"); header_add(' ', "%s", dcc_xtra_hdrs); DEBUG(D_acl) debug_printf("adding additional headers in $acl_m_dcc_add_header: %s", dcc_xtra_hdrs); } } dcc_ok = 1; /* Now return to exim main process */ DEBUG(D_acl) debug_printf("Before returning to exim main process:\nreturn_text = %s - retval = %d\ndcc_result = %s\n", dcc_return_text, retval, dcc_result); (void)fclose(data_file); dcc_rc = retval; return dcc_rc; }
int regex(uschar **listptr) { int sep = 0; uschar *list = *listptr; uschar *regex_string; uschar regex_string_buffer[1024]; unsigned long mbox_size; FILE *mbox_file; pcre *re; pcre_list *re_list_head = NULL; pcre_list *re_list_item; const char *pcre_error; int pcre_erroffset; uschar *linebuffer; long f_pos = 0; /* reset expansion variable */ regex_match_string = NULL; if (mime_stream == NULL) { /* We are in the DATA ACL */ mbox_file = spool_mbox(&mbox_size, NULL); if (mbox_file == NULL) { /* error while spooling */ log_write(0, LOG_MAIN|LOG_PANIC, "regex acl condition: error while creating mbox spool file"); return DEFER; }; } else { f_pos = ftell(mime_stream); mbox_file = mime_stream; }; /* precompile our regexes */ while ((regex_string = string_nextinlist(&list, &sep, regex_string_buffer, sizeof(regex_string_buffer))) != NULL) { /* parse option */ if ( (strcmpic(regex_string,US"false") == 0) || (Ustrcmp(regex_string,"0") == 0) ) { /* explicitly no matching */ continue; }; /* compile our regular expression */ re = pcre_compile( CS regex_string, 0, &pcre_error, &pcre_erroffset, NULL ); if (re == NULL) { log_write(0, LOG_MAIN, "regex acl condition warning - error in regex '%s': %s at offset %d, skipped.", regex_string, pcre_error, pcre_erroffset); continue; } else { re_list_item = store_get(sizeof(pcre_list)); re_list_item->re = re; re_list_item->pcre_text = string_copy(regex_string); re_list_item->next = re_list_head; re_list_head = re_list_item; }; }; /* no regexes -> nothing to do */ if (re_list_head == NULL) { return FAIL; }; /* match each line against all regexes */ linebuffer = store_get(32767); while (fgets(CS linebuffer, 32767, mbox_file) != NULL) { if ( (mime_stream != NULL) && (mime_current_boundary != NULL) ) { /* check boundary */ if (Ustrncmp(linebuffer,"--",2) == 0) { if (Ustrncmp((linebuffer+2),mime_current_boundary,Ustrlen(mime_current_boundary)) == 0) /* found boundary */ break; }; }; re_list_item = re_list_head; do { /* try matcher on the line */ if (pcre_exec(re_list_item->re, NULL, CS linebuffer, (int)Ustrlen(linebuffer), 0, 0, NULL, 0) >= 0) { Ustrncpy(regex_match_string_buffer, re_list_item->pcre_text, 1023); regex_match_string = regex_match_string_buffer; if (mime_stream == NULL) (void)fclose(mbox_file); else { clearerr(mime_stream); fseek(mime_stream,f_pos,SEEK_SET); }; return OK; }; re_list_item = re_list_item->next; } while (re_list_item != NULL); }; if (mime_stream == NULL) (void)fclose(mbox_file); else { clearerr(mime_stream); fseek(mime_stream,f_pos,SEEK_SET); }; /* no matches ... */ return FAIL; }
void auth_cyrus_sasl_init(auth_instance *ablock) { auth_cyrus_sasl_options_block *ob = (auth_cyrus_sasl_options_block *)(ablock->options_block); const uschar *list, *listptr, *buffer; int rc, i; unsigned int len; uschar *rs_point, *expanded_hostname; char *realm_expanded; sasl_conn_t *conn; sasl_callback_t cbs[] = { {SASL_CB_GETOPT, NULL, NULL }, {SASL_CB_LIST_END, NULL, NULL}}; /* default the mechanism to our "public name" */ if (ob->server_mech == NULL) ob->server_mech = string_copy(ablock->public_name); expanded_hostname = expand_string(ob->server_hostname); if (expanded_hostname == NULL) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " "couldn't expand server_hostname [%s]: %s", ablock->name, ob->server_hostname, expand_string_message); realm_expanded = NULL; if (ob->server_realm != NULL) { realm_expanded = CS expand_string(ob->server_realm); if (realm_expanded == NULL) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " "couldn't expand server_realm [%s]: %s", ablock->name, ob->server_realm, expand_string_message); } /* we're going to initialise the library to check that there is an * authenticator of type whatever mechanism we're using */ cbs[0].proc = (int(*)(void)) &mysasl_config; cbs[0].context = ob->server_mech; if ((rc = sasl_server_init(cbs, "exim")) != SASL_OK ) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " "couldn't initialise Cyrus SASL library.", ablock->name); if ((rc = sasl_server_new(CS ob->server_service, CS expanded_hostname, realm_expanded, NULL, NULL, NULL, 0, &conn)) != SASL_OK ) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " "couldn't initialise Cyrus SASL server connection.", ablock->name); if ((rc = sasl_listmech(conn, NULL, "", ":", "", (const char **)&list, &len, &i)) != SASL_OK ) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " "couldn't get Cyrus SASL mechanism list.", ablock->name); i = ':'; listptr = list; HDEBUG(D_auth) { debug_printf("Initialised Cyrus SASL service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n", ob->server_service, expanded_hostname, realm_expanded); debug_printf("Cyrus SASL knows mechanisms: %s\n", list); } /* the store_get / store_reset mechanism is hierarchical * the hierarchy is stored for us behind our back. This point * creates a hierarchy point for this function. */ rs_point = store_get(0); /* loop until either we get to the end of the list, or we match the * public name of this authenticator */ while ( ( buffer = string_nextinlist(&listptr, &i, NULL, 0) ) && strcmpic(buffer,ob->server_mech) ); if (!buffer) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " "Cyrus SASL doesn't know about mechanism %s.", ablock->name, ob->server_mech); store_reset(rs_point); HDEBUG(D_auth) debug_printf("Cyrus SASL driver %s: %s initialised\n", ablock->name, ablock->public_name); /* make sure that if we get here then we're allowed to advertise. */ ablock->server = TRUE; sasl_dispose(&conn); sasl_done(); }
int auth_dovecot_server(auth_instance *ablock, uschar *data) { auth_dovecot_options_block *ob = (auth_dovecot_options_block *)(ablock->options_block); struct sockaddr_un sa; uschar buffer[4096]; uschar *args[8]; uschar *auth_command; uschar *auth_extra_data = US""; int nargs, tmp; int cuid = 0, cont = 1, found = 0, fd, ret = DEFER; HDEBUG(D_auth) debug_printf("dovecot authentication\n"); memset(&sa, 0, sizeof(sa)); sa.sun_family = AF_UNIX; /* This was the original code here: it is nonsense because strncpy() does not return an integer. I have converted this to use the function that formats and checks length. PH */ /* if (strncpy(sa.sun_path, ob->server_socket, sizeof(sa.sun_path)) < 0) { */ if (!string_format(US sa.sun_path, sizeof(sa.sun_path), "%s", ob->server_socket)) { auth_defer_msg = US"authentication socket path too long"; return DEFER; } auth_defer_msg = US"authentication socket connection error"; fd = socket(PF_UNIX, SOCK_STREAM, 0); if (fd < 0) return DEFER; if (connect(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) goto out; auth_defer_msg = US"authentication socket protocol error"; sbp = 0; /* Socket buffer pointer */ while (cont) { if (dc_gets(buffer, sizeof(buffer), fd) == NULL) OUT("authentication socket read error or premature eof"); buffer[Ustrlen(buffer) - 1] = 0; HDEBUG(D_auth) debug_printf("received: %s\n", buffer); nargs = strcut(buffer, args, sizeof(args) / sizeof(args[0])); /* Code below rewritten by Kirill Miazine ([email protected]). Only check commands that Exim will need. Original code also failed if Dovecot server sent unknown command. E.g. COOKIE in version 1.1 of the protocol would cause troubles. */ if (Ustrcmp(args[0], US"CUID") == 0) { CHECK_COMMAND("CUID", 1, 1); cuid = Uatoi(args[1]); } else if (Ustrcmp(args[0], US"VERSION") == 0) { CHECK_COMMAND("VERSION", 2, 2); if (Uatoi(args[1]) != VERSION_MAJOR) OUT("authentication socket protocol version mismatch"); } else if (Ustrcmp(args[0], US"MECH") == 0) { CHECK_COMMAND("MECH", 1, INT_MAX); if (strcmpic(US args[1], ablock->public_name) == 0) found = 1; } else if (Ustrcmp(args[0], US"DONE") == 0) { CHECK_COMMAND("DONE", 0, 0); cont = 0; } } if (!found) goto out; /* Added by PH: data must not contain tab (as it is b64 it shouldn't, but check for safety). */ if (Ustrchr(data, '\t') != NULL) { ret = FAIL; goto out; } /* Added by PH: extra fields when TLS is in use or if the TCP/IP connection is local. */ if (tls_cipher != NULL) auth_extra_data = string_sprintf("secured\t%s%s", tls_certificate_verified? "valid-client-cert" : "", tls_certificate_verified? "\t" : ""); else if (interface_address != NULL && Ustrcmp(sender_host_address, interface_address) == 0) auth_extra_data = US"secured\t"; /**************************************************************************** The code below was the original code here. It didn't work. A reading of the file auth-protocol.txt.gz that came with Dovecot 1.0_beta8 indicated that this was not right. Maybe something changed. I changed it to move the service indication into the AUTH command, and it seems to be better. PH fprintf(f, "VERSION\t%d\t%d\r\nSERVICE\tSMTP\r\nCPID\t%d\r\n" "AUTH\t%d\t%s\trip=%s\tlip=%s\tresp=%s\r\n", VERSION_MAJOR, VERSION_MINOR, getpid(), cuid, ablock->public_name, sender_host_address, interface_address, data ? (char *) data : ""); Subsequently, the command was modified to add "secured" and "valid-client- cert" when relevant. The auth protocol is documented here: http://wiki.dovecot.org/Authentication_Protocol ****************************************************************************/ auth_command = string_sprintf("VERSION\t%d\t%d\nCPID\t%d\n" "AUTH\t%d\t%s\tservice=smtp\t%srip=%s\tlip=%s\tnologin\tresp=%s\n", VERSION_MAJOR, VERSION_MINOR, getpid(), cuid, ablock->public_name, auth_extra_data, sender_host_address, interface_address, data ? (char *) data : ""); if (write(fd, auth_command, Ustrlen(auth_command)) < 0) HDEBUG(D_auth) debug_printf("error sending auth_command: %s\n", strerror(errno)); HDEBUG(D_auth) debug_printf("sent: %s", auth_command); while (1) { uschar *temp; uschar *auth_id_pre = NULL; int i; if (dc_gets(buffer, sizeof(buffer), fd) == NULL) { auth_defer_msg = US"authentication socket read error or premature eof"; goto out; } buffer[Ustrlen(buffer) - 1] = 0; HDEBUG(D_auth) debug_printf("received: %s\n", buffer); nargs = strcut(buffer, args, sizeof(args) / sizeof(args[0])); if (Uatoi(args[1]) != cuid) OUT("authentication socket connection id mismatch"); switch (toupper(*args[0])) { case 'C': CHECK_COMMAND("CONT", 1, 2); tmp = auth_get_no64_data(&data, US args[2]); if (tmp != OK) { ret = tmp; goto out; } /* Added by PH: data must not contain tab (as it is b64 it shouldn't, but check for safety). */ if (Ustrchr(data, '\t') != NULL) { ret = FAIL; goto out; } temp = string_sprintf("CONT\t%d\t%s\n", cuid, data); if (write(fd, temp, Ustrlen(temp)) < 0) OUT("authentication socket write error"); break; case 'F': CHECK_COMMAND("FAIL", 1, -1); for (i=2; (i<nargs) && (auth_id_pre == NULL); i++) { if ( Ustrncmp(args[i], US"user="******"OK", 2, -1); /* * Search for the "user=$USER" string in the args array * and return the proper value. */ for (i=2; (i<nargs) && (auth_id_pre == NULL); i++) { if ( Ustrncmp(args[i], US"user="******"authentication socket protocol error, username missing"); ret = OK; /* fallthrough */ default: goto out; } } out: /* close the socket used by dovecot */ if (fd >= 0) close(fd); /* Expand server_condition as an authorization check */ return (ret == OK)? auth_check_serv_cond(ablock) : ret; }