1 /*
2  * ProFTPD - FTP server daemon
3  * Copyright (c) 1997, 1998 Public Flood Software
4  * Copyright (c) 2001-2017 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, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
19  *
20  * As a special exemption, Public Flood Software/MacGyver aka Habeeb J. Dihu
21  * and other respective copyright holders give permission to link this program
22  * with OpenSSL, and distribute the resulting executable, without including
23  * the source code for OpenSSL in the source distribution.
24  */
25 
26 /* Module handling routines */
27 
28 #include "conf.h"
29 
30 extern module *static_modules[];
31 extern module *loaded_modules;
32 
33 /* Currently running module */
34 module *curr_module = NULL;
35 
36 /* Used to track the priority for loaded modules. */
37 static unsigned int curr_module_pri = 0;
38 
39 static const char *trace_channel = "module";
40 
pr_module_call(module * m,modret_t * (* func)(cmd_rec *),cmd_rec * cmd)41 modret_t *pr_module_call(module *m, modret_t *(*func)(cmd_rec *),
42     cmd_rec *cmd) {
43   modret_t *res;
44   module *prev_module = curr_module;
45 
46   if (m == NULL ||
47       func == NULL ||
48       cmd == NULL) {
49     errno = EINVAL;
50     return NULL;
51   }
52 
53   if (cmd->tmp_pool == NULL) {
54     cmd->tmp_pool = make_sub_pool(cmd->pool);
55     pr_pool_tag(cmd->tmp_pool, "Module call tmp_pool");
56   }
57 
58   curr_module = m;
59   res = func(cmd);
60   curr_module = prev_module;
61 
62   /* Note that we don't clear the pool here because the function may
63    * return data which resides in this pool.
64    */
65   return res;
66 }
67 
mod_create_data(cmd_rec * cmd,void * d)68 modret_t *mod_create_data(cmd_rec *cmd, void *d) {
69   modret_t *res;
70 
71   if (cmd == NULL) {
72     errno = EINVAL;
73     return NULL;
74   }
75 
76   res = pcalloc(cmd->tmp_pool, sizeof(modret_t));
77   res->data = d;
78 
79   return res;
80 }
81 
mod_create_ret(cmd_rec * cmd,unsigned char err,const char * n,const char * m)82 modret_t *mod_create_ret(cmd_rec *cmd, unsigned char err, const char *n,
83     const char *m) {
84   modret_t *res;
85 
86   if (cmd == NULL) {
87     errno = EINVAL;
88     return NULL;
89   }
90 
91   res = pcalloc(cmd->tmp_pool, sizeof(modret_t));
92   res->mr_handler_module = curr_module;
93   res->mr_error = err;
94 
95   if (n != NULL) {
96     res->mr_numeric = pstrdup(cmd->tmp_pool, n);
97   }
98 
99   if (m != NULL) {
100     res->mr_message = pstrdup(cmd->tmp_pool, m);
101   }
102 
103   return res;
104 }
105 
mod_create_error(cmd_rec * cmd,int mr_errno)106 modret_t *mod_create_error(cmd_rec *cmd, int mr_errno) {
107   modret_t *res;
108 
109   if (cmd == NULL) {
110     errno = EINVAL;
111     return NULL;
112   }
113 
114   res = pcalloc(cmd->tmp_pool, sizeof(modret_t));
115   res->mr_handler_module = curr_module;
116   res->mr_error = mr_errno;
117 
118   return res;
119 }
120 
121 /* Called after forking in order to inform/initialize modules
122  * need to know we are a child and have a connection.
123  */
modules_session_init(void)124 int modules_session_init(void) {
125   module *prev_module = curr_module, *m;
126 
127   for (m = loaded_modules; m; m = m->next) {
128     if (m->sess_init) {
129       curr_module = m;
130 
131       pr_trace_msg(trace_channel, 12,
132         "invoking sess_init callback on mod_%s.c", m->name);
133       if (m->sess_init() < 0) {
134         int xerrno = errno;
135 
136         pr_log_pri(PR_LOG_WARNING, "mod_%s.c: error initializing session: %s",
137           m->name, strerror(xerrno));
138 
139         errno = xerrno;
140         return -1;
141       }
142     }
143   }
144 
145   curr_module = prev_module;
146   return 0;
147 }
148 
command_exists(const char * name)149 unsigned char command_exists(const char *name) {
150   int idx = -1;
151   unsigned int hash = 0;
152   cmdtable *cmdtab;
153 
154   cmdtab = pr_stash_get_symbol2(PR_SYM_CMD, name, NULL, &idx, &hash);
155   while (cmdtab && cmdtab->cmd_type != CMD) {
156     pr_signals_handle();
157     cmdtab = pr_stash_get_symbol2(PR_SYM_CMD, name, cmdtab, &idx, &hash);
158   }
159 
160   return (cmdtab ? TRUE : FALSE);
161 }
162 
pr_module_exists(const char * name)163 unsigned char pr_module_exists(const char *name) {
164   return pr_module_get(name) != NULL ? TRUE : FALSE;
165 }
166 
pr_module_get(const char * name)167 module *pr_module_get(const char *name) {
168   char buf[80] = {'\0'};
169   module *m;
170 
171   if (name == NULL) {
172     errno = EINVAL;
173     return NULL;
174   }
175 
176   /* Check the list of compiled-in modules. */
177   for (m = loaded_modules; m; m = m->next) {
178     memset(buf, '\0', sizeof(buf));
179     pr_snprintf(buf, sizeof(buf), "mod_%s.c", m->name);
180     buf[sizeof(buf)-1] = '\0';
181 
182     if (strcmp(buf, name) == 0) {
183       return m;
184     }
185   }
186 
187   errno = ENOENT;
188   return NULL;
189 }
190 
modules_list2(int (* listf)(const char *,...),int flags)191 void modules_list2(int (*listf)(const char *, ...), int flags) {
192   if (listf == NULL) {
193     listf = printf;
194   }
195 
196   if (flags & PR_MODULES_LIST_FL_SHOW_STATIC) {
197     register unsigned int i = 0;
198 
199     listf("Compiled-in modules:\n");
200     for (i = 0; static_modules[i]; i++) {
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");
223     for (m = loaded_modules; m; m = m->next) {
224 
225       if (flags & PR_MODULES_LIST_FL_SHOW_VERSION) {
226         const char *version;
227 
228         version = m->module_version;
229         if (version != NULL) {
230           listf("  %s\n", version);
231 
232         } else {
233           listf("  mod_%s.c\n", m->name);
234         }
235 
236       } else {
237         listf("  mod_%s.c\n", m->name);
238       }
239     }
240   }
241 }
242 
modules_list(int flags)243 void modules_list(int flags) {
244   modules_list2(NULL, flags);
245 }
246 
pr_module_load_authtab(module * m)247 int pr_module_load_authtab(module *m) {
248   if (m == NULL ||
249       m->name == NULL) {
250     errno = EINVAL;
251     return -1;
252   }
253 
254   if (m->authtable) {
255     authtable *authtab;
256 
257     for (authtab = m->authtable; authtab->name; authtab++) {
258       authtab->m = m;
259 
260       if (pr_stash_add_symbol(PR_SYM_AUTH, authtab) < 0) {
261         return -1;
262       }
263     }
264   }
265 
266   return 0;
267 }
268 
pr_module_load_cmdtab(module * m)269 int pr_module_load_cmdtab(module *m) {
270   if (m == NULL ||
271       m->name == NULL) {
272     errno = EINVAL;
273     return -1;
274   }
275 
276   if (m->cmdtable) {
277     cmdtable *cmdtab;
278 
279     for (cmdtab = m->cmdtable; cmdtab->command; cmdtab++) {
280       cmdtab->m = m;
281 
282       if (cmdtab->cmd_type == HOOK) {
283         if (pr_stash_add_symbol(PR_SYM_HOOK, cmdtab) < 0) {
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) {
290           return -1;
291         }
292       }
293     }
294   }
295 
296   return 0;
297 }
298 
pr_module_load_conftab(module * m)299 int pr_module_load_conftab(module *m) {
300   if (m == NULL ||
301       m->name == NULL) {
302     errno = EINVAL;
303     return -1;
304   }
305 
306   if (m->conftable) {
307     conftable *conftab;
308 
309     for (conftab = m->conftable; conftab->directive; conftab++) {
310       conftab->m = m;
311 
312       if (pr_stash_add_symbol(PR_SYM_CONF, conftab) < 0) {
313         return -1;
314       }
315     }
316   }
317 
318   return 0;
319 }
320 
pr_module_load(module * m)321 int pr_module_load(module *m) {
322   char buf[256];
323 
324   if (m == NULL ||
325       m->name == NULL) {
326     errno = EINVAL;
327     return -1;
328   }
329 
330   /* Check the API version the module wants to use. */
331   if (m->api_version < PR_MODULE_API_VERSION) {
332     errno = EACCES;
333     return -1;
334   }
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);
339   buf[sizeof(buf)-1] = '\0';
340 
341   if (pr_module_get(buf) != NULL) {
342     errno = EEXIST;
343     return -1;
344   }
345 
346   /* Invoke the module's initialization routine. */
347   if (!m->init ||
348       m->init() >= 0) {
349 
350     /* Assign a priority to this module. */
351     m->priority = curr_module_pri++;
352 
353     /* Add the module's config, cmd, and auth tables. */
354     if (pr_module_load_conftab(m) < 0) {
355       return -1;
356     }
357 
358     if (pr_module_load_cmdtab(m) < 0) {
359       return -1;
360     }
361 
362     if (pr_module_load_authtab(m) < 0) {
363       return -1;
364     }
365 
366     /* Add the module to the loaded_modules list. */
367     if (loaded_modules) {
368       m->next = loaded_modules;
369       loaded_modules->prev = m;
370     }
371 
372     loaded_modules = m;
373 
374     /* Generate an event. */
375     pr_event_generate("core.module-load", buf);
376     return 0;
377   }
378 
379   errno = EPERM;
380   return -1;
381 }
382 
pr_module_unload(module * m)383 int pr_module_unload(module *m) {
384   char buf[256];
385 
386   if (m == NULL ||
387       m->name == NULL) {
388     errno = EINVAL;
389     return -1;
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));
397   pr_snprintf(buf, sizeof(buf), "mod_%s.c", m->name);
398   buf[sizeof(buf)-1] = '\0';
399 
400   if (pr_module_get(buf) == NULL) {
401     errno = ENOENT;
402     return -1;
403   }
404 
405   /* Generate an event. */
406   pr_event_generate("core.module-unload", buf);
407 
408   /* Remove the module from the loaded_modules list. */
409   if (m->prev) {
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;
417   }
418 
419   if (m->next)
420     m->next->prev = m->prev;
421 
422   m->prev = m->next = NULL;
423 
424   /* Remove the module's config, cmd, and auth tables. */
425   if (m->conftable) {
426     conftable *conftab;
427 
428     for (conftab = m->conftable; conftab->directive; conftab++) {
429       pr_stash_remove_symbol(PR_SYM_CONF, conftab->directive, conftab->m);
430     }
431   }
432 
433   if (m->cmdtable) {
434     cmdtable *cmdtab;
435 
436     for (cmdtab = m->cmdtable; cmdtab->command; cmdtab++) {
437       if (cmdtab->cmd_type == HOOK) {
438         pr_stash_remove_symbol(PR_SYM_HOOK, cmdtab->command, cmdtab->m);
439 
440       } else {
441         /* All other cmd_types are for CMDs: PRE_CMD, CMD, POST_CMD, etc. */
442         pr_stash_remove_symbol(PR_SYM_CMD, cmdtab->command, cmdtab->m);
443       }
444     }
445   }
446 
447   if (m->authtable) {
448     authtable *authtab;
449 
450     for (authtab = m->authtable; authtab->name; authtab++) {
451       pr_stash_remove_symbol(PR_SYM_AUTH, authtab->name, authtab->m);
452     }
453   }
454 
455   /* Remove any callbacks that the module may have registered, i.e.:
456    *
457    * ctrls
458    * events
459    * timers
460    *
461    * Ideally we would also automatically unregister other callbacks that
462    * the module may have registered, such as FSIO, NetIO, variables, and
463    * response handlers.  However, these APIs do not yet allow for
464    * removal of all callbacks for a given module.
465    */
466 
467 #ifdef PR_USE_CTRLS
468   pr_ctrls_unregister(m, NULL);
469 #endif /* PR_USE_CTRLS */
470   pr_event_unregister(m, NULL, NULL);
471   pr_timer_remove(-1, m);
472 
473   return 0;
474 }
475 
modules_init(void)476 int modules_init(void) {
477   register unsigned int i = 0;
478 
479   for (i = 0; static_modules[i]; i++) {
480     module *m = static_modules[i];
481 
482     if (pr_module_load(m) < 0) {
483       pr_log_pri(PR_LOG_WARNING, "fatal: unable to load module 'mod_%s.c': %s",
484         m->name, strerror(errno));
485       exit(1);
486     }
487   }
488 
489   return 0;
490 }
491