/*
Copyright (C) 1999-2004 IC & S dbmail@ic-s.nl
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later
version.
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, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/* $Id: main.c 1943 2005-12-22 12:30:47Z paul $
*
* main file for dbmail-smtp */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "dbmail.h"
#include "main.h"
#include "pipe.h"
#include "list.h"
#include "auth.h"
#include "header.h"
#include "dsn.h"
#include <string.h>
#include <stdlib.h>
#include "dm_getopt.h"
/* For exit codes */
#include <sysexits.h>
#define MESSAGEIDSIZE 100
#define NORMAL_DELIVERY 1
#define SPECIAL_DELIVERY 2
#define INDEX_DELIVERY_MODE 1
/* value for the size of the blocks to read from the input stream.
this can be any value (so 8192 bytes is just a raw guess.. */
#define READ_CHUNK_SIZE 8192
/* syslog */
#define PNAME "dbmail/smtp"
struct list returnpath; /* returnpath (should aways be just 1 hop) */
struct list mimelist; /* raw unformatted mimefields and values */
struct list dsnusers; /* list of deliver_to_user_t structs */
struct list users; /* list of email addresses in message */
struct element *tmp;
char *configFile = DEFAULT_CONFIG_FILE;
extern db_param_t _db_params; /* set up database login data */
deliver_to_user_t dsnuser;
//char *header = NULL;
char *deliver_to_header = NULL;
char *deliver_to_mailbox = NULL;
u64_t headersize, headerrfcsize;
/* loudness and assumptions */
int yes_to_all = 0;
int no_to_all = 0;
int verbose = 0;
/* Don't be helpful. */
int quiet = 0;
/* Don't print errors. */
int reallyquiet = 0;
/**
* read the whole message from the instream
* \param[in] instream input stream (stdin)
* \param[out] whole_message pointer to string which will hold the whole message
* \param[out] whole_message_size will hold size of message
* \return
* - -1 on error
* - 0 on success
*/
static int read_whole_message_pipe(FILE *instream, char **whole_message,
u64_t *whole_message_size)
{
char *tmpmessage = NULL;
size_t read_len = 0;
size_t totalmem = 0;
size_t current_pos = 0;
char read_buffer[READ_CHUNK_SIZE];
int error = 0;
while(!feof(instream) && !ferror(instream)) {
read_len = fread(read_buffer, sizeof(char), READ_CHUNK_SIZE,
instream);
if (read_len < READ_CHUNK_SIZE && ferror(instream)) {
error = 1;
break;
}
if (!(tmpmessage = dm_realloc(tmpmessage, totalmem + read_len))) {
error = 1;
break;
}
if (!(memcpy((void *) &tmpmessage[current_pos],
(void *) read_buffer,
read_len))) {
error = 1;
break;
}
totalmem += read_len;
current_pos += read_len;
}
if (ferror(instream)) {
trace(TRACE_ERROR, "%s,%s: error reading from instream",
__FILE__, __func__);
error = 1;
}
/* add '\0' to tmpmessage, because fread() will not do that for us.*/
totalmem++;
if (!(tmpmessage = dm_realloc(tmpmessage, totalmem)))
error = 1;
else
tmpmessage[current_pos] = '\0';
if (error) {
trace(TRACE_ERROR, "%s,%s: error reading message",
__FILE__, __func__);
dm_free(tmpmessage);
tmpmessage=NULL;
return -1;
}
*whole_message = tmpmessage;
*whole_message_size = totalmem;
return 0;
}
int do_showhelp(void) {
printf("*** dbmail-smtp ***\n");
printf("Use this program to deliver mail from your MTA or on the command line.\n");
printf("See the man page for more info. Summary:\n\n");
printf(" -t [headerfield] for normal deliveries (default is \"deliver-to\")\n");
printf(" -d [addresses] for delivery without using scanner\n");
printf(" -u [usernames] for direct delivery to users\n");
printf(" -m \"mailbox\" for delivery to a specific mailbox\n");
printf(" -r return path for address of bounces and other error reports\n");
printf("\nCommon options for all DBMail utilities:\n");
printf(" -f file specify an alternative config file\n");
printf(" -q quietly skip interactive prompts\n"
" use twice to suppress error messages\n");
printf(" -n show the intended action but do not perform it, no to all\n");
printf(" -y perform all proposed actions, as though yes to all\n");
printf(" -v verbose details\n");
printf(" -V show the version\n");
printf(" -h show this help message\n");
return 0;
}
int main(int argc, char *argv[])
{
int exitcode = 0;
int c, c_prev = 0, usage_error = 0;
u64_t dummyidx = 0, dummysize = 0;
char *whole_message = NULL;
u64_t whole_message_size;
const char *body;
u64_t body_size;
u64_t body_rfcsize;
char *header = NULL;
openlog(PNAME, LOG_PID, LOG_MAIL);
list_init(&users);
list_init(&dsnusers);
list_init(&mimelist);
list_init(&returnpath);
/* Check for commandline options.
* The initial '-' means that arguments which are not associated
* with an immediately preceding option are return with option
* value '1'. We will use this to allow for multiple values to
* follow after each of the supported options. */
while ((c = dm_getopt(argc, argv, "-t::m:u:d:r: f:qnyvVh")) != EOF) {
/* Received an n-th value following the last option,
* so recall the last known option to be used in the switch. */
if (c == 1)
c = c_prev;
c_prev = c;
/* Do something with this option. */
switch (c) {
case 't':
trace(TRACE_INFO, "main(): using NORMAL_DELIVERY");
if (dm_optarg) {
if (deliver_to_header) {
printf("Only one header field may be specified.\n");
usage_error = 1;
} else
deliver_to_header = dm_optarg;
} else
deliver_to_header = "deliver-to";
break;
case 'm':
trace(TRACE_INFO, "main(): using SPECIAL_DELIVERY to mailbox");
if (deliver_to_mailbox) {
printf("Only one mailbox may be specified.\n");
usage_error = 1;
} else
deliver_to_mailbox = dm_optarg;
break;
case 'r':
trace(TRACE_INFO, "main(): using RETURN_PATH for bounces");
/* Add argument onto the returnpath list. */
if (list_nodeadd(&returnpath, dm_optarg, strlen(dm_optarg) + 1) == 0) {
trace(TRACE_ERROR,
"main(): list_nodeadd reports out of memory"
" while adding to returnpath");
exitcode = EX_TEMPFAIL;
goto freeall;
}
break;
case 'u':
trace(TRACE_INFO,
"main(): using SPECIAL_DELIVERY to usernames");
dsnuser_init(&dsnuser);
dsnuser.address = dm_strdup(dm_optarg);
/* Add argument onto the users list. */
if (list_nodeadd
(&dsnusers, &dsnuser,
sizeof(deliver_to_user_t)) == 0) {
trace(TRACE_ERROR,
"main(): list_nodeadd reports out of memory"
" while adding usernames");
exitcode = EX_TEMPFAIL;
goto freeall;
}
break;
case 'd':
trace(TRACE_INFO,
"main(): using SPECIAL_DELIVERY to email addresses");
dsnuser_init(&dsnuser);
dsnuser.address = dm_strdup(dm_optarg);
/* Add argument onto the users list. */
if (list_nodeadd(&dsnusers, &dsnuser, sizeof(deliver_to_user_t)) == 0) {
trace(TRACE_ERROR,
"main(): list_nodeadd reports out of memory"
" while adding email addresses");
exitcode = EX_TEMPFAIL;
goto freeall;
}
break;
/* Common command line options. */
case 'f':
if (dm_optarg && strlen(dm_optarg) > 0)
configFile = dm_optarg;
else {
fprintf(stderr,
"dbmail-smtp: -f requires a filename\n\n" );
return 1;
}
break;
case 'v':
verbose = 1;
break;
case 'V':
/* We must return non-zero in case someone put -V
* into the mail server config and thus may lose mail. */
printf("DBMail: dbmail-smtp\n"
"Version: %s\n"
"$Revision: 1943 $\n"
"Copyright: %s\n", VERSION, COPYRIGHT);
return 1;
default:
usage_error = 1;
break;
}
/* At the end of each round of options, check
* to see if there were any errors worth stopping for. */
if (usage_error) {
do_showhelp();
trace(TRACE_DEBUG,
"main(): usage error; setting EX_USAGE and aborting");
exitcode = EX_USAGE;
goto freeall;
}
}
/* ...or if there weren't any command line arguments at all. */
if (argc < 2) {
do_showhelp();
trace(TRACE_DEBUG,
"main(): no arguments; setting EX_USAGE and aborting");
exitcode = EX_USAGE;
goto freeall;
}
/* Read in the config file; do it after getopt
* in case -f config.alt was specified. */
if (ReadConfig("DBMAIL", configFile) == -1 || ReadConfig("SMTP", configFile) == -1) {
trace(TRACE_ERROR, "main(): error reading alternate config file [%s]", configFile);
exitcode = EX_TEMPFAIL;
goto freeall;
}
SetTraceLevel("SMTP");
GetDBParams(&_db_params);
if (db_connect() != 0) {
trace(TRACE_ERROR, "main(): database connection failed");
exitcode = EX_TEMPFAIL;
goto freeall;
}
if (auth_connect() != 0) {
trace(TRACE_ERROR,
"main(): authentication connection failed");
exitcode = EX_TEMPFAIL;
goto freeall;
}
/* reset trace-level after auth_connect */
SetTraceLevel("SMTP");
if (db_check_version() != 0) {
exitcode = EX_TEMPFAIL;
goto freeall;
}
/* read the whole message */
if (read_whole_message_pipe(stdin, &whole_message,
&whole_message_size) < 0) {
trace(TRACE_ERROR, "%s,%s: read_whole_message_pipe() failed",
__FILE__, __func__);
exitcode = EX_TEMPFAIL;
goto freeall;
}
/* get pointer to header and to body */
if (split_message(whole_message, whole_message_size - 1,
&header, &headersize, &headerrfcsize,
&body, &body_size, &body_rfcsize) < 0) {
trace(TRACE_ERROR, "%s,%s splitmessage failed",
__FILE__, __func__);
dm_free(whole_message);
whole_message=NULL;
exitcode = EX_TEMPFAIL;
goto freeall;
}
if (headersize > READ_BLOCK_SIZE) {
trace(TRACE_ERROR,
"%s,%s: failed to read header because header is "
"too big (bigger than READ_BLOCK_SIZE (%llu))",
__FILE__, __func__, (u64_t) READ_BLOCK_SIZE);
exitcode = EX_DATAERR;
goto freeall;
}
/* parse the list and scan for field and content */
if (mime_readheader(header, &dummyidx, &mimelist, &dummysize) < 0) {
trace(TRACE_ERROR, "%s,%s: mime_readheader failed",
__FILE__, __func__);
exitcode = EX_TEMPFAIL;
goto freeall;
}
/* parse returnpath from header */
if (returnpath.total_nodes == 0)
mail_adr_list("Return-Path", &returnpath, &mimelist);
if (returnpath.total_nodes == 0)
mail_adr_list("From", &returnpath, &mimelist);
if (returnpath.total_nodes == 0)
trace(TRACE_DEBUG, "%s,%s: no return path found.",
__FILE__,__func__);
/* If the NORMAL delivery mode has been selected... */
if (deliver_to_header != NULL) {
/* parse for destination addresses */
trace(TRACE_DEBUG, "main(): scanning for [%s]", deliver_to_header);
if (mail_adr_list(deliver_to_header, &users, &mimelist) != 0) {
trace(TRACE_STOP, "%s,%s: no email addresses found (scanned for %s)",
__FILE__,__func__, deliver_to_header);
exitcode = EX_NOUSER;
goto freeall;
}
/* Loop through the users list, moving the entries into the dsnusers list. */
for (tmp = list_getstart(&users); tmp != NULL; tmp = tmp->nextnode) {
if (! (strlen((char *)tmp->data) > 0))
continue;
dsnuser_init(&dsnuser);
dsnuser.address = dm_strdup((char *) tmp->data);
if (! list_nodeadd(&dsnusers, &dsnuser, sizeof(deliver_to_user_t))) {
trace(TRACE_ERROR,"%s,%s: out of memory in list_nodeadd",
__FILE__, __func__);
exitcode = EX_TEMPFAIL;
goto freeall;
}
}
}
/* If the MAILBOX delivery mode has been selected... */
if (deliver_to_mailbox != NULL) {
trace(TRACE_DEBUG, "main(): setting mailbox for all deliveries to [%s]",
deliver_to_mailbox);
/* Loop through the dsnusers list, setting the destination mailbox. */
for (tmp = list_getstart(&dsnusers); tmp != NULL; tmp = tmp->nextnode)
((deliver_to_user_t *)tmp->data)->mailbox = dm_strdup(deliver_to_mailbox);
}
if (dsnuser_resolve_list(&dsnusers) == -1) {
trace(TRACE_ERROR, "main(): dsnuser_resolve_list failed");
/* Most likely a random failure... */
exitcode = EX_TEMPFAIL;
goto freeall;
}
/* inserting messages into the database */
if (insert_messages(header, body, headersize, headerrfcsize,
body_size, body_rfcsize,
&mimelist, &dsnusers, &returnpath) == -1) {
trace(TRACE_ERROR, "main(): insert_messages failed");
/* Most likely a random failure... */
exitcode = EX_TEMPFAIL;
}
freeall: /* Goto's here! */
/* If there wasn't already an EX_TEMPFAIL from insert_messages(),
* then see if one of the status flags was marked with an error. */
if (!exitcode) {
delivery_status_t final_dsn;
/* Get one reasonable error code for everyone. */
final_dsn = dsnuser_worstcase_list(&dsnusers);
switch (final_dsn.class) {
case DSN_CLASS_OK:
exitcode = EX_OK;
break;
case DSN_CLASS_TEMP:
exitcode = EX_TEMPFAIL;
break;
case DSN_CLASS_NONE:
case DSN_CLASS_QUOTA:
case DSN_CLASS_FAIL:
/* If we're over-quota, say that,
* else it's a generic user error. */
if (final_dsn.subject == 2)
exitcode = EX_CANTCREAT;
else
exitcode = EX_NOUSER;
break;
}
}
trace(TRACE_DEBUG, "main(): freeing dsnuser list");
dsnuser_free_list(&dsnusers);
trace(TRACE_DEBUG, "main(): freeing all other lists");
list_freelist(&mimelist.start);
list_freelist(&returnpath.start);
list_freelist(&users.start);
trace(TRACE_DEBUG, "main(): freeing memory blocks");
if (header != NULL)
dm_free(header);
if (whole_message != NULL)
dm_free(whole_message);
trace(TRACE_DEBUG, "main(): they're all free. we're done.");
db_disconnect();
auth_disconnect();
config_free();
trace(TRACE_DEBUG, "main(): exit code is [%d].", exitcode);
return exitcode;
}
syntax highlighted by Code2HTML, v. 0.9.1