• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

proftpd / proftpd / 26127302554

19 May 2026 07:34PM UTC coverage: 92.635% (-0.4%) from 93.024%
26127302554

push

github

Castaglia
Make a flaky, racy mod_copy regression test more reliable.

47303 of 51064 relevant lines covered (92.63%)

241.07 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.45
/src/modules.c
1
/*
2
 * ProFTPD - FTP server daemon
3
 * Copyright (c) 1997, 1998 Public Flood Software
4
 * Copyright (c) 2001-2026 The ProFTPD Project team
5
 *
6
 * This program is free software; you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation; either version 2 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program; if not, see <https://www.gnu.org/licenses/>.
18
 *
19
 * As a special exemption, Public Flood Software/MacGyver aka Habeeb J. Dihu
20
 * and other respective copyright holders give permission to link this program
21
 * with OpenSSL, and distribute the resulting executable, without including
22
 * the source code for OpenSSL in the source distribution.
23
 */
24

25
/* Module handling routines */
26

27
#include "conf.h"
28

29
extern module *static_modules[];
30
extern module *loaded_modules;
31

32
/* Currently running module */
33
module *curr_module = NULL;
34

35
/* Used to track the priority for loaded modules. */
36
static unsigned int curr_module_pri = 0;
37

38
static const char *trace_channel = "module";
39

40
modret_t *pr_module_call(module *m, modret_t *(*func)(cmd_rec *),
86✔
41
    cmd_rec *cmd) {
42
  modret_t *res;
43
  module *prev_module = curr_module;
86✔
44

45
  if (m == NULL ||
172✔
46
      func == NULL ||
166✔
47
      cmd == NULL) {
48
    errno = EINVAL;
7✔
49
    return NULL;
7✔
50
  }
51

52
  if (cmd->tmp_pool == NULL) {
79✔
53
    cmd->tmp_pool = make_sub_pool(cmd->pool);
9✔
54
    pr_pool_tag(cmd->tmp_pool, "Module call tmp_pool");
9✔
55
  }
56

57
  curr_module = m;
79✔
58
  res = func(cmd);
79✔
59
  curr_module = prev_module;
79✔
60

61
  /* Note that we don't clear the pool here because the function may
62
   * return data which resides in this pool.
63
   */
64
  return res;
79✔
65
}
66

67
modret_t *mod_create_data(cmd_rec *cmd, void *d) {
29✔
68
  modret_t *res;
69

70
  if (cmd == NULL) {
29✔
71
    errno = EINVAL;
1✔
72
    return NULL;
1✔
73
  }
74

75
  res = pcalloc(cmd->tmp_pool, sizeof(modret_t));
28✔
76
  res->data = d;
28✔
77

78
  return res;
28✔
79
}
80

81
modret_t *mod_create_ret(cmd_rec *cmd, unsigned char err, const char *n,
75✔
82
    const char *m) {
83
  modret_t *res;
84

85
  if (cmd == NULL) {
75✔
86
    errno = EINVAL;
1✔
87
    return NULL;
1✔
88
  }
89

90
  res = pcalloc(cmd->tmp_pool, sizeof(modret_t));
74✔
91
  res->mr_handler_module = curr_module;
74✔
92
  res->mr_error = err;
74✔
93

94
  if (n != NULL) {
74✔
95
    res->mr_numeric = pstrdup(cmd->tmp_pool, n);
1✔
96
  }
97

98
  if (m != NULL) {
74✔
99
    res->mr_message = pstrdup(cmd->tmp_pool, m);
54✔
100
  }
101

102
  return res;
103
}
104

105
modret_t *mod_create_error(cmd_rec *cmd, int mr_errno) {
20✔
106
  modret_t *res;
107

108
  if (cmd == NULL) {
20✔
109
    errno = EINVAL;
1✔
110
    return NULL;
1✔
111
  }
112

113
  res = pcalloc(cmd->tmp_pool, sizeof(modret_t));
19✔
114
  res->mr_handler_module = curr_module;
19✔
115
  res->mr_error = mr_errno;
19✔
116

117
  return res;
19✔
118
}
119

120
/* Called after forking in order to inform/initialize modules
121
 * need to know we are a child and have a connection.
122
 */
