26static asciichat_error_t https_fetch_keys(
const char *url,
char **response_text,
size_t *response_len) {
27 if (!url || !response_text || !response_len) {
34 if (strncmp(url,
"https://", 8) != 0) {
39 const char *hostname_start = url + 8;
40 const char *path_start = strchr(hostname_start,
'/');
48 size_t hostname_len = path_start - hostname_start;
49 if (hostname_len == 0 || hostname_len > 255) {
55 memcpy(hostname, hostname_start, hostname_len);
56 hostname[hostname_len] =
'\0';
59 char *response =
https_get(hostname, path_start);
65 *response_text = response;
66 *response_len = strlen(response);
75 if (!username || !url_out) {
86 int result =
safe_snprintf(url_out, url_size,
"https://github.com/%s.keys", username);
87 if (result < 0 || result >= (
int)url_size) {
96 if (!username || !url_out) {
107 int result =
safe_snprintf(url_out, url_size,
"https://gitlab.com/%s.keys", username);
108 if (result < 0 || result >= (
int)url_size) {
117 if (!username || !url_out) {
128 char clean_username[256];
129 SAFE_STRNCPY(clean_username, username,
sizeof(clean_username) - 1);
130 size_t len = strlen(clean_username);
131 if (len > 4 && strcmp(clean_username + len - 4,
".gpg") == 0) {
132 clean_username[len - 4] =
'\0';
136 int result =
safe_snprintf(url_out, url_size,
"https://github.com/%s.gpg", clean_username);
137 if (result < 0 || result >= (
int)url_size) {
146 if (!username || !url_out) {
157 char clean_username[256];
158 SAFE_STRNCPY(clean_username, username,
sizeof(clean_username) - 1);
159 size_t len = strlen(clean_username);
160 if (len > 4 && strcmp(clean_username + len - 4,
".gpg") == 0) {
161 clean_username[len - 4] =
'\0';
165 int result =
safe_snprintf(url_out, url_size,
"https://gitlab.com/%s.gpg", clean_username);
166 if (result < 0 || result >= (
int)url_size) {
179 if (!username || !keys_out || !num_keys) {
193 char *response_text = NULL;
194 size_t response_len = 0;
195 asciichat_error_t fetch_result = https_fetch_keys(url, &response_text, &response_len);
211 if (!username || !keys_out || !num_keys) {
225 char *response_text = NULL;
226 size_t response_len = 0;
227 asciichat_error_t fetch_result = https_fetch_keys(url, &response_text, &response_len);
243 if (!username || !keys_out || !num_keys) {
257 char *response_text = NULL;
258 size_t response_len = 0;
259 asciichat_error_t fetch_result = https_fetch_keys(url, &response_text, &response_len);
275 if (!username || !keys_out || !num_keys) {
289 char *response_text = NULL;
290 size_t response_len = 0;
291 asciichat_error_t fetch_result = https_fetch_keys(url, &response_text, &response_len);
311 size_t *num_keys,
size_t max_keys) {
312 if (!response_text || !keys_out || !num_keys) {
321 size_t key_count = 0;
322 const char *line_start = response_text;
323 const char *line_end;
325 while ((line_end = strchr(line_start,
'\n')) != NULL) {
326 size_t line_len = line_end - line_start;
327 if (line_len > 0 && line_start[0] !=
'\r' && line_start[0] !=
'\n') {
330 line_start = line_end + 1;
334 if (line_start < response_text + response_len) {
338 if (key_count == 0) {
343 if (key_count > max_keys) {
344 key_count = max_keys;
348 *keys_out =
SAFE_MALLOC(
sizeof(
char *) * key_count,
char **);
354 line_start = response_text;
355 size_t parsed_keys = 0;
357 while (parsed_keys < key_count && (line_end = strchr(line_start,
'\n')) != NULL) {
358 size_t line_len = line_end - line_start;
361 if (line_len > 0 && line_start[0] !=
'\r' && line_start[0] !=
'\n') {
363 (*keys_out)[parsed_keys] =
SAFE_MALLOC(line_len + 1,
char *);
364 if (!(*keys_out)[parsed_keys]) {
366 for (
size_t i = 0; i < parsed_keys; i++) {
375 memcpy((*keys_out)[parsed_keys], line_start, line_len);
376 (*keys_out)[parsed_keys][line_len] =
'\0';
381 line_start = line_end + 1;
385 if (parsed_keys < key_count && line_start < response_text + response_len) {
386 size_t line_len = (response_text + response_len) - line_start;
388 (*keys_out)[parsed_keys] =
SAFE_MALLOC(line_len + 1,
char *);
389 if (!(*keys_out)[parsed_keys]) {
391 for (
size_t i = 0; i < parsed_keys; i++) {
398 memcpy((*keys_out)[parsed_keys], line_start, line_len);
399 (*keys_out)[parsed_keys][line_len] =
'\0';
404 *num_keys = parsed_keys;
409 size_t *num_keys,
size_t max_keys) {
411 if (!response_text || !keys_out || !num_keys) {
420 if (strncmp(response_text,
"-----BEGIN PGP", 14) != 0) {
426 char temp_file[] =
"/tmp/asciichat_gpg_import_XXXXXX";
427 int fd = mkstemp(temp_file);
432 ssize_t written = write(fd, response_text, response_len);
435 if (written != (ssize_t)response_len) {
441 char import_cmd[512];
442 snprintf(import_cmd,
sizeof(import_cmd),
"gpg --import '%s' 2>&1", temp_file);
443 FILE *import_fp = popen(import_cmd,
"r");
449 char import_output[2048];
450 size_t import_len = fread(import_output, 1,
sizeof(import_output) - 1, import_fp);
451 import_output[import_len] =
'\0';
457 const char *key_marker =
"gpg: key ";
458 char key_ids[16][17];
459 size_t key_count = 0;
461 log_debug(
"GPG import output:\n%s", import_output);
463 char *search_pos = import_output;
464 while (key_count < 16) {
465 char *key_line = strstr(search_pos, key_marker);
469 key_line += strlen(key_marker);
471 while (i < 16 && key_line[i] !=
':' && key_line[i] !=
' ' && key_line[i] !=
'\n') {
472 key_ids[key_count][i] = key_line[i];
475 key_ids[key_count][i] =
'\0';
478 log_debug(
"Extracted GPG key ID #%zu: %s", key_count, key_ids[key_count]);
481 search_pos = key_line + i;
484 if (key_count == 0) {
488 log_debug(
"Total GPG keys extracted from import: %zu", key_count);
491 *keys_out =
SAFE_MALLOC(
sizeof(
char *) * key_count,
char **);
497 size_t valid_keys = 0;
498 for (
size_t k = 0; k < key_count; k++) {
501 snprintf(list_cmd,
sizeof(list_cmd),
"gpg --list-keys --with-colons --fingerprint '%s' 2>/dev/null", key_ids[k]);
502 FILE *list_fp = popen(list_cmd,
"r");
507 char list_output[4096];
508 size_t list_len = fread(list_output, 1,
sizeof(list_output) - 1, list_fp);
509 list_output[list_len] =
'\0';
513 if (!strstr(list_output,
":22:") && !strstr(list_output,
"ed25519")) {
520 char fingerprint[41] = {0};
521 const char *fpr_marker =
"\nfpr:";
522 char *fpr_line = strstr(list_output, fpr_marker);
527 while (*fpr_line && colon_count < 9) {
528 if (*fpr_line ==
':')
535 while (fpr_len < 40 && fpr_line[fpr_len] && fpr_line[fpr_len] !=
':' && fpr_line[fpr_len] !=
'\n') {
536 fingerprint[fpr_len] = fpr_line[fpr_len];
539 fingerprint[fpr_len] =
'\0';
543 if (strlen(fingerprint) == 0) {
544 log_warn(
"Failed to extract fingerprint for key %s, using short key ID", key_ids[k]);
545 SAFE_STRNCPY(fingerprint, key_ids[k],
sizeof(fingerprint));
548 log_debug(
"Key %s -> fingerprint: %s (length: %zu)", key_ids[k], fingerprint, strlen(fingerprint));
551 size_t gpg_key_len = strlen(
"gpg:") + strlen(fingerprint) + 1;
552 (*keys_out)[valid_keys] =
SAFE_MALLOC(gpg_key_len,
char *);
553 if (!(*keys_out)[valid_keys]) {
555 for (
size_t cleanup = 0; cleanup < valid_keys; cleanup++) {
563 snprintf((*keys_out)[valid_keys], gpg_key_len,
"gpg:%s", fingerprint);
564 log_debug(
"Added valid Ed25519 key #%zu: %s", valid_keys, (*keys_out)[valid_keys]);
568 if (valid_keys == 0) {
574 *num_keys = valid_keys;
⚠️‼️ Error and/or exit() when things go bad.
#define SAFE_STRNCPY(dst, src, size)
#define SAFE_MALLOC(size, cast)
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
asciichat_error_t
Error and exit codes - unified status values (0-255)
asciichat_error_t build_github_ssh_url(const char *username, char *url_out, size_t url_size)
Construct GitHub SSH keys URL.
asciichat_error_t fetch_gitlab_gpg_keys(const char *username, char ***keys_out, size_t *num_keys)
Fetch GPG keys from GitLab using HTTPS.
asciichat_error_t build_github_gpg_url(const char *username, char *url_out, size_t url_size)
Construct GitHub GPG keys URL.
asciichat_error_t parse_ssh_keys_from_response(const char *response_text, size_t response_len, char ***keys_out, size_t *num_keys, size_t max_keys)
Parse SSH keys from HTTPS response text.
asciichat_error_t fetch_github_ssh_keys(const char *username, char ***keys_out, size_t *num_keys)
Fetch SSH keys from GitHub using HTTPS.
asciichat_error_t fetch_github_gpg_keys(const char *username, char ***keys_out, size_t *num_keys)
Fetch GPG keys from GitHub using HTTPS.
asciichat_error_t parse_gpg_keys_from_response(const char *response_text, size_t response_len, char ***keys_out, size_t *num_keys, size_t max_keys)
Parse GPG keys from HTTPS response text.
asciichat_error_t fetch_gitlab_ssh_keys(const char *username, char ***keys_out, size_t *num_keys)
Fetch SSH keys from GitLab using HTTPS.
asciichat_error_t build_gitlab_ssh_url(const char *username, char *url_out, size_t url_size)
Construct GitLab SSH keys URL.
asciichat_error_t build_gitlab_gpg_url(const char *username, char *url_out, size_t url_size)
Construct GitLab GPG keys URL.
#define MAX_CLIENTS
Maximum possible clients (static array size) - actual runtime limit set by –max-clients (1-32)
#define log_warn(...)
Log a WARN message.
#define log_debug(...)
Log a DEBUG message.
char * https_get(const char *hostname, const char *path)
Perform HTTPS GET request.
Simple HTTPS client for fetching public keys from GitHub/GitLab.
🔤 String Manipulation and Shell Escaping Utilities