aboutsummaryrefslogtreecommitdiff
path: root/src/main.c
blob: 545d05d3a6faae5f2f28d35bce126385f376b11c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
#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 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'},
		{"help",   no_argument,       0, 'h'},
		{"period", required_argument, 0, 'p'},
		{0},
	};

	argv[0] = basename(argv[0]);
	while ((opt = getopt_long(argc, argv, "d:hp:", longopts, NULL)) != -1) {
		switch (opt) {
		case 'h':
			/* TODO: Open the manual page! */
			usage(argv[0]);
			break;
		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);

			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;
				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;

	if (argc == 0)
		process_stdin();
	else for (int i = 0; i < argc; i++)
		process(argv[i], strlen(argv[i]));

	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';
}