- case CONTROL_AUTH_UNADVERTISED:
- allow_auth_unadvertised = TRUE;
- break;
-
- #ifdef EXPERIMENTAL_BRIGHTMAIL
- case CONTROL_BMI_RUN:
- bmi_run = 1;
- break;
- #endif
-
- #ifndef DISABLE_DKIM
- case CONTROL_DKIM_VERIFY:
- dkim_disable_verify = TRUE;
- break;
- #endif
-
- case CONTROL_DSCP:
- if (*p == '/')
- {
- int fd, af, level, optname, value;
- /* If we are acting on stdin, the setsockopt may fail if stdin is not
- a socket; we can accept that, we'll just debug-log failures anyway. */
- fd = fileno(smtp_in);
- af = ip_get_address_family(fd);
- if (af < 0)
- {
- HDEBUG(D_acl)
- debug_printf("smtp input is probably not a socket [%s], not setting DSCP\n",
- strerror(errno));
- break;
- }
- if (dscp_lookup(p+1, af, &level, &optname, &value))
- {
- if (setsockopt(fd, level, optname, &value, sizeof(value)) < 0)
- {
- HDEBUG(D_acl) debug_printf("failed to set input DSCP[%s]: %s\n",
- p+1, strerror(errno));
- }
- else
- {
- HDEBUG(D_acl) debug_printf("set input DSCP to \"%s\"\n", p+1);
- }
- }
- else
- {
- *log_msgptr = string_sprintf("unrecognised DSCP value in \"control=%s\"", arg);
- return ERROR;
- }
- }
- else
- {
- *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg);
- return ERROR;
- }
- break;
-
- case CONTROL_ERROR:
- return ERROR;
-
- case CONTROL_CASEFUL_LOCAL_PART:
- deliver_localpart = addr->cc_local_part;
- break;
-
- case CONTROL_CASELOWER_LOCAL_PART:
- deliver_localpart = addr->lc_local_part;
- break;
-
- case CONTROL_ENFORCE_SYNC:
- smtp_enforce_sync = TRUE;
- break;
-
- case CONTROL_NO_ENFORCE_SYNC:
- smtp_enforce_sync = FALSE;
- break;
-
- #ifdef WITH_CONTENT_SCAN
- case CONTROL_NO_MBOX_UNSPOOL:
- no_mbox_unspool = TRUE;
- break;
- #endif
-
- case CONTROL_NO_MULTILINE:
- no_multiline_responses = TRUE;
- break;
-
- case CONTROL_NO_PIPELINING:
- pipelining_enable = FALSE;
- break;
-
- case CONTROL_NO_DELAY_FLUSH:
- disable_delay_flush = TRUE;
- break;
-
- case CONTROL_NO_CALLOUT_FLUSH:
- disable_callout_flush = TRUE;
- break;
-
- case CONTROL_FAKEDEFER:
- case CONTROL_FAKEREJECT:
- fake_response = (control_type == CONTROL_FAKEDEFER) ? DEFER : FAIL;
- if (*p == '/')
- {
- uschar *pp = p + 1;
- while (*pp != 0) pp++;
- fake_response_text = expand_string(string_copyn(p+1, pp-p-1));
- p = pp;
- }
- else
- {
- /* Explicitly reset to default string */
- fake_response_text = US"Your message has been rejected but is being kept for evaluation.\nIf it was a legitimate message, it may still be delivered to the target recipient(s).";
- }
- break;
-
- case CONTROL_FREEZE:
- deliver_freeze = TRUE;
- deliver_frozen_at = time(NULL);
- freeze_tell = freeze_tell_config; /* Reset to configured value */
- if (Ustrncmp(p, "/no_tell", 8) == 0)
- {
- p += 8;
- freeze_tell = NULL;
- }
- if (*p != 0)
- {
- *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg);
- return ERROR;
- }
- break;
-
- case CONTROL_QUEUE_ONLY:
- queue_only_policy = TRUE;
- break;
-
- case CONTROL_SUBMISSION:
- originator_name = US"";
- submission_mode = TRUE;
- while (*p == '/')
- {
- if (Ustrncmp(p, "/sender_retain", 14) == 0)
- {
- p += 14;
- active_local_sender_retain = TRUE;
- active_local_from_check = FALSE;
- }
- else if (Ustrncmp(p, "/domain=", 8) == 0)
- {
- uschar *pp = p + 8;
- while (*pp != 0 && *pp != '/') pp++;
- submission_domain = string_copyn(p+8, pp-p-8);
- p = pp;
- }
- /* The name= option must be last, because it swallows the rest of
- the string. */
- else if (Ustrncmp(p, "/name=", 6) == 0)
- {
- uschar *pp = p + 6;
- while (*pp != 0) pp++;
- submission_name = string_copy(parse_fix_phrase(p+6, pp-p-6,
- big_buffer, big_buffer_size));
- p = pp;
- }
- else break;
- }
- if (*p != 0)
- {
- *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg);
- return ERROR;
- }
- break;
-
- case CONTROL_DEBUG:
- while (*p == '/')
- {
- if (Ustrncmp(p, "/tag=", 5) == 0)
- {
- uschar *pp = p + 5;
- while (*pp != '\0' && *pp != '/') pp++;
- debug_tag = string_copyn(p+5, pp-p-5);
- p = pp;
- }
- else if (Ustrncmp(p, "/opts=", 6) == 0)
- {
- uschar *pp = p + 6;
- while (*pp != '\0' && *pp != '/') pp++;
- debug_opts = string_copyn(p+6, pp-p-6);
- p = pp;
- }
- }
- debug_logging_activate(debug_tag, debug_opts);
- break;
-
- case CONTROL_SUPPRESS_LOCAL_FIXUPS:
- suppress_local_fixups = TRUE;
- break;
+ const uschar *p = NULL;
+ control_type = decode_control(arg, &p, where, log_msgptr);
+
+ /* Check if this control makes sense at this time */
+
+ if ((control_forbids[control_type] & (1 << where)) != 0)
+ {
+ *log_msgptr = string_sprintf("cannot use \"control=%s\" in %s ACL",
+ controls[control_type], acl_wherenames[where]);
+ return ERROR;
+ }
+
+ switch(control_type)
+ {
+ case CONTROL_AUTH_UNADVERTISED:
+ allow_auth_unadvertised = TRUE;
+ break;
+
+ #ifdef EXPERIMENTAL_BRIGHTMAIL
+ case CONTROL_BMI_RUN:
+ bmi_run = 1;
+ break;
+ #endif
+
+ #ifndef DISABLE_DKIM
+ case CONTROL_DKIM_VERIFY:
+ dkim_disable_verify = TRUE;
+ #ifdef EXPERIMENTAL_DMARC
+ /* Since DKIM was blocked, skip DMARC too */
+ dmarc_disable_verify = TRUE;
+ dmarc_enable_forensic = FALSE;
+ #endif
+ break;
+ #endif
+
+ #ifdef EXPERIMENTAL_DMARC
+ case CONTROL_DMARC_VERIFY:
+ dmarc_disable_verify = TRUE;
+ break;
+
+ case CONTROL_DMARC_FORENSIC:
+ dmarc_enable_forensic = TRUE;
+ break;
+ #endif
+
+ case CONTROL_DSCP:
+ if (*p == '/')
+ {
+ int fd, af, level, optname, value;
+ /* If we are acting on stdin, the setsockopt may fail if stdin is not
+ a socket; we can accept that, we'll just debug-log failures anyway. */
+ fd = fileno(smtp_in);
+ af = ip_get_address_family(fd);
+ if (af < 0)
+ {
+ HDEBUG(D_acl)
+ debug_printf("smtp input is probably not a socket [%s], not setting DSCP\n",
+ strerror(errno));
+ break;
+ }
+ if (dscp_lookup(p+1, af, &level, &optname, &value))
+ {
+ if (setsockopt(fd, level, optname, &value, sizeof(value)) < 0)
+ {
+ HDEBUG(D_acl) debug_printf("failed to set input DSCP[%s]: %s\n",
+ p+1, strerror(errno));
+ }
+ else
+ {
+ HDEBUG(D_acl) debug_printf("set input DSCP to \"%s\"\n", p+1);
+ }
+ }
+ else
+ {
+ *log_msgptr = string_sprintf("unrecognised DSCP value in \"control=%s\"", arg);
+ return ERROR;
+ }
+ }
+ else
+ {
+ *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg);
+ return ERROR;
+ }
+ break;
+
+ case CONTROL_ERROR:
+ return ERROR;
+
+ case CONTROL_CASEFUL_LOCAL_PART:
+ deliver_localpart = addr->cc_local_part;
+ break;
+
+ case CONTROL_CASELOWER_LOCAL_PART:
+ deliver_localpart = addr->lc_local_part;
+ break;
+
+ case CONTROL_ENFORCE_SYNC:
+ smtp_enforce_sync = TRUE;
+ break;
+
+ case CONTROL_NO_ENFORCE_SYNC:
+ smtp_enforce_sync = FALSE;
+ break;
+
+ #ifdef WITH_CONTENT_SCAN
+ case CONTROL_NO_MBOX_UNSPOOL:
+ no_mbox_unspool = TRUE;
+ break;
+ #endif
+
+ case CONTROL_NO_MULTILINE:
+ no_multiline_responses = TRUE;
+ break;
+
+ case CONTROL_NO_PIPELINING:
+ pipelining_enable = FALSE;
+ break;
+
+ case CONTROL_NO_DELAY_FLUSH:
+ disable_delay_flush = TRUE;
+ break;
+
+ case CONTROL_NO_CALLOUT_FLUSH:
+ disable_callout_flush = TRUE;
+ break;
+
+ case CONTROL_FAKEREJECT:
+ cancel_cutthrough_connection("fakereject");
+ case CONTROL_FAKEDEFER:
+ fake_response = (control_type == CONTROL_FAKEDEFER) ? DEFER : FAIL;
+ if (*p == '/')
+ {
+ const uschar *pp = p + 1;
+ while (*pp != 0) pp++;
+ fake_response_text = expand_string(string_copyn(p+1, pp-p-1));
+ p = pp;
+ }
+ else
+ {
+ /* Explicitly reset to default string */
+ fake_response_text = US"Your message has been rejected but is being kept for evaluation.\nIf it was a legitimate message, it may still be delivered to the target recipient(s).";
+ }
+ break;
+
+ case CONTROL_FREEZE:
+ deliver_freeze = TRUE;
+ deliver_frozen_at = time(NULL);
+ freeze_tell = freeze_tell_config; /* Reset to configured value */
+ if (Ustrncmp(p, "/no_tell", 8) == 0)
+ {
+ p += 8;
+ freeze_tell = NULL;
+ }
+ if (*p != 0)
+ {
+ *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg);
+ return ERROR;
+ }
+ cancel_cutthrough_connection("item frozen");
+ break;
+
+ case CONTROL_QUEUE_ONLY:
+ queue_only_policy = TRUE;
+ cancel_cutthrough_connection("queueing forced");
+ break;
+
+ case CONTROL_SUBMISSION:
+ originator_name = US"";
+ submission_mode = TRUE;
+ while (*p == '/')
+ {
+ if (Ustrncmp(p, "/sender_retain", 14) == 0)
+ {
+ p += 14;
+ active_local_sender_retain = TRUE;
+ active_local_from_check = FALSE;
+ }
+ else if (Ustrncmp(p, "/domain=", 8) == 0)
+ {
+ const uschar *pp = p + 8;
+ while (*pp != 0 && *pp != '/') pp++;
+ submission_domain = string_copyn(p+8, pp-p-8);
+ p = pp;
+ }
+ /* The name= option must be last, because it swallows the rest of
+ the string. */
+ else if (Ustrncmp(p, "/name=", 6) == 0)
+ {
+ const uschar *pp = p + 6;
+ while (*pp != 0) pp++;
+ submission_name = string_copy(parse_fix_phrase(p+6, pp-p-6,
+ big_buffer, big_buffer_size));
+ p = pp;
+ }
+ else break;
+ }
+ if (*p != 0)
+ {
+ *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg);
+ return ERROR;
+ }
+ break;
+
+ case CONTROL_DEBUG:
+ while (*p == '/')
+ {
+ if (Ustrncmp(p, "/tag=", 5) == 0)
+ {
+ const uschar *pp = p + 5;
+ while (*pp != '\0' && *pp != '/') pp++;
+ debug_tag = string_copyn(p+5, pp-p-5);
+ p = pp;
+ }
+ else if (Ustrncmp(p, "/opts=", 6) == 0)
+ {
+ const uschar *pp = p + 6;
+ while (*pp != '\0' && *pp != '/') pp++;
+ debug_opts = string_copyn(p+6, pp-p-6);
+ p = pp;
+ }
+ }
+ debug_logging_activate(debug_tag, debug_opts);
+ break;
+
+ case CONTROL_SUPPRESS_LOCAL_FIXUPS:
+ suppress_local_fixups = TRUE;
+ break;
+
+ case CONTROL_CUTTHROUGH_DELIVERY:
+#ifndef DISABLE_PRDR
+ if (prdr_requested)
+#else
+ if (0)
+#endif
+ /* Too hard to think about for now. We might in future cutthrough
+ the case where both sides handle prdr and this-node prdr acl
+ is "accept" */
+ *log_msgptr = string_sprintf("PRDR on %s reception\n", arg);
+ else
+ {
+ if (deliver_freeze)
+ *log_msgptr = US"frozen";
+ else if (queue_only_policy)
+ *log_msgptr = US"queue-only";
+ else if (fake_response == FAIL)
+ *log_msgptr = US"fakereject";
+ else
+ {
+ if (rcpt_count == 1) cutthrough.delivery = TRUE;
+ break;
+ }
+ *log_msgptr = string_sprintf("\"control=%s\" on %s item",
+ arg, *log_msgptr);
+ }
+ return ERROR;
+
+#ifdef SUPPORT_I18N
+ case CONTROL_UTF8_DOWNCONVERT:
+ if (*p == '/')
+ {
+ if (p[1] == '1')
+ {
+ message_utf8_downconvert = 1;
+ addr->prop.utf8_downcvt = TRUE;
+ addr->prop.utf8_downcvt_maybe = FALSE;
+ p += 2;
+ break;
+ }
+ if (p[1] == '0')
+ {
+ message_utf8_downconvert = 0;
+ addr->prop.utf8_downcvt = FALSE;
+ addr->prop.utf8_downcvt_maybe = FALSE;
+ p += 2;
+ break;
+ }
+ if (p[1] == '-' && p[2] == '1')
+ {
+ message_utf8_downconvert = -1;
+ addr->prop.utf8_downcvt = FALSE;
+ addr->prop.utf8_downcvt_maybe = TRUE;
+ p += 3;
+ break;
+ }
+ *log_msgptr = US"bad option value for control=utf8_downconvert";
+ }
+ else
+ {
+ message_utf8_downconvert = 1;
+ addr->prop.utf8_downcvt = TRUE;
+ addr->prop.utf8_downcvt_maybe = FALSE;
+ break;
+ }
+ return ERROR;
+#endif