/* * Copyright (c) 2016, Manuel Mausz , * All rights reserved. * * Some maildir specified code is copied from dovecot (dovecot.org), * Copyright by Timo Sirainen (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 #include #include #include #include #include #include #include #include #include #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; }