/* * Copyright (c) 2012, 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 #include #include #define MAILDIR_NAME "Maildir" #define MAILDIRSIZE_FILENAME "maildirsize" #define LOGPREFIX "QUOTA: " struct maildir_quota { const char *file; uint64_t total_bytes; uint64_t total_count; uint64_t bytes_limit; uint64_t count_limit; }; int check_parent(const char *username) { pid_t parent; int num; char buf[256], buf2[256]; const char *smtpbin = "/var/qmail/bin/qmail-smtpd"; /* check parent */ parent = getppid(); 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 for user \"%s\".\n", username); return 0; } num = readlink(buf, buf2, sizeof(buf2)); if (num < 0) { (void)fprintf(stderr, LOGPREFIX "Unable to read parent from proc-fs for user \"%s\": %m\n", username); return 0; } buf2[num] = '\0'; if (strcmp(smtpbin, buf2) != 0) { (void)fprintf(stderr, LOGPREFIX "Error: Parent \"%s\" doesn't match qmail-smtp for user \"%s\".\n", buf2, username); return 0; } return 1; } int maildir_file_read(const char *file, char *buf, size_t size) { unsigned int readb; int fd, num, i; fd = open(file, O_RDONLY); if (fd == -1) { /* no file, no quota */ if (errno != ENOENT) (void)fprintf(stderr, LOGPREFIX "Unable to open maildirsize-file \"%s\": %m\n", file); return 0; } /* read the whole file into our buffer. we won't do any recalculations, dovecot will do this for us */ readb = 0; while ((num = read(fd, buf + readb, size - readb)) != 0) { readb += num; if (readb >= size) break; } (void)close(fd); /* skip the last line if there's no LF at the end. Remove the last LF so we don't get one empty line in the strsplit */ while (readb > 0 && buf[readb - 1] != '\n') readb--; if (readb > 0) readb--; buf[readb] = '\0'; /* the read failed and there's no usable header, fail */ if (num < 0 && readb == 0) { (void)fprintf(stderr, LOGPREFIX "Error while reading maildirsize-file \"%s\": %m\n", file); return 0; } /* if there are any NUL bytes, the file is broken */ for (i = 0; i < readb; i++) { if (buf[i] == '\0') { (void)fprintf(stderr, LOGPREFIX "Invalid NUL-characters in maildirsize-file \"%s\"\n", file); return 0; } } return readb; } int maildir_parse_header(char *str, uint64_t *bytes, uint64_t *count) { char *limit, *saveptr, *pos; unsigned long long value; bool ret = true; if (str == NULL) return false; *bytes = 0; *count = 0; /* 0 values mean unlimited */ while((limit = strtok_r(str, ",", &saveptr))) { value = strtoull(limit, &pos, 10); if (pos[0] != '\0' && pos[1] == '\0') { switch (pos[0]) { case 'C': if (value != 0) *count = value; break; case 'S': if (value != 0) *bytes = value; break; default: ret = false; break; } } else ret = false; str = NULL; } return ret; } int maildir_file_parse(struct maildir_quota *quota, char *buf) { char *line, *saveptr; long long total_bytes, bytes_diff; int total_count, count_diff; /* first line contains the limits */ if (buf == NULL || !(line = strtok_r(buf, "\n", &saveptr))) { (void)fprintf(stderr, LOGPREFIX "No or invalid header in maildirsize-file \"%s\"\n", quota->file); return 0; } if (!maildir_parse_header(line, "a->bytes_limit, "a->count_limit)) { (void)fprintf(stderr, LOGPREFIX "Unable to parse header in maildirsize-file \"%s\"\n", quota->file); return 0; } /* truncate too high limits to signed 64bit int range */ if (quota->bytes_limit >= (1ULL << 63)) quota->bytes_limit = (1ULL << 63) - 1; if (quota->count_limit >= (1ULL << 63)) quota->count_limit = (1ULL << 63) - 1; /* rest of the lines contains diffs */ total_bytes = total_count = 0; while((line = strtok_r(NULL, "\n", &saveptr))) { if (sscanf(line, "%lld %d", &bytes_diff, &count_diff) != 2) { (void)fprintf(stderr, LOGPREFIX "Invalid content maildirsize-file in \"%s\": %s\n", quota->file, line); return 0; } total_bytes += bytes_diff; total_count += count_diff; } /* corrupted */ if (total_bytes < 0 || total_count < 0) { (void)fprintf(stderr, LOGPREFIX "Corrupted maildirsize-file \"%s\": total_bytes=%lld, total_count=%d\n", quota->file, total_bytes, total_count); return 0; } quota->total_bytes = (uint64_t)total_bytes; quota->total_count = (uint64_t)total_count; return 1; } int main() { const char *username = NULL; struct maildir_quota quota; struct passwd *pw; char path[PATH_MAX]; char buf[5120 + 100 + 1]; /* dovecot will recalculate at 5120 bytes for us */ int num; username = getenv("DTUSER"); if (!username) return 0; /* lookup user in passwd */ pw = getpwnam(username); if (pw == NULL) { (void)fprintf(stderr, LOGPREFIX "Unable to get user \"%s\" from passwd!: %m\n", username); return 0; } /* check parent */ if (!check_parent(username)) return 0; /* seteuid */ if (seteuid(pw->pw_uid)) { (void)fprintf(stderr, LOGPREFIX "Unable to call seteuid for user \"%s\": %m\n", username); return 0; } if (pw->pw_dir == NULL) { (void)fprintf(stderr, LOGPREFIX "User \"%s\" has no homedir?\n", username); return 0; } num = snprintf(path, sizeof(path), "%s/" MAILDIR_NAME "/" MAILDIRSIZE_FILENAME, pw->pw_dir); if (num < 0 || num > sizeof(path)) { (void)fprintf(stderr, LOGPREFIX "Unable to copy maildir string to buffer for user \"%s\".\n", username); return 0; } if (maildir_file_read(path, buf, sizeof(buf) - 1) <= 0) return 0; quota.file = path; quota.total_bytes = quota.total_count = 0; if (!maildir_file_parse("a, buf)) return 0; if ((quota.bytes_limit != 0 && quota.total_bytes > quota.bytes_limit) || (quota.count_limit != 0 && quota.total_count > quota.count_limit)) { (void)fprintf(stderr, LOGPREFIX "User \"%s\" is over quota.\n", username); (void)printf("E553 User over quota. (#5.1.1)\n"); } return 0; }