Coverage Report

Created: 2026-05-30 06:56

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tor/src/lib/fs/conffile.c
Line
Count
Source
1
/* Copyright (c) 2001 Matej Pfajfar.
2
 * Copyright (c) 2001-2004, Roger Dingledine.
3
 * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
4
 * Copyright (c) 2007-2021, The Tor Project, Inc. */
5
/* See LICENSE for licensing information */
6
7
/**
8
 * \file conffile.h
9
 *
10
 * \brief Read configuration files from disk, with full `%include` support.
11
 **/
12
13
#include "lib/fs/conffile.h"
14
15
#include "lib/container/smartlist.h"
16
#include "lib/encoding/confline.h"
17
#include "lib/fs/dir.h"
18
#include "lib/fs/files.h"
19
#include "lib/fs/path.h"
20
#include "lib/log/log.h"
21
#include "lib/malloc/malloc.h"
22
#include "lib/sandbox/sandbox.h"
23
#include "lib/string/printf.h"
24
25
#include <stdbool.h>
26
#include <errno.h>
27
28
static smartlist_t *config_get_file_list(const char *path,
29
                                         smartlist_t *opened_files);
30
static int config_get_included_config(const char *path, int recursion_level,
31
                                      int extended, config_line_t **config,
32
                                      config_line_t **config_last,
33
                                      smartlist_t *opened_lst);
34
static int config_process_include(const char *path, int recursion_level,
35
                                  int extended, config_line_t **list,
36
                                  config_line_t **list_last,
37
                                  smartlist_t *opened_lst);
38
39
/** Helper: parse the config string and strdup into key/value
40
 * strings. Set *result to the list, or NULL if parsing the string
41
 * failed. Set *has_include to 1 if <b>result</b> has values from
42
 * %included files. <b>opened_lst</b> will have a list of opened files if
43
 * provided. Return 0 on success, -1 on failure. Warn and ignore any
44
 * misformatted lines.
45
 *
46
 * If <b>extended</b> is set, then treat keys beginning with / and with + as
47
 * indicating "clear" and "append" respectively. */
48
int
49
config_get_lines_include(const char *string, config_line_t **result,
50
                         int extended, int *has_include,
51
                         smartlist_t *opened_lst)
52
0
{
53
0
  return config_get_lines_aux(string, result, extended, 1, has_include,
54
0
                              opened_lst, 1, NULL, config_process_include);
55
0
}
56
57
/** Return a list of paths obtained when expanding globs in <b>pattern</b>.
58
 * If <b>pattern</b> has no globs, return a list with <b>pattern</b> in it.
59
 * If <b>opened_files</b> is provided, add paths opened by glob to it.
60
 * Return NULL on failure. */
61
static smartlist_t *
62
expand_glob(const char *pattern, smartlist_t *opened_files)
63
0
{
64
0
  if (! has_glob(pattern)) {
65
0
    smartlist_t *matches = smartlist_new();
66
0
    smartlist_add_strdup(matches, pattern);
67
0
    return matches;
68
0
  }
69
70
0
  smartlist_t *matches = tor_glob(pattern);
71
0
  if (!matches) {
72
0
    if (errno == EPERM) {
73
0
      log_err(LD_CONFIG, "Sandbox is active, but the configuration pattern "
74
0
              "\"%s\" listed with %%include would access files or folders not "
75
0
              "allowed by it. Cannot proceed.", pattern);
76
0
    }
77
0
    return NULL;
78
0
  }
79
80
0
  if (opened_files) {
81
0
    smartlist_t *glob_opened = get_glob_opened_files(pattern);
82
0
    if (!glob_opened) {
83
0
      SMARTLIST_FOREACH(matches, char *, f, tor_free(f));
84
0
      smartlist_free(matches);
85
0
      return NULL;
86
0
    }
87
0
    smartlist_add_all(opened_files, glob_opened);
88
0
    smartlist_free(glob_opened);
89
0
  }
90
0
  smartlist_sort_strings(matches);
91
0
  return matches;
92
0
}
93
94
/** Returns a list of configuration files present on paths that match
95
 * <b>pattern</b>. The pattern is expanded and then all the paths are
96
 * processed. A path can be a file or a directory. If it is a file, that file
97
 * will be added to the list to be returned. If it is a directory,
98
 * all paths for files on that directory root (no recursion) except for files
99
 * whose name starts with a dot will be added to the list to be returned.
100
 * <b>opened_files</b> will have a list of files opened by this function
101
 * if provided. Return NULL on failure. Ignores empty files.
102
 */
