]> git.mar77i.info Git - ga-cli/commitdiff
accept environment and command line arguments.
authormar77i <mar77i@protonmail.ch>
Tue, 21 May 2024 20:09:30 +0000 (22:09 +0200)
committermar77i <mar77i@protonmail.ch>
Tue, 21 May 2024 20:09:30 +0000 (22:09 +0200)
.gitignore
Makefile
totp.c
totp_args.c [new file with mode: 0644]
totp_args.h [new file with mode: 0644]

index 008f457334aa46f3ddf6cd3241426a0cca8d0937..b0135542fcf125cc10216e4d20b494e292d8005c 100644 (file)
@@ -1,2 +1,2 @@
+*.o
 totp
-totp.o
index f1a84c1b1dea514c9388c22c06bb791202c74073..1a491476e88e25f3c79ed10334fe084c0d4d2a75 100644 (file)
--- 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 0475c893fdc0754dae2800f045a80f28b67e9330..3eda6889af1f17f3b8934658620012735100e0d1 100644 (file)
--- a/totp.c
+++ b/totp.c
@@ -1,7 +1,7 @@
 
 /* totp.c
  *
- * Copyright (c) 2017, mar77i <mar77i at protonmail dot ch>
+ * Copyright (c) 2017-2024, 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.
 #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;
+#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 (file)
index 0000000..ee28501
--- /dev/null
@@ -0,0 +1,141 @@
+
+/* totp_args.c
+ *
+ * Copyright (c) 2024, 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.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "totp_args.h"
+
+static inline int print_help(const char *argv0) {
+    printf(
+        "Usage: %s -h|--help\n"
+        "       %s [-f<file>] [--] [search]\n"
+        "\n"
+        "Options:\n"
+        "-h, --help:  display this help\n"
+        "-f<file>:    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 (file)
index 0000000..25a2d53
--- /dev/null
@@ -0,0 +1,34 @@
+
+/* totp_args.h
+ *
+ * Copyright (c) 2024, 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.
+ */
+
+#ifndef TOTP_ARGS_H
+#define TOTP_ARGS_H
+
+#include <stdio.h>
+
+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