#include <sys_defs.h>
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <ctype.h>
#include <msg.h>
#include <scan_dir.h>
#include <vstring.h>
#include <vstream.h>
#include <set_ugid.h>
#include <safe_open.h>
#include <stringops.h>
#include <mail_queue.h>
#include <mail_open_ok.h>
#include <mymalloc.h>
#include <mail_proto.h>
#include <cleanup_user.h>
#include <mail_date.h>
#include <mail_params.h>
#include <mail_conf.h>
#include <record.h>
#include <rec_type.h>
#include <lex_822.h>
#include <mail_server.h>
char *var_always_bcc;
char *var_filter_xport;
static time_t last_mod_time = 0;
typedef struct {
char *id;
struct stat st;
char *path;
char *sender;
char *rcpt;
} PICKUP_INFO;
#define REMOVE_MESSAGE_FILE 1
#define KEEP_MESSAGE_FILE 2
static int file_read_error(PICKUP_INFO *info, int type)
{
msg_warn("uid=%ld: unexpected or malformed record type %d",
(long) info->st.st_uid, type);
return (REMOVE_MESSAGE_FILE);
}
static int cleanup_service_error(PICKUP_INFO *info, int status)
{
msg_warn("%s: %s", info->path, cleanup_strerror(status));
return ((status & CLEANUP_STAT_BAD) ?
REMOVE_MESSAGE_FILE : KEEP_MESSAGE_FILE);
}
static int copy_segment(VSTREAM *qfile, VSTREAM *cleanup, PICKUP_INFO *info,
VSTRING *buf, char *expected)
{
int type;
int check_first = (*expected == REC_TYPE_CONTENT[0]);
const char *error_text;
char *attr_name;
char *attr_value;
for (;;) {
if ((type = rec_get(qfile, buf, var_line_limit)) < 0
|| strchr(expected, type) == 0)
return (file_read_error(info, type));
if (msg_verbose)
msg_info("%s: read %c %s", info->id, type, vstring_str(buf));
if (type == *expected)
break;
if (type == REC_TYPE_FROM)
if (info->sender == 0)
info->sender = mystrdup(vstring_str(buf));
if (type == REC_TYPE_ORCP)
if (info->st.st_uid != var_owner_uid) {
msg_warn("uid=%ld: ignoring original recipient record: %.200s",
(long) info->st.st_uid, vstring_str(buf));
continue;
}
if (type == REC_TYPE_RCPT)
if (info->rcpt == 0)
info->rcpt = mystrdup(vstring_str(buf));
if (type == REC_TYPE_TIME)
continue;
if (type == REC_TYPE_SIZE)
continue;
if (type == REC_TYPE_ATTR) {
if ((error_text = split_nameval(vstring_str(buf), &attr_name,
&attr_value)) != 0) {
msg_warn("uid=%ld: malformed attribute record: %s: %.200s",
(long) info->st.st_uid, error_text, vstring_str(buf));
continue;
}
#define STREQ(x,y) (strcmp(x,y) == 0)
if (STREQ(attr_name, MAIL_ATTR_ENCODING)
&& (STREQ(attr_value, MAIL_ATTR_ENC_7BIT)
|| STREQ(attr_value, MAIL_ATTR_ENC_8BIT)
|| STREQ(attr_value, MAIL_ATTR_ENC_NONE))) {
rec_fprintf(cleanup, REC_TYPE_ATTR, "%s=%s",
attr_name, attr_value);
} else if (info->st.st_uid != var_owner_uid) {
msg_warn("uid=%ld: ignoring attribute record: %.200s=%.200s",
(long) info->st.st_uid, attr_name, attr_value);
}
continue;
}
if (type == REC_TYPE_RRTO)
continue;
if (type == REC_TYPE_ERTO)
continue;
if (type == REC_TYPE_INSP)
continue;
if (type == REC_TYPE_FILT && *expected != REC_TYPE_CONTENT[0])
continue;
else {
if (check_first) {
check_first = 0;
if (VSTRING_LEN(buf) > 0 && IS_SPACE_TAB(vstring_str(buf)[0]))
rec_put(cleanup, REC_TYPE_NORM, "", 0);
}
if ((REC_PUT_BUF(cleanup, type, buf)) < 0)
return (cleanup_service_error(info, CLEANUP_STAT_WRITE));
}
}
return (0);
}
static int pickup_copy(VSTREAM *qfile, VSTREAM *cleanup,
PICKUP_INFO *info, VSTRING *buf)
{
time_t now = time((time_t *) 0);
int status;
#define DAY_SECONDS 86400
#define HOUR_SECONDS 3600
if (info->st.st_mtime > now + 2 * HOUR_SECONDS) {
msg_warn("%s: message dated %ld seconds into the future",
info->id, (long) (info->st.st_mtime - now));
info->st.st_mtime = now;
} else if (info->st.st_mtime < now - DAY_SECONDS) {
msg_warn("%s: message has been queued for %d days",
info->id, (int) (now - info->st.st_mtime) / DAY_SECONDS);
}
rec_fprintf(cleanup, REC_TYPE_TIME, "%ld", (long) info->st.st_mtime);
if (*var_filter_xport)
rec_fprintf(cleanup, REC_TYPE_FILT, "%s", var_filter_xport);
rec_fprintf(cleanup, REC_TYPE_ATTR, "%s=%s",
MAIL_ATTR_ORIGIN, MAIL_ATTR_ORG_LOCAL);
if ((status = copy_segment(qfile, cleanup, info, buf, REC_TYPE_ENVELOPE)) != 0)
return (status);
if (info->sender == 0) {
msg_warn("%s: uid=%ld: no envelope sender",
info->id, (long) info->st.st_uid);
return (REMOVE_MESSAGE_FILE);
}
msg_info("%s: uid=%d from=<%s>", info->id,
(int) info->st.st_uid, info->sender);
if (info->rcpt) {
if (*var_always_bcc)
rec_fputs(cleanup, REC_TYPE_RCPT, var_always_bcc);
}
rec_fputs(cleanup, REC_TYPE_MESG, "");
rec_fprintf(cleanup, REC_TYPE_NORM, "Received: by %s (%s, from userid %ld)",
var_myhostname, var_mail_name, (long) info->st.st_uid);
rec_fprintf(cleanup, REC_TYPE_NORM, "\tid %s; %s", info->id,
mail_date(info->st.st_mtime));
if ((status = copy_segment(qfile, cleanup, info, buf, REC_TYPE_CONTENT)) != 0)
return (status);
rec_fputs(cleanup, REC_TYPE_XTRA, "");
if ((status = copy_segment(qfile, cleanup, info, buf, REC_TYPE_EXTRACT)) != 0)
return (status);
rec_fputs(cleanup, REC_TYPE_END, "");
if (attr_scan(cleanup, ATTR_FLAG_MISSING,
ATTR_TYPE_NUM, MAIL_ATTR_STATUS, &status,
ATTR_TYPE_END) != 1)
return (cleanup_service_error(info, CLEANUP_STAT_WRITE));
if (status) {
return (cleanup_service_error(info, status));
} else {
return (REMOVE_MESSAGE_FILE);
}
}
static int pickup_file(PICKUP_INFO *info)
{
VSTRING *buf = vstring_alloc(100);
int status;
VSTREAM *qfile;
VSTREAM *cleanup;
qfile = safe_open(info->path, O_RDONLY | O_NONBLOCK, 0,
(struct stat *) 0, -1, -1, buf);
if (qfile == 0) {
if (errno != ENOENT)
msg_warn("open input file %s: %s", info->path, vstring_str(buf));
vstring_free(buf);
if (errno == EACCES)
msg_warn("if this file was created by Postfix < 1.1, then you may have to chmod a+r %s/%s",
var_queue_dir, info->path);
return (errno == EACCES ? KEEP_MESSAGE_FILE : REMOVE_MESSAGE_FILE);
}
#define PICKUP_CLEANUP_FLAGS (CLEANUP_FLAG_BOUNCE | CLEANUP_FLAG_FILTER)
cleanup = mail_connect_wait(MAIL_CLASS_PUBLIC, var_cleanup_service);
if (attr_scan(cleanup, ATTR_FLAG_STRICT,
ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, buf,
ATTR_TYPE_END) != 1
|| attr_print(cleanup, ATTR_FLAG_NONE,
ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, PICKUP_CLEANUP_FLAGS,
ATTR_TYPE_END) != 0) {
status = KEEP_MESSAGE_FILE;
} else {
info->id = mystrdup(vstring_str(buf));
status = pickup_copy(qfile, cleanup, info, buf);
}
vstream_fclose(qfile);
vstream_fclose(cleanup);
vstring_free(buf);
return (status);
}
static void pickup_init(PICKUP_INFO *info)
{
info->id = 0;
info->path = 0;
info->sender = 0;
info->rcpt = 0;
}
static void pickup_free(PICKUP_INFO *info)
{
#define SAFE_FREE(x) { if (x) myfree(x); }
SAFE_FREE(info->id);
SAFE_FREE(info->path);
SAFE_FREE(info->sender);
SAFE_FREE(info->rcpt);
}
static void pickup_service(char *unused_buf, int unused_len,
char *unused_service, char **argv)
{
SCAN_DIR *scan;
char *queue_name;
PICKUP_INFO info;
const char *path;
char *id;
int file_count;
if (argv[0])
msg_fatal("unexpected command-line argument: %s", argv[0]);
queue_name = MAIL_QUEUE_MAILDROP;
struct stat sb;
if ( !stat( queue_name, &sb ) )
{
if ( sb.st_mtime == last_mod_time )
return;
last_mod_time = sb.st_mtime;
}
do {
file_count = 0;
scan = scan_dir_open(queue_name);
while ((id = scan_dir_next(scan)) != 0) {
if (mail_open_ok(queue_name, id, &info.st, &path) == MAIL_OPEN_YES) {
pickup_init(&info);
info.path = mystrdup(path);
if (pickup_file(&info) == REMOVE_MESSAGE_FILE) {
if (REMOVE(info.path))
msg_warn("remove %s: %m", info.path);
else
file_count++;
}
pickup_free(&info);
}
}
scan_dir_close(scan);
} while (file_count);
}
static void drop_privileges(char *unused_name, char **unused_argv)
{
if (getuid() != var_owner_uid)
set_ugid(var_owner_uid, var_owner_gid);
}
int main(int argc, char **argv)
{
static CONFIG_STR_TABLE str_table[] = {
VAR_ALWAYS_BCC, DEF_ALWAYS_BCC, &var_always_bcc, 0, 0,
VAR_FILTER_XPORT, DEF_FILTER_XPORT, &var_filter_xport, 0, 0,
0,
};
trigger_server_main(argc, argv, pickup_service,
MAIL_SERVER_STR_TABLE, str_table,
MAIL_SERVER_POST_INIT, drop_privileges,
MAIL_SERVER_SOLITARY,
0);
}