summaryrefslogtreecommitdiff
path: root/src/srs2.c
diff options
context:
space:
mode:
authorJannis Hoffmann <jannis@fehcom.de>2024-07-03 15:48:04 +0200
committerJannis Hoffmann <jannis@fehcom.de>2024-07-03 15:48:04 +0200
commit89b7b67a13ebb7965cc7f13ad0595e2194a2d34c (patch)
tree25efd77a90ae87236e6730d8ea3846bbe0fd126f /src/srs2.c
add sqmail-4.2.29asqmail-4.2
Diffstat (limited to 'src/srs2.c')
-rw-r--r--src/srs2.c641
1 files changed, 641 insertions, 0 deletions
diff --git a/src/srs2.c b/src/srs2.c
new file mode 100644
index 0000000..1bb431b
--- /dev/null
+++ b/src/srs2.c
@@ -0,0 +1,641 @@
+/* Copyright (c) 2004 Shevek (srs@anarres.org)
+ * All rights reserved.
+ *
+ * This file is a part of libsrs2 from http://www.libsrs2.org/
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, under the terms of either the GNU General Public
+ * License version 2 or the BSD license, at the discretion of the
+ * user. Copies of these licenses have been included in the libsrs2
+ * distribution. See the the file called LICENSE for more
+ * information.
+ */
+
+/* This is a minimal adapted s/qmail version; it requires complete
+ refactoring:
+
+ a) Use stralloc for addresses
+ b) Replace stdio, str*, and mem* functions
+ c) Use tai64 for timestamp function
+ d) Remove va args
+ e) Reduce code by 50%
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <time.h> /* time */
+#include <sys/types.h> /* tyepdefs */
+#include <sys/time.h> /* timeval / timezone struct */
+#include <string.h> /* memcpy, strcpy, memset */
+#include "srs2.h"
+#include "sha1.h"
+
+#ifndef HAVE_STRCASECMP
+# ifdef HAVE__STRICMP
+# define strcasecmp _stricmp
+# endif
+#endif
+
+#ifndef HAVE_STRNCASECMP
+# ifdef HAVE__STRNICMP
+# define strncasecmp _strnicmp
+# endif
+#endif
+
+ /* Use this */
+#define STRINGP(s) ((s != NULL) && (*(s) != '\0'))
+
+static const char *srs_separators = "=-+";
+
+static srs_malloc_t srs_f_malloc = malloc;
+static srs_realloc_t srs_f_realloc = realloc;
+static srs_free_t srs_f_free = free;
+
+int srs_set_malloc(srs_malloc_t m, srs_realloc_t r, srs_free_t f)
+{
+ srs_f_malloc = m;
+ srs_f_realloc = r;
+ srs_f_free = f;
+ return SRS_SUCCESS;
+}
+
+#define X(e,s) if (code == e) return s;
+
+const char *srs_strerror(int code)
+{
+ X(0,"")
+ /* Simple errors */
+ X(SRS_SUCCESS,"Success")
+ X(SRS_ENOTSRSADDRESS,"Not an SRS address.")
+
+ /* Config errors */
+ X(SRS_ENOSECRETS,"No secrets in SRS configuration.")
+ X(SRS_ESEPARATORINVALID,"Invalid separator suggested.")
+
+ /* Input errors */
+ X(SRS_ENOSENDERATSIGN,"No at sign in sender address")
+ X(SRS_EBUFTOOSMALL,"Buffer too small.")
+
+ /* Syntax errors */
+ X(SRS_ENOSRS0HOST,"No host in SRS0 address.")
+ X(SRS_ENOSRS0USER,"No user in SRS0 address.")
+ X(SRS_ENOSRS0HASH,"No hash in SRS0 address.")
+ X(SRS_ENOSRS0STAMP,"No timestamp in SRS0 address.")
+ X(SRS_ENOSRS1HOST,"No host in SRS1 address.")
+ X(SRS_ENOSRS1USER,"No user in SRS1 address.")
+ X(SRS_ENOSRS1HASH,"No hash in SRS1 address.")
+ X(SRS_EBADTIMESTAMPCHAR,"Bad base32 character in timestamp.")
+ X(SRS_EHASHTOOSHORT,"Hash too short in SRS address.")
+
+ /* SRS errors */
+ X(SRS_ETIMESTAMPOUTOFDATE,"Time stamp out of date.")
+ X(SRS_EHASHINVALID,"Hash invalid in SRS address.")
+
+ return "Unknown SRS error.";
+}
+
+srs_t *srs_new()
+{
+ srs_t *srs = (srs_t *)srs_f_malloc(sizeof(srs_t));
+ srs_init(srs);
+ return srs;
+}
+
+void srs_init(srs_t *srs)
+{
+ memset(srs, 0, sizeof(srs_t));
+ srs->secrets = NULL;
+ srs->numsecrets = 0;
+ srs->separator = '=';
+ srs->maxage = 21;
+ srs->hashlen = 4;
+ srs->hashmin = srs->hashlen;
+ srs->alwaysrewrite = FALSE;
+}
+
+void srs_free(srs_t *srs)
+{
+ int i;
+ for (i = 0; i < srs->numsecrets; i++) {
+ memset(srs->secrets[i], 0, strlen(srs->secrets[i]));
+ srs_f_free(srs->secrets[i]);
+ srs->secrets[i] = '\0';
+ }
+ srs_f_free(srs);
+}
+
+int srs_add_secret(srs_t *srs, const char *secret)
+{
+ int newlen = (srs->numsecrets + 1) * sizeof(char *);
+ srs->secrets = (char **)srs_f_realloc(srs->secrets, newlen);
+ srs->secrets[srs->numsecrets++] = strdup(secret);
+ return SRS_SUCCESS;
+}
+
+const char *srs_get_secret(srs_t *srs, int idx)
+{
+ if (idx < srs->numsecrets)
+ return srs->secrets[idx];
+ return NULL;
+}
+
+#define SRS_PARAM_DEFINE(n, t) \
+ int srs_set_ ## n (srs_t *srs, t value) { \
+ srs->n = value; \
+ return SRS_SUCCESS; \
+ } \
+ t srs_get_ ## n (srs_t *srs) { \
+ return srs->n; \
+ }
+
+int srs_set_separator(srs_t *srs, char value)
+{
+ if (strchr(srs_separators, value) == NULL)
+ return SRS_ESEPARATORINVALID;
+ srs->separator = value;
+ return SRS_SUCCESS;
+}
+
+char srs_get_separator(srs_t *srs)
+{
+ return srs->separator;
+}
+
+SRS_PARAM_DEFINE(maxage, int)
+ /* XXX Check hashlen >= hashmin */
+SRS_PARAM_DEFINE(hashlen, int)
+SRS_PARAM_DEFINE(hashmin, int)
+SRS_PARAM_DEFINE(alwaysrewrite, srs_bool)
+SRS_PARAM_DEFINE(noforward, srs_bool)
+SRS_PARAM_DEFINE(noreverse, srs_bool)
+
+/* Don't mess with these unless you know what you're doing well
+ * enough to rewrite the timestamp functions. These are based on
+ * a 2 character timestamp. Changing these in the wild is probably
+ * a bad idea. */
+#define SRS_TIME_PRECISION (60 * 60 * 24) /* One day */
+#define SRS_TIME_BASEBITS 5 /* 2^5 = 32 = strlen(CHARS) */
+/* This had better be a real variable since we do arithmethic
+ * with it. */
+const char *SRS_TIME_BASECHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
+#define SRS_TIME_SIZE 2
+#define SRS_TIME_SLOTS (1<<(SRS_TIME_BASEBITS<<(SRS_TIME_SIZE-1)))
+
+int srs_timestamp_create(srs_t *srs, char *buf, time_t now)
+{
+ now = now / SRS_TIME_PRECISION;
+ buf[1] = SRS_TIME_BASECHARS[now & ((1 << SRS_TIME_BASEBITS) - 1)];
+ now = now >> SRS_TIME_BASEBITS;
+ buf[0] = SRS_TIME_BASECHARS[now & ((1 << SRS_TIME_BASEBITS) - 1)];
+ buf[2] = '\0';
+ return SRS_SUCCESS;
+}
+
+int srs_timestamp_check(srs_t *srs, const char *stamp)
+{
+ const char *sp;
+ char *bp;
+ int off;
+ time_t now;
+ time_t then;
+
+ /* We had better go around this loop exactly twice! */
+ then = 0;
+ for (sp = stamp; *sp; sp++) {
+ bp = strchr(SRS_TIME_BASECHARS, toupper(*sp));
+ if (bp == NULL)
+ return SRS_EBADTIMESTAMPCHAR;
+ off = bp - SRS_TIME_BASECHARS;
+ then = (then << SRS_TIME_BASEBITS) | off;
+ }
+
+ time(&now);
+ now = (now / SRS_TIME_PRECISION) % SRS_TIME_SLOTS;
+ while (now < then)
+ now = now + SRS_TIME_SLOTS;
+
+ if (now <= then + srs->maxage)
+ return SRS_SUCCESS;
+ return SRS_ETIMESTAMPOUTOFDATE;
+}
+
+const char *SRS_HASH_BASECHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789+/";
+
+static void srs_hash_create_v(srs_t *srs, int idx, char *buf, int nargs, va_list ap)
+{
+ sha1_ctx ctx;
+ char srshash[SHA1_DIGESTSIZE + 1];
+ char *secret;
+ char *data;
+ int len;
+ char *lcdata;
+ unsigned char *hp;
+ char *bp;
+ int i;
+ int j;
+
+ secret = srs->secrets[idx];
+ sha1_init(&ctx);
+ sha1_update(&ctx, secret, strlen(secret));
+
+ for (i = 0; i < nargs; i++) {
+ data = va_arg(ap, char *);
+ len = strlen(data);
+ lcdata = alloca(len + 1);
+ for (j = 0; j < len; j++) {
+ if (isupper(data[j]))
+ lcdata[j] = tolower(data[j]);
+ else
+ lcdata[j] = data[j];
+ }
+ sha1_update(&ctx, lcdata, len);
+ }
+
+ sha1_final(srshash, &ctx); /* args inverted */
+ srshash[SHA1_DIGESTSIZE] = '\0';
+
+ /* A little base64 encoding. Just a little. */
+ hp = (unsigned char *)srshash;
+ bp = buf;
+ for (i = 0; i < srs->hashlen; i++) {
+ switch (i & 0x03) {
+ default: /* NOTREACHED */
+ case 0:
+ j = (*hp >> 2);
+ break;
+ case 1:
+ j = ((*hp & 0x03) << 4) |
+ ((*(hp + 1) & 0xF0) >> 4);
+ hp++;
+ break;
+ case 2:
+ j = ((*hp & 0x0F) << 2) |
+ ((*(hp + 1) & 0xC0) >> 6);
+ hp++;
+ break;
+ case 3:
+ j = (*hp++ & 0x3F);
+ break;
+ }
+ *bp++ = SRS_HASH_BASECHARS[j];
+ }
+
+ *bp = '\0';
+ buf[srs->hashlen] = '\0';
+}
+
+int srs_hash_create(srs_t *srs, char *buf, int nargs, ...)
+{
+ va_list ap;
+
+ if (srs->numsecrets == 0)
+ return SRS_ENOSECRETS;
+ if (srs->secrets == NULL)
+ return SRS_ENOSECRETS;
+ if (srs->secrets[0] == NULL)
+ return SRS_ENOSECRETS;
+
+ va_start(ap, nargs);
+ srs_hash_create_v(srs, 0, buf, nargs, ap);
+ va_end(ap);
+
+ return SRS_SUCCESS;
+}
+
+int srs_hash_check(srs_t *srs, char *hash, int nargs, ...)
+{
+ va_list ap;
+ char *srshash;
+ char *tmp;
+ int len;
+ int i;
+
+ len = strlen(hash);
+ if (len < srs->hashmin)
+ return SRS_EHASHTOOSHORT;
+ if (len < srs->hashlen) {
+ tmp = alloca(srs->hashlen + 1);
+ strncpy(tmp, hash, srs->hashlen);
+ tmp[srs->hashlen] = '\0';
+ hash = tmp;
+ len = srs->hashlen;
+ }
+
+ for (i = 0; i < srs->numsecrets; i++) {
+ va_start(ap, nargs);
+ srshash = alloca(srs->hashlen + 1);
+ srs_hash_create_v(srs, i, srshash, nargs, ap);
+ va_end(ap);
+ if (strncasecmp(hash, srshash, len) == 0)
+ return SRS_SUCCESS;
+ }
+
+ return SRS_EHASHINVALID;
+}
+
+int srs_compile_shortcut(srs_t *srs,
+ char *buf, int buflen,
+ char *sendhost, char *senduser,
+ const char *aliashost) {
+ char *srshash;
+ char srsstamp[SRS_TIME_SIZE + 1];
+ int len;
+ int ret;
+
+ /* This never happens if we get called from guarded() */
+ if ((strncasecmp(senduser, SRS0TAG, 4) == 0) &&
+ (strchr(srs_separators, senduser[4]) != NULL)) {
+ sendhost = senduser + 5;
+ if (*sendhost == '\0')
+ return SRS_ENOSRS0HOST;
+ senduser = strchr(sendhost, SRSSEP);
+ if ((senduser == NULL) || (*senduser == '\0'))
+ return SRS_ENOSRS0USER;
+ }
+
+ len = strlen(SRS0TAG) + 1 +
+ srs->hashlen + 1 +
+ SRS_TIME_SIZE + 1 +
+ strlen(sendhost) + 1 + strlen(senduser)
+ + 1 + strlen(aliashost);
+ if (len >= buflen)
+ return SRS_EBUFTOOSMALL;
+
+ ret = srs_timestamp_create(srs, srsstamp, time(NULL));
+ if (ret != SRS_SUCCESS)
+ return ret;
+ srshash = alloca(srs->hashlen + 1);
+ ret = srs_hash_create(srs, srshash,3, srsstamp, sendhost, senduser);
+ if (ret != SRS_SUCCESS)
+ return ret;
+
+ sprintf(buf, SRS0TAG "%c%s%c%s%c%s%c%s@%s", srs->separator,
+ srshash, SRSSEP, srsstamp, SRSSEP,
+ sendhost, SRSSEP, senduser,
+ aliashost);
+
+ return SRS_SUCCESS;
+}
+
+int srs_compile_guarded(srs_t *srs,
+ char *buf, int buflen,
+ char *sendhost, char *senduser,
+ const char *aliashost) {
+ char *srshost;
+ char *srsuser;
+ char *srshash;
+ int len;
+ int ret;
+
+ if ((strncasecmp(senduser, SRS1TAG, 4) == 0) &&
+ (strchr(srs_separators, senduser[4]) != NULL)) {
+ /* Used as a temporary convenience var */
+ srshash = senduser + 5;
+ if (*srshash == '\0')
+ return SRS_ENOSRS1HASH;
+ /* Used as a temporary convenience var */
+ srshost = strchr(srshash, SRSSEP);
+ if (!STRINGP(srshost))
+ return SRS_ENOSRS1HOST;
+ *srshost++ = '\0';
+ srsuser = strchr(srshost, SRSSEP);
+ if (!STRINGP(srsuser))
+ return SRS_ENOSRS1USER;
+ *srsuser++ = '\0';
+ srshash = alloca(srs->hashlen + 1);
+ ret = srs_hash_create(srs, srshash, 2, srshost, srsuser);
+ if (ret != SRS_SUCCESS)
+ return ret;
+ len = strlen(SRS1TAG) + 1 +
+ srs->hashlen + 1 +
+ strlen(srshost) + 1 + strlen(srsuser)
+ + 1 + strlen(aliashost);
+ if (len >= buflen)
+ return SRS_EBUFTOOSMALL;
+ sprintf(buf, SRS1TAG "%c%s%c%s%c%s@%s", srs->separator,
+ srshash, SRSSEP,
+ srshost, SRSSEP, srsuser,
+ aliashost);
+ return SRS_SUCCESS;
+ }
+ else if ((strncasecmp(senduser, SRS0TAG, 4) == 0) &&
+ (strchr(srs_separators, senduser[4]) != NULL)) {
+ srsuser = senduser + 4;
+ srshost = sendhost;
+ srshash = alloca(srs->hashlen + 1);
+ ret = srs_hash_create(srs, srshash, 2, srshost, srsuser);
+ if (ret != SRS_SUCCESS)
+ return ret;
+ len = strlen(SRS1TAG) + 1 +
+ srs->hashlen + 1 +
+ strlen(srshost) + 1 + strlen(srsuser)
+ + 1 + strlen(aliashost);
+ if (len >= buflen)
+ return SRS_EBUFTOOSMALL;
+ sprintf(buf, SRS1TAG "%c%s%c%s%c%s@%s", srs->separator,
+ srshash, SRSSEP,
+ srshost, SRSSEP, srsuser,
+ aliashost);
+ }
+ else {
+ return srs_compile_shortcut(srs, buf, buflen,
+ sendhost, senduser, aliashost);
+ }
+
+ return SRS_SUCCESS;
+}
+
+int srs_parse_shortcut(srs_t *srs, char *buf, int buflen, char *senduser)
+{
+ char *srshash;
+ char *srsstamp;
+ char *srshost;
+ char *srsuser;
+ int ret;
+
+ if (strncasecmp(senduser, SRS0TAG, 4) == 0) {
+ srshash = senduser + 5;
+ if (!STRINGP(srshash))
+ return SRS_ENOSRS0HASH;
+ srsstamp = strchr(srshash, SRSSEP);
+ if (!STRINGP(srsstamp))
+ return SRS_ENOSRS0STAMP;
+ *srsstamp++ = '\0';
+ srshost = strchr(srsstamp, SRSSEP);
+ if (!STRINGP(srshost))
+ return SRS_ENOSRS0HOST;
+ *srshost++ = '\0';
+ srsuser = strchr(srshost, SRSSEP);
+ if (!STRINGP(srsuser))
+ return SRS_ENOSRS0USER;
+ *srsuser++ = '\0';
+ ret = srs_timestamp_check(srs, srsstamp);
+ if (ret != SRS_SUCCESS)
+ return ret;
+ ret = srs_hash_check(srs, srshash, 3, srsstamp,
+ srshost, srsuser);
+ if (ret != SRS_SUCCESS)
+ return ret;
+ sprintf(buf, "%s@%s", srsuser, srshost);
+ return SRS_SUCCESS;
+ }
+
+ return SRS_ENOTSRSADDRESS;
+}
+
+int srs_parse_guarded(srs_t *srs, char *buf, int buflen, char *senduser)
+{
+ char *srshash;
+ char *srshost;
+ char *srsuser;
+ int ret;
+
+ if (strncasecmp(senduser, SRS1TAG, 4) == 0) {
+ srshash = senduser + 5;
+ if (!STRINGP(srshash))
+ return SRS_ENOSRS1HASH;
+ srshost = strchr(srshash, SRSSEP);
+ if (!STRINGP(srshost))
+ return SRS_ENOSRS1HOST;
+ *srshost++ = '\0';
+ srsuser = strchr(srshost, SRSSEP);
+ if (!STRINGP(srsuser))
+ return SRS_ENOSRS1USER;
+ *srsuser++ = '\0';
+ ret = srs_hash_check(srs, srshash, 2, srshost, srsuser);
+ if (ret != SRS_SUCCESS)
+ return ret;
+ sprintf(buf, SRS0TAG "%s@%s", srsuser, srshost);
+ return SRS_SUCCESS;
+ }
+ else {
+ return srs_parse_shortcut(srs, buf, buflen, senduser);
+ }
+}
+
+int srs_forward(srs_t *srs, char *buf, int buflen,
+ const char *sender, const char *alias)
+{
+ char *senduser;
+ char *sendhost;
+ char *tmp;
+ int len;
+
+ if (srs->noforward)
+ return SRS_ENOTREWRITTEN;
+
+ /* This is allowed to be a plain domain */
+ while ((tmp = strchr(alias, '@')) != NULL)
+ alias = tmp + 1;
+
+ tmp = strchr(sender, '@');
+ if (tmp == NULL)
+ return SRS_ENOSENDERATSIGN;
+ sendhost = tmp + 1;
+
+ len = strlen(sender);
+
+ if (! srs->alwaysrewrite) {
+ if (strcasecmp(sendhost, alias) == 0) {
+ if (strlen(sender) >= buflen)
+ return SRS_EBUFTOOSMALL;
+ strcpy(buf, sender);
+ return SRS_SUCCESS;
+ }
+ }
+
+ /* Reconstruct the whole show into our alloca() buffer. */
+ senduser = alloca(len + 1);
+ strcpy(senduser, sender);
+ tmp = (senduser + (tmp - sender));
+ sendhost = tmp + 1;
+ *tmp = '\0';
+
+ return srs_compile_guarded(srs, buf, buflen,
+ sendhost, senduser, alias);
+}
+
+int srs_forward_alloc(srs_t *srs, char **sptr,
+ const char *sender, const char *alias)
+{
+ char *buf;
+ int slen;
+ int alen;
+ int len;
+ int ret;
+
+ if (srs->noforward)
+ return SRS_ENOTREWRITTEN;
+
+ slen = strlen(sender);
+ alen = strlen(alias);
+
+ /* strlen(SRSxTAG) + strlen("====+@") < 64 */
+ len = slen + alen + srs->hashlen + SRS_TIME_SIZE + 64;
+ buf = (char *)srs_f_malloc(len);
+
+ ret = srs_forward(srs, buf, len, sender, alias);
+
+ if (ret == SRS_SUCCESS)
+ *sptr = buf;
+ else
+ srs_f_free(buf);
+
+ return ret;
+}
+
+int srs_reverse(srs_t *srs, char *buf, int buflen, const char *sender)
+{
+ char *senduser;
+ char *tmp;
+ int len;
+
+ if (!SRS_IS_SRS_ADDRESS(sender))
+ return SRS_ENOTSRSADDRESS;
+
+ if (srs->noreverse)
+ return SRS_ENOTREWRITTEN;
+
+ len = strlen(sender);
+ if (len >= buflen)
+ return SRS_EBUFTOOSMALL;
+ senduser = alloca(len + 1);
+ strcpy(senduser, sender);
+
+ /* We don't really care about the host for reversal. */
+ tmp = strchr(senduser, '@');
+ if (tmp != NULL)
+ *tmp = '\0';
+ return srs_parse_guarded(srs, buf, buflen, senduser);
+}
+
+int srs_reverse_alloc(srs_t *srs, char **sptr, const char *sender)
+{
+ char *buf;
+ int len;
+ int ret;
+
+ *sptr = NULL;
+
+ if (!SRS_IS_SRS_ADDRESS(sender))
+ return SRS_ENOTSRSADDRESS;
+
+ if (srs->noreverse)
+ return SRS_ENOTREWRITTEN;
+
+ len = strlen(sender) + 1;
+ buf = (char *)srs_f_malloc(len);
+
+ ret = srs_reverse(srs, buf, len, sender);
+
+ if (ret == SRS_SUCCESS)
+ *sptr = buf;
+ else
+ srs_f_free(buf);
+
+ return ret;
+}