103
static smartlist_t *
104
config_get_file_list(const char *pattern, smartlist_t *opened_files)
105
0
{
106
0
  smartlist_t *glob_matches = expand_glob(pattern, opened_files);
107
0
  if (!glob_matches) {
108
0
    return NULL;
109
0
  }
110
111
0
  bool error_found = false;
112
0
  smartlist_t *file_list = smartlist_new();
113
0
  SMARTLIST_FOREACH_BEGIN(glob_matches, char *, path) {
114
0
    if (opened_files) {
115
0
      smartlist_add_strdup(opened_files, path);
116
0
    }
117
0
    if (sandbox_interned_string_is_missing(path)) {
118
0
      log_err(LD_CONFIG, "Sandbox is active, but a new configuration "
119
0
              "file \"%s\" has been listed with %%include. Cannot proceed.",
120
0
              path);
121
0
      error_found = true;
122
0
      break;
123
0
    }
124
125
0
    file_status_t file_type = file_status(path);
126
0
    if (file_type == FN_FILE) {
127
0
      smartlist_add_strdup(file_list, path);
128
0
    } else if (file_type == FN_DIR) {
129
0
      smartlist_t *all_files = tor_listdir(path);
130
0
      if (!all_files) {
131
0
        error_found = true;
132
0
        break;
133
0
      }
134
0
      smartlist_sort_strings(all_files);
135
0
      SMARTLIST_FOREACH_BEGIN(all_files, char *, f) {
136
0
        if (f[0] == '.') {
137
0
          continue;
138
0
        }
139
140
0
        char *fullname;
141
0
        tor_asprintf(&fullname, "%s"PATH_SEPARATOR"%s", path, f);
142
143
0
        if (opened_files) {
144
0
          smartlist_add_strdup(opened_files, fullname);
145
0
        }
146
147
0
        if (file_status(fullname) != FN_FILE) {
148
0
          tor_free(fullname);
149
0
          continue;
150
0
        }
151
0
        smartlist_add(file_list, fullname);
152
0
      } SMARTLIST_FOREACH_END(f);
153
0
      SMARTLIST_FOREACH(all_files, char *, f, tor_free(f));
154
0
      smartlist_free(all_files);
155
0
    } else if (file_type == FN_EMPTY) {
156
0
        continue;
157
0
    } else {
158
0
      error_found = true;
159
0
      break;
160
0
    }
161
0
  } SMARTLIST_FOREACH_END(path);
162
0
  SMARTLIST_FOREACH(glob_matches, char *, f, tor_free(f));
163
0
  smartlist_free(glob_matches);
164
165
0
  if (error_found) {
166
0
    SMARTLIST_FOREACH(file_list, char *, f, tor_free(f));
167
0
    smartlist_free(file_list);
168
0
    file_list = NULL;
169
0
  }
170
171
0
  return file_list;
172
0
}
173
174
/** Creates a list of config lines present on included <b>path</b>.
175
 * Set <b>config</b> to the list and <b>config_last</b> to the last element of
176
 * <b>config</b>. <b>opened_lst</b> will have a list of opened files if
177
 * provided. Return 0 on success, -1 on failure. */
178
static int
179
config_get_included_config(const char *path, int recursion_level, int extended,
180
                           config_line_t **config, config_line_t **config_last,
181
                           smartlist_t *opened_lst)
182
0
{
183
0
  char *included_conf = read_file_to_str(path, 0, NULL);
184
0
  if (!included_conf) {
185
0
    return -1;
186
0
  }
187
188
0
  if (config_get_lines_aux(included_conf, config, extended, 1, NULL,
189
0
                           opened_lst, recursion_level+1, config_last,
190
0
                           config_process_include) < 0) {
191
0
    tor_free(included_conf);
192
0
    return -1;
193
0
  }
194
195
0
  tor_free(included_conf);
196
0
  return 0;
197
0
}
198
199
/** Process an %include <b>pattern</b> in a config file. Set <b>list</b> to the
200
 * list of configuration settings obtained and <b>list_last</b> to the last
201
 * element of the same list. <b>opened_lst</b> will have a list of opened
202
 * files if provided. Return 0 on success, -1 on failure. */
203
static int
204
config_process_include(const char *pattern, int recursion_level, int extended,
205
                       config_line_t **list, config_line_t **list_last,
206
                       smartlist_t *opened_lst)
207
0
{
208
0
  config_line_t *ret_list = NULL;
209
0
  config_line_t **next = &ret_list;
210
211
0
  smartlist_t *config_files = config_get_file_list(pattern, opened_lst);
212
0
  if (!config_files) {
213
0
    return -1;
214
0
  }
215
216
0
  int rv = -1;
217
0
  SMARTLIST_FOREACH_BEGIN(config_files, const char *, config_file) {
218
0
    if (sandbox_interned_string_is_missing(config_file)) {
219
0
      log_err(LD_CONFIG, "Sandbox is active, but a new configuration "
220
0
              "file \"%s\" has been listed with %%include. Cannot proceed.",
221
0
              config_file);
222
0
      goto done;
223
0
    }
224
225
0
    log_notice(LD_CONFIG, "Including configuration file \"%s\".", config_file);
226
0
    config_line_t *included_config = NULL;
227
0
    config_line_t *included_config_last = NULL;
228
0
    if (config_get_included_config(config_file, recursion_level, extended,
229
0
                                   &included_config, &included_config_last,
230
0
                                   opened_lst) < 0) {
231
0
      goto done;
232
0
    }
233
234
0
    *next = included_config;
235
0
    if (included_config_last) {
236
0
      next = &included_config_last->next;
237
0
      *list_last = included_config_last;
238
0
    }
239
0
  } SMARTLIST_FOREACH_END(config_file);
240
0
  *list = ret_list;
241
0
  rv = 0;
242
243
0
 done:
244
0
  SMARTLIST_FOREACH(config_files, char *, f, tor_free(f));
245
  smartlist_free(config_files);
246
0
  return rv;
247
0
}