#ifndef _DEFAULT_SOURCE #define _DEFAULT_SOURCE #endif #include "cst_agent.h" #include "os_compat.h" #include #include #include #include #ifdef _WIN32 #include #else #include #include #include #endif /* Config dir via os_compat: $XDG_CONFIG_HOME/cst-agent (~/.config/cst-agent) * or %APPDATA%\cst-agent */ static int credentials_path(char *out, size_t n) { char dir[OS_PATH_MAX]; if (os_config_dir(dir, sizeof(dir)) != 0) return -1; return os_path_join(out, n, dir, "credentials"); } static void chomp(char *s) { size_t len = strlen(s); while (len > 0 && (s[len - 1] == '\n' || s[len - 1] == '\r' || s[len - 1] == ' ' || s[len - 1] == '\t')) s[--len] = 0; } static int same_base(const char *a, const char *b) { size_t la = strlen(a), lb = strlen(b); while (la > 0 && a[la - 1] == '/') la--; while (lb > 0 && b[lb - 1] == '/') lb--; return la == lb && strncmp(a, b, la) == 0; } int cst_creds_load(const char *provider, char *out, size_t out_sz) { if (!provider || !provider[0] || !out || out_sz < 2) return -1; out[0] = 0; char path[OS_PATH_MAX]; if (credentials_path(path, sizeof(path)) != 0) return -1; FILE *fp = fopen(path, "r"); if (!fp) return -1; char want[64]; snprintf(want, sizeof(want), "%s.api_key=", provider); size_t wl = strlen(want); /* Old single-key files ("api_key=" + "api_base=") belong to the provider * whose base URL matches; without a base they were MiniMax keys. */ char legacy_key[1024] = "", legacy_base[1024] = ""; char line[1024]; int found = 0; while (fgets(line, sizeof(line), fp)) { chomp(line); if (strncmp(line, want, wl) == 0 && line[wl]) { snprintf(out, out_sz, "%s", line + wl); found = 1; break; } if (strncmp(line, "api_key=", 8) == 0) snprintf(legacy_key, sizeof(legacy_key), "%s", line + 8); else if (strncmp(line, "api_base=", 9) == 0) snprintf(legacy_base, sizeof(legacy_base), "%s", line + 9); } fclose(fp); if (found) return 0; if (legacy_key[0]) { const cst_provider_t *p = cst_provider_find(provider); int match = p && (legacy_base[0] ? same_base(legacy_base, p->api_base) : strcmp(provider, "minimax") == 0); if (match && strlen(legacy_key) < out_sz) { snprintf(out, out_sz, "%s", legacy_key); return 0; } } return -1; } int cst_creds_save(const char *provider, const char *key) { if (!provider || !provider[0] || !key || !key[0] || strchr(key, '\n')) return -1; char dir[OS_PATH_MAX], path[OS_PATH_MAX], tmp[OS_PATH_MAX + 8]; if (os_config_dir(dir, sizeof(dir)) != 0 || os_mkdir_p(dir, 0700) != 0 || credentials_path(path, sizeof(path)) != 0) return -1; if (snprintf(tmp, sizeof(tmp), "%s.tmp", path) >= (int)sizeof(tmp)) return -1; char want[64]; snprintf(want, sizeof(want), "%s.api_key=", provider); size_t wl = strlen(want); #ifdef _WIN32 FILE *out = fopen(tmp, "w"); #else /* never exists world-readable, even briefly */ int fd = open(tmp, O_WRONLY | O_CREAT | O_TRUNC, 0600); FILE *out = fd >= 0 ? fdopen(fd, "w") : NULL; if (!out && fd >= 0) close(fd); #endif if (!out) return -1; FILE *in = fopen(path, "r"); if (in) { char line[1024]; while (fgets(line, sizeof(line), in)) { if (strncmp(line, want, wl) != 0) fputs(line, out); } fclose(in); } fprintf(out, "%s%s\n", want, key); if (fclose(out) != 0) { remove(tmp); return -1; } #ifdef _WIN32 remove(path); #endif if (rename(tmp, path) != 0) { remove(tmp); return -1; } (void)os_chmod_private(path); return 0; } int cst_creds_resolve(const char *provider, char *out, size_t out_sz, const char **from) { if (from) *from = NULL; if (cst_creds_load(provider, out, out_sz) == 0 && out[0]) { if (from) *from = "saved"; return 0; } const cst_provider_t *p = cst_provider_find(provider); const char *env = p ? getenv(p->key_env) : NULL; if (env && env[0]) { snprintf(out, out_sz, "%s", env); if (from) *from = p->key_env; return 0; } out[0] = 0; return -1; } int cst_creds_prompt_console(const char *provider, char *out, size_t out_sz) { if (!out || out_sz < 2) return -1; out[0] = 0; fprintf(stderr, "API key for %s (input hidden, saved to the config dir): ", provider ? provider : "?"); fflush(stderr); #ifndef _WIN32 struct termios old, quiet; int tty = isatty(STDIN_FILENO) && tcgetattr(STDIN_FILENO, &old) == 0; if (tty) { quiet = old; quiet.c_lflag &= ~(tcflag_t)ECHO; tcsetattr(STDIN_FILENO, TCSAFLUSH, &quiet); } #endif char *got = fgets(out, (int)out_sz, stdin); #ifndef _WIN32 if (tty) { tcsetattr(STDIN_FILENO, TCSAFLUSH, &old); fputc('\n', stderr); } #endif if (!got) { out[0] = 0; return -1; } chomp(out); return out[0] ? 0 : -1; }