|
@@ -0,0 +1,242 @@
|
|
|
+/*
|
|
|
+ * Argument parser
|
|
|
+ *
|
|
|
+ * Copyright (c) 2017, Max von Buelow
|
|
|
+ * All rights reserved.
|
|
|
+ * Contact: https://maxvonbuelow.de
|
|
|
+ *
|
|
|
+ * This file is part of the MilterFrom project.
|
|
|
+ * https://github.com/magcks/milterfrom
|
|
|
+ *
|
|
|
+ * Redistribution and use in source and binary forms, with or without
|
|
|
+ * modification, are permitted provided that the following conditions are met:
|
|
|
+ * * Redistributions of source code must retain the above copyright
|
|
|
+ * notice, this list of conditions and the following disclaimer.
|
|
|
+ * * Redistributions in binary form must reproduce the above copyright
|
|
|
+ * notice, this list of conditions and the following disclaimer in the
|
|
|
+ * documentation and/or other materials provided with the distribution.
|
|
|
+ * * Neither the name of the copyright holder nor the
|
|
|
+ * names of its contributors may be used to endorse or promote products
|
|
|
+ * derived from this software without specific prior written permission.
|
|
|
+ *
|
|
|
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
|
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
|
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
|
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
|
|
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
|
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
|
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
|
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
|
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
+ */
|
|
|
+
|
|
|
+#include <sys/types.h>
|
|
|
+#include <sys/stat.h>
|
|
|
+#include <stdio.h>
|
|
|
+#include <stdlib.h>
|
|
|
+#include <string.h>
|
|
|
+#include <sysexits.h>
|
|
|
+#include <unistd.h>
|
|
|
+#include <errno.h>
|
|
|
+
|
|
|
+#include "libmilter/mfapi.h"
|
|
|
+#include "libmilter/mfdef.h"
|
|
|
+
|
|
|
+struct mlfiPriv
|
|
|
+{
|
|
|
+ int is_auth;
|
|
|
+ char *env_from;
|
|
|
+ int env_from_len;
|
|
|
+ int reject;
|
|
|
+};
|
|
|
+
|
|
|
+#define MLFIPRIV ((struct mlfiPriv*)smfi_getpriv(ctx))
|
|
|
+
|
|
|
+static unsigned long mta_caps = 0;
|
|
|
+
|
|
|
+// Function to extract addresses from the header/envelope fields.
|
|
|
+// If the field contains a < with a subsequent >, the inner part is used. If not, the whole header field is used. This allows matching "Max Mustermann <max.mustermann@example.invalid>" matching.
|
|
|
+const char *parse_address(const char *address, int *len)
|
|
|
+{
|
|
|
+ int inlen = strlen(address);
|
|
|
+ int pos_open = -1, pos_close = -1;
|
|
|
+ int i;
|
|
|
+ for (i = 0; i < inlen; ++i) {
|
|
|
+ if (address[i] == '<') pos_open = i;
|
|
|
+ else if (address[i] == '>') pos_close = i;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (pos_open != -1 && pos_close != -1 && pos_open < pos_close) {
|
|
|
+ *len = pos_close - pos_open - 1;
|
|
|
+ return address + pos_open + 1;
|
|
|
+ } else {
|
|
|
+ *len = inlen;
|
|
|
+ return address;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void mlfi_cleanup(SMFICTX *ctx)
|
|
|
+{
|
|
|
+ struct mlfiPriv *priv = MLFIPRIV;
|
|
|
+
|
|
|
+ if (priv == NULL) return;
|
|
|
+
|
|
|
+ free(priv->env_from);
|
|
|
+ free(priv);
|
|
|
+ smfi_setpriv(ctx, NULL);
|
|
|
+}
|
|
|
+
|
|
|
+sfsistat mlfi_envfrom(SMFICTX *ctx, char **envfrom)
|
|
|
+{
|
|
|
+ struct mlfiPriv *priv;
|
|
|
+
|
|
|
+ // Allocate some private memory.
|
|
|
+ priv = malloc(sizeof *priv);
|
|
|
+ if (priv == NULL) return SMFIS_TEMPFAIL;
|
|
|
+ memset(priv, '\0', sizeof *priv);
|
|
|
+
|
|
|
+ // Parse envelope from.
|
|
|
+ int len;
|
|
|
+ const char *from = parse_address(*envfrom, &len);
|
|
|
+ char *fromcp = strndup(from, len);
|
|
|
+ if (fromcp == NULL) return SMFIS_TEMPFAIL;
|
|
|
+
|
|
|
+ // Set private values.
|
|
|
+ priv->is_auth = smfi_getsymval(ctx, "{auth_type}") ? 1 : 0;
|
|
|
+ priv->env_from = fromcp;
|
|
|
+ priv->env_from_len = len;
|
|
|
+ priv->reject = 0;
|
|
|
+
|
|
|
+ smfi_setpriv(ctx, priv);
|
|
|
+
|
|
|
+ return SMFIS_CONTINUE;
|
|
|
+}
|
|
|
+
|
|
|
+sfsistat mlfi_header(SMFICTX *ctx, char *headerf, char *headerv)
|
|
|
+{
|
|
|
+ struct mlfiPriv *priv = MLFIPRIV;
|
|
|
+
|
|
|
+ // Perform checks if the sender is authenticated and the message is not rejected yet (the mail may contain multiple from tags, all have to match!).
|
|
|
+ if (priv->is_auth && !priv->reject) {
|
|
|
+ if (strcasecmp(headerf, "from") == 0) {
|
|
|
+ int len;
|
|
|
+ const char *from = parse_address(headerv, &len);
|
|
|
+
|
|
|
+ // Check whether header from matches envelope from and reject if not.
|
|
|
+ if (len != priv->env_from_len || strncasecmp(from, priv->env_from, len) != 0) priv->reject = 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return ((mta_caps & SMFIP_NR_HDR) != 0) ? SMFIS_NOREPLY : SMFIS_CONTINUE;
|
|
|
+}
|
|
|
+
|
|
|
+sfsistat
|
|
|
+mlfi_eom(SMFICTX *ctx)
|
|
|
+{
|
|
|
+ struct mlfiPriv *priv = MLFIPRIV;
|
|
|
+ if (priv->reject) {
|
|
|
+ smfi_setreply(ctx, "550", "5.7.1", "Rejected due to unmatching envelope and header sender.");
|
|
|
+ mlfi_cleanup(ctx);
|
|
|
+ return SMFIS_REJECT;
|
|
|
+ }
|
|
|
+ mlfi_cleanup(ctx);
|
|
|
+ return SMFIS_CONTINUE;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+sfsistat
|
|
|
+mlfi_abort(SMFICTX *ctx)
|
|
|
+{
|
|
|
+ mlfi_cleanup(ctx);
|
|
|
+ return SMFIS_CONTINUE;
|
|
|
+}
|
|
|
+
|
|
|
+sfsistat
|
|
|
+mlfi_negotiate(SMFICTX *ctx, unsigned long f0, unsigned long f1, unsigned long f2, unsigned long f3, unsigned long *pf0, unsigned long *pf1, unsigned long *pf2, unsigned long *pf3)
|
|
|
+{
|
|
|
+ *pf0 = 0;
|
|
|
+ /* milter protocol steps: all but connect, HELO, RCPT */
|
|
|
+ *pf1 = SMFIP_NOCONNECT|SMFIP_NOHELO|SMFIP_NORCPT;
|
|
|
+ mta_caps = f1;
|
|
|
+ if ((mta_caps & SMFIP_NR_HDR) != 0) *pf1 |= SMFIP_NR_HDR;
|
|
|
+ *pf2 = 0;
|
|
|
+ *pf3 = 0;
|
|
|
+ return SMFIS_CONTINUE;
|
|
|
+}
|
|
|
+
|
|
|
+struct smfiDesc smfilter =
|
|
|
+{
|
|
|
+ "Header from check", /* filter name */
|
|
|
+ SMFI_VERSION, /* version code -- do not change */
|
|
|
+ 0, /* flags */
|
|
|
+ NULL, /* connection info filter */
|
|
|
+ NULL, /* SMTP HELO command filter */
|
|
|
+ mlfi_envfrom, /* envelope sender filter */
|
|
|
+ NULL, /* envelope recipient filter */
|
|
|
+ mlfi_header, /* header filter */
|
|
|
+ NULL, /* end of header */
|
|
|
+ NULL, /* body block filter */
|
|
|
+ mlfi_eom, /* end of message */
|
|
|
+ mlfi_abort, /* message aborted */
|
|
|
+ NULL, /* connection cleanup */
|
|
|
+ NULL, /* unknown/unimplemented SMTP commands */
|
|
|
+ NULL, /* DATA command filter */
|
|
|
+ mlfi_negotiate /* option negotiation at connection startup */
|
|
|
+};
|
|
|
+
|
|
|
+int main(int argc, char **argv)
|
|
|
+{
|
|
|
+ int c, daemonize = 0;
|
|
|
+ char *pidfilename = NULL, *sockname = NULL;
|
|
|
+ FILE *pidfile = NULL;
|
|
|
+
|
|
|
+ while ((c = getopt(argc, argv, "ds:p:")) != -1) {
|
|
|
+ switch (c) {
|
|
|
+ case 's':
|
|
|
+ sockname = strdup(optarg);
|
|
|
+ break;
|
|
|
+ case 'p':
|
|
|
+ pidfilename = strdup(optarg);
|
|
|
+ break;
|
|
|
+ case 'd':
|
|
|
+ daemonize = 1;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!sockname) {
|
|
|
+ fprintf(stderr, "%s: Missing required -s argument\n", argv[0]);
|
|
|
+ exit(EX_USAGE);
|
|
|
+ }
|
|
|
+ if (pidfilename) {
|
|
|
+ unlink(pidfilename);
|
|
|
+ pidfile = fopen(pidfilename, "w");
|
|
|
+ if (!pidfile)
|
|
|
+ {
|
|
|
+ fprintf(stderr, "Could not open pidfile: %s\n", strerror(errno));
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+ free(pidfilename);
|
|
|
+ }
|
|
|
+ if (daemonize) {
|
|
|
+ if (daemon(0, 0) == -1) {
|
|
|
+ fprintf(stderr, "daemon() failed: %s\n", strerror(errno));
|
|
|
+ exit(EXIT_FAILURE);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (pidfile) {
|
|
|
+ fprintf(pidfile, "%ld\n", (long)getpid());
|
|
|
+ fclose(pidfile);
|
|
|
+ }
|
|
|
+
|
|
|
+ struct stat junk;
|
|
|
+ if (stat(sockname, &junk) == 0) unlink(sockname);
|
|
|
+ smfi_setconn(sockname);
|
|
|
+ free(sockname);
|
|
|
+
|
|
|
+ if (smfi_register(smfilter) == MI_FAILURE) {
|
|
|
+ fprintf(stderr, "smfi_register failed\n");
|
|
|
+ exit(EX_UNAVAILABLE);
|
|
|
+ }
|
|
|
+ return smfi_main();
|
|
|
+}
|