920 lines
30 KiB
C
920 lines
30 KiB
C
/****************************************************************************
|
|
* <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 "protocol.h"
|
|
|
|
#include <ctype.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <nmap.h>
|
|
|
|
typedef struct {
|
|
const char *name;
|
|
SMTPCommand command;
|
|
} SMTPCommandEntry;
|
|
|
|
static const SMTPCommandEntry Commands[] = {
|
|
{ "AUTH", SMTP_COMMAND_AUTH },
|
|
{ "BDAT", SMTP_COMMAND_BDAT },
|
|
{ "DATA", SMTP_COMMAND_DATA },
|
|
{ "EHLO", SMTP_COMMAND_EHLO },
|
|
{ "ETRN", SMTP_COMMAND_ETRN },
|
|
{ "EXPN", SMTP_COMMAND_EXPN },
|
|
{ "HELO", SMTP_COMMAND_HELO },
|
|
{ "HELP", SMTP_COMMAND_HELP },
|
|
{ "MAIL", SMTP_COMMAND_MAIL },
|
|
{ "NOOP", SMTP_COMMAND_NOOP },
|
|
{ "QUIT", SMTP_COMMAND_QUIT },
|
|
{ "RCPT", SMTP_COMMAND_RCPT },
|
|
{ "RSET", SMTP_COMMAND_RSET },
|
|
{ "SAML", SMTP_COMMAND_SAML },
|
|
{ "SEND", SMTP_COMMAND_SEND },
|
|
{ "SOML", SMTP_COMMAND_SOML },
|
|
{ "STARTTLS", SMTP_COMMAND_STARTTLS },
|
|
{ "VRFY", SMTP_COMMAND_VRFY }
|
|
};
|
|
|
|
static int
|
|
CommandEquals(const char *command, size_t length, const char *expected)
|
|
{
|
|
size_t i;
|
|
|
|
if (strlen(expected) != length) return 0;
|
|
for (i = 0; i < length; i++) {
|
|
if (toupper((unsigned char)command[i]) !=
|
|
(unsigned char)expected[i]) return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
SMTPRequireTlsHasAuthenticatedPolicy(int mx_dnssec_authenticated,
|
|
int dane_authenticated,
|
|
int mta_sts_enforced,
|
|
int web_pki_verification)
|
|
{
|
|
return dane_authenticated || mta_sts_enforced ||
|
|
(mx_dnssec_authenticated && web_pki_verification);
|
|
}
|
|
|
|
SMTPCommand
|
|
SMTPParseCommandLine(const char *line, size_t line_length,
|
|
const char **arguments)
|
|
{
|
|
const char *cursor;
|
|
size_t command_length;
|
|
size_t i;
|
|
|
|
if (arguments) *arguments = NULL;
|
|
if (!line || line_length == 0 || strlen(line) != line_length ||
|
|
*line == ' ' || *line == '\t')
|
|
return SMTP_COMMAND_UNKNOWN;
|
|
for (i = 0; i < line_length; i++) {
|
|
unsigned char current = (unsigned char)line[i];
|
|
|
|
if ((current < 0x20U && current != '\t') || current == 0x7fU)
|
|
return SMTP_COMMAND_UNKNOWN;
|
|
}
|
|
|
|
cursor = line;
|
|
while (*cursor && *cursor != ' ' && *cursor != '\t') {
|
|
if ((unsigned char)*cursor < 0x21U ||
|
|
(unsigned char)*cursor > 0x7eU) return SMTP_COMMAND_UNKNOWN;
|
|
cursor++;
|
|
}
|
|
command_length = (size_t)(cursor - line);
|
|
while (*cursor == ' ' || *cursor == '\t') cursor++;
|
|
if (arguments) *arguments = cursor;
|
|
|
|
for (i = 0; i < sizeof(Commands) / sizeof(Commands[0]); i++) {
|
|
if (CommandEquals(line, command_length, Commands[i].name))
|
|
return Commands[i].command;
|
|
}
|
|
return SMTP_COMMAND_UNKNOWN;
|
|
}
|
|
|
|
int
|
|
SMTPArgumentsEmpty(const char *arguments)
|
|
{
|
|
return arguments != NULL && *arguments == '\0';
|
|
}
|
|
|
|
int
|
|
SMTPParseGreetingArgument(const char *arguments, char *domain,
|
|
size_t domain_size)
|
|
{
|
|
const unsigned char *cursor = (const unsigned char *)arguments;
|
|
size_t length = 0;
|
|
|
|
if (!arguments || !domain || domain_size == 0 || !*cursor) return 0;
|
|
while (*cursor) {
|
|
if (*cursor <= 0x20U || *cursor >= 0x7fU ||
|
|
length >= 255U || length + 1 >= domain_size) {
|
|
domain[0] = '\0';
|
|
return 0;
|
|
}
|
|
domain[length++] = (char)*cursor++;
|
|
}
|
|
domain[length] = '\0';
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
SMTPParseBdatArguments(const char *arguments, uint64_t *chunk_size,
|
|
int *last_chunk)
|
|
{
|
|
const char *cursor = arguments;
|
|
uint64_t parsed = 0U;
|
|
|
|
if (chunk_size) *chunk_size = 0U;
|
|
if (last_chunk) *last_chunk = 0;
|
|
if (!cursor || !chunk_size || !last_chunk ||
|
|
!isdigit((unsigned char)*cursor)) return 0;
|
|
do {
|
|
unsigned int digit = (unsigned int)(*cursor - '0');
|
|
|
|
if (parsed > (UINT64_MAX - digit) / 10U) return 0;
|
|
parsed = parsed * 10U + digit;
|
|
cursor++;
|
|
} while (isdigit((unsigned char)*cursor));
|
|
|
|
if (*cursor != '\0' && *cursor != ' ' && *cursor != '\t') return 0;
|
|
while (*cursor == ' ' || *cursor == '\t') cursor++;
|
|
if (*cursor != '\0') {
|
|
const char *token = cursor;
|
|
|
|
while (*cursor != '\0' && *cursor != ' ' && *cursor != '\t')
|
|
cursor++;
|
|
if (!CommandEquals(token, (size_t)(cursor - token), "LAST")) return 0;
|
|
while (*cursor == ' ' || *cursor == '\t') cursor++;
|
|
if (*cursor != '\0') return 0;
|
|
*last_chunk = 1;
|
|
}
|
|
*chunk_size = parsed;
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
SMTPParseVerifyArguments(const char *arguments, char *query,
|
|
size_t query_size, int *smtp_utf8)
|
|
{
|
|
const char *end;
|
|
const char *token;
|
|
size_t length;
|
|
size_t i;
|
|
|
|
if (query != NULL && query_size != 0U) query[0] = '\0';
|
|
if (smtp_utf8 != NULL) *smtp_utf8 = 0;
|
|
if (arguments == NULL || query == NULL || query_size == 0U ||
|
|
smtp_utf8 == NULL || *arguments == '\0') return 0;
|
|
|
|
end = arguments + strlen(arguments);
|
|
while (end > arguments && (end[-1] == ' ' || end[-1] == '\t')) end--;
|
|
if (end == arguments) return 0;
|
|
|
|
token = end;
|
|
while (token > arguments && token[-1] != ' ' && token[-1] != '\t')
|
|
token--;
|
|
if (token > arguments &&
|
|
CommandEquals(token, (size_t)(end - token), "SMTPUTF8")) {
|
|
*smtp_utf8 = 1;
|
|
end = token;
|
|
while (end > arguments && (end[-1] == ' ' || end[-1] == '\t')) end--;
|
|
if (end == arguments) return 0;
|
|
}
|
|
|
|
length = (size_t)(end - arguments);
|
|
if (length + 1U > query_size) return 0;
|
|
for (i = 0U; i < length; i++) {
|
|
unsigned char current = (unsigned char)arguments[i];
|
|
|
|
if ((current < 0x20U && current != '\t') || current == 0x7fU)
|
|
return 0;
|
|
}
|
|
memcpy(query, arguments, length);
|
|
query[length] = '\0';
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
SMTPPathIsSafe(const char *path)
|
|
{
|
|
const unsigned char *cursor = (const unsigned char *)path;
|
|
|
|
if (cursor == NULL) return 0;
|
|
while (*cursor != '\0') {
|
|
if (*cursor < 0x21U || *cursor == 0x7fU ||
|
|
*cursor == '<' || *cursor == '>') return 0;
|
|
cursor++;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
SMTPRecipientPathIsValid(const char *path)
|
|
{
|
|
return path != NULL && path[0] != '\0' && SMTPPathIsSafe(path);
|
|
}
|
|
|
|
static int
|
|
HexValue(unsigned char value)
|
|
{
|
|
if (value >= '0' && value <= '9') return (int)(value - '0');
|
|
if (value >= 'A' && value <= 'F') return (int)(value - 'A') + 10;
|
|
if (value >= 'a' && value <= 'f') return (int)(value - 'a') + 10;
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
SMTPXtextEncode(const char *value, char *encoded, size_t encoded_size)
|
|
{
|
|
static const char hex[] = "0123456789ABCDEF";
|
|
const unsigned char *cursor = (const unsigned char *)value;
|
|
size_t used = 0U;
|
|
|
|
if (encoded != NULL && encoded_size != 0U) encoded[0] = '\0';
|
|
if (value == NULL || encoded == NULL || encoded_size == 0U) return 0;
|
|
while (*cursor != '\0') {
|
|
if (*cursor >= 0x21U && *cursor <= 0x7eU &&
|
|
*cursor != '+' && *cursor != '=') {
|
|
if (used + 1U >= encoded_size) return 0;
|
|
encoded[used++] = (char)*cursor;
|
|
} else {
|
|
if (used + 3U >= encoded_size) return 0;
|
|
encoded[used++] = '+';
|
|
encoded[used++] = hex[*cursor >> 4U];
|
|
encoded[used++] = hex[*cursor & 0x0fU];
|
|
}
|
|
cursor++;
|
|
}
|
|
encoded[used] = '\0';
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
SMTPXtextDecode(const char *encoded, char *value, size_t value_size)
|
|
{
|
|
const unsigned char *cursor = (const unsigned char *)encoded;
|
|
size_t used = 0U;
|
|
|
|
if (value != NULL && value_size != 0U) value[0] = '\0';
|
|
if (encoded == NULL || value == NULL || value_size == 0U) return 0;
|
|
while (*cursor != '\0') {
|
|
unsigned char decoded;
|
|
|
|
if (*cursor == '+') {
|
|
int high;
|
|
int low;
|
|
|
|
if (cursor[1] == '\0' || cursor[2] == '\0' ||
|
|
(high = HexValue(cursor[1])) < 0 ||
|
|
(low = HexValue(cursor[2])) < 0) return 0;
|
|
decoded = (unsigned char)((high << 4) | low);
|
|
cursor += 3;
|
|
} else {
|
|
if (*cursor < 0x21U || *cursor > 0x7eU || *cursor == '=')
|
|
return 0;
|
|
decoded = *cursor++;
|
|
}
|
|
if (decoded == '\0' || used + 1U >= value_size) return 0;
|
|
value[used++] = (char)decoded;
|
|
}
|
|
value[used] = '\0';
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
TokenEquals(const char *token, size_t length, const char *expected)
|
|
{
|
|
return CommandEquals(token, length, expected);
|
|
}
|
|
|
|
static int
|
|
TokenPrefix(const char *token, size_t length, const char *prefix,
|
|
const char **value, size_t *value_length)
|
|
{
|
|
size_t prefix_length = strlen(prefix);
|
|
|
|
if (length < prefix_length ||
|
|
!CommandEquals(token, prefix_length, prefix)) return 0;
|
|
*value = token + prefix_length;
|
|
*value_length = length - prefix_length;
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
NextParameter(const char **cursor, const char **token, size_t *length)
|
|
{
|
|
const char *current = *cursor;
|
|
|
|
if (!current) return 0;
|
|
while (*current == ' ' || *current == '\t') current++;
|
|
if (!*current) {
|
|
*cursor = current;
|
|
return 0;
|
|
}
|
|
*token = current;
|
|
while (*current && *current != ' ' && *current != '\t') current++;
|
|
*length = (size_t)(current - *token);
|
|
*cursor = current;
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
ParseSizeToken(const char *value, size_t length, uint64_t *size)
|
|
{
|
|
uint64_t parsed = 0;
|
|
size_t i;
|
|
|
|
if (!length) return 0;
|
|
for (i = 0; i < length; i++) {
|
|
unsigned int digit;
|
|
if (!isdigit((unsigned char)value[i])) return 0;
|
|
digit = (unsigned int)(value[i] - '0');
|
|
if (parsed > (UINT64_MAX - digit) / 10U) return 0;
|
|
parsed = parsed * 10U + digit;
|
|
}
|
|
*size = parsed;
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
HexDigit(unsigned char value)
|
|
{
|
|
return (value >= '0' && value <= '9') ||
|
|
(value >= 'A' && value <= 'F') ||
|
|
(value >= 'a' && value <= 'f');
|
|
}
|
|
|
|
static int
|
|
XtextValid(const char *value, size_t length, size_t maximum)
|
|
{
|
|
size_t i;
|
|
|
|
if (!length || length > maximum) return 0;
|
|
for (i = 0; i < length; i++) {
|
|
unsigned char current = (unsigned char)value[i];
|
|
if (current < 0x21U || current > 0x7eU || current == '=') return 0;
|
|
if (current == '+') {
|
|
unsigned int decoded;
|
|
|
|
if (length - i < 3U || !HexDigit((unsigned char)value[i + 1]) ||
|
|
!HexDigit((unsigned char)value[i + 2])) return 0;
|
|
decoded = (unsigned int)(
|
|
HexValue((unsigned char)value[i + 1]) * 16 +
|
|
HexValue((unsigned char)value[i + 2]));
|
|
if (decoded < 0x20U || decoded > 0x7eU) return 0;
|
|
i += 2U;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
SMTPParameterResult
|
|
SMTPParseMailParameters(const char *parameters, SMTPMailParameters *result)
|
|
{
|
|
const char *cursor = parameters;
|
|
const char *token;
|
|
const char *value;
|
|
size_t length;
|
|
size_t value_length;
|
|
|
|
if (!result) return SMTP_PARAMETER_SYNTAX;
|
|
memset(result, 0, sizeof(*result));
|
|
while (NextParameter(&cursor, &token, &length)) {
|
|
if (TokenEquals(token, length, "BODY=7BIT") ||
|
|
TokenEquals(token, length, "BODY=8BITMIME") ||
|
|
TokenEquals(token, length, "BODY=BINARYMIME")) {
|
|
if (result->body != SMTP_BODY_UNSPECIFIED)
|
|
return SMTP_PARAMETER_SYNTAX;
|
|
if (TokenEquals(token, length, "BODY=7BIT"))
|
|
result->body = SMTP_BODY_7BIT;
|
|
else if (TokenEquals(token, length, "BODY=8BITMIME"))
|
|
result->body = SMTP_BODY_8BITMIME;
|
|
else {
|
|
result->body = SMTP_BODY_BINARYMIME;
|
|
return SMTP_PARAMETER_BINARYMIME;
|
|
}
|
|
} else if (TokenPrefix(token, length, "SIZE=", &value,
|
|
&value_length)) {
|
|
if (result->size_supplied ||
|
|
!ParseSizeToken(value, value_length, &result->size))
|
|
return SMTP_PARAMETER_SYNTAX;
|
|
result->size_supplied = 1;
|
|
} else if (TokenEquals(token, length, "SMTPUTF8")) {
|
|
if (result->smtp_utf8) return SMTP_PARAMETER_SYNTAX;
|
|
result->smtp_utf8 = 1;
|
|
} else if (TokenEquals(token, length, "REQUIRETLS")) {
|
|
if (result->require_tls) return SMTP_PARAMETER_SYNTAX;
|
|
result->require_tls = 1;
|
|
} else if (TokenPrefix(token, length, "RET=", &value,
|
|
&value_length)) {
|
|
if (result->ret_supplied) return SMTP_PARAMETER_SYNTAX;
|
|
if (TokenEquals(value, value_length, "FULL"))
|
|
result->ret_full = 1;
|
|
else if (!TokenEquals(value, value_length, "HDRS"))
|
|
return SMTP_PARAMETER_SYNTAX;
|
|
result->ret_supplied = 1;
|
|
} else if (TokenPrefix(token, length, "ENVID=", &value,
|
|
&value_length)) {
|
|
if (result->envid_supplied ||
|
|
!XtextValid(value, value_length, SMTP_ENVID_MAX))
|
|
return SMTP_PARAMETER_SYNTAX;
|
|
memcpy(result->envid, value, value_length);
|
|
result->envid[value_length] = '\0';
|
|
result->envid_supplied = 1;
|
|
} else if (TokenPrefix(token, length, "AUTH=", &value,
|
|
&value_length)) {
|
|
if (result->auth_supplied ||
|
|
!XtextValid(value, value_length, SMTP_ORCPT_MAX))
|
|
return SMTP_PARAMETER_SYNTAX;
|
|
result->auth_supplied = 1;
|
|
} else {
|
|
return SMTP_PARAMETER_UNSUPPORTED;
|
|
}
|
|
}
|
|
return SMTP_PARAMETER_OK;
|
|
}
|
|
|
|
static int
|
|
ParseNotify(const char *value, size_t length, unsigned int *notify)
|
|
{
|
|
const char *cursor = value;
|
|
const char *end = value + length;
|
|
unsigned int flags = 0U;
|
|
|
|
while (cursor < end) {
|
|
const char *item = cursor;
|
|
size_t item_length;
|
|
unsigned int flag;
|
|
|
|
while (cursor < end && *cursor != ',') cursor++;
|
|
item_length = (size_t)(cursor - item);
|
|
if (TokenEquals(item, item_length, "SUCCESS"))
|
|
flag = SMTP_NOTIFY_SUCCESS;
|
|
else if (TokenEquals(item, item_length, "FAILURE"))
|
|
flag = SMTP_NOTIFY_FAILURE;
|
|
else if (TokenEquals(item, item_length, "DELAY"))
|
|
flag = SMTP_NOTIFY_DELAY;
|
|
else if (TokenEquals(item, item_length, "NEVER"))
|
|
flag = SMTP_NOTIFY_NEVER;
|
|
else
|
|
return 0;
|
|
if ((flags & flag) != 0U) return 0;
|
|
flags |= flag;
|
|
if (cursor < end) cursor++;
|
|
}
|
|
if (!flags || ((flags & SMTP_NOTIFY_NEVER) != 0U &&
|
|
flags != SMTP_NOTIFY_NEVER)) return 0;
|
|
*notify = flags;
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
Utf8CharacterLength(const unsigned char *value, size_t remaining,
|
|
size_t *character_length, unsigned int *decoded)
|
|
{
|
|
unsigned int codepoint;
|
|
size_t length;
|
|
size_t i;
|
|
|
|
if (remaining == 0U || value[0] < 0x80U) return 0;
|
|
if (value[0] >= 0xc2U && value[0] <= 0xdfU) {
|
|
length = 2U;
|
|
codepoint = value[0] & 0x1fU;
|
|
} else if (value[0] >= 0xe0U && value[0] <= 0xefU) {
|
|
length = 3U;
|
|
codepoint = value[0] & 0x0fU;
|
|
} else if (value[0] >= 0xf0U && value[0] <= 0xf4U) {
|
|
length = 4U;
|
|
codepoint = value[0] & 0x07U;
|
|
} else {
|
|
return 0;
|
|
}
|
|
if (remaining < length) return 0;
|
|
for (i = 1U; i < length; i++) {
|
|
if ((value[i] & 0xc0U) != 0x80U) return 0;
|
|
codepoint = (codepoint << 6U) | (value[i] & 0x3fU);
|
|
}
|
|
if ((length == 3U && codepoint < 0x800U) ||
|
|
(length == 4U && codepoint < 0x10000U) ||
|
|
codepoint > 0x10ffffU ||
|
|
(codepoint >= 0xd800U && codepoint <= 0xdfffU)) return 0;
|
|
*character_length = length;
|
|
if (decoded != NULL) *decoded = codepoint;
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
EmbeddedUnicodeLength(const unsigned char *value, size_t remaining,
|
|
size_t *encoded_length)
|
|
{
|
|
size_t digits = 0U;
|
|
unsigned int codepoint = 0U;
|
|
|
|
if (remaining < 6U || value[0] != '\\' || value[1] != 'x' ||
|
|
value[2] != '{') return 0;
|
|
while (3U + digits < remaining && digits < 6U &&
|
|
isxdigit(value[3U + digits])) {
|
|
unsigned char digit = value[3U + digits];
|
|
|
|
codepoint <<= 4U;
|
|
if (digit >= '0' && digit <= '9') codepoint |= digit - '0';
|
|
else codepoint |= (unsigned int)(toupper(digit) - 'A' + 10);
|
|
digits++;
|
|
}
|
|
if (digits < 2U || 3U + digits >= remaining ||
|
|
value[3U + digits] != '}' || codepoint == 0U ||
|
|
codepoint > 0x10ffffU ||
|
|
(codepoint >= 0xd800U && codepoint <= 0xdfffU)) return 0;
|
|
if (codepoint < 0x80U && codepoint > 0x20U && codepoint != 0x2bU &&
|
|
codepoint != 0x3dU && codepoint != 0x5cU && codepoint != 0x7fU)
|
|
return 0;
|
|
*encoded_length = digits + 4U;
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
Utf8OrcptAddressValid(const char *value, size_t length, int smtp_utf8)
|
|
{
|
|
const unsigned char *cursor = (const unsigned char *)value;
|
|
size_t remaining = length;
|
|
|
|
if (remaining == 0U) return 0;
|
|
while (remaining > 0U) {
|
|
size_t consumed;
|
|
unsigned char current = *cursor;
|
|
|
|
if (current == '\\') {
|
|
if (!EmbeddedUnicodeLength(cursor, remaining, &consumed))
|
|
return 0;
|
|
} else if (current >= 0x80U) {
|
|
if (!smtp_utf8 ||
|
|
!Utf8CharacterLength(cursor, remaining, &consumed, NULL))
|
|
return 0;
|
|
} else {
|
|
if (current < 0x21U || current == 0x2bU || current == 0x3dU ||
|
|
current == 0x5cU || current > 0x7eU) return 0;
|
|
consumed = 1U;
|
|
}
|
|
cursor += consumed;
|
|
remaining -= consumed;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
AppendWire(char *result, size_t result_size, size_t *used,
|
|
const char *value, size_t length)
|
|
{
|
|
if (length >= result_size - *used) return 0;
|
|
memcpy(result + *used, value, length);
|
|
*used += length;
|
|
result[*used] = '\0';
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
AppendEmbedded(char *result, size_t result_size, size_t *used,
|
|
unsigned int codepoint)
|
|
{
|
|
static const char hex[] = "0123456789ABCDEF";
|
|
char encoded[11];
|
|
size_t digits = 0U;
|
|
size_t hex_digits;
|
|
unsigned int value = codepoint;
|
|
|
|
do {
|
|
digits++;
|
|
value >>= 4U;
|
|
} while (value != 0U);
|
|
if (digits < 2U) digits = 2U;
|
|
hex_digits = digits;
|
|
encoded[0] = '\\';
|
|
encoded[1] = 'x';
|
|
encoded[2] = '{';
|
|
value = codepoint;
|
|
while (digits > 0U) {
|
|
encoded[2U + digits] = hex[value & 0x0fU];
|
|
value >>= 4U;
|
|
digits--;
|
|
}
|
|
encoded[3U + hex_digits] = '}';
|
|
return AppendWire(result, result_size, used, encoded, hex_digits + 4U);
|
|
}
|
|
|
|
int
|
|
SMTPFormatOrcptValue(const char *original, int smtp_utf8,
|
|
char *wire_value, size_t wire_value_size)
|
|
{
|
|
const char *separator;
|
|
const unsigned char *cursor;
|
|
const char *address;
|
|
size_t type_length;
|
|
size_t address_length;
|
|
size_t remaining;
|
|
size_t used = 0U;
|
|
int untyped_utf8 = 0;
|
|
|
|
if (wire_value != NULL && wire_value_size != 0U) wire_value[0] = '\0';
|
|
if (original == NULL || original[0] == '\0' || wire_value == NULL ||
|
|
wire_value_size == 0U) return 0;
|
|
separator = strchr(original, ';');
|
|
if (separator == NULL) {
|
|
int has_utf8 = 0;
|
|
char encoded[SMTP_ORCPT_MAX * 3U + 1U];
|
|
|
|
for (cursor = (const unsigned char *)original; *cursor; cursor++) {
|
|
if (*cursor >= 0x80U) {
|
|
has_utf8 = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (!has_utf8) {
|
|
int written;
|
|
|
|
if (!SMTPXtextEncode(original, encoded, sizeof(encoded))) return 0;
|
|
written = snprintf(wire_value, wire_value_size, "rfc822;%s",
|
|
encoded);
|
|
return written > 0 && (size_t)written < wire_value_size;
|
|
}
|
|
address = original;
|
|
address_length = strlen(original);
|
|
untyped_utf8 = 1;
|
|
} else {
|
|
size_t i;
|
|
int utf8_type;
|
|
|
|
type_length = (size_t)(separator - original);
|
|
address = separator + 1;
|
|
address_length = strlen(address);
|
|
if (type_length == 0U || type_length > 64U || address_length == 0U)
|
|
return 0;
|
|
for (i = 0U; i < type_length; i++) {
|
|
unsigned char current = (unsigned char)original[i];
|
|
if (!isalnum(current) && current != '-') return 0;
|
|
}
|
|
utf8_type = type_length == 5U &&
|
|
CommandEquals(original, type_length, "UTF-8");
|
|
if (!utf8_type) {
|
|
if (!XtextValid(address, address_length, SMTP_ORCPT_MAX) ||
|
|
type_length + 1U + address_length >= wire_value_size) return 0;
|
|
memcpy(wire_value, original, type_length + 1U + address_length + 1U);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if (!untyped_utf8 &&
|
|
!Utf8OrcptAddressValid(address, address_length, 1)) return 0;
|
|
if (!AppendWire(wire_value, wire_value_size, &used, "utf-8;", 6U))
|
|
return 0;
|
|
if (smtp_utf8 && !untyped_utf8)
|
|
return AppendWire(wire_value, wire_value_size, &used, address,
|
|
address_length);
|
|
|
|
cursor = (const unsigned char *)address;
|
|
remaining = address_length;
|
|
while (remaining > 0U) {
|
|
size_t consumed = 1U;
|
|
|
|
if (*cursor >= 0x80U) {
|
|
unsigned int codepoint;
|
|
|
|
if (!Utf8CharacterLength(cursor, remaining, &consumed,
|
|
&codepoint)) return 0;
|
|
if (smtp_utf8) {
|
|
if (!AppendWire(wire_value, wire_value_size, &used,
|
|
(const char *)cursor, consumed)) return 0;
|
|
} else if (!AppendEmbedded(wire_value, wire_value_size, &used,
|
|
codepoint)) return 0;
|
|
} else if (untyped_utf8 &&
|
|
(*cursor <= 0x20U || *cursor == 0x2bU ||
|
|
*cursor == 0x3dU || *cursor == 0x5cU ||
|
|
*cursor == 0x7fU)) {
|
|
if (!AppendEmbedded(wire_value, wire_value_size, &used,
|
|
*cursor)) return 0;
|
|
} else {
|
|
if (!AppendWire(wire_value, wire_value_size, &used,
|
|
(const char *)cursor, 1U)) return 0;
|
|
}
|
|
cursor += consumed;
|
|
remaining -= consumed;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
SMTPFormatDsnMailOptions(int ret_explicit, int ret_full,
|
|
const char *envid, char *options,
|
|
size_t options_size)
|
|
{
|
|
size_t used = 0U;
|
|
size_t envid_length;
|
|
|
|
if (options == NULL || options_size == 0U) return 0;
|
|
options[0] = '\0';
|
|
if (ret_explicit &&
|
|
!AppendWire(options, options_size, &used,
|
|
ret_full ? " RET=FULL" : " RET=HDRS", 9U))
|
|
return 0;
|
|
if (envid == NULL || envid[0] == '\0') return 1;
|
|
envid_length = strlen(envid);
|
|
if (!XtextValid(envid, envid_length, SMTP_ENVID_MAX) ||
|
|
!AppendWire(options, options_size, &used, " ENVID=", 7U) ||
|
|
!AppendWire(options, options_size, &used, envid, envid_length))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
SMTPFormatDsnRcptOptions(int notify_explicit,
|
|
unsigned int notify,
|
|
const char *original_recipient,
|
|
int smtp_utf8, char *options,
|
|
size_t options_size)
|
|
{
|
|
char orcpt[SMTP_ORCPT_WIRE_MAX];
|
|
size_t used = 0U;
|
|
int started = 0;
|
|
|
|
if (options == NULL || options_size == 0U ||
|
|
(notify & ~(SMTP_NOTIFY_SUCCESS | SMTP_NOTIFY_FAILURE |
|
|
SMTP_NOTIFY_DELAY)) != 0U)
|
|
return 0;
|
|
options[0] = '\0';
|
|
if (original_recipient != NULL && original_recipient[0] != '\0') {
|
|
size_t length;
|
|
|
|
if (!SMTPFormatOrcptValue(original_recipient, smtp_utf8, orcpt,
|
|
sizeof(orcpt)))
|
|
return 0;
|
|
length = strlen(orcpt);
|
|
if (!AppendWire(options, options_size, &used, " ORCPT=", 7U) ||
|
|
!AppendWire(options, options_size, &used, orcpt, length))
|
|
return 0;
|
|
}
|
|
if (!notify_explicit) return 1;
|
|
if (!AppendWire(options, options_size, &used, " NOTIFY=", 8U))
|
|
return 0;
|
|
if (notify == 0U)
|
|
return AppendWire(options, options_size, &used, "NEVER", 5U);
|
|
if (notify & SMTP_NOTIFY_SUCCESS) {
|
|
if (!AppendWire(options, options_size, &used, "SUCCESS", 7U))
|
|
return 0;
|
|
started = 1;
|
|
}
|
|
if (notify & SMTP_NOTIFY_FAILURE) {
|
|
if (started &&
|
|
!AppendWire(options, options_size, &used, ",", 1U))
|
|
return 0;
|
|
if (!AppendWire(options, options_size, &used, "FAILURE", 7U))
|
|
return 0;
|
|
started = 1;
|
|
}
|
|
if (notify & SMTP_NOTIFY_DELAY) {
|
|
if (started &&
|
|
!AppendWire(options, options_size, &used, ",", 1U))
|
|
return 0;
|
|
if (!AppendWire(options, options_size, &used, "DELAY", 5U))
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
SMTPDeliveryResultFromReply(unsigned int reply_code,
|
|
const char *reply_text)
|
|
{
|
|
const char *enhanced = reply_text;
|
|
|
|
if (reply_code / 100U == 2U) return DELIVER_SUCCESS;
|
|
if (reply_code / 100U == 4U) return DELIVER_TRY_LATER;
|
|
if (reply_code / 100U != 5U) return DELIVER_TRY_LATER;
|
|
if (enhanced == NULL) return DELIVER_FAILURE;
|
|
if (isdigit((unsigned char)enhanced[0]) &&
|
|
isdigit((unsigned char)enhanced[1]) &&
|
|
isdigit((unsigned char)enhanced[2]) &&
|
|
(enhanced[3] == ' ' || enhanced[3] == '-'))
|
|
enhanced += 4;
|
|
while (*enhanced == ' ' || *enhanced == '\t') enhanced++;
|
|
if (enhanced[0] != '5' || enhanced[1] != '.' ||
|
|
!isdigit((unsigned char)enhanced[2]) || enhanced[3] != '.' ||
|
|
!isdigit((unsigned char)enhanced[4]))
|
|
return DELIVER_FAILURE;
|
|
if (enhanced[2] == '1' && enhanced[4] == '1')
|
|
return DELIVER_USER_UNKNOWN;
|
|
if (enhanced[2] == '1' && enhanced[4] == '3')
|
|
return DELIVER_BOGUS_NAME;
|
|
if (enhanced[2] == '2' && enhanced[4] == '2')
|
|
return DELIVER_QUOTA_EXCEEDED;
|
|
if (enhanced[2] == '4' && enhanced[4] == '1')
|
|
return DELIVER_REFUSED;
|
|
if (enhanced[2] == '4' && enhanced[4] == '4')
|
|
return DELIVER_HOST_UNKNOWN;
|
|
if (enhanced[2] == '4' && enhanced[4] == '6')
|
|
return DELIVER_HOPCOUNT_EXCEEDED;
|
|
if (enhanced[2] == '4' && enhanced[4] == '7')
|
|
return DELIVER_TIMEOUT;
|
|
if (enhanced[2] == '7') return DELIVER_BLOCKED;
|
|
return DELIVER_FAILURE;
|
|
}
|
|
|
|
static int
|
|
OrcptValid(const char *value, size_t length, int smtp_utf8)
|
|
{
|
|
const char *separator = memchr(value, ';', length);
|
|
size_t type_length;
|
|
size_t address_length;
|
|
size_t i;
|
|
|
|
if (!separator || separator == value) return 0;
|
|
type_length = (size_t)(separator - value);
|
|
address_length = length - type_length - 1U;
|
|
if (!address_length || type_length > 64U) return 0;
|
|
for (i = 0; i < type_length; i++) {
|
|
unsigned char current = (unsigned char)value[i];
|
|
if (!isalnum(current) && current != '-') return 0;
|
|
}
|
|
if (TokenEquals(value, type_length, "UTF-8"))
|
|
return Utf8OrcptAddressValid(separator + 1, address_length,
|
|
smtp_utf8);
|
|
return XtextValid(separator + 1, address_length, SMTP_ORCPT_MAX);
|
|
}
|
|
|
|
SMTPParameterResult
|
|
SMTPParseRcptParameters(const char *parameters, int smtp_utf8,
|
|
SMTPRcptParameters *result)
|
|
{
|
|
const char *cursor = parameters;
|
|
const char *token;
|
|
const char *value;
|
|
size_t length;
|
|
size_t value_length;
|
|
|
|
if (!result) return SMTP_PARAMETER_SYNTAX;
|
|
memset(result, 0, sizeof(*result));
|
|
while (NextParameter(&cursor, &token, &length)) {
|
|
if (TokenPrefix(token, length, "NOTIFY=", &value, &value_length)) {
|
|
if (result->notify_supplied ||
|
|
!ParseNotify(value, value_length, &result->notify))
|
|
return SMTP_PARAMETER_SYNTAX;
|
|
result->notify_supplied = 1;
|
|
} else if (TokenPrefix(token, length, "ORCPT=", &value,
|
|
&value_length)) {
|
|
if (result->orcpt_supplied || value_length > SMTP_ORCPT_MAX ||
|
|
!OrcptValid(value, value_length, smtp_utf8))
|
|
return SMTP_PARAMETER_SYNTAX;
|
|
memcpy(result->orcpt, value, value_length);
|
|
result->orcpt[value_length] = '\0';
|
|
result->orcpt_supplied = 1;
|
|
} else {
|
|
return SMTP_PARAMETER_UNSUPPORTED;
|
|
}
|
|
}
|
|
return SMTP_PARAMETER_OK;
|
|
}
|
|
|
|
int
|
|
SMTPParseReplyLine(const char *line, size_t line_length, unsigned int *code,
|
|
int *continuation, const char **text)
|
|
{
|
|
unsigned int parsed;
|
|
|
|
if (code) *code = 0U;
|
|
if (continuation) *continuation = 0;
|
|
if (text) *text = NULL;
|
|
if (!line || line_length < 3U || strlen(line) != line_length ||
|
|
!isdigit((unsigned char)line[0]) ||
|
|
!isdigit((unsigned char)line[1]) ||
|
|
!isdigit((unsigned char)line[2])) return 0;
|
|
parsed = (unsigned int)(line[0] - '0') * 100U +
|
|
(unsigned int)(line[1] - '0') * 10U +
|
|
(unsigned int)(line[2] - '0');
|
|
if (parsed < 200U || parsed > 599U) return 0;
|
|
if (line_length > 3U && line[3] != ' ' && line[3] != '-') return 0;
|
|
if (code) *code = parsed;
|
|
if (continuation) *continuation = line_length > 3U && line[3] == '-';
|
|
if (text) *text = line + (line_length > 3U ? 4U : 3U);
|
|
return 1;
|
|
}
|