--- /dev/null
+Copyright 2017 mar77i <mar77i at protonmail dot ch>
+
+Permission to use, copy, modify, and/or distribute this software for any purpose
+with or without fee is hereby granted, provided that the above copyright notice
+and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+THIS SOFTWARE.
--- /dev/null
+
+# global.mk
+#
+# Copyright (c) 2017, mar77i <mar77i at protonmail dot ch>
+#
+# This software may be modified and distributed under the terms
+# of the ISC license. See the LICENSE file for details.
+
+# useful compiler flags; thanks xavier roche @ httrack blog
+
+CFLAGS += -std=c99 -pipe -fvisibility=hidden -Wall -Wextra -Wformat \
+ -Wformat-security -Wreturn-type -Wpointer-arith -Winit-self \
+ -Wsign-compare -Wmultichar -Wuninitialized -Werror -funroll-loops \
+ -funswitch-loops -pedantic
+
+SHARED_CFLAGS = -fPIC -fvisibility=default
+
+LDFLAGS += -Wl,-z,relro,-z,now,-O1,--as-needed,--no-undefined \
+ -Wl,--build-id=sha1,--no-allow-shlib-undefined -rdynamic
+
+VALGRIND_FLAGS = --trace-children=yes --leak-check=full --track-origins=yes \
+ --show-leak-kinds=all
+
+LINK = $(CC) $(LDFLAGS) $> $^ $(LOADLIBES) $(LDLIBS) $(LDADD) -o $@
+LINK_SHARED = \
+ $(CC) $(LDFLAGS) $> $^ $(LOADLIBES) $(LDLIBS) $(LDADD) -shared -o $@
+
+vg:
+ valgrind $(VALGRIND_FLAGS) $(ARGS)
+
+DEV_CFLAGS = -Og -g
+PROD_CFLAGS = -O3
+PROD_LDFLAGS = -Wl,--discard-all
+
+.PHONY: vg
--- /dev/null
+
+// otp.c
+
+#include <ctype.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <openssl/hmac.h>
+
+extern char **environ;
+
+#define INTERVAL_SECONDS 30
+
+char *find_env_var(const char *match) {
+ size_t len = strlen(match);
+ char **env;
+ if(match == NULL)
+ return NULL;
+ for(env = environ; *env != NULL; env++)
+ if(strncmp(*env, match, len) == 0 && (*env)[len] == '=')
+ return *env + len + 1;
+ return NULL;
+}
+
+int concat(char *dest, size_t *dest_used, size_t dest_size, char *src) {
+ size_t len = strlen(src);
+ if(dest == NULL || dest_used == NULL || src == NULL)
+ return -1;
+ if(*dest_used + len + (*dest_used == 0) > dest_size)
+ return -1;
+ if(*dest_used == 0)
+ (*dest_used)++;
+ memcpy(dest + *dest_used - 1, src, len + 1);
+ *dest_used += len;
+ return 0;
+}
+
+int b32_decode(unsigned char *buf, size_t *len) {
+ const char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
+ size_t i = 0;
+ char x = 0, *ptr;
+ unsigned char *output = buf, count, lsh;
+ if(buf == NULL)
+ return -1;
+ for(count = 1;; count++) {
+ while(isspace(buf[i]))
+ i++;
+ if(buf[i] == '\0')
+ break;
+ ptr = strchr(alphabet, toupper(buf[i++]));
+ if(ptr == NULL || *ptr == '\0')
+ return -1;
+ x = ptr - alphabet;
+ if(x < 0)
+ return -1;
+ lsh = (count * 3) % 8;
+ if(lsh > 3)
+ *output |= x >> (8 - lsh);
+ if(lsh + (count > 1) > 3)
+ output++;
+ *output = (x << lsh) | ((lsh < 3) * *output);
+ }
+ if(len != NULL)
+ *len = output - buf + 1;
+ return 0;
+}
+
+static inline unsigned char *pack_be64(unsigned char data[], uint64_t value) {
+ size_t i;
+ for(i = 0; i < sizeof value; i++)
+ data[i] = (value >> ((sizeof value - 1 - i) * CHAR_BIT)) & 0xff;
+ return data;
+}
+
+int calculate_otp(const char *line) {
+ size_t i, len = 0;
+ unsigned int hmac_len = 0;
+ unsigned char data[sizeof(uint64_t)], *ptr;
+ if(line == NULL)
+ return -1;
+ if(strlen(line) < 2)
+ return 0;
+ ptr = (unsigned char*)strchr(line, ':');
+ if(ptr == NULL)
+ return -1;
+ printf("%.*s: ", (int)((char*)ptr - line), line);
+ ptr++;
+ if(b32_decode(ptr, &len) < 0) {
+ fprintf(stderr, "Error: b32_decode.\n");
+ return -1;
+ }
+
+ // fill data with the current time divided by INTERVAL_SECONDS
+ pack_be64(data, time(NULL) / INTERVAL_SECONDS);
+ if(HMAC(EVP_sha1(), ptr, len, data, sizeof data, ptr, &hmac_len) == NULL) {
+ fprintf(stderr, "\nError: hmac.\n");
+ return -1;
+ }
+ i = ptr[hmac_len - 1] & 0x0f;
+ printf("%06"PRIu32"\n", (((ptr[i + 0] & 0x7f) << 24) | (ptr[i + 1] << 16) |
+ (ptr[i + 2] << 8) | ptr[i + 3]) % 1000000);
+ return 0;
+}
+
+int main(void) {
+ FILE *fh;
+ size_t buf_used = 0;
+ int ret = EXIT_FAILURE;
+ char buf[PATH_MAX], *ptr = find_env_var("HOME");
+ if(ptr == NULL)
+ return EXIT_FAILURE;
+ if(concat(buf, &buf_used, sizeof buf, ptr) < 0)
+ return EXIT_FAILURE;
+ ptr = buf + buf_used - 2;
+ while(*ptr == '/') {
+ *ptr-- = '\0';
+ buf_used--;
+ }
+ if(concat(buf, &buf_used, sizeof buf, "/.otp_secrets") < 0)
+ return EXIT_FAILURE;
+ fh = fopen(buf, "rb");
+ if(fh == NULL) {
+ perror("fopen");
+ return EXIT_FAILURE;
+ }
+ while(fgets(buf, sizeof buf, fh) != NULL)
+ if(calculate_otp(buf) < 0)
+ goto error;
+ if(ferror(fh)) {
+ fprintf(stderr, "Error: read error.");
+ goto error;
+ }
+ ret = EXIT_SUCCESS;
+error:
+ fclose(fh);
+ return ret;
+}