/* Copyright (C) 2022 June McEnroe * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7: * * If you modify this Program, or any covered work, by linking or * combining it with OpenSSL (or a modified version of that library), * containing parts covered by the terms of the OpenSSL License and the * original SSLeay license, the licensors of this Program grant you * additional permission to convey the resulting work. Corresponding * Source for a non-source form of such a combination shall include the * source code for the parts of OpenSSL used as well as that of the * covered work. */ #include #include #include #include #include #include #include #include #include #include #include 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"); }