123
int modules_session_init(void) {
4✔
124
  module *prev_module = curr_module, *m;
4✔
125

126
  for (m = loaded_modules; m; m = m->next) {
6✔
127
    if (m->sess_init) {
3✔
128
      curr_module = m;
2✔
129

130
      pr_trace_msg(trace_channel, 12,
2✔
131
        "invoking sess_init callback on mod_%s.c", m->name);
132
      if (m->sess_init() < 0) {
2✔
133
        int xerrno = errno;
1✔
134

135
        pr_log_pri(PR_LOG_WARNING,
1✔
136
          "mod_%s.c: error initializing session (%s), check module logs "
137
          "for details", m->name, strerror(xerrno));
138

139
        errno = xerrno;
1✔
140
        return -1;
1✔
141
      }
142
    }
143
  }
144

145
  curr_module = prev_module;
3✔
146
  return 0;
3✔
147
}
148

149
unsigned char command_exists(const char *name) {
1✔
150
  int idx = -1;
1✔
151
  unsigned int hash = 0;
1✔
152
  cmdtable *cmdtab;
153

154
  cmdtab = pr_stash_get_symbol2(PR_SYM_CMD, name, NULL, &idx, &hash);
1✔
155
  while (cmdtab && cmdtab->cmd_type != CMD) {
2✔
156
    pr_signals_handle();
×
157
    cmdtab = pr_stash_get_symbol2(PR_SYM_CMD, name, cmdtab, &idx, &hash);
×
158
  }
159

160
  return (cmdtab ? TRUE : FALSE);
1✔
161
}
162

163
unsigned char pr_module_exists(const char *name) {
5✔
164
  return pr_module_get(name) != NULL ? TRUE : FALSE;
5✔
165
}
166

167
module *pr_module_get(const char *name) {
52✔
168
  char buf[80] = {'\0'};
52✔
169
  module *m;
170

171
  if (name == NULL) {
52✔
172
    errno = EINVAL;
2✔
173
    return NULL;
2✔
174
  }
175

176
  /* Check the list of compiled-in modules. */
177
  for (m = loaded_modules; m; m = m->next) {
58✔
178
    memset(buf, '\0', sizeof(buf));
32✔
179
    pr_snprintf(buf, sizeof(buf), "mod_%s.c", m->name);
32✔
180
    buf[sizeof(buf)-1] = '\0';
32✔
181

182
    if (strcmp(buf, name) == 0) {
32✔
183
      return m;
184
    }
185
  }
186

187
  errno = ENOENT;
26✔
188
  return NULL;
26✔
189
}
190

191
void modules_list2(int (*listf)(const char *, ...), int flags) {
4✔
192
  if (listf == NULL) {
4✔
193
    listf = printf;
1✔
194
  }
195

196
  if (flags & PR_MODULES_LIST_FL_SHOW_STATIC) {
4✔
197
    register unsigned int i = 0;
2✔
198

199
    listf("Compiled-in modules:\n");
2✔
200
    for (i = 0; static_modules[i]; i++) {
2✔
201
      module *m = static_modules[i];
×
202

203
      if (flags & PR_MODULES_LIST_FL_SHOW_VERSION) {
×
204
        const char *version;
205

206
        version = m->module_version;
×
207
        if (version != NULL) {
×
208
          listf("  %s\n", version);
×
209

210
        } else {
211
          listf("  mod_%s.c\n", m->name);
×
212
        }
213

214
      } else {
215
        listf("  mod_%s.c\n", m->name);
×
216
      }
217
    }
218

219
  } else {
220
    module *m;
221

222
    listf("Loaded modules:\n");
2✔
223
    for (m = loaded_modules; m; m = m->next) {
4✔
224

225
      if (flags & PR_MODULES_LIST_FL_SHOW_VERSION) {
2✔
226
        const char *version;
227

228
        version = m->module_version;
2✔
229
        if (version != NULL) {
2✔
230
          listf("  %s\n", version);
1✔
231

232
        } else {
233
          listf("  mod_%s.c\n", m->name);
1✔
234
        }
235

236
      } else {
237
        listf("  mod_%s.c\n", m->name);
×
238
      }
239
    }
240
  }
241
}
4✔
242

