Download | Plain Text | Line Numbers
/*
* Copyright (c) 2016, Manuel Mausz <manuel at mausz dot at>,
* All rights reserved.
*
* Some maildir specified code is copied from dovecot (dovecot.org),
* Copyright by Timo Sirainen <tss at iki dot fi> (and others).
*
* 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., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netdb.h>
#define LOGPREFIX "QUOTA: "
typedef struct
{
const char *action, *msg;
} policy_reply;
int check_parent(void)
{
/* check parent */
pid_t parent = getppid();
char buf[256];
int num = snprintf(buf, sizeof(buf), "/proc/%d/exe", parent);
if (num < 0 || num > sizeof(buf))
{
(void)fprintf(stderr, LOGPREFIX "Unable to copy string to buffer\n");
return 0;
}
char buf2[256];
num = readlink(buf, buf2, sizeof(buf2));
if (num < 0)
{
(void)fprintf(stderr, LOGPREFIX "Unable to read parent from proc-fs: %m\n");
return 0;
}
buf2[num] = '\0';
const char *smtpbin = "/var/qmail/bin/qmail-smtpd";
if (strcmp(smtpbin, buf2) != 0)
{
(void)fprintf(stderr, LOGPREFIX "Parent \"%s\" doesn't match qmail-smtp\n", buf2);
return 0;
}
return 1;
}
static int net_connect_inet(const char *host, const char *port)
{
struct addrinfo hints = {
.ai_family = AF_UNSPEC,
.ai_socktype = SOCK_STREAM
};
struct addrinfo *result;
int s = getaddrinfo(host, port, &hints, &result);
if (s != 0)
{
(void)fprintf(stderr, LOGPREFIX "getaddrinfo: %s\n", gai_strerror(s));
return -1;
}
//FIXME: connect might hang
int sockfd = -1;
struct addrinfo *r;
for (r = result; r != NULL; r=r->ai_next)
{
sockfd = socket(r->ai_family, r->ai_socktype | SOCK_CLOEXEC,
r->ai_protocol);
if (sockfd == -1)
continue;
if (connect(sockfd, r->ai_addr, r->ai_addrlen) != -1)
break; /* success */
(void)close(sockfd);
sockfd = -1;
break; /* we only try the first record */
}
(void)freeaddrinfo(result);
return sockfd;
}
static int net_connect_unix(const char *path)
{
int sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
if (sockfd < 0)
return -1;
struct sockaddr_un saddr;
saddr.sun_family = AF_UNIX;
(void)strcpy(saddr.sun_path, path);
int conn = connect(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));
if (conn != -1)
return sockfd;
(void)close(sockfd);
return -1;
}
static int net_connect(const char *uri, bool readenv)
{
int sockfd = -1;
if (strncmp(uri, "tcp://", strlen("tcp://")) == 0)
{
char *path = strdup(uri + strlen("tcp://"));
char *portptr = strrchr(path, ':');
if (portptr == NULL)
{
(void)fprintf(stderr, LOGPREFIX "Invalid uri: port missing?\n");
(void)free(path);
return -1;
}
*portptr = '\0';
char *host = path;
if (host[0] == '[' && *(portptr - 1) == ']')
{
host++;
*(portptr - 1) = '\0';
}
sockfd = net_connect_inet(host, portptr + 1);
(void)free(path);
}
else if (strncmp(uri, "unix://", strlen("unix://")) == 0)
sockfd = net_connect_unix(uri + strlen("unix://"));
else if (readenv && strncmp(uri, "env://", strlen("env://")) == 0)
{
if ((uri = getenv(uri + strlen("env://"))) != NULL)
return net_connect(uri, false);
(void)fprintf(stderr, LOGPREFIX "Invalid uri: env var undefined\n");
return -1;
}
else
{
(void)fprintf(stderr, LOGPREFIX "Invalid uri: unknown syntax\n");
return -1;
}
if (sockfd < 0)
(void)fprintf(stderr, LOGPREFIX "Unable to connect to socket: %m\n");
return sockfd;
}
static const policy_reply policy_check(const char *uri, const char *format, ...)
{
policy_reply reply = { .action = NULL, .msg = NULL };
/* connect to policy server */
int sockfd = net_connect(uri, true);
if (sockfd < 0)
goto end;
/* send data */
va_list ap;
va_start(ap, format);
(void)vdprintf(sockfd, format, ap);
(void)dprintf(sockfd, "\n");
va_end(ap);
if (errno)
{
(void)fprintf(stderr, LOGPREFIX "Error while sending to policy server: %m\n");
goto end;
}
/* wait for reply */
static char buf[2048];
ssize_t read = recv(sockfd, &buf, sizeof(buf) - 1, 0);
if (read <= 0)
{
(void)fprintf(stderr, LOGPREFIX "Error while receiving from policy server: %m\n");
goto end;
}
/* parse reply */
if (read < strlen("action=") + 3 /* 1x reply char + 2x LF */
|| strncmp("action=", buf, strlen("action=")) != 0
|| buf[read - 2] != '\n')
{
(void)fprintf(stderr, LOGPREFIX "Uknown reply format from policy server\n");
goto end;
}
buf[read - 2] = '\0';
reply.action = buf + strlen("action=");
char *msgptr = strchr(reply.action, ' ');
if (msgptr)
{
*msgptr = '\0';
reply.msg = ++msgptr;
}
end:
(void)close(sockfd);
return reply;
}
int main(int argc, char *argv[])
{
if (argc != 2)
return 0;
const char *username = getenv("DTUSER");
if (!username)
return 0;
/* check parent */
if (!check_parent())
return 0;
// size not exported by qmail (at least for now)
// assume a minimum size of 500 bytes
size_t size = 500;
const policy_reply reply = policy_check(argv[1],
"recipient=%s\nsize=%zu\n", username, size);
if (reply.action == NULL)
return 0;
if (*reply.action == '5' || *reply.action == '4'
|| strcmp(reply.action, "REJECT") == 0)
{
const char *code = (*reply.action == '5' || *reply.action == '4')
? reply.action : "552";
const char *msg = (reply.msg) ? reply.msg : "User over quota. (#5.2.2)";
(void)fprintf(stderr, LOGPREFIX "User \"%s\" is over quota.\n", username);
(void)printf("E%s %s\n", code, msg);
}
else if (strcmp(reply.action, "OK") == 0
|| strcmp(reply.action, "DUNNO") == 0)
{ ; } /* nothing here */
else
{
(void)fprintf(stderr, LOGPREFIX "Unsupported reply from policy server:"
" reply=%s msg=%s\n", reply.action, (reply.msg) ? reply.msg : "NULL");
}
return 0;
}