Download | Plain Text | No Line Numbers


  1. /*
  2.  * Copyright (c) 2012, Manuel Mausz <manuel at mausz dot at>,
  3.  * All rights reserved.
  4.  *
  5.  * Some maildir specified code is copied from dovecot (dovecot.org),
  6.  * Copyright by Timo Sirainen <tss at iki dot fi> (and others).
  7.  *
  8.  * This library is free software; you can redistribute it and/or
  9.  * modify it under the terms of the GNU Lesser General Public
  10.  * License as published by the Free Software Foundation; either
  11.  * version 2.1 of the License, or (at your option) any later version.
  12.  *
  13.  * This library is distributed in the hope that it will be useful,
  14.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  16.  * Lesser General Public License for more details.
  17.  *
  18.  * You should have received a copy of the GNU Lesser General Public
  19.  * License along with this library; if not, write to the Free Software
  20.  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  21.  * MA 02110-1301 USA
  22.  */
  23.  
  24. #include <stdio.h>
  25. #include <stdlib.h>
  26. #include <stdint.h>
  27. #include <stdbool.h>
  28. #include <string.h>
  29. #include <unistd.h>
  30. #include <errno.h>
  31. #include <pwd.h>
  32. #include <limits.h>
  33. #include <sys/types.h>
  34. #include <sys/stat.h>
  35. #include <fcntl.h>
  36.  
  37. #define MAILDIR_NAME "Maildir"
  38. #define MAILDIRSIZE_FILENAME "maildirsize"
  39. #define LOGPREFIX "QUOTA: "
  40.  
  41. struct maildir_quota
  42. {
  43. const char *file;
  44. uint64_t total_bytes;
  45. uint64_t total_count;
  46. uint64_t bytes_limit;
  47. uint64_t count_limit;
  48. };
  49.  
  50. int check_parent(const char *username)
  51. {
  52. pid_t parent;
  53. int num;
  54. char buf[256], buf2[256];
  55. const char *smtpbin = "/var/qmail/bin/qmail-smtpd";
  56.  
  57. /* check parent */
  58. parent = getppid();
  59. num = snprintf(buf, sizeof(buf), "/proc/%d/exe", parent);
  60. if (num < 0 || num > sizeof(buf))
  61. {
  62. (void)fprintf(stderr, LOGPREFIX "Unable to copy string to buffer for user \"%s\".\n", username);
  63. return 0;
  64. }
  65.  
  66. num = readlink(buf, buf2, sizeof(buf2));
  67. if (num < 0)
  68. {
  69. (void)fprintf(stderr, LOGPREFIX "Unable to read parent from proc-fs for user \"%s\": %m\n", username);
  70. return 0;
  71. }
  72. buf2[num] = '\0';
  73.  
  74. if (strcmp(smtpbin, buf2) != 0)
  75. {
  76. (void)fprintf(stderr, LOGPREFIX "Error: Parent \"%s\" doesn't match qmail-smtp for user \"%s\".\n", buf2, username);
  77. return 0;
  78. }
  79.  
  80. return 1;
  81. }
  82.  
  83. int maildir_file_read(const char *file, char *buf, size_t size)
  84. {
  85. unsigned int readb;
  86. int fd, num, i;
  87.  
  88. fd = open(file, O_RDONLY);
  89. if (fd == -1)
  90. {
  91. /* no file, no quota */
  92. if (errno != ENOENT)
  93. (void)fprintf(stderr, LOGPREFIX "Unable to open maildirsize-file \"%s\": %m\n", file);
  94. return 0;
  95. }
  96.  
  97. /* read the whole file into our buffer. we won't do any
  98.   recalculations, dovecot will do this for us */
  99. readb = 0;
  100. while ((num = read(fd, buf + readb, size - readb)) != 0)
  101. {
  102. readb += num;
  103. if (readb >= size)
  104. break;
  105. }
  106. (void)close(fd);
  107.  
  108. /* skip the last line if there's no LF at the end. Remove the last LF
  109.   so we don't get one empty line in the strsplit */
  110. while (readb > 0 && buf[readb - 1] != '\n')
  111. readb--;
  112. if (readb > 0)
  113. readb--;
  114. buf[readb] = '\0';
  115.  
  116. /* the read failed and there's no usable header, fail */
  117. if (num < 0 && readb == 0)
  118. {
  119. (void)fprintf(stderr, LOGPREFIX "Error while reading maildirsize-file \"%s\": %m\n", file);
  120. return 0;
  121. }
  122.  
  123. /* if there are any NUL bytes, the file is broken */
  124. for (i = 0; i < readb; i++)
  125. {
  126. if (buf[i] == '\0')
  127. {
  128. (void)fprintf(stderr, LOGPREFIX "Invalid NUL-characters in maildirsize-file \"%s\"\n", file);
  129. return 0;
  130. }
  131. }
  132.  
  133. return readb;
  134. }
  135.  
  136. int maildir_parse_header(char *str, uint64_t *bytes, uint64_t *count)
  137. {
  138. char *limit, *saveptr, *pos;
  139. unsigned long long value;
  140. bool ret = true;
  141.  
  142. if (str == NULL)
  143. return false;
  144.  
  145. *bytes = 0;
  146. *count = 0;
  147.  
  148. /* 0 values mean unlimited */
  149. while((limit = strtok_r(str, ",", &saveptr)))
  150. {
  151. value = strtoull(limit, &pos, 10);
  152. if (pos[0] != '\0' && pos[1] == '\0')
  153. {
  154. switch (pos[0]) {
  155. case 'C':
  156. if (value != 0)
  157. *count = value;
  158. break;
  159. case 'S':
  160. if (value != 0)
  161. *bytes = value;
  162. break;
  163. default:
  164. ret = false;
  165. break;
  166. }
  167. }
  168. else
  169. ret = false;
  170. str = NULL;
  171. }
  172. return ret;
  173. }
  174.  
  175. int maildir_file_parse(struct maildir_quota *quota, char *buf)
  176. {
  177. char *line, *saveptr;
  178. long long total_bytes, bytes_diff;
  179. int total_count, count_diff;
  180.  
  181. /* first line contains the limits */
  182. if (buf == NULL || !(line = strtok_r(buf, "\n", &saveptr)))
  183. {
  184. (void)fprintf(stderr, LOGPREFIX "No or invalid header in maildirsize-file \"%s\"\n", quota->file);
  185. return 0;
  186. }
  187. if (!maildir_parse_header(line, &quota->bytes_limit, &quota->count_limit))
  188. {
  189. (void)fprintf(stderr, LOGPREFIX "Unable to parse header in maildirsize-file \"%s\"\n", quota->file);
  190. return 0;
  191. }
  192.  
  193. /* truncate too high limits to signed 64bit int range */
  194. if (quota->bytes_limit >= (1ULL << 63))
  195. quota->bytes_limit = (1ULL << 63) - 1;
  196. if (quota->count_limit >= (1ULL << 63))
  197. quota->count_limit = (1ULL << 63) - 1;
  198.  
  199. /* rest of the lines contains <bytes> <count> diffs */
  200. total_bytes = total_count = 0;
  201. while((line = strtok_r(NULL, "\n", &saveptr)))
  202. {
  203. if (sscanf(line, "%lld %d", &bytes_diff, &count_diff) != 2)
  204. {
  205. (void)fprintf(stderr, LOGPREFIX "Invalid content maildirsize-file in \"%s\": %s\n",
  206. quota->file, line);
  207. return 0;
  208. }
  209. total_bytes += bytes_diff;
  210. total_count += count_diff;
  211. }
  212.  
  213. /* corrupted */
  214. if (total_bytes < 0 || total_count < 0)
  215. {
  216. (void)fprintf(stderr, LOGPREFIX "Corrupted maildirsize-file \"%s\": total_bytes=%lld, total_count=%d\n",
  217. quota->file, total_bytes, total_count);
  218. return 0;
  219. }
  220.  
  221. quota->total_bytes = (uint64_t)total_bytes;
  222. quota->total_count = (uint64_t)total_count;
  223. return 1;
  224. }
  225.  
  226. int main()
  227. {
  228. const char *username = NULL;
  229. struct maildir_quota quota;
  230. struct passwd *pw;
  231. char path[PATH_MAX];
  232. char buf[5120 + 100 + 1]; /* dovecot will recalculate at 5120 bytes for us */
  233. int num;
  234.  
  235. username = getenv("DTUSER");
  236. if (!username)
  237. return 0;
  238.  
  239. /* lookup user in passwd */
  240. pw = getpwnam(username);
  241. if (pw == NULL)
  242. {
  243. (void)fprintf(stderr, LOGPREFIX "Unable to get user \"%s\" from passwd!: %m\n", username);
  244. return 0;
  245. }
  246.  
  247. /* check parent */
  248. if (!check_parent(username))
  249. return 0;
  250.  
  251. /* seteuid */
  252. if (seteuid(pw->pw_uid))
  253. {
  254. (void)fprintf(stderr, LOGPREFIX "Unable to call seteuid for user \"%s\": %m\n", username);
  255. return 0;
  256. }
  257.  
  258. if (pw->pw_dir == NULL)
  259. {
  260. (void)fprintf(stderr, LOGPREFIX "User \"%s\" has no homedir?\n", username);
  261. return 0;
  262. }
  263.  
  264. num = snprintf(path, sizeof(path), "%s/" MAILDIR_NAME "/" MAILDIRSIZE_FILENAME, pw->pw_dir);
  265. if (num < 0 || num > sizeof(path))
  266. {
  267. (void)fprintf(stderr, LOGPREFIX "Unable to copy maildir string to buffer for user \"%s\".\n", username);
  268. return 0;
  269. }
  270.  
  271. if (maildir_file_read(path, buf, sizeof(buf) - 1) <= 0)
  272. return 0;
  273.  
  274. quota.file = path;
  275. quota.total_bytes = quota.total_count = 0;
  276. if (!maildir_file_parse(&quota, buf))
  277. return 0;
  278.  
  279. if ((quota.bytes_limit != 0 && quota.total_bytes > quota.bytes_limit) ||
  280. (quota.count_limit != 0 && quota.total_count > quota.count_limit))
  281. {
  282. (void)fprintf(stderr, LOGPREFIX "User \"%s\" is over quota.\n", username);
  283. (void)printf("E553 User over quota. (#5.1.1)\n");
  284. }
  285.  
  286. return 0;
  287. }
  288.