diff options
Diffstat (limited to 'src/srs2.c')
-rw-r--r-- | src/srs2.c | 641 |
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; +} |