From 3a51b4445eac2d6c121fc2db062ef8c54ddf4b07 Mon Sep 17 00:00:00 2001 From: Mario Fetka Date: Tue, 21 Jul 2026 07:49:54 +0200 Subject: [PATCH] Generate internationalized delivery status reports --- src/agents/queue/CMakeLists.txt | 12 ++ src/agents/queue/dsn-format.c | 237 +++++++++++++++++++++++ src/agents/queue/dsn-format.h | 35 ++++ src/agents/queue/queue.c | 158 +++++++++++---- src/agents/queue/tests/dsn-format-test.c | 80 ++++++++ 5 files changed, 485 insertions(+), 37 deletions(-) create mode 100644 src/agents/queue/dsn-format.c create mode 100644 src/agents/queue/dsn-format.h create mode 100644 src/agents/queue/tests/dsn-format-test.c 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; +}