aboutsummaryrefslogtreecommitdiff
path: root/src/main.c
diff options
context:
space:
mode:
authorThomas Voss <mail@thomasvoss.com> 2024-08-26 09:04:11 +0200
committerThomas Voss <mail@thomasvoss.com> 2024-08-26 09:04:11 +0200
commit3621a04cf020cba747ba75136aec7a575890cceb (patch)
tree1cb7e00db51863beb015f349ac8990db9261a873 /src/main.c
parent94ea8d1ac53b3aada2c616f84b8e4686ae51054c (diff)
Huge overhall; fix code; remove external deps
Diffstat (limited to 'src/main.c')
-rw-r--r--src/main.c191
1 files changed, 191 insertions, 0 deletions
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..40318cf
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,191 @@
+#include <err.h>
+#include <errno.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <libgen.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdnoreturn.h>
+#include <string.h>
+#include <time.h>
+
+#include "base32.h"
+#include "common.h"
+#include "hmac.h"
+#include "sha1.h"
+#include "xendian.h"
+
+static void process(const char *, size_t);
+static void process_stdin(void);
+static inline uint32_t pow32(uint32_t, uint32_t)
+ __attribute__((always_inline, const));
+static inline bool xisdigit(char)
+ __attribute__((always_inline, const));
+static inline bool bigendian(void)
+ __attribute__((always_inline, const));
+
+static int digits = 6, period = 30;
+
+static noreturn void
+usage(const char *argv0)
+{
+ fprintf(stderr,
+ "Usage: %s [-d digits] [-p period] [secret]\n"
+ " %s -h\n",
+ argv0, argv0);
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char **argv)
+{
+ int opt;
+ static const struct option longopts[] = {
+ {"digits", required_argument, 0, 'd'},
+ {"period", required_argument, 0, 'p'},
+ {0},
+ };
+
+ argv[0] = basename(argv[0]);
+ while ((opt = getopt_long(argc, argv, "d:p:", longopts, NULL)) != -1) {
+ switch (opt) {
+ case 'd':
+ case 'p': {
+ /* strtol() allows for numbers with leading spaces and a
+ ‘+’/‘-’. We don’t want that, so assert that the input
+ begins with a number. */
+ if (!xisdigit(optarg[0]))
+ errx(1, "%s: Invalid integer", optarg);
+
+ errno = 0;
+ char *endptr;
+ long n = strtol(optarg, &endptr, 10);
+
+ /* There are trailing invalid digits */
+ if (*endptr != 0)
+ errx(1, "%s: Invalid integer", optarg);
+
+ /* The number was too large. We asserted that the input
+ didn’t start with ‘-’ so we can ignore checking for
+ LONG_MIN. */
+ if (n > INT_MAX)
+ errno = ERANGE;
+ if (errno == ERANGE)
+ err(1, "%s", optarg);
+
+ if (n == 0)
+ errx(1, "%s: Integer must be non-zero", optarg);
+ if (opt == 'd')
+ digits = (int)n;
+ else
+ period = (int)n;
+ break;
+ }
+ default:
+ usage(argv[0]);
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ switch (argc) {
+ case 0:
+ process_stdin();
+ break;
+ case 1:
+ process(argv[0], strlen(argv[0]));
+ break;
+ default:
+ usage(argv[-optind]);
+ }
+
+ return EXIT_SUCCESS;
+}
+
+void
+process_stdin(void)
+{
+ ssize_t nr;
+ size_t len;
+ char *line = NULL;
+ while ((nr = getline(&line, &len, stdin)) != -1) {
+ if (line[nr - 1] == '\n')
+ line[--nr] = 0;
+ process(line, nr);
+ }
+ if (errno != 0)
+ err(1, "getline");
+}
+
+void
+process(const char *s, size_t n)
+{
+ /* Remove padding bytes */
+ while (n > 0 && s[n - 1] == '=')
+ n--;
+ if (n == 0)
+ errx(1, "Empty Base32 input");
+
+ static uint8_t _key[256];
+ uint8_t *key = _key;
+
+ size_t keysz = n * 5 / 8;
+ if (keysz > sizeof(_key)) {
+ if ((key = malloc(keysz)) == NULL)
+ err(1, "malloc");
+ }
+
+ if (!b32toa(key, s, n))
+ errx(1, "%s: Invalid Base32 input", s);
+
+ /* time(2) claims that this call will never fail if passed a NULL
+ argument. We cast the time_t to uint64_t which will always be
+ safe to do. */
+ uint64_t epoch = htobe64((uint64_t)time(NULL) / (uint64_t)period);
+ uint8_t dgst[SHA1DGSTSZ];
+ hmac_sha1(dgst, key, keysz, (uint8_t *)&epoch, sizeof(epoch));
+
+ int off = dgst[19] & 0x0F;
+ uint32_t binc = (dgst[off + 0] & 0x7F) << 24
+ | (dgst[off + 1] & 0xFF) << 16
+ | (dgst[off + 2] & 0xFF) << 8
+ | (dgst[off + 3] & 0xFF) << 0;
+ printf("%0*" PRId32 "\n", digits, binc % pow32(10, digits));
+
+ if (key != _key)
+ free(key);
+}
+
+/* TODO: Check for overflow? */
+uint32_t
+pow32(uint32_t x, uint32_t y)
+{
+ uint32_t n = x;
+ if (y == 0)
+ return 1;
+ while (--y != 0)
+ x *= n;
+ return x;
+}
+
+bool
+xisdigit(char ch)
+{
+ return ch >= '0' && ch <= '9';
+}
+
+bool
+bigendian(void)
+{
+ union {
+ uint16_t u16;
+ uint8_t u8[2];
+ } u = {
+ .u16 = 0x0102,
+ };
+ u.u16 = 0x0102U;
+ return u.u8[0] == 1;
+}