static int dnsdb_find(void *handle, uschar *filename, const uschar *keystring, int length, uschar **result, uschar **errmsg, uint *do_cache) { int rc; int size = 256; int ptr = 0; int sep = 0; int defer_mode = PASS; int dnssec_mode = OK; int save_retrans = dns_retrans; int save_retry = dns_retry; int type; int failrc = FAIL; const uschar *outsep = CUS"\n"; const uschar *outsep2 = NULL; uschar *equals, *domain, *found; /* Because we're the working in the search pool, we try to reclaim as much store as possible later, so we preallocate the result here */ uschar *yield = store_get(size); dns_record *rr; dns_answer dnsa; dns_scan dnss; handle = handle; /* Keep picky compilers happy */ filename = filename; length = length; do_cache = do_cache; /* If the string starts with '>' we change the output separator. If it's followed by ';' or ',' we set the TXT output separator. */ while (isspace(*keystring)) keystring++; if (*keystring == '>') { outsep = keystring + 1; keystring += 2; if (*keystring == ',') { outsep2 = keystring + 1; keystring += 2; } else if (*keystring == ';') { outsep2 = US""; keystring++; } while (isspace(*keystring)) keystring++; } /* Check for a modifier keyword. */ for (;;) { if (strncmpic(keystring, US"defer_", 6) == 0) { keystring += 6; if (strncmpic(keystring, US"strict", 6) == 0) { defer_mode = DEFER; keystring += 6; } else if (strncmpic(keystring, US"lax", 3) == 0) { defer_mode = PASS; keystring += 3; } else if (strncmpic(keystring, US"never", 5) == 0) { defer_mode = OK; keystring += 5; } else { *errmsg = US"unsupported dnsdb defer behaviour"; return DEFER; } } else if (strncmpic(keystring, US"dnssec_", 7) == 0) { keystring += 7; if (strncmpic(keystring, US"strict", 6) == 0) { dnssec_mode = DEFER; keystring += 6; } else if (strncmpic(keystring, US"lax", 3) == 0) { dnssec_mode = PASS; keystring += 3; } else if (strncmpic(keystring, US"never", 5) == 0) { dnssec_mode = OK; keystring += 5; } else { *errmsg = US"unsupported dnsdb dnssec behaviour"; return DEFER; } } else if (strncmpic(keystring, US"retrans_", 8) == 0) { int timeout_sec; if ((timeout_sec = readconf_readtime(keystring += 8, ',', FALSE)) <= 0) { *errmsg = US"unsupported dnsdb timeout value"; return DEFER; } dns_retrans = timeout_sec; while (*keystring != ',') keystring++; } else if (strncmpic(keystring, US"retry_", 6) == 0) { int retries; if ((retries = (int)strtol(CCS keystring + 6, CSS &keystring, 0)) < 0) { *errmsg = US"unsupported dnsdb retry count"; return DEFER; } dns_retry = retries; } else break; while (isspace(*keystring)) keystring++; if (*keystring++ != ',') { *errmsg = US"dnsdb modifier syntax error"; return DEFER; } while (isspace(*keystring)) keystring++; } /* Figure out the "type" value if it is not T_TXT. If the keystring contains an = this must be preceded by a valid type name. */ type = T_TXT; if ((equals = Ustrchr(keystring, '=')) != NULL) { int i, len; uschar *tend = equals; while (tend > keystring && isspace(tend[-1])) tend--; len = tend - keystring; for (i = 0; i < nelem(type_names); i++) if (len == Ustrlen(type_names[i]) && strncmpic(keystring, US type_names[i], len) == 0) { type = type_values[i]; break; } if (i >= nelem(type_names)) { *errmsg = US"unsupported DNS record type"; return DEFER; } keystring = equals + 1; while (isspace(*keystring)) keystring++; } /* Initialize the resolver in case this is the first time it has been used. */ dns_init(FALSE, FALSE, dnssec_mode != OK); /* The remainder of the string must be a list of domains. As long as the lookup for at least one of them succeeds, we return success. Failure means that none of them were found. The original implementation did not support a list of domains. Adding the list feature is compatible, except in one case: when PTR records are being looked up for a single IPv6 address. Fortunately, we can hack in a compatibility feature here: If the type is PTR and no list separator is specified, and the entire remaining string is valid as an IP address, set an impossible separator so that it is treated as one item. */ if (type == T_PTR && keystring[0] != '<' && string_is_ip_address(keystring, NULL) != 0) sep = -1; /* SPF strings should be concatenated without a separator, thus make it the default if not defined (see RFC 4408 section 3.1.3). Multiple SPF records are forbidden (section 3.1.2) but are currently not handled specially, thus they are concatenated with \n by default. MX priority and value are space-separated by default. SRV and TLSA record parts are space-separated by default. */ if (!outsep2) switch(type) { case T_SPF: outsep2 = US""; break; case T_SRV: case T_MX: case T_TLSA: outsep2 = US" "; break; } /* Now scan the list and do a lookup for each item */ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0))) { uschar rbuffer[256]; int searchtype = (type == T_CSA)? T_SRV : /* record type we want */ (type == T_MXH)? T_MX : (type == T_ZNS)? T_NS : type; /* If the type is PTR or CSA, we have to construct the relevant magic lookup key if the original is an IP address (some experimental protocols are using PTR records for different purposes where the key string is a host name, and Exim's extended CSA can be keyed by domains or IP addresses). This code for doing the reversal is now in a separate function. */ if ((type == T_PTR || type == T_CSA) && string_is_ip_address(domain, NULL) != 0) { dns_build_reverse(domain, rbuffer); domain = rbuffer; } do { DEBUG(D_lookup) debug_printf("dnsdb key: %s\n", domain); /* Do the lookup and sort out the result. There are four special types that are handled specially: T_CSA, T_ZNS, T_ADDRESSES and T_MXH. The first two are handled in a special lookup function so that the facility could be used from other parts of the Exim code. T_ADDRESSES is handled by looping over the types of A lookup. T_MXH affects only what happens later on in this function, but for tidiness it is handled by the "special". If the lookup fails, continue with the next domain. In the case of DEFER, adjust the final "nothing found" result, but carry on to the next domain. */ found = domain; #if HAVE_IPV6 if (type == T_ADDRESSES) /* NB cannot happen unless HAVE_IPV6 */ { if (searchtype == T_ADDRESSES) searchtype = T_AAAA; else if (searchtype == T_AAAA) searchtype = T_A; rc = dns_special_lookup(&dnsa, domain, searchtype, CUSS &found); } else #endif rc = dns_special_lookup(&dnsa, domain, type, CUSS &found); lookup_dnssec_authenticated = dnssec_mode==OK ? NULL : dns_is_secure(&dnsa) ? US"yes" : US"no"; if (rc == DNS_NOMATCH || rc == DNS_NODATA) continue; if ( rc != DNS_SUCCEED || (dnssec_mode == DEFER && !dns_is_secure(&dnsa)) ) { if (defer_mode == DEFER) { dns_retrans = save_retrans; dns_retry = save_retry; dns_init(FALSE, FALSE, FALSE); /* clr dnssec bit */ return DEFER; /* always defer */ } if (defer_mode == PASS) failrc = DEFER; /* defer only if all do */ continue; /* treat defer as fail */ } /* Search the returned records */ for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS); rr != NULL; rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT)) { if (rr->type != searchtype) continue; if (*do_cache > rr->ttl) *do_cache = rr->ttl; if (type == T_A || type == T_AAAA || type == T_ADDRESSES) { dns_address *da; for (da = dns_address_from_rr(&dnsa, rr); da; da = da->next) { if (ptr != 0) yield = string_catn(yield, &size, &ptr, outsep, 1); yield = string_cat(yield, &size, &ptr, da->address); } continue; } /* Other kinds of record just have one piece of data each, but there may be several of them, of course. */ if (ptr != 0) yield = string_catn(yield, &size, &ptr, outsep, 1); if (type == T_TXT || type == T_SPF) { if (outsep2 == NULL) { /* output only the first item of data */ yield = string_catn(yield, &size, &ptr, (uschar *)(rr->data+1), (rr->data)[0]); } else { /* output all items */ int data_offset = 0; while (data_offset < rr->size) { uschar chunk_len = (rr->data)[data_offset++]; if (outsep2[0] != '\0' && data_offset != 1) yield = string_catn(yield, &size, &ptr, outsep2, 1); yield = string_catn(yield, &size, &ptr, US ((rr->data)+data_offset), chunk_len); data_offset += chunk_len; } } } else if (type == T_TLSA) { uint8_t usage, selector, matching_type; uint16_t i, payload_length; uschar s[MAX_TLSA_EXPANDED_SIZE]; uschar * sp = s; uschar * p = US rr->data; usage = *p++; selector = *p++; matching_type = *p++; /* What's left after removing the first 3 bytes above */ payload_length = rr->size - 3; sp += sprintf(CS s, "%d%c%d%c%d%c", usage, *outsep2, selector, *outsep2, matching_type, *outsep2); /* Now append the cert/identifier, one hex char at a time */ for (i=0; i < payload_length && sp-s < (MAX_TLSA_EXPANDED_SIZE - 4); i++) sp += sprintf(CS sp, "%02x", (unsigned char)p[i]); yield = string_cat(yield, &size, &ptr, s); } else /* T_CNAME, T_CSA, T_MX, T_MXH, T_NS, T_PTR, T_SOA, T_SRV */ { int priority, weight, port; uschar s[264]; uschar * p = US rr->data; switch (type) { case T_MXH: /* mxh ignores the priority number and includes only the hostnames */ GETSHORT(priority, p); break; case T_MX: GETSHORT(priority, p); sprintf(CS s, "%d%c", priority, *outsep2); yield = string_cat(yield, &size, &ptr, s); break; case T_SRV: GETSHORT(priority, p); GETSHORT(weight, p); GETSHORT(port, p); sprintf(CS s, "%d%c%d%c%d%c", priority, *outsep2, weight, *outsep2, port, *outsep2); yield = string_cat(yield, &size, &ptr, s); break; case T_CSA: /* See acl_verify_csa() for more comments about CSA. */ GETSHORT(priority, p); GETSHORT(weight, p); GETSHORT(port, p); if (priority != 1) continue; /* CSA version must be 1 */ /* If the CSA record we found is not the one we asked for, analyse the subdomain assertions in the port field, else analyse the direct authorization status in the weight field. */ if (Ustrcmp(found, domain) != 0) { if (port & 1) *s = 'X'; /* explicit authorization required */ else *s = '?'; /* no subdomain assertions here */ } else { if (weight < 2) *s = 'N'; /* not authorized */ else if (weight == 2) *s = 'Y'; /* authorized */ else if (weight == 3) *s = '?'; /* unauthorizable */ else continue; /* invalid */ } s[1] = ' '; yield = string_catn(yield, &size, &ptr, s, 2); break; default: break; } /* GETSHORT() has advanced the pointer to the target domain. */ rc = dn_expand(dnsa.answer, dnsa.answer + dnsa.answerlen, p, (DN_EXPAND_ARG4_TYPE)s, sizeof(s)); /* If an overlong response was received, the data will have been truncated and dn_expand may fail. */ if (rc < 0) { log_write(0, LOG_MAIN, "host name alias list truncated: type=%s " "domain=%s", dns_text_type(type), domain); break; } else yield = string_cat(yield, &size, &ptr, s); if (type == T_SOA && outsep2 != NULL) { unsigned long serial, refresh, retry, expire, minimum; p += rc; yield = string_catn(yield, &size, &ptr, outsep2, 1); rc = dn_expand(dnsa.answer, dnsa.answer + dnsa.answerlen, p, (DN_EXPAND_ARG4_TYPE)s, sizeof(s)); if (rc < 0) { log_write(0, LOG_MAIN, "responsible-mailbox truncated: type=%s " "domain=%s", dns_text_type(type), domain); break; } else yield = string_cat(yield, &size, &ptr, s); p += rc; GETLONG(serial, p); GETLONG(refresh, p); GETLONG(retry, p); GETLONG(expire, p); GETLONG(minimum, p); sprintf(CS s, "%c%lu%c%lu%c%lu%c%lu%c%lu", *outsep2, serial, *outsep2, refresh, *outsep2, retry, *outsep2, expire, *outsep2, minimum); yield = string_cat(yield, &size, &ptr, s); } } } /* Loop for list of returned records */ /* Loop for set of A-lookup types */ } while (type == T_ADDRESSES && searchtype != T_A); } /* Loop for list of domains */ /* Reclaim unused memory */ store_reset(yield + ptr + 1); /* If ptr == 0 we have not found anything. Otherwise, insert the terminating zero and return the result. */ dns_retrans = save_retrans; dns_retry = save_retry; dns_init(FALSE, FALSE, FALSE); /* clear the dnssec bit for getaddrbyname */ if (ptr == 0) return failrc; yield[ptr] = 0; *result = yield; return OK; }
BOOL autoreply_transport_entry( transport_instance *tblock, /* data for this instantiation */ address_item *addr) /* address we are working on */ { int fd, pid, rc; int cache_fd = -1; int log_fd = -1; int cache_size = 0; int add_size = 0; EXIM_DB *dbm_file = NULL; BOOL file_expand, return_message; uschar *from, *reply_to, *to, *cc, *bcc, *subject, *headers, *text, *file; uschar *logfile, *oncelog; uschar *cache_buff = NULL; uschar *cache_time = NULL; uschar *message_id = NULL; header_line *h; time_t now = time(NULL); time_t once_repeat_sec = 0; FILE *f; FILE *ff = NULL; autoreply_transport_options_block *ob = (autoreply_transport_options_block *)(tblock->options_block); DEBUG(D_transport) debug_printf("%s transport entered\n", tblock->name); /* Set up for the good case */ addr->transport_return = OK; addr->basic_errno = 0; /* If the address is pointing to a reply block, then take all the data from that block. It has typically been set up by a mail filter processing router. Otherwise, the data must be supplied by this transport, and it has to be expanded here. */ if (addr->reply != NULL) { DEBUG(D_transport) debug_printf("taking data from address\n"); from = addr->reply->from; reply_to = addr->reply->reply_to; to = addr->reply->to; cc = addr->reply->cc; bcc = addr->reply->bcc; subject = addr->reply->subject; headers = addr->reply->headers; text = addr->reply->text; file = addr->reply->file; logfile = addr->reply->logfile; oncelog = addr->reply->oncelog; once_repeat_sec = addr->reply->once_repeat; file_expand = addr->reply->file_expand; expand_forbid = addr->reply->expand_forbid; return_message = addr->reply->return_message; } else { uschar *oncerepeat = ob->once_repeat; DEBUG(D_transport) debug_printf("taking data from transport\n"); from = ob->from; reply_to = ob->reply_to; to = ob->to; cc = ob->cc; bcc = ob->bcc; subject = ob->subject; headers = ob->headers; text = ob->text; file = ob->file; logfile = ob->logfile; oncelog = ob->oncelog; file_expand = ob->file_expand; return_message = ob->return_message; if ((from != NULL && (from = checkexpand(from, addr, tblock->name, cke_hdr)) == NULL) || (reply_to != NULL && (reply_to = checkexpand(reply_to, addr, tblock->name, cke_hdr)) == NULL) || (to != NULL && (to = checkexpand(to, addr, tblock->name, cke_hdr)) == NULL) || (cc != NULL && (cc = checkexpand(cc, addr, tblock->name, cke_hdr)) == NULL) || (bcc != NULL && (bcc = checkexpand(bcc, addr, tblock->name, cke_hdr)) == NULL) || (subject != NULL && (subject = checkexpand(subject, addr, tblock->name, cke_hdr)) == NULL) || (headers != NULL && (headers = checkexpand(headers, addr, tblock->name, cke_text)) == NULL) || (text != NULL && (text = checkexpand(text, addr, tblock->name, cke_text)) == NULL) || (file != NULL && (file = checkexpand(file, addr, tblock->name, cke_file)) == NULL) || (logfile != NULL && (logfile = checkexpand(logfile, addr, tblock->name, cke_file)) == NULL) || (oncelog != NULL && (oncelog = checkexpand(oncelog, addr, tblock->name, cke_file)) == NULL) || (oncerepeat != NULL && (oncerepeat = checkexpand(oncerepeat, addr, tblock->name, cke_file)) == NULL)) return FALSE; if (oncerepeat != NULL) { once_repeat_sec = readconf_readtime(oncerepeat, 0, FALSE); if (once_repeat_sec < 0) { addr->transport_return = FAIL; addr->message = string_sprintf("Invalid time value \"%s\" for " "\"once_repeat\" in %s transport", oncerepeat, tblock->name); return FALSE; } } } /* If the never_mail option is set, we have to scan all the recipients and remove those that match. */ if (ob->never_mail != NULL) { uschar *never_mail = expand_string(ob->never_mail); if (never_mail == NULL) { addr->transport_return = FAIL; addr->message = string_sprintf("Failed to expand \"%s\" for " "\"never_mail\" in %s transport", ob->never_mail, tblock->name); return FALSE; } if (to != NULL) check_never_mail(&to, never_mail); if (cc != NULL) check_never_mail(&cc, never_mail); if (bcc != NULL) check_never_mail(&bcc, never_mail); if (to == NULL && cc == NULL && bcc == NULL) { DEBUG(D_transport) debug_printf("*** all recipients removed by never_mail\n"); return OK; } } /* If the -N option is set, can't do any more. */ if (dont_deliver) { DEBUG(D_transport) debug_printf("*** delivery by %s transport bypassed by -N option\n", tblock->name); return FALSE; } /* If the oncelog field is set, we send want to send only one message to the given recipient(s). This works only on the "To" field. If there is no "To" field, the message is always sent. If the To: field contains more than one recipient, the effect might not be quite as envisaged. If once_file_size is set, instead of a dbm file, we use a regular file containing a circular buffer recipient cache. */ if (oncelog != NULL && *oncelog != 0 && to != NULL) { time_t then = 0; /* Handle fixed-size cache file. */ if (ob->once_file_size > 0) { uschar *p; struct stat statbuf; cache_fd = Uopen(oncelog, O_CREAT|O_RDWR, ob->mode); if (cache_fd < 0 || fstat(cache_fd, &statbuf) != 0) { addr->transport_return = DEFER; addr->message = string_sprintf("Failed to %s \"once\" file %s when " "sending message from %s transport: %s", (cache_fd < 0)? "open" : "stat", oncelog, tblock->name, strerror(errno)); goto END_OFF; } /* Get store in the temporary pool and read the entire file into it. We get an amount of store that is big enough to add the new entry on the end if we need to do that. */ cache_size = statbuf.st_size; add_size = sizeof(time_t) + Ustrlen(to) + 1; cache_buff = store_get(cache_size + add_size); if (read(cache_fd, cache_buff, cache_size) != cache_size) { addr->transport_return = DEFER; addr->basic_errno = errno; addr->message = US"error while reading \"once\" file"; goto END_OFF; } DEBUG(D_transport) debug_printf("%d bytes read from %s\n", cache_size, oncelog); /* Scan the data for this recipient. Each entry in the file starts with a time_t sized time value, followed by the address, followed by a binary zero. If we find a match, put the time into "then", and the place where it was found into "cache_time". Otherwise, "then" is left at zero. */ p = cache_buff; while (p < cache_buff + cache_size) { uschar *s = p + sizeof(time_t); uschar *nextp = s + Ustrlen(s) + 1; if (Ustrcmp(to, s) == 0) { memcpy(&then, p, sizeof(time_t)); cache_time = p; break; } p = nextp; } } /* Use a DBM file for the list of previous recipients. */ else { EXIM_DATUM key_datum, result_datum; EXIM_DBOPEN(oncelog, O_RDWR|O_CREAT, ob->mode, &dbm_file); if (dbm_file == NULL) { addr->transport_return = DEFER; addr->message = string_sprintf("Failed to open %s file %s when sending " "message from %s transport: %s", EXIM_DBTYPE, oncelog, tblock->name, strerror(errno)); goto END_OFF; } EXIM_DATUM_INIT(key_datum); /* Some DBM libraries need datums */ EXIM_DATUM_INIT(result_datum); /* to be cleared */ EXIM_DATUM_DATA(key_datum) = CS to; EXIM_DATUM_SIZE(key_datum) = Ustrlen(to) + 1; if (EXIM_DBGET(dbm_file, key_datum, result_datum)) { /* If the datum size is that of a binary time, we are in the new world where messages are sent periodically. Otherwise the file is an old one, where the datum was filled with a tod_log time, which is assumed to be different in size. For that, only one message is ever sent. This change introduced at Exim 3.00. In a couple of years' time the test on the size can be abolished. */ if (EXIM_DATUM_SIZE(result_datum) == sizeof(time_t)) { memcpy(&then, EXIM_DATUM_DATA(result_datum), sizeof(time_t)); } else then = now; } } /* Either "then" is set zero, if no message has yet been sent, or it is set to the time of the last sending. */ if (then != 0 && (once_repeat_sec <= 0 || now - then < once_repeat_sec)) { DEBUG(D_transport) debug_printf("message previously sent to %s%s\n", to, (once_repeat_sec > 0)? " and repeat time not reached" : ""); log_fd = Uopen(logfile, O_WRONLY|O_APPEND|O_CREAT, ob->mode); if (log_fd >= 0) { uschar *ptr = log_buffer; sprintf(CS ptr, "%s\n previously sent to %.200s\n", tod_stamp(tod_log), to); while(*ptr) ptr++; if(write(log_fd, log_buffer, ptr - log_buffer) != ptr-log_buffer || close(log_fd)) DEBUG(D_transport) debug_printf("Problem writing log file %s for %s " "transport\n", logfile, tblock->name); } goto END_OFF; } DEBUG(D_transport) debug_printf("%s %s\n", (then <= 0)? "no previous message sent to" : "repeat time reached for", to); } /* We are going to send a message. Ensure any requested file is available. */ if (file != NULL) { ff = Ufopen(file, "rb"); if (ff == NULL && !ob->file_optional) { addr->transport_return = DEFER; addr->message = string_sprintf("Failed to open file %s when sending " "message from %s transport: %s", file, tblock->name, strerror(errno)); return FALSE; } } /* Make a subprocess to send the message */ pid = child_open_exim(&fd); /* Creation of child failed; defer this delivery. */ if (pid < 0) { addr->transport_return = DEFER; addr->message = string_sprintf("Failed to create child process to send " "message from %s transport: %s", tblock->name, strerror(errno)); DEBUG(D_transport) debug_printf("%s\n", addr->message); return FALSE; } /* Create the message to be sent - recipients are taken from the headers, as the -t option is used. The "headers" stuff *must* be last in case there are newlines in it which might, if placed earlier, screw up other headers. */ f = fdopen(fd, "wb"); if (from != NULL) fprintf(f, "From: %s\n", from); if (reply_to != NULL) fprintf(f, "Reply-To: %s\n", reply_to); if (to != NULL) fprintf(f, "To: %s\n", to); if (cc != NULL) fprintf(f, "Cc: %s\n", cc); if (bcc != NULL) fprintf(f, "Bcc: %s\n", bcc); if (subject != NULL) fprintf(f, "Subject: %s\n", subject); /* Generate In-Reply-To from the message_id header; there should always be one, but code defensively. */ for (h = header_list; h != NULL; h = h->next) if (h->type == htype_id) break; if (h != NULL) { message_id = Ustrchr(h->text, ':') + 1; while (isspace(*message_id)) message_id++; fprintf(f, "In-Reply-To: %s", message_id); } /* Generate a References header if there is at least one of Message-ID:, References:, or In-Reply-To: (see RFC 2822). */ for (h = header_list; h != NULL; h = h->next) if (h->type != htype_old && strncmpic(US"References:", h->text, 11) == 0) break; if (h == NULL) for (h = header_list; h != NULL; h = h->next) if (h->type != htype_old && strncmpic(US"In-Reply-To:", h->text, 12) == 0) break; /* We limit the total length of references. Although there is no fixed limit, some systems do not like headers growing beyond recognition. Keep the first message ID for the thread root and the last few for the position inside the thread, up to a maximum of 12 altogether. */ if (h != NULL || message_id != NULL) { fprintf(f, "References:"); if (h != NULL) { uschar *s, *id, *error; uschar *referenced_ids[12]; int reference_count = 0; int i; s = Ustrchr(h->text, ':') + 1; parse_allow_group = FALSE; while (*s != 0 && (s = parse_message_id(s, &id, &error)) != NULL) { if (reference_count == sizeof(referenced_ids)/sizeof(uschar *)) { memmove(referenced_ids + 1, referenced_ids + 2, sizeof(referenced_ids) - 2*sizeof(uschar *)); referenced_ids[reference_count - 1] = id; } else referenced_ids[reference_count++] = id; } for (i = 0; i < reference_count; ++i) fprintf(f, " %s", referenced_ids[i]); } /* The message id will have a newline on the end of it. */ if (message_id != NULL) fprintf(f, " %s", message_id); else fprintf(f, "\n"); } /* Add an Auto-Submitted: header */ fprintf(f, "Auto-Submitted: auto-replied\n"); /* Add any specially requested headers */ if (headers != NULL) fprintf(f, "%s\n", headers); fprintf(f, "\n"); if (text != NULL) { fprintf(f, "%s", CS text); if (text[Ustrlen(text)-1] != '\n') fprintf(f, "\n"); } if (ff != NULL) { while (Ufgets(big_buffer, big_buffer_size, ff) != NULL) { if (file_expand) { uschar *s = expand_string(big_buffer); DEBUG(D_transport) { if (s == NULL) debug_printf("error while expanding line from file:\n %s\n %s\n", big_buffer, expand_string_message); } fprintf(f, "%s", (s == NULL)? CS big_buffer : CS s); } else fprintf(f, "%s", CS big_buffer); } }
static int spamd_param(const uschar * param, spamd_address_container * spamd) { static int timesinceday = -1; const uschar * s; const uschar * name; /*XXX more clever parsing could discard embedded spaces? */ if (sscanf(CCS param, "pri=%u", &spamd->priority)) return 0; /* OK */ if (sscanf(CCS param, "weight=%u", &spamd->weight)) { if (spamd->weight == 0) /* this server disabled: skip it */ return 1; return 0; /* OK */ } if (Ustrncmp(param, "time=", 5) == 0) { unsigned int start_h = 0, start_m = 0, start_s = 0; unsigned int end_h = 24, end_m = 0, end_s = 0; unsigned int time_start, time_end; const uschar * end_string; name = US"time"; s = param+5; if ((end_string = Ustrchr(s, '-'))) { end_string++; if ( sscanf(CS end_string, "%u.%u.%u", &end_h, &end_m, &end_s) == 0 || sscanf(CS s, "%u.%u.%u", &start_h, &start_m, &start_s) == 0 ) goto badval; } else goto badval; if (timesinceday < 0) { time_t now = time(NULL); struct tm *tmp = localtime(&now); timesinceday = tmp->tm_hour*3600 + tmp->tm_min*60 + tmp->tm_sec; } time_start = start_h*3600 + start_m*60 + start_s; time_end = end_h*3600 + end_m*60 + end_s; if (timesinceday < time_start || timesinceday >= time_end) return 1; /* skip spamd server */ return 0; /* OK */ } if (Ustrcmp(param, "variant=rspamd") == 0) { spamd->is_rspamd = TRUE; return 0; } if (Ustrncmp(param, "tmo=", 4) == 0) { int sec = readconf_readtime((s = param+4), '\0', FALSE); name = US"timeout"; if (sec < 0) goto badval; spamd->timeout = sec; return 0; } if (Ustrncmp(param, "retry=", 6) == 0) { int sec = readconf_readtime((s = param+6), '\0', FALSE); name = US"retry"; if (sec < 0) goto badval; spamd->retry = sec; return 0; } log_write(0, LOG_MAIN, "%s warning - invalid spamd parameter: '%s'", loglabel, param); return -1; /* syntax error */ badval: log_write(0, LOG_MAIN, "%s warning - invalid spamd %s value: '%s'", loglabel, name, s); return -1; /* syntax error */ }