243
void modules_list(int flags) {
1✔
244
  modules_list2(NULL, flags);
1✔
245
}
1✔
246

247
int pr_module_load_authtab(module *m) {
7✔
248
  if (m == NULL ||
13✔
249
      m->name == NULL) {
6✔
250
    errno = EINVAL;
2✔
251
    return -1;
2✔
252
  }
253

254
  if (m->authtable) {
5✔
255
    authtable *authtab;
256

257
    for (authtab = m->authtable; authtab->name; authtab++) {
1✔
258
      authtab->m = m;
1✔
259

260
      if (pr_stash_add_symbol(PR_SYM_AUTH, authtab) < 0) {
1✔
261
        return -1;
262
      }
263
    }
264
  }
265

266
  return 0;
267
}
268

269
int pr_module_load_cmdtab(module *m) {
7✔
270
  if (m == NULL ||
13✔
271
      m->name == NULL) {
6✔
272
    errno = EINVAL;
2✔
273
    return -1;
2✔
274
  }
275

276
  if (m->cmdtable) {
5✔
277
    cmdtable *cmdtab;
278

279
    for (cmdtab = m->cmdtable; cmdtab->command; cmdtab++) {
2✔
280
      cmdtab->m = m;
2✔
281

282
      if (cmdtab->cmd_type == HOOK) {
2✔
283
        if (pr_stash_add_symbol(PR_SYM_HOOK, cmdtab) < 0) {
1✔
284
          return -1;
285
        }
286

287
      } else {
288
        /* All other cmd_types are for CMDs: PRE_CMD, CMD, POST_CMD, etc. */
289
        if (pr_stash_add_symbol(PR_SYM_CMD, cmdtab) < 0) {
1✔
290
          return -1;
291
        }
292
      }
293
    }
294
  }
295

296
  return 0;
297
}
298

299
int pr_module_load_conftab(module *m) {
9✔
300
  if (m == NULL ||
17✔
301
      m->name == NULL) {
8✔
302
    errno = EINVAL;
2✔
303
    return -1;
2✔
304
  }
305

306
  if (m->conftable) {
7✔
307
    conftable *conftab;
308

309
    for (conftab = m->conftable; conftab->directive; conftab++) {
5✔
310
      conftab->m = m;
5✔
311

312
      if (pr_stash_add_symbol(PR_SYM_CONF, conftab) < 0) {
5✔
313
        return -1;
314
      }
315
    }
316
  }
317

318
  return 0;
319
}
320

321
int pr_module_load(module *m) {
8✔
322
  char buf[256];
323

324
  if (m == NULL ||
15✔
325
      m->name == NULL) {
7✔
326
    errno = EINVAL;
2✔
327
    return -1;
2✔
328
  }
329

330
  /* Check the API version the module wants to use. */
331
  if (m->api_version < PR_MODULE_API_VERSION) {
6✔
332
    errno = EACCES;
1✔
333
    return -1;
1✔
334
  }
335

336
  /* Do not allow multiple modules with the same name. */
337
  memset(buf, '\0', sizeof(buf));
5✔
338
  pr_snprintf(buf, sizeof(buf), "mod_%s.c", m->name);
5✔
339
  buf[sizeof(buf)-1] = '\0';
5✔
340

341
  if (pr_module_get(buf) != NULL) {
5✔
342
    errno = EEXIST;
1✔
343
    return -1;
1✔
344
  }
345

346
  /* Invoke the module's initialization routine. */
347
  if (!m->init ||
5✔
348
      m->init() >= 0) {
1✔
349

350
    /* Assign a priority to this module. */
351
    m->priority = curr_module_pri++;
3✔
352

353
    /* Add the module's config, cmd, and auth tables. */
354
    if (pr_module_load_conftab(m) < 0) {
3✔
355
      return -1;
356
    }
357

358
    if (pr_module_load_cmdtab(m) < 0) {
3✔
359
      return -1;
360
    }
361

362
    if (pr_module_load_authtab(m) < 0) {
3✔
363
      return -1;
364
    }
365

366
    /* Add the module to the loaded_modules list. */
367
    if (loaded_modules) {
3✔
368
      m->next = loaded_modules;
×
369
      loaded_modules->prev = m;
×
370
    }
371

372
    loaded_modules = m;
3✔
373

374
    /* Generate an event. */
375
    pr_event_generate("core.module-load", buf);
3✔
376
    return 0;
3✔
377
  }
378

379
  errno = EPERM;
1✔
380
  return -1;
1✔
381
}
382

