Download | Plain Text | No Line Numbers


  1. /*
  2.  * Copyright (c) 2016, 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 <stdbool.h>
  27. #include <stdarg.h>
  28. #include <string.h>
  29. #include <unistd.h>
  30. #include <errno.h>
  31. #include <sys/socket.h>
  32. #include <sys/un.h>
  33. #include <netdb.h>
  34.  
  35. #define LOGPREFIX "QUOTA: "
  36.  
  37. typedef struct
  38. {
  39. const char *action, *msg;
  40. } policy_reply;
  41.  
  42. int check_parent(void)
  43. {
  44. /* check parent */
  45. pid_t parent = getppid();
  46. char buf[256];
  47. int num = snprintf(buf, sizeof(buf), "/proc/%d/exe", parent);
  48. if (num < 0 || num > sizeof(buf))
  49. {
  50. (void)fprintf(stderr, LOGPREFIX "Unable to copy string to buffer\n");
  51. return 0;
  52. }
  53.  
  54. char buf2[256];
  55. num = readlink(buf, buf2, sizeof(buf2));
  56. if (num < 0)
  57. {
  58. (void)fprintf(stderr, LOGPREFIX "Unable to read parent from proc-fs: %m\n");
  59. return 0;
  60. }
  61. buf2[num] = '\0';
  62.  
  63. const char *smtpbin = "/var/qmail/bin/qmail-smtpd";
  64. if (strcmp(smtpbin, buf2) != 0)
  65. {
  66. (void)fprintf(stderr, LOGPREFIX "Parent \"%s\" doesn't match qmail-smtp\n", buf2);
  67. return 0;
  68. }
  69.  
  70. return 1;
  71. }
  72.  
  73. static int net_connect_inet(const char *host, const char *port)
  74. {
  75. struct addrinfo hints = {
  76. .ai_family = AF_UNSPEC,
  77. .ai_socktype = SOCK_STREAM
  78. };
  79. struct addrinfo *result;
  80.  
  81. int s = getaddrinfo(host, port, &hints, &result);
  82. if (s != 0)
  83. {
  84. (void)fprintf(stderr, LOGPREFIX "getaddrinfo: %s\n", gai_strerror(s));
  85. return -1;
  86. }
  87.  
  88. //FIXME: connect might hang
  89. int sockfd = -1;
  90. struct addrinfo *r;
  91. for (r = result; r != NULL; r=r->ai_next)
  92. {
  93. sockfd = socket(r->ai_family, r->ai_socktype | SOCK_CLOEXEC,
  94. r->ai_protocol);
  95. if (sockfd == -1)
  96. continue;
  97. if (connect(sockfd, r->ai_addr, r->ai_addrlen) != -1)
  98. break; /* success */
  99. (void)close(sockfd);
  100. sockfd = -1;
  101. break; /* we only try the first record */
  102. }
  103.  
  104. (void)freeaddrinfo(result);
  105. return sockfd;
  106. }
  107.  
  108. static int net_connect_unix(const char *path)
  109. {
  110. int sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
  111. if (sockfd < 0)
  112. return -1;
  113.  
  114. struct sockaddr_un saddr;
  115. saddr.sun_family = AF_UNIX;
  116. (void)strcpy(saddr.sun_path, path);
  117. int conn = connect(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));
  118. if (conn != -1)
  119. return sockfd;
  120. (void)close(sockfd);
  121. return -1;
  122. }
  123.  
  124. static int net_connect(const char *uri, bool readenv)
  125. {
  126. int sockfd = -1;
  127. if (strncmp(uri, "tcp://", strlen("tcp://")) == 0)
  128. {
  129. char *path = strdup(uri + strlen("tcp://"));
  130.  
  131. char *portptr = strrchr(path, ':');
  132. if (portptr == NULL)
  133. {
  134. (void)fprintf(stderr, LOGPREFIX "Invalid uri: port missing?\n");
  135. (void)free(path);
  136. return -1;
  137. }
  138. *portptr = '\0';
  139.  
  140. char *host = path;
  141. if (host[0] == '[' && *(portptr - 1) == ']')
  142. {
  143. host++;
  144. *(portptr - 1) = '\0';
  145. }
  146.  
  147. sockfd = net_connect_inet(host, portptr + 1);
  148. (void)free(path);
  149. }
  150. else if (strncmp(uri, "unix://", strlen("unix://")) == 0)
  151. sockfd = net_connect_unix(uri + strlen("unix://"));
  152. else if (readenv && strncmp(uri, "env://", strlen("env://")) == 0)
  153. {
  154. if ((uri = getenv(uri + strlen("env://"))) != NULL)
  155. return net_connect(uri, false);
  156. (void)fprintf(stderr, LOGPREFIX "Invalid uri: env var undefined\n");
  157. return -1;
  158. }
  159. else
  160. {
  161. (void)fprintf(stderr, LOGPREFIX "Invalid uri: unknown syntax\n");
  162. return -1;
  163. }
  164.  
  165. if (sockfd < 0)
  166. (void)fprintf(stderr, LOGPREFIX "Unable to connect to socket: %m\n");
  167. return sockfd;
  168. }
  169.  
  170. static const policy_reply policy_check(const char *uri, const char *format, ...)
  171. {
  172. policy_reply reply = { .action = NULL, .msg = NULL };
  173.  
  174. /* connect to policy server */
  175. int sockfd = net_connect(uri, true);
  176. if (sockfd < 0)
  177. goto end;
  178.  
  179. /* send data */
  180. va_list ap;
  181. va_start(ap, format);
  182. (void)vdprintf(sockfd, format, ap);
  183. (void)dprintf(sockfd, "\n");
  184. va_end(ap);
  185. if (errno)
  186. {
  187. (void)fprintf(stderr, LOGPREFIX "Error while sending to policy server: %m\n");
  188. goto end;
  189. }
  190.  
  191. /* wait for reply */
  192. static char buf[2048];
  193. ssize_t read = recv(sockfd, &buf, sizeof(buf) - 1, 0);
  194. if (read <= 0)
  195. {
  196. (void)fprintf(stderr, LOGPREFIX "Error while receiving from policy server: %m\n");
  197. goto end;
  198. }
  199.  
  200. /* parse reply */
  201. if (read < strlen("action=") + 3 /* 1x reply char + 2x LF */
  202. || strncmp("action=", buf, strlen("action=")) != 0
  203. || buf[read - 2] != '\n')
  204. {
  205. (void)fprintf(stderr, LOGPREFIX "Uknown reply format from policy server\n");
  206. goto end;
  207. }
  208. buf[read - 2] = '\0';
  209.  
  210. reply.action = buf + strlen("action=");
  211. char *msgptr = strchr(reply.action, ' ');
  212. if (msgptr)
  213. {
  214. *msgptr = '\0';
  215. reply.msg = ++msgptr;
  216. }
  217.  
  218. end:
  219. (void)close(sockfd);
  220. return reply;
  221. }
  222.  
  223. int main(int argc, char *argv[])
  224. {
  225. if (argc != 2)
  226. return 0;
  227.  
  228. const char *username = getenv("DTUSER");
  229. if (!username)
  230. return 0;
  231.  
  232. /* check parent */
  233. if (!check_parent())
  234. return 0;
  235.  
  236. // size not exported by qmail (at least for now)
  237. // assume a minimum size of 500 bytes
  238. size_t size = 500;
  239.  
  240. const policy_reply reply = policy_check(argv[1],
  241. "recipient=%s\nsize=%zu\n", username, size);
  242. if (reply.action == NULL)
  243. return 0;
  244.  
  245. if (*reply.action == '5' || *reply.action == '4'
  246. || strcmp(reply.action, "REJECT") == 0)
  247. {
  248. const char *code = (*reply.action == '5' || *reply.action == '4')
  249. ? reply.action : "552";
  250. const char *msg = (reply.msg) ? reply.msg : "User over quota. (#5.2.2)";
  251. (void)fprintf(stderr, LOGPREFIX "User \"%s\" is over quota.\n", username);
  252. (void)printf("E%s %s\n", code, msg);
  253. }
  254. else if (strcmp(reply.action, "OK") == 0
  255. || strcmp(reply.action, "DUNNO") == 0)
  256. { ; } /* nothing here */
  257. else
  258. {
  259. (void)fprintf(stderr, LOGPREFIX "Unsupported reply from policy server:"
  260. " reply=%s msg=%s\n", reply.action, (reply.msg) ? reply.msg : "NULL");
  261. }
  262.  
  263. return 0;
  264. }
  265.