diff options
Diffstat (limited to '')
-rw-r--r-- | enroll.c | 220 |
1 files changed, 219 insertions, 1 deletions
diff --git a/enroll.c b/enroll.c index dea1951..62b72b7 100644 --- a/enroll.c +++ b/enroll.c @@ -25,12 +25,230 @@ * covered work. */ +#include <err.h> +#include <errno.h> +#include <limits.h> +#include <stdarg.h> +#include <stdbool.h> #include <stdio.h> #include <stdlib.h> -#include <err.h> +#include <string.h> +#include <sys/stat.h> #include <sysexits.h> +#include <unistd.h> char *readpassphrase(const char *prompt, char *buf, size_t bufsiz, int flags); +static bool verbose; +static void log(const char *format, ...) __attribute__((format(printf, 1, 2))); +static void log(const char *format, ...) { + va_list ap; + if (!verbose) return; + va_start(ap, format); + fprintf(stderr, "# "); + vfprintf(stderr, format, ap); + fprintf(stderr, "\n"); + va_end(ap); +} + +static int prompt1(char **resp, const char *def, const char *desc) { + static char *buf; + static size_t cap; + + size_t head = strcspn(desc, "\n"); + for (*resp = NULL; !*resp;) { + printf("%.*s [%s] ", (int)head, desc, (def ?: "")); + ssize_t len = getline(&buf, &cap, stdin); + if (len < 0 && feof(stdin)) { + printf("\n"); + clearerr(stdin); + return -1; + } else if (len < 0) { + err(EX_IOERR, "getline"); + } + if (len && buf[len-1] == '\n') buf[len-1] = '\0'; + + if (buf[0] == '?') { + printf("\n%s\n", &desc[head+1]); + } else if (buf[0]) { + *resp = strdup(buf); + if (!*resp) err(EX_OSERR, "strdup"); + } else if (def) { + *resp = strdup(def); + if (!*resp) err(EX_OSERR, "strdup"); + } + } + return 0; +} + +static int prompt(char **resp, const char *def, const char *format, ...) +__attribute__((format(printf, 3, 4))); +static int prompt(char **resp, const char *def, const char *format, ...) { + va_list ap; + char desc[1024]; + va_start(ap, format); + vsnprintf(desc, sizeof(desc), format, ap); + va_end(ap); + return prompt1(resp, def, desc); +} + +static int yesno(bool *resp, bool def, const char *format, ...) +__attribute__((format(printf, 3, 4))); +static int yesno(bool *resp, bool def, const char *format, ...) { + va_list ap; + char desc[1024]; + va_start(ap, format); + vsnprintf(desc, sizeof(desc), format, ap); + va_end(ap); + char *str; + for (;;) { + int pop = prompt1(&str, (def ? "yes" : "no"), desc); + if (pop) return pop; + char yn = str[0]; + free(str); + if (yn == 'Y' || yn == 'y') { + *resp = true; + return 0; + } else if (yn == 'N' || yn == 'n') { + *resp = false; + return 0; + } + } +} + +static struct { + char *network; + char *config; +} info; + +static int getNetwork(const char *def) { + return prompt( + &info.network, def, + "Which network would you like to connect to?\n" + "Enter the domain name of the IRC network you wish to connect to.\n" + "Examples: tilde.chat, libera.chat\n" + ); +} + +static int getConfig(const char *configHome) { + char *name = strdup(info.network); + if (!name) err(EX_OSERR, "strdup"); + char *prev = NULL, *next = NULL; + while (name) { + prev = next; + next = strsep(&name, "."); + } + + const char *home = getenv("HOME"); + const char *suffix = configHome; + size_t homeLen = strlen(home); + if (!strncmp(configHome, home, homeLen)) { + suffix += homeLen; + } + int pop = prompt( + &info.config, prev, + "What would you like to name this configuration?\n" + "This is the name you will pass to catgirl to connect to %s.\n" + "Example: $ catgirl %s\n" + "The configuration will be written to: %s%s/catgirl\n", + info.network, prev, (suffix != configHome ? "~" : ""), suffix + ); + if (pop) return pop; + + char path[PATH_MAX]; + snprintf(path, sizeof(path), "%s/catgirl/%s", configHome, info.config); + if (!access(path, F_OK)) { + printf( + "There is already a configuration named %s.\n" + "Please remove or rename the file: %s%s/catgirl/%s\n" + "Otherwise, enter a different name.\n", + info.config, (suffix != configHome ? "~" : ""), suffix, info.config + ); + free(info.config); + info.config = NULL; + } + return 0; +} + +static void unset(char **resp) { + free(*resp); + *resp = NULL; +} + int main(int argc, char *argv[]) { + for (int opt; 0 < (opt = getopt(argc, argv, "v"));) { + switch (opt) { + break; case 'v': verbose = true; + break; default: return EX_USAGE; + } + } + + char configHome[PATH_MAX]; + if (getenv("XDG_CONFIG_HOME")) { + snprintf( + configHome, sizeof(configHome), "%s", getenv("XDG_CONFIG_HOME") + ); + } else if (getenv("HOME")) { + snprintf(configHome, sizeof(configHome), "%s/.config", getenv("HOME")); + } else { + errx(EX_USAGE, "HOME unset"); + } + + char path[PATH_MAX]; + snprintf(path, sizeof(path), "%s/catgirl", configHome); + int error = mkdir(path, S_IRWXU); + if (error && errno != EEXIST) err(EX_CANTCREAT, "%s", path); + umask(S_IRWXG | S_IRWXO); + + printf( + "Welcome to catgirl enrollment! This tool should get you connected to\n" + "IRC using catgirl :3\n" + "\n" + "Type ? at a prompt for a more detailed explanation.\n" + "The default response is shown in [brackets].\n" + "Press enter at a prompt to select the default response.\n" + "Press control-D at a prompt to return to the previous one.\n" + "\n" + ); + + int pop; + for (;;) { + if (!info.network) { + pop = getNetwork((optind < argc ? argv[optind] : NULL)); + if (pop) return EX_USAGE; + } else if (!info.config) { + pop = getConfig(configHome); + if (pop) unset(&info.network); + } else { + break; + } + } + + snprintf(path, sizeof(path), "%s/catgirl/%s", configHome, info.config); + log("Writing configuration to: %s", path); + FILE *file = fopen(path, "w"); + if (!file) err(EX_CANTCREAT, "%s", path); + + fprintf(file, "host = %s\n", info.network); // FIXME: info.host + + error = fclose(file); + if (error) err(EX_IOERR, "%s", path); + + printf( + "\n" + "You're all set! You can connect to %s using:\n" + "$ catgirl %s\n" + "\n", + info.network, info.config + ); + bool run; + pop = yesno( + &run, true, + "Connect now?\n" + "There's nothing left to do. This will start catgirl.\n" + ); + if (pop || !run) return EX_OK; + + execlp("catgirl", "catgirl", info.config, NULL); + err(EX_UNAVAILABLE, "catgirl"); } |