383
int pr_module_unload(module *m) {
16✔
384
  char buf[256];
385

386
  if (m == NULL ||
31✔
387
      m->name == NULL) {
15✔
388
    errno = EINVAL;
2✔
389
    return -1;
2✔
390
  }
391

392
  /* Make sure this module has been loaded.  We can't unload a module that
393
   * has not been loaded, now can we?
394
   */
395

396
  memset(buf, '\0', sizeof(buf));
14✔
397
  pr_snprintf(buf, sizeof(buf), "mod_%s.c", m->name);
14✔
398
  buf[sizeof(buf)-1] = '\0';
14✔
399

400
  if (pr_module_get(buf) == NULL) {
14✔
401
    errno = ENOENT;
10✔
402
    return -1;
10✔
403
  }
404

405
  /* Generate an event. */
406
  pr_event_generate("core.module-unload", buf);
4✔
407

408
  /* Remove the module from the loaded_modules list. */
409
  if (m->prev) {
4✔
410
    m->prev->next = m->next;
×
411

412
  } else {
413
    /* This module is the start of the loaded_modules list (prev is NULL),
414
     * so we need to update that pointer, too.
415
     */
416
    loaded_modules = m->next;
4✔
417
  }
418

419
  if (m->next) {
4✔
420
    m->next->prev = m->prev;
×
421
  }
422

423
  m->prev = m->next = NULL;
4✔
424

425
  /* Remove the module's config, cmd, and auth tables. */
426
  if (m->conftable) {
4✔
427
    conftable *conftab;
428

429
    for (conftab = m->conftable; conftab->directive; conftab++) {
1✔
430
      pr_stash_remove_symbol(PR_SYM_CONF, conftab->directive, conftab->m);
1✔
431
    }
432
  }
433

434
  if (m->cmdtable) {
4✔
435
    cmdtable *cmdtab;
436

437
    for (cmdtab = m->cmdtable; cmdtab->command; cmdtab++) {
2✔
438
      if (cmdtab->cmd_type == HOOK) {
2✔
439
        pr_stash_remove_symbol(PR_SYM_HOOK, cmdtab->command, cmdtab->m);
1✔
440

441
      } else {
442
        /* All other cmd_types are for CMDs: PRE_CMD, CMD, POST_CMD, etc. */
443
        pr_stash_remove_symbol(PR_SYM_CMD, cmdtab->command, cmdtab->m);
1✔
444
      }
445
    }
446
  }
447

448
  if (m->authtable) {
4✔
449
    authtable *authtab;
450

451
    for (authtab = m->authtable; authtab->name; authtab++) {
1✔
452
      pr_stash_remove_symbol(PR_SYM_AUTH, authtab->name, authtab->m);
1✔
453
    }
454
  }
455

456
  /* Remove any callbacks that the module may have registered, i.e.:
457
   *
458
   * ctrls
459
   * events
460
   * timers
461
   *
462
   * Ideally we would also automatically unregister other callbacks that
463
   * the module may have registered, such as FSIO, NetIO, variables, and
464
   * response handlers.  However, these APIs do not yet allow for
465
   * removal of all callbacks for a given module.
466
   */
467

468
#ifdef PR_USE_CTRLS
469
  pr_ctrls_unregister(m, NULL);
4✔
470
#endif /* PR_USE_CTRLS */
471
  pr_event_unregister(m, NULL, NULL);
4✔
472
  pr_timer_remove(-1, m);
4✔
473

474
  return 0;
4✔
475
}
476

477
int modules_init(void) {
25✔
478
  register unsigned int i = 0;
25✔
479

480
  for (i = 0; static_modules[i]; i++) {
25✔
481
    module *m = static_modules[i];
×
482

483
    if (pr_module_load(m) < 0) {
×
484
      pr_log_pri(PR_LOG_WARNING, "fatal: unable to load module 'mod_%s.c': %s",
×
485
        m->name, strerror(errno));
×
486
      exit(1);
×
487
    }
488
  }
489

490
  return 0;
25✔
491
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc