Generate internationalized delivery status reports
Debian Trixie package bundle / packages (push) Successful in 15m22s

This commit is contained in:
Mario Fetka
2026-07-21 07:49:54 +02:00
parent b99d358989
commit 3a51b4445e
5 changed files with 485 additions and 37 deletions
+12
View File
@@ -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()
+237
View File
@@ -0,0 +1,237 @@
/****************************************************************************
* <Novell-copyright>
* 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.
* </Novell-copyright>
****************************************************************************/
#include "dsn-format.h"
#include <nmap.h>
#include <strings.h>
#include <string.h>
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;
}
+35
View File
@@ -0,0 +1,35 @@
/****************************************************************************
* <Novell-copyright>
* 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.
* </Novell-copyright>
****************************************************************************/
#ifndef BONGO_QUEUE_DSN_FORMAT_H
#define BONGO_QUEUE_DSN_FORMAT_H
#include <stddef.h>
#include <stdio.h>
#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
+121 -37
View File
@@ -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<Body content limited to %lu bytes>\r\n", Conf.bounceMaxBodySize);
fprintf(returnPart, "\r\n<Body content limited to %lu bytes>\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);
+80
View File
@@ -0,0 +1,80 @@
/****************************************************************************
* <Novell-copyright>
* 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.
* </Novell-copyright>
****************************************************************************/
#include "dsn-format.h"
#include <nmap.h>
#include <assert.h>
#include <string.h>
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;
}