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

proftpd / proftpd / 26182518137

20 May 2026 06:49PM UTC coverage: 93.024% (+0.4%) from 92.635%
26182518137

push

github

51329 of 55178 relevant lines covered (93.02%)

226.63 hits per line

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

90.52
/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 *),
41
    cmd_rec *cmd) {
86✔
42
  modret_t *res;
43
  module *prev_module = curr_module;
86✔
44

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

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

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

79✔
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;
65
}
79✔
66

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

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

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

28✔
78
  return res;
79
}
28✔
80

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

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

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

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

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

102
  return res;
103
}
104

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

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

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

19✔
117
  return res;
118
}
19✔
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) {
124
  module *prev_module = curr_module, *m;
4✔
125

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2✔
199
    listf("Compiled-in modules:\n");
200
    for (i = 0; static_modules[i]; i++) {
2✔
201
      module *m = static_modules[i];
4✔
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

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

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

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

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

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

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

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

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

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

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

266
  return 0;
267
}
268

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

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

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

2✔
282
      if (cmdtab->cmd_type == HOOK) {
283
        if (pr_stash_add_symbol(PR_SYM_HOOK, cmdtab) < 0) {
2✔
284
          return -1;
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) {
290
          return -1;
1✔
291
        }
292
      }
293
    }
294
  }
295

296
  return 0;
297
}
298

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

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

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

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

318
  return 0;
319
}
320

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

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

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

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

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

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

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

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

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

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

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

372
    loaded_modules = m;
373

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

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

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

16✔
386
  if (m == NULL ||
387
      m->name == NULL) {
16✔
388
    errno = EINVAL;
15✔
389
    return -1;
2✔
390
  }
2✔
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));
397
  pr_snprintf(buf, sizeof(buf), "mod_%s.c", m->name);
14✔
398
  buf[sizeof(buf)-1] = '\0';
14✔
399

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

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

4✔
408
  /* Remove the module from the loaded_modules list. */
409
  if (m->prev) {
410
    m->prev->next = m->next;
4✔
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;
417
  }
4✔
418

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

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

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

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

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

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

1✔
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);
444
      }
1✔
445
    }
446
  }
447

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

451
    for (authtab = m->authtable; authtab->name; authtab++) {
452
      pr_stash_remove_symbol(PR_SYM_AUTH, authtab->name, authtab->m);
2✔
453
    }
1✔
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);
470
#endif /* PR_USE_CTRLS */
4✔
471
  pr_event_unregister(m, NULL, NULL);
472
  pr_timer_remove(-1, m);
4✔
473

4✔
474
  return 0;
475
}
4✔
476

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

25✔
480
  for (i = 0; static_modules[i]; i++) {
481
    module *m = static_modules[i];
25✔
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;
491
}
25✔
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