LCOV - code coverage report
Current view: top level - src/app - bootstrap.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 36.6 % 101 37
Test Date: 2026-05-06 13:17:06 Functions: 100.0 % 4 4

            Line data    Source code
       1              : /* SPDX-License-Identifier: GPL-3.0-or-later */
       2              : /* Copyright 2026 Peter Csaszar */
       3              : 
       4              : /**
       5              :  * @file app/bootstrap.c
       6              :  * @brief Shared startup path: directories, logger, config overrides (FEAT-38).
       7              :  */
       8              : 
       9              : #include "app/bootstrap.h"
      10              : #include "app/dc_config.h"
      11              : 
      12              : #include "logger.h"
      13              : #include "fs_util.h"
      14              : #include "platform/path.h"
      15              : #include "telegram_server_key.h"
      16              : 
      17              : #include <stdio.h>
      18              : #include <string.h>
      19              : #include <stdlib.h>
      20              : 
      21              : #define BOOTSTRAP_CONFIG_SUBDIR "tg-cli"
      22              : #define BOOTSTRAP_CONFIG_FILE   "config.ini"
      23              : 
      24              : /**
      25              :  * Read the value of @p key from INI file at @p path into @p out (cap @p cap).
      26              :  *
      27              :  * Supports two value formats:
      28              :  *   key = single line value          (unquoted, trailing whitespace stripped)
      29              :  *   key = "multi                     (double-quoted: content spans until the
      30              :  *           line                      next " on any line; newlines preserved)
      31              :  *   line"
      32              :  *
      33              :  * Respects comment lines (# and ;).  Last occurrence of the key wins.
      34              :  * Returns  0 on success, -1 if not found, -2 if value exceeds cap-1 bytes.
      35              :  */
      36           60 : static int bootstrap_read_ini_key(const char *path, const char *key,
      37              :                                   char *out, size_t cap) {
      38           60 :     FILE *fp = fopen(path, "r");
      39           60 :     if (!fp) return -1;
      40              : 
      41              :     char line[8192];
      42            0 :     size_t klen = strlen(key);
      43            0 :     int result = -1;
      44              : 
      45            0 :     while (fgets(line, sizeof(line), fp)) {
      46              :         /* Detect truncated line. */
      47            0 :         size_t line_len = strlen(line);
      48            0 :         if (line_len == sizeof(line) - 1 && line[line_len - 1] != '\n') {
      49              :             int c;
      50            0 :             while ((c = fgetc(fp)) != '\n' && c != EOF) {}
      51            0 :             fprintf(stderr,
      52              :                     "bootstrap: config.ini: line for key '%s' exceeds %zu bytes"
      53              :                     " and was truncated\n", key, sizeof(line) - 1);
      54              :         }
      55              : 
      56            0 :         char *p = line;
      57            0 :         while (*p == ' ' || *p == '\t') p++;
      58            0 :         if (!*p || *p == '\n' || *p == '\r' || *p == '#' || *p == ';') continue;
      59            0 :         if (strncmp(p, key, klen) != 0) continue;
      60            0 :         p += klen;
      61            0 :         while (*p == ' ' || *p == '\t') p++;
      62            0 :         if (*p != '=') continue;
      63            0 :         p++;
      64            0 :         while (*p == ' ' || *p == '\t') p++;
      65              : 
      66            0 :         if (*p == '"') {
      67              :             /* Quoted value — may span multiple lines until the next '"'. */
      68            0 :             p++;  /* skip opening '"' */
      69            0 :             size_t pos = 0;
      70            0 :             for (;;) {
      71            0 :                 char *close = strchr(p, '"');
      72            0 :                 if (close) {
      73              :                     /* Closing quote found on this line. */
      74            0 :                     size_t chunk = (size_t)(close - p);
      75            0 :                     if (pos + chunk >= cap) { fclose(fp); return -2; }
      76            0 :                     memcpy(out + pos, p, chunk);
      77            0 :                     pos += chunk;
      78            0 :                     out[pos] = '\0';
      79            0 :                     result = (pos > 0) ? 0 : -1;
      80            0 :                     break;
      81              :                 }
      82              :                 /* No closing quote yet — copy line content + '\n', read next. */
      83            0 :                 size_t chunk = strlen(p);
      84              :                 /* Strip trailing CR/LF before appending '\n'. */
      85            0 :                 while (chunk > 0 && (p[chunk-1] == '\n' || p[chunk-1] == '\r'))
      86            0 :                     chunk--;
      87            0 :                 if (pos + chunk + 1 >= cap) { fclose(fp); return -2; }
      88            0 :                 memcpy(out + pos, p, chunk);
      89            0 :                 pos += chunk;
      90            0 :                 out[pos++] = '\n';
      91            0 :                 out[pos] = '\0';
      92            0 :                 if (!fgets(line, sizeof(line), fp)) {
      93            0 :                     result = (pos > 0) ? 0 : -1;
      94            0 :                     break;
      95              :                 }
      96            0 :                 p = line;
      97              :             }
      98              :         } else {
      99              :             /* Unquoted single-line value — rtrim whitespace. */
     100            0 :             size_t n = strlen(p);
     101            0 :             if (n >= cap) { fclose(fp); return -2; }
     102            0 :             memcpy(out, p, n);
     103            0 :             out[n] = '\0';
     104            0 :             while (n > 0 && (out[n-1] == '\n' || out[n-1] == '\r' ||
     105            0 :                              out[n-1] == ' '  || out[n-1] == '\t'))
     106            0 :                 out[--n] = '\0';
     107            0 :             result = (n > 0) ? 0 : -1;
     108              :         }
     109              :     }
     110            0 :     fclose(fp);
     111            0 :     return result;
     112              : }
     113              : 
     114              : /**
     115              :  * Apply DC-host and RSA-key overrides from config.ini.
     116              :  */
     117           10 : static void apply_config_overrides(const char *config_dir) {
     118           10 :     if (!config_dir) return;
     119              : 
     120              :     char path[1024];
     121           10 :     snprintf(path, sizeof(path), "%s/%s/%s",
     122              :              config_dir, BOOTSTRAP_CONFIG_SUBDIR, BOOTSTRAP_CONFIG_FILE);
     123              : 
     124           10 :     logger_log(LOG_INFO, "bootstrap: reading config from %s", path);
     125              : 
     126              :     /* rsa_pem */
     127              :     char rsa_buf[8192];
     128           10 :     int rc = bootstrap_read_ini_key(path, "rsa_pem", rsa_buf, sizeof(rsa_buf));
     129           10 :     if (rc == 0) {
     130            0 :         size_t pem_len = strlen(rsa_buf);
     131              :         /* Log the first line of the PEM to confirm format, not the full key. */
     132              :         char first_line[80];
     133            0 :         size_t fl = 0;
     134            0 :         while (fl < pem_len && fl < sizeof(first_line) - 1 &&
     135            0 :                rsa_buf[fl] != '\n' && rsa_buf[fl] != '\\') {
     136            0 :             first_line[fl] = rsa_buf[fl];
     137            0 :             fl++;
     138              :         }
     139            0 :         first_line[fl] = '\0';
     140            0 :         logger_log(LOG_INFO,
     141              :                    "bootstrap: rsa_pem found (%zu chars), first line: \"%s\"",
     142              :                    pem_len, first_line);
     143            0 :         if (telegram_server_key_set_override(rsa_buf) != 0) {
     144            0 :             logger_log(LOG_ERROR,
     145              :                        "bootstrap: rsa_pem could not be loaded as an RSA public key. "
     146              :                        "Expected PEM starting with "
     147              :                        "\"-----BEGIN PUBLIC KEY-----\" (PKCS#8) or "
     148              :                        "\"-----BEGIN RSA PUBLIC KEY-----\" (PKCS#1)");
     149              :         }
     150           10 :     } else if (rc == -2) {
     151            0 :         logger_log(LOG_ERROR,
     152              :                    "bootstrap: rsa_pem value in config.ini was truncated "
     153              :                    "(exceeds 8191 bytes) — key not loaded");
     154              :     } else {
     155           10 :         logger_log(LOG_WARN,
     156              :                    "bootstrap: rsa_pem not found in %s", path);
     157              :     }
     158              : 
     159              :     /* dc_N_host overrides (1..5). */
     160           60 :     for (int id = 1; id <= 5; id++) {
     161              :         char key[16];
     162           50 :         snprintf(key, sizeof(key), "dc_%d_host", id);
     163              :         char host_buf[256];
     164           50 :         if (bootstrap_read_ini_key(path, key, host_buf, sizeof(host_buf)) == 0) {
     165            0 :             dc_config_set_host_override(id, host_buf);
     166            0 :             logger_log(LOG_INFO,
     167              :                        "bootstrap: DC %d host overridden to %s", id, host_buf);
     168              :         }
     169              :     }
     170              : }
     171              : 
     172           10 : int app_bootstrap(AppContext *ctx, const char *program_name) {
     173           10 :     if (!ctx || !program_name) return -1;
     174              : 
     175           10 :     memset(ctx, 0, sizeof(*ctx));
     176           10 :     ctx->cache_dir  = platform_cache_dir();
     177           10 :     ctx->config_dir = platform_config_dir();
     178              : 
     179           10 :     if (ctx->cache_dir)  fs_mkdir_p(ctx->cache_dir, 0700);
     180           10 :     if (ctx->config_dir) fs_mkdir_p(ctx->config_dir, 0700);
     181              : 
     182           10 :     const char *base = ctx->cache_dir ? ctx->cache_dir : "/tmp/tg-cli";
     183           10 :     snprintf(ctx->log_path, sizeof(ctx->log_path), "%s/logs", base);
     184           10 :     fs_mkdir_p(ctx->log_path, 0700);
     185              : 
     186           10 :     size_t dir_len = strlen(ctx->log_path);
     187           10 :     snprintf(ctx->log_path + dir_len, sizeof(ctx->log_path) - dir_len,
     188              :              "/%s.log", program_name);
     189              : 
     190           10 :     logger_init(ctx->log_path, LOG_INFO);
     191           10 :     logger_log(LOG_INFO, "%s starting", program_name);
     192              : 
     193              :     /* Apply DC-host and RSA-key overrides from config.ini. */
     194           10 :     apply_config_overrides(ctx->config_dir);
     195              : 
     196           10 :     if (!telegram_server_key_get_pem()) {
     197            6 :         logger_log(LOG_ERROR,
     198              :                    "No RSA public key configured. "
     199              :                    "Add rsa_pem = <key> to ~/.config/tg-cli/config.ini "
     200              :                    "(obtain your api_id, api_hash, and RSA key at https://my.telegram.org)");
     201            6 :         return -1;
     202              :     }
     203              : 
     204            4 :     return 0;
     205              : }
     206              : 
     207            4 : void app_shutdown(AppContext *ctx) {
     208              :     (void)ctx;
     209            4 :     logger_log(LOG_INFO, "shutdown");
     210            4 :     logger_close();
     211            4 : }
        

Generated by: LCOV version 2.0-1