From 8a73b96ea408cfe0e7b70c1fba7c2a1bfb304326 Mon Sep 17 00:00:00 2001 From: mar77i Date: Tue, 21 May 2024 22:09:30 +0200 Subject: [PATCH] accept environment and command line arguments. --- .gitignore | 2 +- Makefile | 6 +- totp.c | 289 ++++++++++++++++++++++++++-------------------------- totp_args.c | 141 +++++++++++++++++++++++++ totp_args.h | 34 +++++++ 5 files changed, 321 insertions(+), 151 deletions(-) create mode 100644 totp_args.c create mode 100644 totp_args.h diff --git a/.gitignore b/.gitignore index 008f457..b013554 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ +*.o totp -totp.o diff --git a/Makefile b/Makefile index f1a84c1..1a49147 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,9 @@ -CFLAGS += -D_DEFAULT_SOURCE $(PROD_CFLAGS) -LDFLAGS += $(PROD_LDFLAGS) +CFLAGS += -D_DEFAULT_SOURCE $(DEV_CFLAGS) +LDFLAGS += $(DEV_LDFLAGS) LDLIBS += -lcrypto -totp: totp.o +totp: totp.o totp_args.o all: diff --git a/totp.c b/totp.c index 0475c89..3eda688 100644 --- a/totp.c +++ b/totp.c @@ -1,7 +1,7 @@ /* totp.c * - * Copyright (c) 2017, mar77i + * Copyright (c) 2017-2024, mar77i * * This software may be modified and distributed under the terms * of the ISC license. See the LICENSE file for details. @@ -11,166 +11,161 @@ #include #include #include -#include #include #include #include #include -extern char **environ; +#include "totp_args.h" #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 b32_decode(char *buf, size_t *len) { + const char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + size_t i = 0, used = 0; + uint8_t x; + char *ptr, *output = buf; + for(;;) { + while(isspace(buf[i]) || buf[i] == '\n') + i++; + if(buf[i] == '\0' || buf[i] == '=') + break; + ptr = strchr(alphabet, toupper(buf[i])); + if(ptr == NULL || *ptr == '\0') { + fprintf( + stderr, + "Error: invalid input: '%c' (%02X)\n", + buf[i], + (uint8_t)buf[i] + ); + return -1; + } + i++; + x = ptr - alphabet; + switch(used++ % 8) { + case 0: + *output = x << 3; + break; + case 1: + *output |= x >> 2; + output++; + *output = x << 6; + break; + case 2: + *output |= x << 1; + break; + case 3: + *output |= x >> 4; + output++; + *output = x << 4; + break; + case 4: + *output |= x >> 1; + output++; + *output = x << 7; + break; + case 5: + *output |= x << 2; + break; + case 6: + *output |= x >> 3; + output++; + *output = x << 5; + break; + case 7: + *output |= x; + output++; + break; + } + } + if(len != NULL) + *len = output - buf; + return 0; } -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; +static inline uint8_t *pack_be64( + uint8_t 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 b32_decode(unsigned char *buf, size_t *len) { - const char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; - size_t i = 0, used = 0; - unsigned char x, *output = buf; - char *ptr; - for(;;) { - while(isspace(buf[i]) || buf[i] == '\n') - i++; - if(buf[i] == '\0' || buf[i] == '=') - break; - ptr = strchr(alphabet, toupper(buf[i++])); - if(ptr == NULL || *ptr == '\0') - return -1; - x = ptr - alphabet; - switch(used++ % 8) { - case 0: - *output = x << 3; - break; - case 1: - *output |= x >> 2; - output++; - *output = x << 6; - break; - case 2: - *output |= x << 1; - break; - case 3: - *output |= x >> 4; - output++; - *output = x << 4; - break; - case 4: - *output |= x >> 1; - output++; - *output = x << 7; - break; - case 5: - *output |= x << 2; - break; - case 6: - *output |= x >> 3; - output++; - *output = x << 5; - break; - case 7: - *output |= x; - output++; - break; - } - } - if(len != NULL) - *len = output - buf; - 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_totp(const char *line) { - size_t len = 0; - unsigned int hmac_len = 0; - unsigned char data[sizeof(uint64_t)], *ptr, i; - 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; - } +int calculate_totp(const char *line, const char *search) { + size_t len = 0; + unsigned int hmac_len = 0, label_len; + uint8_t data[sizeof(uint64_t)], i; + char *ptr, *find; + if(line == NULL) + return -1; + if(strlen(line) < 2) + return 0; + ptr = strrchr(line, ':'); + if(ptr == NULL) + return -1; + label_len = ptr - line; + if (search != NULL) { + find = strcasestr(line, search); + if (find == NULL || find + strlen(search) - line > label_len) + return 0; + } + 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] & 0xf; - printf("%06"PRIu32"\n", ( - ((ptr[i] & 0x7f) << 24) | (ptr[i + 1] << 16) | - (ptr[i + 2] << 8) | ptr[i + 3] - ) % 1000000); - return 0; + // 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, + (uint8_t*)ptr, + &hmac_len + ) == NULL + ) { + fprintf(stderr, "\nError: hmac.\n"); + return -1; + } + i = ptr[hmac_len - 1] & 0xf; + printf( + "%.*s: %06"PRIu32"\n", + label_len, + line, + ( + (((uint8_t)ptr[i] & 0x7f) << 24) | ((uint8_t)ptr[i + 1] << 16) | + ((uint8_t)ptr[i + 2] << 8) | (uint8_t)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_totp(buf) < 0) - goto error; - if(ferror(fh)) { - fprintf(stderr, "Error: read error."); - goto error; - } - ret = EXIT_SUCCESS; +int main(int argc, char *argv[]) { + struct totp_args ta = TOTP_ARGS_INIT(argc, argv); + int ret = EXIT_FAILURE; + char buf[PATH_MAX]; + switch (totp_args_setup(&ta)) { + case 1: + ret = EXIT_SUCCESS; + // FALLTHROUGH + case -1: + goto error; + } + while(fgets(buf, sizeof buf, ta.fh) != NULL) + if(calculate_totp(buf, ta.search) < 0) + goto error; + if(ferror(ta.fh)) { + fprintf(stderr, "Error: read error.\n"); + goto error; + } + ret = EXIT_SUCCESS; error: - fclose(fh); - return ret; + totp_args_cleanup(ta); + return ret; } diff --git a/totp_args.c b/totp_args.c new file mode 100644 index 0000000..ee28501 --- /dev/null +++ b/totp_args.c @@ -0,0 +1,141 @@ + +/* totp_args.c + * + * Copyright (c) 2024, mar77i + * + * This software may be modified and distributed under the terms + * of the ISC license. See the LICENSE file for details. + */ + +#include +#include + +#include "totp_args.h" + +static inline int print_help(const char *argv0) { + printf( + "Usage: %s -h|--help\n" + " %s [-f] [--] [search]\n" + "\n" + "Options:\n" + "-h, --help: display this help\n" + "-f: specify input file.\n" + "search: output filter string.\n" + "\n" + "Environment:\n" + "OTP_SECRETS set a default input file (defaults to ~/.otp_secrets)\n" + "\n", + argv0, + argv0 + ); + return 1; +} + +#define ARG (ta->argv[ta->argi]) +static inline int parse_arg_file(struct totp_args *ta) { + char *ptr = ARG + 2; + if (*ptr == '\0') { + if (++ta->argi >= ta->argc) { + fprintf(stderr, "Error: filename missing.\n"); + return -1; + } + ptr = ARG; + } + ta->path = strdup(ptr); + if (ta->path == NULL) { + perror("strdup"); + return -1; + } + return 0; +} + +#define TOTP_DEFAULT_FILE "/.otp_secrets" + +static inline char *get_otp_secrets_path(void) { + size_t len; + char *ptr, *env = getenv("OTP_SECRETS"); + if (env == NULL) + goto default_path; + len = strlen(env); + if (len == 0) { + fprintf(stderr, "Error: path is empty: OTP_SECRETS\n"); + return NULL; + } + ptr = strdup(env); + if (ptr == NULL) + perror("strdup"); + return ptr; +default_path: + env = getenv("HOME"); + if (env == NULL) { + fprintf(stderr, "Error: HOME is unset.\n"); + return NULL; + } else if (*env == '\0') { + fprintf(stderr, "Error: HOME is empty.\n"); + return NULL; + } + len = strlen(env); + while (env[--len] == '/'); + len++; + ptr = malloc(len + sizeof TOTP_DEFAULT_FILE); + if (ptr != NULL) { + memcpy(ptr, env, len); + memcpy(ptr + len, TOTP_DEFAULT_FILE, sizeof TOTP_DEFAULT_FILE); + } else + perror("malloc"); + return ptr; +} + +static inline int parse_arg(struct totp_args *ta) { + if (ta->double_minus != 0) + goto double_minus_set; + if (!strcmp(ARG, "-h") || !strcmp(ARG, "--help")) + return print_help(ta->argv[0]); + else if (!strncmp(ARG, "-f", 2)) + return parse_arg_file(ta); + else if (!strcmp(ARG, "--")) { + ta->double_minus = 1; + return 0; + } +double_minus_set: + if (ta->search != NULL) { + fprintf(stderr, "Error: search string has already been passed.\n"); + return -1; + } + ta->search = ARG; + return 0; +} + +int totp_args_setup(struct totp_args *ta) { + int ret = -1; + for (; ta->argi < ta->argc; ta->argi++) + switch (parse_arg(ta)) { + case 1: + ret = 1; + // FALLTHROUGH + case -1: + goto error; + } + if (ta->path == NULL) + ta->path = get_otp_secrets_path(); + if (ta->path == NULL) + goto error; + if (strcmp(ta->path, "-")) { + ta->fh = fopen(ta->path, "rb"); + if (ta->fh == NULL) { + perror("fopen"); + goto error; + } + } else + ta->fh = stdin; + ret = 0; +error: + return ret; +} +#undef ARG + +void totp_args_cleanup(const struct totp_args ta) { + if (ta.fh != NULL && ta.fh != stdin) + fclose(ta.fh); + free(ta.path); +} diff --git a/totp_args.h b/totp_args.h new file mode 100644 index 0000000..25a2d53 --- /dev/null +++ b/totp_args.h @@ -0,0 +1,34 @@ + +/* totp_args.h + * + * Copyright (c) 2024, mar77i + * + * This software may be modified and distributed under the terms + * of the ISC license. See the LICENSE file for details. + */ + +#ifndef TOTP_ARGS_H +#define TOTP_ARGS_H + +#include + +struct totp_args { + FILE *fh; + int argc, argi; + char **argv, *path, *search, double_minus; +}; + +#define TOTP_ARGS_INIT(c, v) ((struct totp_args){ \ + .fh = NULL, \ + .argc = (c), \ + .argi = 1, \ + .argv = (v), \ + .path = NULL, \ + .search = NULL, \ + .double_minus = 0, \ +}) + +int totp_args_setup(struct totp_args *ta); +void totp_args_cleanup(const struct totp_args ta); + +#endif // TOTP_ARGS_H -- 2.47.0