diff --git a/src/agents/queue/CMakeLists.txt b/src/agents/queue/CMakeLists.txt
index 05afc27..d031fd5 100644
--- a/src/agents/queue/CMakeLists.txt
+++ b/src/agents/queue/CMakeLists.txt
@@ -1,5 +1,6 @@
add_executable(bongoqueue
conf.c
+ dsn-format.c
domain.c
queued.c
mime.c
@@ -15,3 +16,14 @@ target_link_libraries(bongoqueue
)
install(TARGETS bongoqueue DESTINATION ${SBIN_INSTALL_DIR})
+
+if(BUILD_TESTING)
+ add_executable(queue-dsn-format-test
+ tests/dsn-format-test.c
+ dsn-format.c
+ )
+ target_include_directories(queue-dsn-format-test PRIVATE
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ )
+ add_test(NAME queue-dsn-format COMMAND queue-dsn-format-test)
+endif()
diff --git a/src/agents/queue/dsn-format.c b/src/agents/queue/dsn-format.c
new file mode 100644
index 0000000..aa87372
--- /dev/null
+++ b/src/agents/queue/dsn-format.c
@@ -0,0 +1,237 @@
+/****************************************************************************
+ *
+ * Copyright (c) 2001 Novell, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you
+ * may find current contact information at www.novell.com.
+ *
+ ****************************************************************************/
+
+#include "dsn-format.h"
+
+#include
+
+#include
+#include
+
+static int
+HexValue(unsigned char value)
+{
+ if (value >= '0' && value <= '9') return value - '0';
+ if (value >= 'A' && value <= 'F') return value - 'A' + 10;
+ if (value >= 'a' && value <= 'f') return value - 'a' + 10;
+ return -1;
+}
+
+static int
+AppendUtf8(char *result, size_t result_size, size_t *used,
+ unsigned int codepoint)
+{
+ unsigned char encoded[4];
+ size_t length;
+
+ if (codepoint <= 0x7fU) {
+ encoded[0] = (unsigned char)codepoint;
+ length = 1U;
+ } else if (codepoint <= 0x7ffU) {
+ encoded[0] = (unsigned char)(0xc0U | (codepoint >> 6U));
+ encoded[1] = (unsigned char)(0x80U | (codepoint & 0x3fU));
+ length = 2U;
+ } else if (codepoint <= 0xffffU) {
+ if (codepoint >= 0xd800U && codepoint <= 0xdfffU) return 0;
+ encoded[0] = (unsigned char)(0xe0U | (codepoint >> 12U));
+ encoded[1] = (unsigned char)(0x80U | ((codepoint >> 6U) & 0x3fU));
+ encoded[2] = (unsigned char)(0x80U | (codepoint & 0x3fU));
+ length = 3U;
+ } else if (codepoint <= 0x10ffffU) {
+ encoded[0] = (unsigned char)(0xf0U | (codepoint >> 18U));
+ encoded[1] = (unsigned char)(0x80U | ((codepoint >> 12U) & 0x3fU));
+ encoded[2] = (unsigned char)(0x80U | ((codepoint >> 6U) & 0x3fU));
+ encoded[3] = (unsigned char)(0x80U | (codepoint & 0x3fU));
+ length = 4U;
+ } else {
+ return 0;
+ }
+ if (length >= result_size - *used) return 0;
+ memcpy(result + *used, encoded, length);
+ *used += length;
+ result[*used] = '\0';
+ return 1;
+}
+
+static int
+DecodeUnitext(const char *address, char *result, size_t result_size)
+{
+ const unsigned char *cursor = (const unsigned char *)address;
+ size_t used = 0U;
+
+ while (*cursor != '\0') {
+ if (cursor[0] == '\\' && cursor[1] == 'x' && cursor[2] == '{') {
+ const unsigned char *digits = cursor + 3;
+ unsigned int codepoint = 0U;
+ size_t count = 0U;
+ int digit;
+
+ while (count < 6U && (digit = HexValue(digits[count])) >= 0) {
+ codepoint = (codepoint << 4U) | (unsigned int)digit;
+ count++;
+ }
+ if (count < 2U || digits[count] != '}' || codepoint == 0U ||
+ !AppendUtf8(result, result_size, &used, codepoint)) return 0;
+ cursor = digits + count + 1U;
+ } else {
+ if (*cursor == '\\') return 0;
+ if (used + 1U >= result_size) return 0;
+ result[used++] = (char)*cursor++;
+ result[used] = '\0';
+ }
+ }
+ return used != 0U;
+}
+
+int
+BongoDsnWriteBase64(FILE *input, FILE *output)
+{
+ static const char alphabet[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ unsigned char input_buffer[57];
+ char output_buffer[78];
+ size_t count;
+
+ if (input == NULL || output == NULL) return 0;
+ while ((count = fread(input_buffer, 1U, sizeof(input_buffer), input)) > 0U) {
+ size_t input_offset = 0U;
+ size_t output_offset = 0U;
+
+ while (input_offset < count) {
+ size_t remaining = count - input_offset;
+ unsigned int a = input_buffer[input_offset++];
+ unsigned int b = remaining > 1U ? input_buffer[input_offset++] : 0U;
+ unsigned int c = remaining > 2U ? input_buffer[input_offset++] : 0U;
+
+ output_buffer[output_offset++] = alphabet[a >> 2U];
+ output_buffer[output_offset++] =
+ alphabet[((a & 0x03U) << 4U) | (b >> 4U)];
+ output_buffer[output_offset++] = remaining > 1U
+ ? alphabet[((b & 0x0fU) << 2U) | (c >> 6U)] : '=';
+ output_buffer[output_offset++] = remaining > 2U
+ ? alphabet[c & 0x3fU] : '=';
+ }
+ output_buffer[output_offset++] = '\r';
+ output_buffer[output_offset++] = '\n';
+ if (fwrite(output_buffer, 1U, output_offset, output) != output_offset)
+ return 0;
+ }
+ return !ferror(input) && !ferror(output);
+}
+
+int
+BongoDsnAddress(const char *address, int international, char *result,
+ size_t result_size)
+{
+ const unsigned char *cursor;
+ const char *type;
+ int written;
+
+ if (result != NULL && result_size != 0U) result[0] = '\0';
+ if (address == NULL || address[0] == '\0' || result == NULL ||
+ result_size == 0U) return 0;
+ if (strchr(address, ';') != NULL) {
+ if (international && !strncasecmp(address, "utf-8;", 6U)) {
+ char decoded[BONGO_DSN_ADDRESS_MAX];
+
+ if (DecodeUnitext(address + 6U, decoded, sizeof(decoded))) {
+ written = snprintf(result, result_size, "utf-8;%s", decoded);
+ return written > 0 && (size_t)written < result_size;
+ }
+ /* RFC 6533 requires malformed stored UTF-8 address values to be
+ * copied without alteration rather than guessed at. */
+ }
+ written = snprintf(result, result_size, "%s", address);
+ return written > 0 && (size_t)written < result_size;
+ }
+ type = "rfc822";
+ for (cursor = (const unsigned char *)address; *cursor; cursor++) {
+ if (*cursor >= 0x80U) {
+ type = "utf-8";
+ break;
+ }
+ }
+ written = snprintf(result, result_size, "%s;%s", type, address);
+ return written > 0 && (size_t)written < result_size;
+}
+
+const char *
+BongoDsnStatus(int reason, const char **action)
+{
+ const char *result;
+
+ if (action == NULL) return NULL;
+ *action = "failed";
+ switch (reason) {
+ case DELIVER_SUCCESS:
+ *action = "delivered";
+ result = "2.0.0";
+ break;
+ case DELIVER_PENDING:
+ *action = "delayed";
+ result = "4.0.0";
+ break;
+ case DELIVER_TRY_LATER:
+ case DELIVER_PROCESSING_ERROR:
+ *action = "delayed";
+ result = "4.3.0";
+ break;
+ case DELIVER_LOCKED:
+ *action = "delayed";
+ result = "4.2.0";
+ break;
+ case DELIVER_USER_UNKNOWN:
+ result = "5.1.1";
+ break;
+ case DELIVER_HOST_UNKNOWN:
+ case DELIVER_UNREACHABLE:
+ result = "5.4.4";
+ break;
+ case DELIVER_BOGUS_NAME:
+ result = "5.1.3";
+ break;
+ case DELIVER_REFUSED:
+ result = "5.4.1";
+ break;
+ case DELIVER_HOPCOUNT_EXCEEDED:
+ result = "5.4.6";
+ break;
+ case DELIVER_TIMEOUT:
+ case DELIVER_TOO_LONG:
+ result = "5.4.7";
+ break;
+ case DELIVER_QUOTA_EXCEEDED:
+ result = "5.2.2";
+ break;
+ case DELIVER_BLOCKED:
+ case DELIVER_AUTH_ERROR:
+ case DELIVER_VIRUS_REJECT:
+ result = "5.7.1";
+ break;
+ case DELIVER_INTERNAL_ERROR:
+ result = "5.3.0";
+ break;
+ default:
+ result = "5.0.0";
+ break;
+ }
+ return result;
+}
diff --git a/src/agents/queue/dsn-format.h b/src/agents/queue/dsn-format.h
new file mode 100644
index 0000000..8525396
--- /dev/null
+++ b/src/agents/queue/dsn-format.h
@@ -0,0 +1,35 @@
+/****************************************************************************
+ *
+ * Copyright (c) 2001 Novell, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you
+ * may find current contact information at www.novell.com.
+ *
+ ****************************************************************************/
+
+#ifndef BONGO_QUEUE_DSN_FORMAT_H
+#define BONGO_QUEUE_DSN_FORMAT_H
+
+#include
+#include
+
+#define BONGO_DSN_ADDRESS_MAX 4096U
+
+int BongoDsnWriteBase64(FILE *input, FILE *output);
+int BongoDsnAddress(const char *address, int international, char *result,
+ size_t result_size);
+const char *BongoDsnStatus(int reason, const char **action);
+
+#endif
diff --git a/src/agents/queue/queue.c b/src/agents/queue/queue.c
index f89044c..ed363e2 100644
--- a/src/agents/queue/queue.c
+++ b/src/agents/queue/queue.c
@@ -29,6 +29,7 @@
#include "queue.h"
#include "mime.h"
#include "messages.h"
+#include "dsn-format.h"
static BOOL
QueueFormatBuffer(char *destination, size_t destinationSize,
@@ -1882,6 +1883,8 @@ CreateDSNMessage(FILE *data, FILE *control, FILE *rtsData, FILE *rtsControl, BOO
int reason = 0;
int oReason;
unsigned long flags;
+ unsigned long messageFlags = 0;
+ unsigned long returnFlags = DSN_HEADER;
unsigned long bodySize = 0;
unsigned long maxBodySize;
unsigned long received = 0;
@@ -1892,13 +1895,14 @@ CreateDSNMessage(FILE *data, FILE *control, FILE *rtsData, FILE *rtsControl, BOO
char timeLine[80];
char line[CONN_BUFSIZE + 1];
char postmaster[MAXEMAILNAMESIZE + 1];
- char sender[MAXEMAILNAMESIZE + 1];
+ char sender[MAXEMAILNAMESIZE + 1] = "";
char aSender[MAXEMAILNAMESIZE + 1] = "";
char recipient[MAXEMAILNAMESIZE + 1] = "";
char oRecipient[MAXEMAILNAMESIZE + 1] = "";
char envID[128] = "";
BOOL mBounce=FALSE;
BOOL header;
+ BOOL international;
time_t now;
/* Step 0, check if we want to bounce at all */
@@ -1924,11 +1928,14 @@ CreateDSNMessage(FILE *data, FILE *control, FILE *rtsData, FILE *rtsControl, BOO
oReason = 0;
do {
if (fgets(line, CONN_BUFSIZE, control)) {
+ CHOP_NEWLINE(line);
switch(line[0]) {
case QUEUE_BOUNCE: {
/* Syntax: BRecip ORecip DSN failure reason transcript */
oReason = reason;
- if (sscanf(line + 1, "%s %s %lu %d", recipient, oRecipient, &flags, &reason) == 4) {
+ if (sscanf(line + 1, "%256s %256s %lu %d", recipient,
+ oRecipient, &flags, &reason) == 4) {
+ returnFlags |= flags;
if (!mBounce && (oReason != 0) && (oReason != reason)) {
mBounce = TRUE;
reason = 0;
@@ -1945,13 +1952,19 @@ CreateDSNMessage(FILE *data, FILE *control, FILE *rtsData, FILE *rtsControl, BOO
ptr2 = strchr(ptr+1,' ');
if (ptr2) {
*ptr2 = '\0';
- strncpy(envID, ptr2, 127);
+ if (!QueueFormatBuffer(envID, sizeof(envID),
+ "%s", ptr2 + 1))
+ return FALSE;
}
- strcpy(aSender, ptr + 1);
+ if (!QueueFormatBuffer(aSender, sizeof(aSender),
+ "%s", ptr + 1))
+ return FALSE;
}
- strcpy(sender, line + 1);
+ if (!QueueFormatBuffer(sender, sizeof(sender), "%s",
+ line + 1))
+ return FALSE;
if ((sender[0] == '-') && (sender[1] == '\0')) {
/* We don't bounce bounces */
return(FALSE);
@@ -1964,6 +1977,14 @@ CreateDSNMessage(FILE *data, FILE *control, FILE *rtsData, FILE *rtsControl, BOO
received = atol(line + 1);
break;
}
+
+ case QUEUE_FLAGS: {
+ char *end = NULL;
+ unsigned long parsed = strtoul(line + 1, &end, 10);
+
+ if (end != line + 1) messageFlags = parsed;
+ break;
+ }
}
}
} while (!feof(control) && !ferror(control));
@@ -1971,6 +1992,7 @@ CreateDSNMessage(FILE *data, FILE *control, FILE *rtsData, FILE *rtsControl, BOO
if (recipient[0] == '\0') {
return(FALSE);
}
+ international = (messageFlags & MSG_FLAG_SMTPUTF8) != 0;
QueueFormat(postmaster, "%s@%s", BongoGlobals.postmaster, BongoGlobals.hostname);
handling = Conf.bounceHandling;
@@ -2046,7 +2068,9 @@ CreateDSNMessage(FILE *data, FILE *control, FILE *rtsData, FILE *rtsControl, BOO
fprintf(rtsData, "Precedence: bulk\r\n\r\nThis is a MIME-encapsulated message\r\n\r\n");
/* First section, human readable */
- fprintf(rtsData, "--%" PRIdMAX "-%lu-%s\r\nContent-type: text/plain; charset=US-ASCII\r\n\r\n", (intmax_t)now, (long unsigned int)XplGetThreadID(), BongoGlobals.hostname);
+ fprintf(rtsData, "--%" PRIdMAX "-%lu-%s\r\nContent-type: text/plain; charset=%s\r\nContent-Transfer-Encoding: %s\r\n\r\n", (intmax_t)now, (long unsigned int)XplGetThreadID(), BongoGlobals.hostname,
+ international ? "UTF-8" : "US-ASCII",
+ international ? "8bit" : "7bit");
MsgGetRFC822Date(-1, received, timeLine);
fprintf(rtsData, "The original message was received %s\r\nfrom %s\r\n\r\n", timeLine, aSender[0]!='\0' ? aSender : sender);
@@ -2081,7 +2105,10 @@ CreateDSNMessage(FILE *data, FILE *control, FILE *rtsData, FILE *rtsControl, BOO
ptr2 = strchr(ptr, ' ');
if (ptr2) {
*ptr2 = '\0';
- strcpy(oRecipient, ptr);
+ if (!QueueFormatBuffer(oRecipient,
+ sizeof(oRecipient), "%s",
+ ptr))
+ return FALSE;
ptr2++;
flags = (atol(ptr2) & DSN_FLAGS);
ptr = strchr(ptr2, ' ');
@@ -2093,7 +2120,11 @@ CreateDSNMessage(FILE *data, FILE *control, FILE *rtsData, FILE *rtsControl, BOO
}
}
} else {
- strcpy(oRecipient, ptr);
+ if (!QueueFormatBuffer(oRecipient,
+ sizeof(oRecipient), "%s",
+ ptr)) {
+ return FALSE;
+ }
}
}
@@ -2213,18 +2244,23 @@ CreateDSNMessage(FILE *data, FILE *control, FILE *rtsData, FILE *rtsControl, BOO
}
} while (!feof(control) && !ferror(control));
- /* Second section, computer readable */
- fprintf(rtsData, "--%" PRIdMAX "-%lu-%s\r\nContent-type: message/delivery-status\r\n\r\n", (intmax_t)now, (long unsigned int)XplGetThreadID(), BongoGlobals.hostname);
+ /* Second section, computer readable. RFC 6533 explicitly permits a
+ * 7-bit Content-Transfer-Encoding for the global status media type. */
+ FILE *statusPart = international ? tmpfile() : rtsData;
+ if (statusPart == NULL) return FALSE;
+ if (!international) {
+ fprintf(rtsData, "--%" PRIdMAX "-%lu-%s\r\nContent-type: message/delivery-status\r\n\r\n", (intmax_t)now, (long unsigned int)XplGetThreadID(), BongoGlobals.hostname);
+ }
/* Per message fields */
if (envID[0]!='\0') {
- fprintf(rtsData, "Original-Envelope-Id: %s\r\n", envID);
+ fprintf(statusPart, "Original-Envelope-Id: %s\r\n", envID);
}
- fprintf(rtsData, "Reporting-MTA: dns; %s\r\n", BongoGlobals.hostname);
+ fprintf(statusPart, "Reporting-MTA: dns; %s\r\n", BongoGlobals.hostname);
MsgGetRFC822Date(-1, received, timeLine);
- fprintf(rtsData, "Arrival-Date: %s\r\n", timeLine);
+ fprintf(statusPart, "Arrival-Date: %s\r\n", timeLine);
/* Per recipient fields */
/* Need to go through the file and enumerate all recipients */
@@ -2246,7 +2282,12 @@ CreateDSNMessage(FILE *data, FILE *control, FILE *rtsData, FILE *rtsControl, BOO
ptr2 = strchr(ptr, ' ');
if (ptr2) {
*ptr2 = '\0';
- strcpy(oRecipient, ptr);
+ if (!QueueFormatBuffer(oRecipient,
+ sizeof(oRecipient), "%s",
+ ptr)) {
+ if (international) fclose(statusPart);
+ return FALSE;
+ }
ptr2++;
flags = (atol(ptr2) & DSN_FLAGS);
ptr = strchr(ptr2, ' ');
@@ -2254,35 +2295,65 @@ CreateDSNMessage(FILE *data, FILE *control, FILE *rtsData, FILE *rtsControl, BOO
reason = atol(ptr + 1);
}
} else {
- strcpy(oRecipient, ptr);
+ if (!QueueFormatBuffer(oRecipient,
+ sizeof(oRecipient), "%s",
+ ptr)) {
+ if (international) fclose(statusPart);
+ return FALSE;
+ }
}
}
/* Each per recipient block in DSN requires CRLF */
- fprintf(rtsData, "\r\n");
+ fprintf(statusPart, "\r\n");
if (oRecipient[0] != '\0') {
- fprintf(rtsData, "Original-recipient: %s\r\n", oRecipient);
+ char dsnAddress[BONGO_DSN_ADDRESS_MAX];
+ if (BongoDsnAddress(oRecipient, international, dsnAddress,
+ sizeof(dsnAddress)))
+ fprintf(statusPart, "Original-recipient: %s\r\n",
+ dsnAddress);
}
- fprintf(rtsData, "Final-recipient: %s\r\n", line + 1);
- if (reason == DELIVER_SUCCESS) {
- fprintf(rtsData, "Action: delivered\r\n");
- fprintf(rtsData, "Status: 2.0.0\r\n");
- } else if (reason == DELIVER_PENDING) {
- fprintf(rtsData, "Action: delayed\r\n");
- fprintf(rtsData, "Status: 4.0.0\r\n");
- } else {
- /* fixme - this could be smarter, use the status code */
- fprintf(rtsData, "Action: failed\r\n");
- fprintf(rtsData, "Status: 5.0.0\r\n");
+ {
+ char dsnAddress[BONGO_DSN_ADDRESS_MAX];
+ if (!BongoDsnAddress(line + 1, international, dsnAddress,
+ sizeof(dsnAddress))) {
+ if (international) fclose(statusPart);
+ return FALSE;
+ }
+ fprintf(statusPart, "Final-recipient: %s\r\n",
+ dsnAddress);
+ }
+ {
+ const char *action;
+ const char *status = BongoDsnStatus(reason, &action);
+
+ fprintf(statusPart, "Action: %s\r\n", action);
+ fprintf(statusPart, "Status: %s\r\n", status);
}
}
}
} while (!feof(control) && !ferror(control));
+ if (international) {
+ fprintf(rtsData, "--%" PRIdMAX "-%lu-%s\r\nContent-type: message/global-delivery-status\r\nContent-Transfer-Encoding: base64\r\n\r\n", (intmax_t)now, (long unsigned int)XplGetThreadID(), BongoGlobals.hostname);
+ rewind(statusPart);
+ if (!BongoDsnWriteBase64(statusPart, rtsData)) {
+ fclose(statusPart);
+ return FALSE;
+ }
+ fclose(statusPart);
+ }
+
/* Third section, original message */
- fprintf(rtsData, "--%" PRIdMAX "-%lu-%s\r\nContent-type: message/rfc822\r\n\r\n", (intmax_t)now, (long unsigned int)XplGetThreadID(), BongoGlobals.hostname);
+ FILE *returnPart = international ? tmpfile() : rtsData;
+ if (returnPart == NULL) return FALSE;
+ if (!international) {
+ fprintf(rtsData, "--%" PRIdMAX "-%lu-%s\r\nContent-type: %s\r\n\r\n", (intmax_t)now, (long unsigned int)XplGetThreadID(), BongoGlobals.hostname,
+ (returnFlags & DSN_BODY) ? "message/rfc822" :
+ "text/rfc822-headers");
+ }
header = TRUE;
maxBodySize = -1;
@@ -2292,21 +2363,21 @@ CreateDSNMessage(FILE *data, FILE *control, FILE *rtsData, FILE *rtsControl, BOO
if((line[0] == '\r') && (line[1] == '\n') && (line[2] == 0)) {
header = FALSE;
if (Conf.bounceMaxBodySize) {
- fprintf(rtsData, "\r\n\r\n", Conf.bounceMaxBodySize);
+ fprintf(returnPart, "\r\n\r\n", Conf.bounceMaxBodySize);
maxBodySize = Conf.bounceMaxBodySize;
}
}
- if (flags & DSN_HEADER) {
+ if (returnFlags & DSN_HEADER) {
if ((line[0] == 'F') && (line[1] == 'r') && (line[4] == ' ')) {
- fwrite(">", sizeof(char), 1, rtsData);
+ fwrite(">", sizeof(char), 1, returnPart);
}
- bodySize += fwrite(line, sizeof(char), strlen(line), rtsData);
+ bodySize += fwrite(line, sizeof(char), strlen(line), returnPart);
}
} else {
- if (flags & DSN_BODY) {
- bodySize += fwrite(line, sizeof(char), strlen(line), rtsData);
+ if (returnFlags & DSN_BODY) {
+ bodySize += fwrite(line, sizeof(char), strlen(line), returnPart);
if (bodySize > maxBodySize) {
break;
}
@@ -2315,8 +2386,16 @@ CreateDSNMessage(FILE *data, FILE *control, FILE *rtsData, FILE *rtsControl, BOO
}
}
- if (!(flags & DSN_BODY)) {
- fprintf(rtsData, "-- Message body has been omitted --\r\n");
+ if (international) {
+ fprintf(rtsData, "--%" PRIdMAX "-%lu-%s\r\nContent-type: %s\r\nContent-Transfer-Encoding: base64\r\n\r\n", (intmax_t)now, (long unsigned int)XplGetThreadID(), BongoGlobals.hostname,
+ (returnFlags & DSN_BODY) ? "message/global" :
+ "message/global-headers");
+ rewind(returnPart);
+ if (!BongoDsnWriteBase64(returnPart, rtsData)) {
+ fclose(returnPart);
+ return FALSE;
+ }
+ fclose(returnPart);
}
/* End of message */
@@ -2324,6 +2403,11 @@ CreateDSNMessage(FILE *data, FILE *control, FILE *rtsData, FILE *rtsControl, BOO
/* Now create the control file */
fprintf(rtsControl, "D%" PRIdMAX "\r\n", (intmax_t)now);
+ if (international) {
+ fprintf(rtsControl, "%c%lu\r\n", QUEUE_FLAGS,
+ (unsigned long)(MSG_FLAG_SMTPUTF8 |
+ MSG_FLAG_ENCODING_8BITM));
+ }
fprintf(rtsControl, "F- -\r\n");
XplRWReadLockAcquire(&Conf.lock);
diff --git a/src/agents/queue/tests/dsn-format-test.c b/src/agents/queue/tests/dsn-format-test.c
new file mode 100644
index 0000000..65478d2
--- /dev/null
+++ b/src/agents/queue/tests/dsn-format-test.c
@@ -0,0 +1,80 @@
+/****************************************************************************
+ *
+ * Copyright (c) 2001 Novell, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you
+ * may find current contact information at www.novell.com.
+ *
+ ****************************************************************************/
+
+#include "dsn-format.h"
+
+#include
+
+#include
+#include
+
+static void
+TestBase64(void)
+{
+ FILE *input = tmpfile();
+ FILE *output = tmpfile();
+ char result[64];
+ size_t length;
+
+ assert(input != NULL && output != NULL);
+ assert(fwrite("Bongo UTF-8: \303\274", 1U,
+ strlen("Bongo UTF-8: \303\274"), input) ==
+ strlen("Bongo UTF-8: \303\274"));
+ rewind(input);
+ assert(BongoDsnWriteBase64(input, output));
+ rewind(output);
+ length = fread(result, 1U, sizeof(result) - 1U, output);
+ result[length] = '\0';
+ assert(!strcmp(result, "Qm9uZ28gVVRGLTg6IMO8\r\n"));
+ fclose(input);
+ fclose(output);
+}
+
+int
+main(void)
+{
+ char address[128];
+ const char *action;
+
+ TestBase64();
+ assert(BongoDsnAddress("user@example.test", 0, address,
+ sizeof(address)));
+ assert(!strcmp(address, "rfc822;user@example.test"));
+ assert(BongoDsnAddress("m\303\274ller@example.test", 1, address,
+ sizeof(address)));
+ assert(!strcmp(address, "utf-8;m\303\274ller@example.test"));
+ assert(BongoDsnAddress("UTF-8;m\\x{FC}ller@example.test", 1, address,
+ sizeof(address)));
+ assert(!strcmp(address, "utf-8;m\303\274ller@example.test"));
+ assert(BongoDsnAddress("UTF-8;m\\x{FC}ller@example.test", 0, address,
+ sizeof(address)));
+ assert(!strcmp(address, "UTF-8;m\\x{FC}ller@example.test"));
+ assert(BongoDsnAddress("utf-8;bad\\x{D800}@example.test", 1, address,
+ sizeof(address)));
+ assert(!strcmp(address, "utf-8;bad\\x{D800}@example.test"));
+ assert(!strcmp(BongoDsnStatus(DELIVER_SUCCESS, &action), "2.0.0"));
+ assert(!strcmp(action, "delivered"));
+ assert(!strcmp(BongoDsnStatus(DELIVER_USER_UNKNOWN, &action), "5.1.1"));
+ assert(!strcmp(action, "failed"));
+ assert(!strcmp(BongoDsnStatus(DELIVER_TRY_LATER, &action), "4.3.0"));
+ assert(!strcmp(action, "delayed"));
+ return 0;
+}