+ /* Check for previous delivery */
+
+ if (tree_search(tree_nonrecipients, addr->unique))
+ {
+ DEBUG(D_deliver|D_route)
+ debug_printf("%s was previously delivered: discarded\n", addr->address);
+ child_done(addr, tod_stamp(tod_log));
+ continue;
+ }
+
+ /* Save for checking future duplicates */
+
+ tree_add_duplicate(addr->unique, addr);
+
+ /* Set local part and domain */
+
+ addr->local_part = addr->address;
+ addr->domain = addr->parent->domain;
+
+ /* Ensure that the delivery is permitted. */
+
+ if (testflag(addr, af_file))
+ {
+ if (!testflag(addr, af_allow_file))
+ {
+ addr->basic_errno = ERRNO_FORBIDFILE;
+ addr->message = US"delivery to file forbidden";
+ (void)post_process_one(addr, FAIL, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
+ continue; /* with the next new address */
+ }
+ }
+ else if (addr->address[0] == '|')
+ {
+ if (!testflag(addr, af_allow_pipe))
+ {
+ addr->basic_errno = ERRNO_FORBIDPIPE;
+ addr->message = US"delivery to pipe forbidden";
+ (void)post_process_one(addr, FAIL, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
+ continue; /* with the next new address */
+ }
+ }
+ else if (!testflag(addr, af_allow_reply))
+ {
+ addr->basic_errno = ERRNO_FORBIDREPLY;
+ addr->message = US"autoreply forbidden";
+ (void)post_process_one(addr, FAIL, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
+ continue; /* with the next new address */
+ }
+
+ /* If the errno field is already set to BADTRANSPORT, it indicates
+ failure to expand a transport string, or find the associated transport,
+ or an unset transport when one is required. Leave this test till now so
+ that the forbid errors are given in preference. */
+
+ if (addr->basic_errno == ERRNO_BADTRANSPORT)
+ {
+ (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
+ continue;
+ }
+
+ /* Treat /dev/null as a special case and abandon the delivery. This
+ avoids having to specify a uid on the transport just for this case.
+ Arrange for the transport name to be logged as "**bypassed**".
+ Copy the transport for this fairly unusual case rather than having
+ to make all transports mutable. */
+
+ if (Ustrcmp(addr->address, "/dev/null") == 0)
+ {
+ transport_instance * save_t = addr->transport;
+ transport_instance * t = store_get(sizeof(*t), save_t);
+ *t = *save_t;
+ t->name = US"**bypassed**";
+ addr->transport = t;
+ (void)post_process_one(addr, OK, LOG_MAIN, EXIM_DTYPE_TRANSPORT, '=');
+ addr->transport= save_t;
+ continue; /* with the next new address */
+ }
+
+ /* Pipe, file, or autoreply delivery is to go ahead as a normal local
+ delivery. */
+
+ DEBUG(D_deliver|D_route)
+ debug_printf("queued for %s transport\n", addr->transport->name);
+ addr->next = addr_local;
+ addr_local = addr;
+ continue; /* with the next new address */
+ }
+
+ /* Handle normal addresses. First, split up into local part and domain,
+ handling the %-hack if necessary. There is the possibility of a defer from
+ a lookup in percent_hack_domains. */
+
+ if ((rc = deliver_split_address(addr)) == DEFER)
+ {
+ addr->message = US"cannot check percent_hack_domains";
+ addr->basic_errno = ERRNO_LISTDEFER;
+ (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_NONE, 0);
+ continue;
+ }
+
+ /* Check to see if the domain is held. If so, proceed only if the
+ delivery was forced by hand. */
+
+ deliver_domain = addr->domain; /* set $domain */
+ if ( !forced && hold_domains
+ && (rc = match_isinlist(addr->domain, (const uschar **)&hold_domains, 0,
+ &domainlist_anchor, addr->domain_cache, MCL_DOMAIN, TRUE,
+ NULL)) != FAIL
+ )
+ {
+ if (rc == DEFER)
+ {
+ addr->message = US"hold_domains lookup deferred";
+ addr->basic_errno = ERRNO_LISTDEFER;
+ }
+ else
+ {
+ addr->message = US"domain is held";
+ addr->basic_errno = ERRNO_HELD;
+ }
+ (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_NONE, 0);
+ continue;
+ }
+
+ /* Now we can check for duplicates and previously delivered addresses. In
+ order to do this, we have to generate a "unique" value for each address,
+ because there may be identical actual addresses in a line of descendents.
+ The "unique" field is initialized to the same value as the "address" field,
+ but gets changed here to cope with identically-named descendents. */
+
+ for (parent = addr->parent; parent; parent = parent->parent)
+ if (strcmpic(addr->address, parent->address) == 0) break;
+
+ /* If there's an ancestor with the same name, set the homonym flag. This
+ influences how deliveries are recorded. Then add a prefix on the front of
+ the unique address. We use \n\ where n starts at 0 and increases each time.
+ It is unlikely to pass 9, but if it does, it may look odd but will still
+ work. This means that siblings or cousins with the same names are treated
+ as duplicates, which is what we want. */
+
+ if (parent)
+ {
+ setflag(addr, af_homonym);
+ if (parent->unique[0] != '\\')
+ addr->unique = string_sprintf("\\0\\%s", addr->address);
+ else
+ addr->unique = string_sprintf("\\%c\\%s", parent->unique[1] + 1,
+ addr->address);
+ }
+
+ /* Ensure that the domain in the unique field is lower cased, because
+ domains are always handled caselessly. */
+
+ for (uschar * p = Ustrrchr(addr->unique, '@'); *p; p++) *p = tolower(*p);
+
+ DEBUG(D_deliver|D_route) debug_printf("unique = %s\n", addr->unique);
+
+ if (tree_search(tree_nonrecipients, addr->unique))
+ {
+ DEBUG(D_deliver|D_route)
+ debug_printf("%s was previously delivered: discarded\n", addr->unique);
+ child_done(addr, tod_stamp(tod_log));
+ continue;
+ }
+
+ /* Get the routing retry status, saving the two retry keys (with and
+ without the local part) for subsequent use. If there is no retry record for
+ the standard address routing retry key, we look for the same key with the
+ sender attached, because this form is used by the smtp transport after a
+ 4xx response to RCPT when address_retry_include_sender is true. */
+
+ addr->domain_retry_key = string_sprintf("R:%s", addr->domain);
+ addr->address_retry_key = string_sprintf("R:%s@%s", addr->local_part,
+ addr->domain);
+
+ if (dbm_file)
+ {
+ domain_retry_record = dbfn_read(dbm_file, addr->domain_retry_key);
+ if ( domain_retry_record
+ && now - domain_retry_record->time_stamp > retry_data_expire
+ )
+ {
+ DEBUG(D_deliver|D_retry)
+ debug_printf("domain retry record present but expired\n");
+ domain_retry_record = NULL; /* Ignore if too old */
+ }
+
+ address_retry_record = dbfn_read(dbm_file, addr->address_retry_key);
+ if ( address_retry_record
+ && now - address_retry_record->time_stamp > retry_data_expire
+ )
+ {
+ DEBUG(D_deliver|D_retry)
+ debug_printf("address retry record present but expired\n");
+ address_retry_record = NULL; /* Ignore if too old */
+ }
+
+ if (!address_retry_record)
+ {
+ uschar *altkey = string_sprintf("%s:<%s>", addr->address_retry_key,
+ sender_address);
+ address_retry_record = dbfn_read(dbm_file, altkey);
+ if ( address_retry_record
+ && now - address_retry_record->time_stamp > retry_data_expire)
+ {
+ DEBUG(D_deliver|D_retry)
+ debug_printf("address<sender> retry record present but expired\n");
+ address_retry_record = NULL; /* Ignore if too old */
+ }
+ }
+ }
+ else
+ domain_retry_record = address_retry_record = NULL;
+
+ DEBUG(D_deliver|D_retry)
+ {
+ if (!domain_retry_record)
+ debug_printf("no domain retry record\n");
+ else
+ debug_printf("have domain retry record; next_try = now%+d\n",
+ f.running_in_test_harness ? 0 :
+ (int)(domain_retry_record->next_try - now));