diff options
Diffstat (limited to 'headers.c')
-rw-r--r-- | headers.c | 906 |
1 files changed, 906 insertions, 0 deletions
diff --git a/headers.c b/headers.c new file mode 100644 index 0000000..abf3bf5 --- /dev/null +++ b/headers.c @@ -0,0 +1,906 @@ +/* + * This file is part of libESMTP, a library for submission of RFC 2822 + * formatted electronic mail messages using the SMTP protocol described + * in RFC 2821. + * + * Copyright (C) 2001,2002 Brian Stafford <brian@stafford.uklinux.net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <assert.h> + +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <time.h> +#include <errno.h> + +#include <missing.h> + +#include "libesmtp-private.h" +#include "headers.h" +#include "htable.h" +#include "rfc2822date.h" +#include "api.h" + +struct rfc2822_header + { + struct rfc2822_header *next; + struct header_info *info; /* Info for setting and printing */ + char *header; /* Header name */ + void *value; /* Header value */ + }; + +typedef int (*hdrset_t) (struct rfc2822_header *, va_list); +typedef void (*hdrprint_t) (smtp_message_t, struct rfc2822_header *); +typedef void (*hdrdestroy_t) (struct rfc2822_header *); + +struct header_actions + { + const char *name; /* Header for which the action is specified */ + unsigned int flags; /* Header flags */ + hdrset_t set; /* Function to set header value from API */ + hdrprint_t print; /* Function to print the header value */ + hdrdestroy_t destroy; /* Function to destroy the header value */ + }; + +struct header_info + { + const struct header_actions *action; + struct rfc2822_header *hdr; /* Pointer to most recently defined header */ + unsigned int seen : 1; /* Header has been seen in the message */ + unsigned int override : 1; /* LibESMTP is overriding the message */ + unsigned int prohibit : 1; /* Header may not appear in the message */ + }; + +#define NELT(x) ((int) (sizeof x / sizeof x[0])) + +#define OPTIONAL 0 +#define SHOULD 1 +#define REQUIRE 2 +#define PROHIBIT 4 +#define PRESERVE 8 +#define LISTVALUE 16 +#define MULTIPLE 32 + +static struct rfc2822_header *create_header (smtp_message_t message, + const char *header, + struct header_info *info); +void destroy_string (struct rfc2822_header *header); +void destroy_mbox_list (struct rfc2822_header *header); +struct header_info *find_header (smtp_message_t message, + const char *name, int len); +struct header_info *insert_header (smtp_message_t message, const char *name); + +/* RFC 2822 headers processing */ + +/**************************************************************************** + * Functions for setting and printing header values + ****************************************************************************/ + +static int +set_string (struct rfc2822_header *header, va_list alist) +{ + const char *value; + + assert (header != NULL); + + if (header->value != NULL) /* Already set */ + return 0; + + value = va_arg (alist, const char *); + if (value == NULL) + return 0; + header->value = strdup (value); + return header->value != NULL; +} + +static int +set_string_null (struct rfc2822_header *header, va_list alist) +{ + const char *value; + + assert (header != NULL); + + if (header->value != NULL) /* Already set */ + return 0; + + value = va_arg (alist, const char *); + if (value == NULL) + return 1; + header->value = strdup (value); + return header->value != NULL; +} + +/* Print header-name ": " header-value "\r\n" */ +static void +print_string (smtp_message_t message, struct rfc2822_header *header) +{ + assert (message != NULL && header != NULL); + + /* TODO: implement line folding at white spaces */ + vconcatenate (&message->hdr_buffer, header->header, ": ", + (header->value != NULL) ? header->value : "", "\r\n", NULL); +} + +void +destroy_string (struct rfc2822_header *header) +{ + assert (header != NULL); + + if (header->value != NULL) + free (header->value); +} + +/* Print header-name ": <" message-id ">\r\n" */ +static void +print_message_id (smtp_message_t message, struct rfc2822_header *header) +{ + const char *message_id; + char buf[64]; +#ifdef HAVE_GETTIMEOFDAY + struct timeval tv; +#endif + + assert (message != NULL && header != NULL); + + message_id = header->value; + if (message_id == NULL) + { +#ifdef HAVE_GETTIMEOFDAY + if (gettimeofday (&tv, NULL) == -1) /* This shouldn't fail ... */ + snprintf (buf, sizeof buf, "%ld.%ld.%d@%s", tv.tv_sec, tv.tv_usec, + getpid (), message->session->localhost); + else /* ... but if it does fall back to using time() */ +#endif + snprintf (buf, sizeof buf, "%ld.%d@%s", time (NULL), + getpid (), message->session->localhost); + message_id = buf; + } + /* TODO: implement line folding at white spaces */ + vconcatenate (&message->hdr_buffer, + header->header, ": <", message_id, ">\r\n", + NULL); +} + +/****/ + +static int +set_date (struct rfc2822_header *header, va_list alist) +{ + const time_t *value; + + assert (header != NULL); + + if ((time_t) header->value != (time_t) 0) /* Already set */ + return 0; + + value = va_arg (alist, const time_t *); + header->value = (void *) *value; + return 1; +} + +/* Print header-name ": " formatted-date "\r\n" */ +static void +print_date (smtp_message_t message, struct rfc2822_header *header) +{ + char buf[64]; + time_t when; + + assert (message != NULL && header != NULL); + + when = (time_t) header->value; + if (when == (time_t) 0) + time (&when); + vconcatenate (&message->hdr_buffer, header->header, ": ", + rfc2822date (buf, sizeof buf, &when), "\r\n", NULL); +} + +/****/ + +struct mbox + { + struct mbox *next; + char *mailbox; + char *phrase; + }; + +void +destroy_mbox_list (struct rfc2822_header *header) +{ + struct mbox *mbox, *next; + + assert (header != NULL); + + mbox = header->value; + while (mbox != NULL) + { + next = mbox->next; + if (mbox->phrase != NULL) + free ((void *) mbox->phrase); + if (mbox->mailbox != NULL) + free ((void *) mbox->mailbox); + free (mbox); + mbox = next; + } +} + +static int +set_from (struct rfc2822_header *header, va_list alist) +{ + struct mbox *mbox; + const char *mailbox; + const char *phrase; + + assert (header != NULL); + + phrase = va_arg (alist, const char *); + mailbox = va_arg (alist, const char *); + + /* Allow this to succeed as a special case. Effectively requesting + default action in print_from(). Fails if explicit values have + already been set. */ + if (phrase == NULL && mailbox == NULL) + return header->value == NULL; + + mbox = malloc (sizeof (struct mbox)); + if (mbox == NULL) + return 0; + mbox->phrase = (phrase != NULL) ? strdup (phrase) : NULL; + mbox->mailbox = strdup (mailbox); + + mbox->next = header->value; + header->value = mbox; + return 1; +} + +/* Print header-name ": " mailbox "\r\n" + or header-name ": \"" phrase "\" <" mailbox ">\r\n" */ +static void +print_from (smtp_message_t message, struct rfc2822_header *header) +{ + struct mbox *mbox; + const char *mailbox; + + assert (message != NULL && header != NULL); + + vconcatenate (&message->hdr_buffer, header->header, ": ", NULL); + /* TODO: implement line folding at white spaces */ + if (header->value == NULL) + { + mailbox = message->reverse_path_mailbox; + vconcatenate (&message->hdr_buffer, + (mailbox != NULL && *mailbox != '\0') ? mailbox : "<>", + "\r\n", NULL); + } + else + for (mbox = header->value; mbox != NULL; mbox = mbox->next) + { + mailbox = mbox->mailbox; + if (mbox->phrase == NULL) + vconcatenate (&message->hdr_buffer, + (mailbox != NULL && *mailbox != '\0') ? mailbox : "<>", + NULL); + else + vconcatenate (&message->hdr_buffer, "\"", mbox->phrase, "\"" + " <", (mailbox != NULL) ? mailbox : "", ">", NULL); + vconcatenate (&message->hdr_buffer, + (mbox->next != NULL) ? ",\r\n " : "\r\n", NULL); + } +} + +/* Same arguments and syntax as from: except that only one value is + allowed. */ +static int +set_sender (struct rfc2822_header *header, va_list alist) +{ + struct mbox *mbox; + const char *mailbox; + const char *phrase; + + assert (header != NULL); + + if (header->value != NULL) + return 0; + + phrase = va_arg (alist, const char *); + mailbox = va_arg (alist, const char *); + if (phrase == NULL && mailbox == NULL) + return 0; + + mbox = malloc (sizeof (struct mbox)); + if (mbox == NULL) + return 0; + mbox->phrase = (phrase != NULL) ? strdup (phrase) : NULL; + mbox->mailbox = strdup (mailbox); + mbox->next = NULL; + + mbox->next = header->value; + return 1; +} + +/* TODO: do nothing if the mailbox is NULL. Check this doesn't fool + the protocol engine into thinking it has seen end of file. */ +/* Print header-name ": " mailbox "\r\n" + or header-name ": \"" phrase "\" <" mailbox ">\r\n" + */ +static void +print_sender (smtp_message_t message, struct rfc2822_header *header) +{ + struct mbox *mbox; + const char *mailbox; + + assert (message != NULL && header != NULL); + + vconcatenate (&message->hdr_buffer, header->header, ": ", NULL); + mbox = header->value; + mailbox = mbox->mailbox; + if (mbox->phrase == NULL) + vconcatenate (&message->hdr_buffer, + (mailbox != NULL && *mailbox != '\0') ? mailbox : "<>", + "\r\n", NULL); + else + vconcatenate (&message->hdr_buffer, "\"", mbox->phrase, "\"" + " <", (mailbox != NULL) ? mailbox : "", ">\r\n", NULL); +} + +static int +set_to (struct rfc2822_header *header, va_list alist) +{ + struct mbox *mbox; + const char *mailbox; + const char *phrase; + + assert (header != NULL); + + phrase = va_arg (alist, const char *); + mailbox = va_arg (alist, const char *); + if (phrase == NULL && mailbox == NULL) + mbox = NULL; + else + { + mbox = malloc (sizeof (struct mbox)); + if (mbox == NULL) + return 0; + mbox->phrase = (phrase != NULL) ? strdup (phrase) : NULL; + mbox->mailbox = strdup (mailbox); + + mbox->next = header->value; + } + header->value = mbox; + return 1; +} + +static int +set_cc (struct rfc2822_header *header, va_list alist) +{ + struct mbox *mbox; + const char *mailbox; + const char *phrase; + + assert (header != NULL); + + phrase = va_arg (alist, const char *); + mailbox = va_arg (alist, const char *); + if (mailbox == NULL) + return 0; + mbox = malloc (sizeof (struct mbox)); + if (mbox == NULL) + return 0; + mbox->phrase = (phrase != NULL) ? strdup (phrase) : NULL; + mbox->mailbox = strdup (mailbox); + + mbox->next = header->value; + header->value = mbox; + return 1; +} + +/* Print header-name ": " mailbox "\r\n" + or header-name ": \"" phrase "\" <" mailbox ">\r\n" + ad nauseum. */ +static void +print_cc (smtp_message_t message, struct rfc2822_header *header) +{ + struct mbox *mbox; + + assert (message != NULL && header != NULL); + + vconcatenate (&message->hdr_buffer, header->header, ": ", NULL); + for (mbox = header->value; mbox != NULL; mbox = mbox->next) + { + if (mbox->phrase == NULL) + vconcatenate (&message->hdr_buffer, mbox->mailbox, NULL); + else + vconcatenate (&message->hdr_buffer, + "\"", mbox->phrase, "\" <", mbox->mailbox, ">", + NULL); + vconcatenate (&message->hdr_buffer, + (mbox->next != NULL) ? ",\r\n " : "\r\n", NULL); + } +} + +/* As above but generate a default value from the recipient list. + */ +static void +print_to (smtp_message_t message, struct rfc2822_header *header) +{ + smtp_recipient_t recipient; + + assert (header != NULL); + + if (header->value != NULL) + { + print_cc (message, header); + return; + } + + /* TODO: implement line folding at white spaces */ + vconcatenate (&message->hdr_buffer, header->header, ": ", NULL); + for (recipient = message->recipients; + recipient != NULL; + recipient = recipient->next) + vconcatenate (&message->hdr_buffer, recipient->mailbox, + (recipient->next != NULL) ? ",\r\n " : "\r\n", + NULL); +} + + +/* Header actions placed here to avoid the need for many akward forward + declarations for set_xxx/print_xxx. */ + +static const struct header_actions header_actions[] = + { + /* This is the default header info for a simple string value. + */ + { NULL, OPTIONAL, + set_string, print_string, destroy_string}, + /* A number of headers should be present in every message + */ + { "Date", REQUIRE, + set_date, print_date, NULL, }, + { "From", REQUIRE, + set_from, print_from, destroy_mbox_list, }, + /* Certain headers are added when a message is delivered and + should not be present in a message being posted or which + is in transit. If present in the message they will be stripped + and if specified by the API, the relevant APIs will fail. */ + { "Return-Path", PROHIBIT, NULL, NULL, NULL, }, + /* RFC 2298 - Delivering MTA may add the Original-Recipient: header + using DSN ORCPT parameter and may discard + Original-Recipient: headers present in the message. + No point in sending it then. */ + { "Original-Recipient", PROHIBIT, NULL, NULL, NULL, }, + /* MIME-*: and Content-*: are MIME headers and must not be generated + or processed by libESMTP. Similarly, Resent-*: and Received: must + be retained unaltered. */ + { "Content-", PRESERVE, NULL, NULL, NULL, }, + { "MIME-", PRESERVE, NULL, NULL, NULL, }, + { "Resent-", PRESERVE, NULL, NULL, NULL, }, + { "Received", PRESERVE, NULL, NULL, NULL, }, + /* Headers which are optional but which are recommended to be + present. Default action is to provide a default unless the + application explicitly requests not to. */ + { "Message-Id", SHOULD, + set_string_null,print_message_id, destroy_string, }, + /* Remaining headers are known to libESMTP to simplify handling them + for the application. All other headers are reaated as simple + string values. */ + { "Sender", OPTIONAL, + set_sender, print_sender, destroy_mbox_list, }, + { "To", OPTIONAL, + set_to, print_to, destroy_mbox_list, }, + { "Cc", OPTIONAL, + set_cc, print_cc, destroy_mbox_list, }, + { "Bcc", OPTIONAL, + set_cc, print_cc, destroy_mbox_list, }, + { "Reply-To", OPTIONAL, + set_cc, print_cc, destroy_mbox_list, }, + /* RFC 2298 - MDN request. Syntax is the same as the From: header and + default when set to NULL is the same as From: */ + { "Disposition-Notification-To", OPTIONAL, + set_from, print_from, destroy_mbox_list, }, + /* TODO: + In-Reply-To: *(phrase / msgid) + References: *(phrase / msgid) + Keywords: #phrase + + Handle Resent- versions of + To Cc Bcc Message-ID Date Reply-To From Sender + */ + }; + +static int +init_header_table (smtp_message_t message) +{ + int i; + struct header_info *hi; + + assert (message != NULL); + + if (message->hdr_action != NULL) + return -1; + + message->hdr_action = h_create (); + if (message->hdr_action == NULL) + return 0; + for (i = 0; i < NELT (header_actions); i++) + if (header_actions[i].name != NULL) + { + hi = h_insert (message->hdr_action, header_actions[i].name, -1, + sizeof (struct header_info)); + if (hi == NULL) + return 0; + hi->action = &header_actions[i]; + + /* REQUIREd headers must be present in the message. SHOULD + means the header is optional but its presence is recommended. + Create a NULL valued header. This will either be set later + with the API, or the print_xxx function will handle the NULL + value as a special case, e.g, the To: header is generated + from the recipient_t list. */ + if (hi->action->flags & (REQUIRE | SHOULD)) + { + if (create_header (message, header_actions[i].name, hi) == NULL) + return 0; + } + } + return 1; +} + +void +destroy_header_table (smtp_message_t message) +{ + struct rfc2822_header *header, *next; + + assert (message != NULL); + + /* Take out the linked list */ + for (header = message->headers; header!= NULL; header = next) + { + next = header->next; + if (header->info->action->destroy != NULL) + (*header->info->action->destroy) (header); + free (header->header); + free (header); + } + + /* Take out the hash table */ + if (message->hdr_action != NULL) + { + h_destroy (message->hdr_action, NULL, NULL); + message->hdr_action = NULL; + } + + message->headers = message->end_headers = NULL; +} + +struct header_info * +find_header (smtp_message_t message, const char *name, int len) +{ + struct header_info *info; + const char *p; + + assert (message != NULL && name != NULL); + + if (len < 0) + len = strlen (name); + if (len == 0) + return NULL; + info = h_search (message->hdr_action, name, len); + if (info == NULL && (p = memchr (name, '-', len)) != NULL) + info = h_search (message->hdr_action, name, p - name + 1); + return info; +} + +struct header_info * +insert_header (smtp_message_t message, const char *name) +{ + struct header_info *info; + + assert (message != NULL && name != NULL); + + info = h_insert (message->hdr_action, name, -1, sizeof (struct header_info)); + if (info == NULL) + return NULL; + info->action = &header_actions[0]; + return info; +} + +static struct rfc2822_header * +create_header (smtp_message_t message, const char *header, + struct header_info *info) +{ + struct rfc2822_header *hdr; + + assert (message != NULL && header != NULL && info != NULL); + + if ((hdr = malloc (sizeof (struct rfc2822_header))) == NULL) + return NULL; + + memset (hdr, 0, sizeof (struct rfc2822_header)); + hdr->header = strdup (header); + hdr->info = info; + info->hdr = hdr; + APPEND_LIST (message->headers, message->end_headers, hdr); + return hdr; +} + +/**************************************************************************** + * Header processing + ****************************************************************************/ + +/* Called just before copying the messge from the application. + Resets the seen flag for headers libESMTP is interested in */ + +static void +reset_headercb (const char *name __attribute__ ((unused)), + void *data, void *arg __attribute__ ((unused))) +{ + struct header_info *info = data; + + assert (info != NULL); + + info->seen = 0; +} + +int +reset_header_table (smtp_message_t message) +{ + int status; + + assert (message != NULL); + + if ((status = init_header_table (message)) < 0) + h_enumerate (message->hdr_action, reset_headercb, NULL); + return status; +} + +/* This is called to process headers present in the application supplied + message. */ +const char * +process_header (smtp_message_t message, const char *header, int *len) +{ + const char *p; + struct header_info *info; + const struct header_actions *action; + hdrprint_t print; + + assert (message != NULL && header != NULL && len != NULL); + + if (*len > 0 + && (p = memchr (header, ':', *len)) != NULL + && (info = find_header (message, header, p - header)) != NULL) + { + if ((action = info->action) != NULL) + { + /* RFC 2822 states that headers may only appear once in a + message with the exception of a few special headers. + This restriction is enforced here. */ + if (info->seen && !(action->flags & (MULTIPLE | PRESERVE))) + header = NULL; + if (info->prohibit || (action->flags & PROHIBIT)) + header = NULL; + + /* When libESMTP is overriding headers in the message with + ones supplied in the API, the substitution is done here + to preserve the original ordering of the headers. */ + if (header != NULL && info->override) + { + if ((print = action->print) == NULL) + print = print_string; + cat_reset (&message->hdr_buffer, 0); + (*print) (message, info->hdr); + header = cat_buffer (&message->hdr_buffer, len); + } + } + else if (info->seen) + header = NULL; + info->seen = 1; + } + return header; +} + +/* This is called to supply headers not present in the application supplied + message. */ +const char * +missing_header (smtp_message_t message, int *len) +{ + struct header_info *info; + hdrprint_t print; + + assert (message != NULL && len != NULL); + + /* Move on to the next header */ + if (message->current_header == NULL) + message->current_header = message->headers; + else + message->current_header = message->current_header->next; + + /* Look for the next header that is actually required */ + print = NULL; + while (message->current_header != NULL) + { + info = message->current_header->info; + if (info == NULL) /* shouldn't happen */ + break; + if (!info->seen) + { + if (info->action != NULL) + print = info->action->print; + break; + } + message->current_header = message->current_header->next; + } + if (message->current_header == NULL) + { + /* Free the buffer created by concatenate() and return NULL to + mark the end of the headers */ + cat_free (&message->hdr_buffer); + return NULL; + } + + if (print == NULL) + print = print_string; + + cat_reset (&message->hdr_buffer, 0); + (*print) (message, message->current_header); + return cat_buffer (&message->hdr_buffer, len); +} + +/**************************************************************************** + * Header API + ****************************************************************************/ + +int +smtp_set_header (smtp_message_t message, const char *header, ...) +{ + va_list alist; + struct rfc2822_header *hdr; + struct header_info *info; + hdrset_t set; + + SMTPAPI_CHECK_ARGS (message != NULL && header != NULL, 0); + + if (!init_header_table (message)) + { + set_errno (ENOMEM); + return 0; + } + + info = find_header (message, header, -1); + if (info == NULL && (info = insert_header (message, header)) == NULL) + { + set_errno (ENOMEM); + return 0; + } + + /* Cannot specify a value for headers that must pass unchanged (MIME) + or which may not appear in a posted message (Return-Path:). */ + if (info->prohibit || (info->action->flags & (PROHIBIT | PRESERVE))) + { + set_error (SMTP_ERR_INVAL); + return 0; + } + + set = info->action->set; + if (set == NULL) + { + set_error (SMTP_ERR_INVAL); + return 0; + } + + if (info->hdr == NULL) + hdr = create_header (message, header, info); + else if (info->hdr->value == NULL) + hdr = info->hdr; + else + { + /* Header has a previous value. If multiple headers are permitted, + create a new value. If the header has a list value, the value + is appended to the iost. If neither condition applies, this + is an error. */ + if (info->action->flags & MULTIPLE) + hdr = create_header (message, header, info); + else if (info->action->flags & LISTVALUE) + hdr = info->hdr; + else + { + set_error (SMTP_ERR_INVAL); + return 0; + } + } + + /* Set its value */ + va_start (alist, header); + (*set) (hdr, alist); + va_end (alist); + + return 1; +} + +int +smtp_set_header_option (smtp_message_t message, const char *header, + enum header_option option, ...) +{ + va_list alist; + struct header_info *info; + + SMTPAPI_CHECK_ARGS (message != NULL && header != NULL, 0); + + if (!init_header_table (message)) + { + set_errno (ENOMEM); + return 0; + } + + info = find_header (message, header, -1); + if (info == NULL && (info = insert_header (message, header)) == NULL) + { + set_errno (ENOMEM); + return 0; + } + + /* Don't permit options to be set on headers that must pass intact or + which are prohibited. */ + if (info->action->flags & (PROHIBIT | PRESERVE)) + { + set_error (SMTP_ERR_INVAL); + return 0; + } + + /* There is an odd quirk when setting options. Setting an option for + the OPTIONAL headers known to libESMTP causes default values to be + generated automatically when not found in the message, so long as + there is no other reason to prevent them appearing in the message! */ + + /* Don't allow the user to set override on prohibited headers. */ + if (option == Hdr_OVERRIDE && !info->prohibit) + { + va_start (alist, option); + info->override = !!va_arg (alist, int); + va_end (alist); + return 1; + } + /* Don't allow the user to prohibit required headers. */ + if (option == Hdr_PROHIBIT && !(info->action->flags & REQUIRE)) + { + va_start (alist, option); + info->prohibit = !!va_arg (alist, int); + va_end (alist); + return 1; + } + + set_error (SMTP_ERR_INVAL); + return 0; +} + +int +smtp_set_resent_headers (smtp_message_t message, int onoff) +{ + SMTPAPI_CHECK_ARGS (message != NULL, 0); + + /* TODO: place holder, implement real functionality here. + For now, succeed if the onoff argument is zero. */ + SMTPAPI_CHECK_ARGS (onoff == 0, 0); + + return 1; +} |