/* MasqMail
Copyright (C) 1999-2001 Oliver Kurth
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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netdb.h>
#include <syslog.h>
#include <signal.h>
#include <glib.h>
#include "masqmail.h"
/* mutually exclusive modes. Note that there is neither a 'get' mode
nor a 'queue daemon' mode. These, as well as the distinction beween
the two (non exclusive) daemon (queue and listen) modes are handled
by flags.*/
typedef enum _mta_mode
{
MODE_ACCEPT = 0, /* accept message on stdin */
MODE_DAEMON, /* run as daemon */
MODE_RUNQUEUE, /* single queue run, online or offline */
MODE_GET_DAEMON, /* run as get (retrieve) daemon */
MODE_SMTP, /* accept SMTP on stdin */
MODE_LIST, /* list queue */
MODE_MCMD, /* do queue manipulation */
MODE_VERSION, /* show version */
MODE_BI, /* fake ;-) */
MODE_NONE /* to prevent default MODE_ACCEPT */
}mta_mode;
char *pidfile = NULL;
volatile int sigterm_in_progress = 0;
static
void sigterm_handler(int sig)
{
if(sigterm_in_progress)
raise(sig);
sigterm_in_progress = 1;
if(pidfile){
uid_t uid;
uid = seteuid(0);
if(unlink(pidfile) != 0)
logwrite(LOG_WARNING, "could not delete pid file %s: %s\n",
pidfile, strerror(errno));
seteuid(uid); /* we exit anyway after this, just to be sure */
}
signal(sig, SIG_DFL);
raise(sig);
}
#ifdef ENABLE_IDENT /* so far used for that only */
static
gboolean is_in_netlist(gchar *host, GList *netlist)
{
guint hostip = inet_addr(host);
struct in_addr addr;
addr.s_addr = hostip;
if(addr.s_addr != INADDR_NONE){
GList *node;
foreach(netlist, node){
struct in_addr *net = (struct in_addr *)(node->data);
if((addr.s_addr & net->s_addr) == net->s_addr)
return TRUE;
}
}
return FALSE;
}
#endif
gchar *get_optarg(char *argv[], gint argc, gint *argp, gint *pos)
{
if(argv[*argp][*pos])
return &(argv[*argp][*pos]);
else{
if(*argp+1 < argc){
if(argv[(*argp)+1][0] != '-'){
(*argp)++;
*pos = 0;
return &(argv[*argp][*pos]);
}
}
}
return NULL;
}
gchar *get_progname(gchar *arg0)
{
gchar *p = arg0 + strlen(arg0) - 1;
while(p > arg0){
if(*p == '/')
return p+1;
p--;
}
return p;
}
gboolean write_pidfile(gchar *name)
{
FILE *fptr;
if((fptr = fopen(name, "wt"))){
fprintf(fptr, "%d\n", getpid());
fclose(fptr);
pidfile = strdup(name);
return TRUE;
}
logwrite(LOG_WARNING, "could not write pid file: %s\n", strerror(errno));
return FALSE;
}
static
void mode_daemon(gboolean do_listen, gint queue_interval, char *argv[])
{
guint pid;
/* daemon */
if(!conf.run_as_user){
if((conf.orig_uid != 0) && (conf.orig_uid != conf.mail_uid)){
fprintf(stderr, "must be root or %s for daemon.\n", DEF_MAIL_USER);
exit(EXIT_FAILURE);
}
}
if((pid = fork()) > 0){
exit(EXIT_SUCCESS);
}else if(pid < 0){
logwrite(LOG_ALERT, "could not fork!");
exit(EXIT_FAILURE);
}
signal(SIGTERM, sigterm_handler);
write_pidfile(PIDFILEDIR"/masqmail.pid");
conf.do_verbose = FALSE;
fclose(stdin);
fclose(stdout);
fclose(stderr);
listen_port(do_listen ? conf.listen_addresses : NULL,
queue_interval, argv);
}
#ifdef ENABLE_POP3
static
void mode_get_daemon(gint get_interval, char *argv[])
{
guint pid;
/* daemon */
if(!conf.run_as_user){
if((conf.orig_uid != 0) && (conf.orig_uid != conf.mail_uid)){
fprintf(stderr, "must be root or %s for daemon.\n", DEF_MAIL_USER);
exit(EXIT_FAILURE);
}
}
if((pid = fork()) > 0){
exit(EXIT_SUCCESS);
}else if(pid < 0){
logwrite(LOG_ALERT, "could not fork!");
exit(EXIT_FAILURE);
}
signal(SIGTERM, sigterm_handler);
write_pidfile(PIDFILEDIR"/masqmail-get.pid");
conf.do_verbose = FALSE;
fclose(stdin);
fclose(stdout);
fclose(stderr);
get_daemon(get_interval, argv);
}
#endif
#ifdef ENABLE_SMTP_SERVER
static void mode_smtp()
{
/* accept smtp message on stdin */
/* write responses to stderr. */
struct sockaddr_in saddr;
gchar *peername = NULL;
int dummy = sizeof(saddr);
#ifdef ENABLE_IDENT
gchar *ident = NULL;
#endif
conf.do_verbose = FALSE;
if(!conf.run_as_user){
seteuid(conf.orig_uid);
setegid(conf.orig_gid);
}
DEBUG(5) debugf("accepting smtp message on stdin\n");
if(getpeername(0, (struct sockaddr *)(&saddr), &dummy) == 0){
peername = g_strdup(inet_ntoa(saddr.sin_addr));
#ifdef ENABLE_IDENT
{
gchar *id = NULL;
if((id = (gchar *)ident_id(0, 60))){
ident = g_strdup(id);
}
}
#endif
}else if(errno != ENOTSOCK)
exit(EXIT_FAILURE);
//smtp_in(stdin, stdout, peername);
smtp_in(stdin, stderr, peername, NULL);
#ifdef ENABLE_IDENT
if(ident) g_free(ident);
#endif
}
#endif
static void mode_accept(address *return_path, gchar *full_sender_name,
guint accept_flags, char **addresses, int addr_cnt)
{
/* accept message on stdin */
accept_error err;
message *msg = create_message();
gint i;
if(return_path != NULL){
if((conf.orig_uid != 0) &&
(conf.orig_uid != conf.mail_uid) &&
(!is_ingroup(conf.orig_uid, conf.mail_gid))){
fprintf(stderr,
"must be in root, %s or in group %s for setting return path.\n",
DEF_MAIL_USER, DEF_MAIL_GROUP);
exit(EXIT_FAILURE);
}
}
if(!conf.run_as_user){
seteuid(conf.orig_uid);
setegid(conf.orig_gid);
}
DEBUG(5) debugf("accepting message on stdin\n");
msg->received_prot = PROT_LOCAL;
for(i = 0; i < addr_cnt; i++){
if(addresses[i][0] != '|')
msg->rcpt_list =
g_list_append(msg->rcpt_list,
create_address_qualified(addresses[i], TRUE, conf.host_name));
else{
logwrite(LOG_ALERT, "no pipe allowed as recipient address: %s\n", addresses[i]);
exit(EXIT_FAILURE);
}
}
/* -f option */
msg->return_path = return_path;
/* -F option */
msg->full_sender_name = full_sender_name;
if((err = accept_message(stdin, msg, accept_flags)) == AERR_OK){
if(spool_write(msg, TRUE)){
pid_t pid;
logwrite(LOG_NOTICE, "%s <= %s with %s\n",
msg->uid, addr_string(msg->return_path),
prot_names[PROT_LOCAL]);
if(!conf.do_queue){
if((pid = fork()) == 0){
conf.do_verbose = FALSE;
fclose(stdin);
fclose(stdout);
fclose(stderr);
if(deliver(msg)){
exit(EXIT_SUCCESS);
}else
exit(EXIT_FAILURE);
}else if(pid < 0){
logwrite(LOG_ALERT, "could not fork for delivery, id = %s",
msg->uid);
}
}
}else{
fprintf(stderr, "Could not write spool file\n");
exit(EXIT_FAILURE);
}
}else{
switch(err){
case AERR_EOF:
fprintf(stderr, "unexpected EOF.\n");
exit(EXIT_FAILURE);
case AERR_NORCPT:
fprintf(stderr, "no recipients.\n");
exit(EXIT_FAILURE);
default:
/* should never happen: */
fprintf(stderr, "Unknown error (%d)\r\n", err);
exit(EXIT_FAILURE);
}
exit(EXIT_FAILURE);
}
}
int
main(int argc, char *argv[])
{
/* cmd line flags */
gchar *conf_file = CONF_FILE;
gint arg = 1;
gboolean do_get = FALSE;
gboolean do_get_online = FALSE;
gboolean do_listen = FALSE;
gboolean do_runq = FALSE;
gboolean do_runq_online = FALSE;
gboolean do_queue = FALSE;
gboolean do_verbose = FALSE;
gint debug_level = -1;
mta_mode mta_mode = MODE_ACCEPT;
gint queue_interval = 0;
gint get_interval = 0;
gboolean opt_t = FALSE;
gboolean opt_i = FALSE;
gboolean opt_odb = FALSE;
gboolean opt_oem = FALSE;
gboolean exit_failure = FALSE;
gchar *M_cmd = NULL;
gint exit_code = EXIT_SUCCESS;
gchar *route_name = NULL;
gchar *get_name = NULL;
gchar *progname;
gchar *f_address = NULL;
gchar *full_sender_name = NULL;
address *return_path = NULL; /* may be changed by -f option */
progname = get_progname(argv[0]);
if(strcmp(progname, "mailq") == 0)
{ mta_mode = MODE_LIST; }
else if(strcmp(progname, "mailrm") == 0)
{ mta_mode = MODE_MCMD; M_cmd = "rm"; }
else if(strcmp(progname, "runq") == 0)
{ mta_mode = MODE_RUNQUEUE; do_runq = TRUE; }
else if(strcmp(progname, "rmail") == 0)
{ mta_mode = MODE_ACCEPT; opt_i = TRUE; }
else if(strcmp(progname, "smtpd") == 0 || strcmp(progname, "in.smtpd") == 0)
{ mta_mode = MODE_SMTP; }
/* parse cmd line */
while(arg < argc){
gint pos = 0;
if((argv[arg][pos] == '-') && (argv[arg][pos+1] != '-')){
pos++;
switch(argv[arg][pos++]){
case 'b':
switch(argv[arg][pos++]){
case 'd':
do_listen = TRUE;
mta_mode = MODE_DAEMON;
break;
case 'i':
/* ignored */
mta_mode = MODE_BI;
break;
case 's':
mta_mode = MODE_SMTP;
break;
case 'p':
mta_mode = MODE_LIST;
break;
case 'V':
mta_mode = MODE_VERSION;
break;
default:
fprintf(stderr, "unrecognized option '%s'\n", argv[arg]);
exit(EXIT_FAILURE);
}
break;
case 'B':
/* we ignore this and throw the argument away */
get_optarg(argv, argc, &arg, &pos);
break;
case 'C':
if(!(conf_file = get_optarg(argv, argc, &arg, &pos))){
fprintf(stderr, "-C requires a filename as argument.\n");
exit(EXIT_FAILURE);
}
break;
case 'F':
{
full_sender_name = get_optarg(argv, argc, &arg, &pos);
if(!full_sender_name){
fprintf(stderr, "-F requires a name as an argument\n");
exit(EXIT_FAILURE);
}
}
break;
case 'd':
if(getuid() == 0){
char *lvl = get_optarg(argv, argc, &arg, &pos);
if(lvl)
debug_level = atoi(lvl);
else{
fprintf(stderr, "-d requires a number as an argument.\n");
exit(EXIT_FAILURE);
}
}else{
fprintf(stderr, "only root may set the debug level.\n");
exit(EXIT_FAILURE);
}
break;
case 'f':
/* set return path */
{
gchar *address;
address = get_optarg(argv, argc, &arg, &pos);
if(address){
f_address = g_strdup(address);
}else{
fprintf(stderr, "-f requires an address as an argument\n");
exit(EXIT_FAILURE);
}
}
break;
case 'g':
do_get = TRUE;
if(!mta_mode) mta_mode = MODE_NONE; /* to prevent default MODE_ACCEPT */
if(argv[arg][pos] == 'o'){
pos++;
do_get_online = TRUE;
/* can be NULL, then we use online detection method */
route_name = get_optarg(argv, argc, &arg, &pos);
if(route_name != NULL){
if(isdigit(route_name[0])){
get_interval = time_interval(route_name, &pos);
route_name = get_optarg(argv, argc, &arg, &pos);
mta_mode = MODE_GET_DAEMON;
do_get = FALSE;
}
}
}else{
if((optarg = get_optarg(argv, argc, &arg, &pos))){
get_name = get_optarg(argv, argc, &arg, &pos);
}
}
break;
case 'i':
if(argv[arg][pos] == 0){
opt_i = TRUE;
exit_failure = FALSE; /* may override -oem */
}else{
fprintf(stderr, "unrecognized option '%s'\n", argv[arg]);
exit(EXIT_FAILURE);
}
break;
case 'M':
{
mta_mode = MODE_MCMD;
M_cmd = g_strdup(&(argv[arg][pos]));
}
break;
case 'o':
switch(argv[arg][pos++]){
case 'e':
if(argv[arg][pos++] == 'm') /* -oem */
if(!opt_i) exit_failure = TRUE;
opt_oem = TRUE;
break;
case 'd':
if(argv[arg][pos] == 'b') /* -odb */
opt_odb = TRUE;
else if(argv[arg][pos] == 'q') /* -odq */
do_queue = TRUE;
break;
case 'i':
opt_i = TRUE;
exit_failure = FALSE; /* may override -oem */
break;
}
break;
case 'q':
{
gchar *optarg;
do_runq = TRUE;
mta_mode = MODE_RUNQUEUE;
if(argv[arg][pos] == 'o'){
pos++;
do_runq = FALSE;
do_runq_online = TRUE;
/* can be NULL, then we use online detection method */
route_name = get_optarg(argv, argc, &arg, &pos);
}else if((optarg = get_optarg(argv, argc, &arg, &pos))){
mta_mode = MODE_DAEMON;
queue_interval = time_interval(optarg, &pos);
}
}
break;
case 't':
if(argv[arg][pos] == 0){
opt_t = TRUE;
}else{
fprintf(stderr, "unrecognized option '%s'\n", argv[arg]);
exit(EXIT_FAILURE);
}
break;
case 'v':
do_verbose = TRUE;
break;
default:
fprintf(stderr, "unrecognized option '%s'\n", argv[arg]);
exit(EXIT_FAILURE);
}
}else{
if(argv[arg][pos+1] == '-'){
if(argv[arg][pos+2] != '\0'){
fprintf(stderr, "unrecognized option '%s'\n", argv[arg]);
exit(EXIT_FAILURE);
}
arg++;
}
break;
}
arg++;
}
if(mta_mode == MODE_VERSION){
gchar *with_resolver = "", *with_smtp_server = "", *with_pop3 = "", *with_auth = "",
*with_maildir = "", *with_ident = "", *with_mserver = "";
#ifdef ENABLE_RESOLVER
with_resolver = " +resolver";
#endif
#ifdef ENABLE_SMTP_SERVER
with_smtp_server = " +smtp-server";
#endif
#ifdef ENABLE_POP3
with_pop3 = " +pop3";
#endif
#ifdef ENABLE_AUTH
with_auth = " +auth";
#endif
#ifdef ENABLE_MAILDIR
with_maildir = " +maildir";
#endif
#ifdef ENABLE_IDENT
with_ident = " +ident";
#endif
#ifdef ENABLE_MSERVER
with_mserver = " +mserver";
#endif
printf("%s %s%s%s%s%s%s%s%s\n", PACKAGE, VERSION,
with_resolver, with_smtp_server, with_pop3, with_auth,
with_maildir, with_ident, with_mserver);
exit(EXIT_SUCCESS);
}
/* initialize random generator */
srand(time(NULL));
/* ignore SIGPIPE signal */
signal(SIGPIPE, SIG_IGN);
/* close all possibly open file descriptors */
{
int i, max_fd = sysconf(_SC_OPEN_MAX);
if(max_fd <= 0) max_fd = 64;
for(i = 3; i < max_fd; i++)
close(i);
}
init_conf();
/* if we are not privileged, and the config file was changed we
implicetely set the the run_as_user flag and give up all
privileges.
So it is possible for a user to run his own daemon without
breaking security.
*/
if(strcmp(conf_file, CONF_FILE) != 0){
if(conf.orig_uid != 0){
conf.run_as_user = TRUE;
seteuid(conf.orig_uid);
setegid(conf.orig_gid);
setuid(conf.orig_uid);
setgid(conf.orig_gid);
}
}
read_conf(conf_file);
if(do_queue) conf.do_queue = TRUE;
if(do_verbose) conf.do_verbose = TRUE;
if(debug_level >= 0) /* if >= 0, it was given by argument */
conf.debug_level = debug_level;
chdir("/");
if(!conf.run_as_user){
if(setgid(0) != 0){
fprintf(stderr,
"could not set gid to 0. Is the setuid bit set? : %s\n",
strerror(errno));
exit(EXIT_FAILURE);
}
if(setuid(0) != 0){
fprintf(stderr,
"could not gain root privileges. Is the setuid bit set? : %s\n",
strerror(errno));
exit(EXIT_FAILURE);
}
}
if(!logopen()){
fprintf(stderr, "could not open log file\n");
exit(EXIT_FAILURE);
}
DEBUG(1) debugf("masqmail %s starting\n", VERSION);
DEBUG(5){
gchar **str = argv;
debugf("args: \n");
while(*str){
debugf("%s \n", *str);
str++;
}
}
DEBUG(5) debugf("queue_interval = %d\n", queue_interval);
if(f_address){
return_path = create_address_qualified(f_address, TRUE, conf.host_name);
g_free(f_address);
if(!return_path){
fprintf(stderr, "invalid RFC821 address: %s\n", f_address);
exit(EXIT_FAILURE);
}
}
if(do_get){
#ifdef ENABLE_POP3
if((mta_mode == MODE_NONE) || (mta_mode == MODE_RUNQUEUE)){
set_identity(conf.orig_uid, "getting mail");
if(do_get_online){
if(route_name != NULL){
conf.online_detect = g_strdup("argument");
set_online_name(route_name);
}
get_online();
}else{
if(get_name)
get_from_name(get_name);
else
get_all();
}
}else{
logwrite(LOG_ALERT, "get (-g) only allowed alone or together with queue run (-q)\n");
}
#else
fprintf(stderr, "get (pop) support not compiled in\n");
#endif
}
switch(mta_mode){
case MODE_DAEMON:
mode_daemon(do_listen, queue_interval, argv);
break;
case MODE_RUNQUEUE:
{
/* queue runs */
set_identity(conf.orig_uid, "queue run");
if(do_runq)
exit_code = queue_run() ? EXIT_SUCCESS : EXIT_FAILURE;
if(do_runq_online){
if(route_name != NULL){
conf.online_detect = g_strdup("argument");
set_online_name(route_name);
}
exit_code = queue_run_online() ? EXIT_SUCCESS : EXIT_FAILURE;
}
}
break;
case MODE_GET_DAEMON:
#ifdef ENABLE_POP3
if(route_name != NULL){
conf.online_detect = g_strdup("argument");
set_online_name(route_name);
}
mode_get_daemon(get_interval, argv);
#endif
break;
case MODE_SMTP:
#ifdef ENABLE_SMTP_SERVER
mode_smtp();
#else
fprintf(stderr, "smtp server support not compiled in\n");
#endif
break;
case MODE_LIST:
queue_list();
break;
case MODE_BI:
exit(EXIT_SUCCESS);
break; /* well... */
case MODE_MCMD:
if(strcmp(M_cmd, "rm") == 0){
gboolean ok = FALSE;
set_euidgid(conf.mail_uid, conf.mail_gid, NULL, NULL);
if(is_privileged_user(conf.orig_uid)){
for(; arg < argc; arg++){
if(queue_delete(argv[arg]))
ok = TRUE;
}
}else{
struct passwd *pw = getpwuid(conf.orig_uid);
if(pw){
for(; arg < argc; arg++){
message *msg = msg_spool_read(argv[arg], FALSE);
#ifdef ENABLE_IDENT
if(((msg->received_host == NULL) && (msg->received_prot == PROT_LOCAL)) ||
is_in_netlist(msg->received_host, conf.ident_trusted_nets)){
#else
if((msg->received_host == NULL) && (msg->received_prot == PROT_LOCAL)){
#endif
if(msg->ident){
if(strcmp(pw->pw_name, msg->ident) == 0){
if(queue_delete(argv[arg]))
ok = TRUE;
}else{
fprintf(stderr, "you do not own message id %s\n", argv[arg]);
}
}else
fprintf(stderr, "message %s does not have an ident.\n", argv[arg]);
}else{
fprintf(stderr, "message %s was not received locally or from a trusted network.\n", argv[arg]);
}
}
}else{
fprintf(stderr, "could not find a passwd entry for uid %d: %s\n", conf.orig_uid, strerror(errno));
}
}
exit(ok ? EXIT_SUCCESS : EXIT_FAILURE);
}else{
fprintf(stderr, "unknown command %s\n", M_cmd);
exit(EXIT_FAILURE);
}
break;
case MODE_ACCEPT:
{
guint accept_flags =
(opt_t ? ACC_DEL_RCPTS|ACC_DEL_BCC|ACC_RCPT_FROM_HEAD : ACC_HEAD_FROM_RCPT) |
(opt_i ? ACC_NODOT_TERM : ACC_NODOT_RELAX);
mode_accept(return_path, full_sender_name, accept_flags, &(argv[arg]), argc - arg);
exit(exit_failure ? EXIT_FAILURE : EXIT_SUCCESS);
}
break;
case MODE_NONE:
break;
default:
fprintf(stderr, "unknown mode: %d\n", mta_mode);
break;
}
logclose();
exit(exit_code);
}
syntax highlighted by Code2HTML, v. 0.9.1