1 /*
2  * ProFTPD: mod_site_misc -- a module implementing miscellaneous SITE commands
3  * Copyright (c) 2004-2020 The ProFTPD Project
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
18  *
19  * As a special exemption, The ProFTPD Project team and other respective
20  * copyright holders give permission to link this program with OpenSSL, and
21  * distribute the resulting executable, without including the source code for
22  * OpenSSL in the source distribution.
23  */
24 
25 #include "conf.h"
26 
27 #define MOD_SITE_MISC_VERSION		"mod_site_misc/1.6"
28 
29 extern pr_response_t *resp_list, *resp_err_list;
30 
31 module site_misc_module;
32 
33 static unsigned int site_misc_engine = TRUE;
34 
35 /* Necessary prototypes */
36 static int site_misc_sess_init(void);
37 
site_misc_check_filters(cmd_rec * cmd,const char * path)38 static int site_misc_check_filters(cmd_rec *cmd, const char *path) {
39 #ifdef PR_USE_REGEX
40   pr_regex_t *pre = get_param_ptr(CURRENT_CONF, "PathAllowFilter", FALSE);
41   if (pre != NULL &&
42       pr_regexp_exec(pre, path, 0, NULL, 0, 0, 0) != 0) {
43     pr_log_pri(PR_LOG_NOTICE, MOD_SITE_MISC_VERSION
44       ": 'SITE %s' denied by PathAllowFilter", cmd->arg);
45     return -1;
46   }
47 
48   pre = get_param_ptr(CURRENT_CONF, "PathDenyFilter", FALSE);
49   if (pre != NULL &&
50       pr_regexp_exec(pre, path, 0, NULL, 0, 0, 0) == 0) {
51     pr_log_pri(PR_LOG_NOTICE, MOD_SITE_MISC_VERSION
52       ": 'SITE %s' denied by PathDenyFilter", cmd->arg);
53     return -1;
54   }
55 #endif
56 
57   return 0;
58 }
59 
site_misc_create_dir(const char * dir)60 static int site_misc_create_dir(const char *dir) {
61   struct stat st;
62   int res;
63 
64   pr_fs_clear_cache2(dir);
65   res = pr_fsio_stat(dir, &st);
66   if (res < 0 &&
67       errno != ENOENT) {
68     int xerrno = errno;
69 
70     pr_log_debug(DEBUG2, MOD_SITE_MISC_VERSION ": error checking '%s': %s",
71       dir, strerror(xerrno));
72 
73     errno = xerrno;
74     return -1;
75   }
76 
77   if (res == 0) {
78     /* Directory already exists */
79     return 1;
80   }
81 
82   if (pr_fsio_mkdir(dir, 0777) < 0) {
83     int xerrno = errno;
84 
85     pr_log_debug(DEBUG2, MOD_SITE_MISC_VERSION ": error creating '%s': %s",
86       dir, strerror(xerrno));
87 
88     errno = xerrno;
89     return -1;
90   }
91 
92   return 0;
93 }
94 
site_misc_create_path(pool * p,const char * path)95 static int site_misc_create_path(pool *p, const char *path) {
96   struct stat st;
97   char *curr_path, *tmp_path;
98 
99   pr_fs_clear_cache2(path);
100   if (pr_fsio_stat(path, &st) == 0) {
101     return 0;
102   }
103 
104   /* The given path should already be canonicalized; we do not need to worry
105    * if it is relative to the current working directory or not.
106    */
107 
108   tmp_path = pstrdup(p, path);
109 
110   curr_path = "/";
111   while (tmp_path &&
112          *tmp_path) {
113     char *curr_dir;
114     int res;
115     cmd_rec *cmd;
116     pool *sub_pool;
117 
118     pr_signals_handle();
119 
120     curr_dir = strsep(&tmp_path, "/");
121     curr_path = pdircat(p, curr_path, curr_dir, NULL);
122 
123     /* Dispatch the fake C_MKD command, e.g. for mod_quotatab. */
124     sub_pool = pr_pool_create_sz(p, 64);
125     cmd = pr_cmd_alloc(sub_pool, 2, pstrdup(sub_pool, C_MKD),
126       pstrdup(sub_pool, curr_path));
127     cmd->arg = pstrdup(cmd->pool, curr_path);
128     cmd->cmd_class = CL_DIRS|CL_WRITE;
129 
130     res = pr_cmd_dispatch_phase(cmd, PRE_CMD, 0);
131     if (res < 0) {
132       int xerrno = errno;
133 
134       pr_log_debug(DEBUG3, MOD_SITE_MISC_VERSION
135         ": creating directory '%s' blocked by MKD handler: %s", curr_path,
136         strerror(xerrno));
137 
138       pr_cmd_dispatch_phase(cmd, POST_CMD_ERR, 0);
139       pr_cmd_dispatch_phase(cmd, LOG_CMD_ERR, 0);
140       pr_response_clear(&resp_err_list);
141 
142       destroy_pool(sub_pool);
143       sub_pool = NULL;
144       cmd = NULL;
145 
146       errno = xerrno;
147       return -1;
148     }
149 
150     res = site_misc_create_dir(curr_path);
151     if (res < 0) {
152       int xerrno = errno;
153 
154       pr_cmd_dispatch_phase(cmd, POST_CMD_ERR, 0);
155       pr_cmd_dispatch_phase(cmd, LOG_CMD_ERR, 0);
156       pr_response_clear(&resp_err_list);
157 
158       destroy_pool(sub_pool);
159       sub_pool = NULL;
160       cmd = NULL;
161 
162       errno = xerrno;
163       return -1;
164     }
165 
166     pr_cmd_dispatch_phase(cmd, POST_CMD, 0);
167     pr_cmd_dispatch_phase(cmd, LOG_CMD, 0);
168     pr_response_clear(&resp_list);
169 
170     destroy_pool(sub_pool);
171     sub_pool = NULL;
172     cmd = NULL;
173   }
174 
175   return 0;
176 }
177 
site_misc_delete_dir(pool * p,const char * dir)178 static int site_misc_delete_dir(pool *p, const char *dir) {
179   void *dirh;
180   struct dirent *dent;
181   int res;
182   cmd_rec *cmd;
183   pool *sub_pool;
184 
185   dirh = pr_fsio_opendir(dir);
186   if (dirh == NULL)
187     return -1;
188 
189   while ((dent = pr_fsio_readdir(dirh)) != NULL) {
190     struct stat st;
191     char *file;
192 
193     pr_signals_handle();
194 
195     if (strncmp(dent->d_name, ".", 2) == 0 ||
196         strncmp(dent->d_name, "..", 3) == 0)
197       continue;
198 
199     file = pdircat(p, dir, dent->d_name, NULL);
200 
201     if (pr_fsio_stat(file, &st) < 0)
202       continue;
203 
204     if (S_ISDIR(st.st_mode)) {
205       res = site_misc_delete_dir(p, file);
206       if (res < 0) {
207         int xerrno = errno;
208 
209         pr_fsio_closedir(dirh);
210 
211         errno = xerrno;
212         return -1;
213       }
214 
215     } else {
216 
217       /* Dispatch fake C_DELE command, e.g. for mod_quotatab */
218       sub_pool = pr_pool_create_sz(p, 64);
219       cmd = pr_cmd_alloc(sub_pool, 2, pstrdup(sub_pool, C_DELE),
220         pstrdup(sub_pool, file));
221       cmd->arg = pstrdup(cmd->pool, file);
222       cmd->cmd_class = CL_WRITE;
223 
224       pr_response_block(TRUE);
225       res = pr_cmd_dispatch_phase(cmd, PRE_CMD, 0);
226       if (res < 0) {
227         int xerrno = errno;
228 
229         pr_log_debug(DEBUG3, MOD_SITE_MISC_VERSION
230           ": deleting file '%s' blocked by DELE handler: %s", file,
231           strerror(xerrno));
232 
233         pr_cmd_dispatch_phase(cmd, POST_CMD_ERR, 0);
234         pr_cmd_dispatch_phase(cmd, LOG_CMD_ERR, 0);
235         pr_response_clear(&resp_err_list);
236         pr_response_block(FALSE);
237 
238         destroy_pool(sub_pool);
239         pr_fsio_closedir(dirh);
240 
241         errno = xerrno;
242         return -1;
243       }
244 
245       res = pr_fsio_unlink(file);
246       if (res < 0) {
247         int xerrno = errno;
248 
249         pr_fsio_closedir(dirh);
250 
251         pr_cmd_dispatch_phase(cmd, POST_CMD_ERR, 0);
252         pr_cmd_dispatch_phase(cmd, LOG_CMD_ERR, 0);
253         pr_response_clear(&resp_err_list);
254         pr_response_block(FALSE);
255 
256         destroy_pool(sub_pool);
257         pr_fsio_closedir(dirh);
258 
259         errno = xerrno;
260         return -1;
261       }
262 
263       pr_response_add(R_250, _("%s command successful"), (char *) cmd->argv[0]);
264       pr_cmd_dispatch_phase(cmd, POST_CMD, 0);
265       pr_cmd_dispatch_phase(cmd, LOG_CMD, 0);
266       pr_response_clear(&resp_list);
267       destroy_pool(sub_pool);
268       pr_response_block(FALSE);
269     }
270   }
271 
272   pr_fsio_closedir(dirh);
273 
274   /* Dispatch fake C_RMD command, e.g. for mod_quotatab */
275   sub_pool = pr_pool_create_sz(p, 64);
276   cmd = pr_cmd_alloc(sub_pool, 2, pstrdup(sub_pool, C_RMD),
277     pstrdup(sub_pool, dir));
278   cmd->arg = pstrdup(cmd->pool, dir);
279   cmd->cmd_class = CL_DIRS|CL_WRITE;
280 
281   pr_response_block(TRUE);
282   res = pr_cmd_dispatch_phase(cmd, PRE_CMD, 0);
283   if (res < 0) {
284     int xerrno = errno;
285 
286     pr_log_debug(DEBUG3, MOD_SITE_MISC_VERSION
287       ": removing directory '%s' blocked by RMD handler: %s", dir,
288       strerror(xerrno));
289 
290     pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
291     pr_cmd_dispatch_phase(cmd, POST_CMD_ERR, 0);
292     pr_cmd_dispatch_phase(cmd, LOG_CMD_ERR, 0);
293     pr_response_clear(&resp_err_list);
294     pr_response_block(FALSE);
295 
296     destroy_pool(sub_pool);
297 
298     errno = xerrno;
299     return -1;
300   }
301 
302   res = pr_fsio_rmdir(dir);
303   if (res < 0) {
304     int xerrno = errno;
305 
306     pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
307     pr_cmd_dispatch_phase(cmd, POST_CMD_ERR, 0);
308     pr_cmd_dispatch_phase(cmd, LOG_CMD_ERR, 0);
309     pr_response_clear(&resp_err_list);
310     pr_response_block(FALSE);
311 
312     destroy_pool(sub_pool);
313     errno = xerrno;
314     return -1;
315   }
316 
317   pr_response_add(R_257, _("\"%s\" - Directory successfully created"),
318     quote_dir(cmd->tmp_pool, (char *) dir));
319   pr_cmd_dispatch_phase(cmd, POST_CMD, 0);
320   pr_cmd_dispatch_phase(cmd, LOG_CMD, 0);
321   pr_response_clear(&resp_list);
322   pr_response_block(FALSE);
323   destroy_pool(sub_pool);
324 
325   return 0;
326 }
327 
site_misc_delete_path(pool * p,const char * path)328 static int site_misc_delete_path(pool *p, const char *path) {
329   struct stat st;
330 
331   pr_fs_clear_cache2(path);
332   if (pr_fsio_stat(path, &st) < 0) {
333     return -1;
334   }
335 
336   if (!S_ISDIR(st.st_mode)) {
337     errno = EINVAL;
338     return -1;
339   }
340 
341   return site_misc_delete_dir(p, path);
342 }
343 
344 /* Parse a timestamp string of the form "YYYYMMDDhhmm[ss]" into its
345  * individual components.
346  *
347  * We assume that the caller has already ensured that the given timestamp
348  * string is long enough, i.e. 12 or 14 characters long.
349  */
site_misc_parsetime(char * timestamp,size_t timestamp_len,unsigned int * year,unsigned int * month,unsigned int * day,unsigned int * hour,unsigned int * min,unsigned int * sec)350 static int site_misc_parsetime(char *timestamp, size_t timestamp_len,
351     unsigned int *year, unsigned int *month, unsigned int *day,
352     unsigned int *hour, unsigned int *min, unsigned int *sec) {
353   register unsigned int i;
354   char c, *ptr;
355   int have_secs = FALSE, valid_timestamp = TRUE;
356 
357   /* Make sure the timestamp is comprised of all digits. */
358   for (i = 0; i < timestamp_len; i++) {
359     if (PR_ISDIGIT((int) timestamp[i]) == 0) {
360       valid_timestamp = FALSE;
361       break;
362     }
363   }
364 
365   if (!valid_timestamp) {
366     pr_log_debug(DEBUG7, MOD_SITE_MISC_VERSION
367       ": timestamp '%s' contains non-digits", timestamp);
368     errno = EINVAL;
369     return -1;
370   }
371 
372   if (timestamp_len == 14) {
373     have_secs = TRUE;
374   }
375 
376   ptr = timestamp;
377   c = timestamp[4];
378   timestamp[4] = '\0';
379   *year = atoi(ptr);
380   timestamp[4] = c;
381 
382   ptr = &(timestamp[4]);
383   c = timestamp[6];
384   timestamp[6] = '\0';
385   *month = atoi(ptr);
386   timestamp[6] = c;
387 
388   if (*month > 12) {
389     pr_log_debug(DEBUG7, MOD_SITE_MISC_VERSION
390       ": bad number of months in '%s' (%u)", timestamp, *month);
391     errno = EINVAL;
392     return -1;
393   }
394 
395   ptr = &(timestamp[6]);
396   c = timestamp[8];
397   timestamp[8] = '\0';
398   *day = atoi(ptr);
399   timestamp[8] = c;
400 
401   if (*day > 31) {
402     pr_log_debug(DEBUG7, MOD_SITE_MISC_VERSION
403       ": bad number of days in '%s' (%u)", timestamp, *day);
404     errno = EINVAL;
405     return -1;
406   }
407 
408   ptr = &(timestamp[8]);
409   c = timestamp[10];
410   timestamp[10] = '\0';
411   *hour = atoi(ptr);
412   timestamp[10] = c;
413 
414   if (*hour > 24) {
415     pr_log_debug(DEBUG7, MOD_SITE_MISC_VERSION
416       ": bad number of hours in '%s' (%u)", timestamp, *hour);
417     errno = EINVAL;
418     return -1;
419   }
420 
421   ptr = &(timestamp[10]);
422 
423   /* Handle optional seconds. */
424   if (have_secs) {
425     c = timestamp[12];
426     timestamp[12] = '\0';
427   }
428 
429   *min = atoi(ptr);
430 
431   if (have_secs) {
432     timestamp[12] = c;
433   }
434 
435   if (*min > 60) {
436     pr_log_debug(DEBUG7, MOD_SITE_MISC_VERSION
437       ": bad number of minutes in '%s' (%u)", timestamp, *min);
438     errno = EINVAL;
439     return -1;
440   }
441 
442   if (have_secs) {
443     ptr = &(timestamp[12]);
444     *sec = atoi(ptr);
445 
446     if (*sec > 60) {
447       pr_log_debug(DEBUG7, MOD_SITE_MISC_VERSION
448         ": bad number of seconds in '%s' (%u)", timestamp, *sec);
449       errno = EINVAL;
450       return -1;
451     }
452   }
453 
454   return 0;
455 }
456 
site_misc_mktime(unsigned int year,unsigned int month,unsigned int mday,unsigned int hour,unsigned int min,unsigned int sec)457 static time_t site_misc_mktime(unsigned int year, unsigned int month,
458     unsigned int mday, unsigned int hour, unsigned int min, unsigned int sec) {
459   struct tm tm;
460   time_t res;
461   char *env;
462 
463 #ifdef HAVE_TZNAME
464   char *tzname_dup[2];
465 
466   /* The mktime(3) function has a nasty habit of changing the tzname global
467    * variable as a side-effect.  This can cause problems, as when the process
468    * has become chrooted, and mktime(3) sets/changes tzname wrong.  (For more
469    * information on the tzname global variable, see the tzset(3) man page.)
470    *
471    * The best way to deal with this issue (which is especially prominent
472    * on systems running glibc-2.3 or later, which is particularly ill-behaved
473    * in a chrooted environment, as it assumes the ability to find system
474    * timezone files at paths which are no longer valid within the chroot)
475    * is to set the TZ environment variable explicitly, before starting
476    * proftpd.  You can also use the SetEnv configuration directive within
477    * the proftpd.conf to set the TZ environment variable, e.g.:
478    *
479    *  SetEnv TZ PST
480    *
481    * To try to help sites which fail to do this, the tzname global variable
482    * will be copied prior to the mktime(3) call, and the copy restored after
483    * the call.  (Note that calling the ctime(3) and localtime(3) functions also
484    * causes a similar overwriting/setting of the tzname environment variable.)
485    */
486   memcpy(&tzname_dup, tzname, sizeof(tzname_dup));
487 #endif /* HAVE_TZNAME */
488 
489   env = pr_env_get(session.pool, "TZ");
490 
491   /* Set the TZ environment to be GMT, so that mktime(3) treats the timestamp
492    * provided by the client as being in GMT/UTC.
493    */
494   if (pr_env_set(session.pool, "TZ", "GMT") < 0) {
495     pr_log_debug(DEBUG8, MOD_SITE_MISC_VERSION
496       ": error setting TZ environment variable to 'GMT': %s", strerror(errno));
497   }
498 
499   tm.tm_sec = sec;
500   tm.tm_min = min;
501   tm.tm_hour = hour;
502   tm.tm_mday = mday;
503   tm.tm_mon = (month - 1);
504   tm.tm_year = (year - 1900);
505   tm.tm_wday = 0;
506   tm.tm_yday = 0;
507   tm.tm_isdst = -1;
508 
509   res = mktime(&tm);
510 
511   /* Restore the old TZ setting, if any. */
512   if (env) {
513     if (pr_env_set(session.pool, "TZ", env) < 0) {
514       pr_log_debug(DEBUG8, MOD_SITE_MISC_VERSION
515         ": error setting TZ environment variable to '%s': %s", env,
516         strerror(errno));
517     }
518   }
519 
520 #ifdef HAVE_TZNAME
521   /* Restore the old tzname values prior to returning. */
522   memcpy(tzname, tzname_dup, sizeof(tzname_dup));
523 #endif /* HAVE_TZNAME */
524 
525   return res;
526 }
527 
528 /* Configuration handlers
529  */
530 
531 /* usage: SiteMiscEngine on|off */
set_sitemiscengine(cmd_rec * cmd)532 MODRET set_sitemiscengine(cmd_rec *cmd) {
533   config_rec *c;
534   int bool;
535 
536   CHECK_ARGS(cmd, 1);
537   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
538 
539   bool = get_boolean(cmd, 1);
540   if (bool == -1)
541     CONF_ERROR(cmd, "expected Boolean parameter");
542 
543   c = add_config_param(cmd->argv[0], 1, NULL);
544   c->argv[0] = pcalloc(c->pool, sizeof(unsigned int));
545   *((unsigned int *) c->argv[0]) = bool;
546 
547   return PR_HANDLED(cmd);
548 }
549 
550 /* Command handlers
551  */
552 
site_misc_mkdir(cmd_rec * cmd)553 MODRET site_misc_mkdir(cmd_rec *cmd) {
554   if (!site_misc_engine) {
555     return PR_DECLINED(cmd);
556   }
557 
558   if (cmd->argc < 2) {
559     pr_log_debug(DEBUG5, MOD_SITE_MISC_VERSION
560       "%s : wrong number of parameters (%d)", (char *) cmd->argv[0], cmd->argc);
561     return PR_DECLINED(cmd);
562   }
563 
564   if (strncasecmp(cmd->argv[1], "MKDIR", 6) == 0) {
565     register unsigned int i;
566     char *cmd_name, *decoded_path, *path = "";
567     unsigned char *authenticated;
568 
569     if (cmd->argc < 3) {
570       return PR_DECLINED(cmd);
571     }
572 
573     authenticated = get_param_ptr(cmd->server->conf, "authenticated", FALSE);
574     if (authenticated == NULL ||
575         *authenticated == FALSE) {
576       pr_response_add_err(R_530, _("Please login with USER and PASS"));
577 
578       pr_cmd_set_errno(cmd, EPERM);
579       errno = EPERM;
580       return PR_ERROR(cmd);
581     }
582 
583     for (i = 2; i < cmd->argc; i++) {
584       path = pstrcat(cmd->tmp_pool, path, *path ? " " : "", cmd->argv[i], NULL);
585     }
586 
587     decoded_path = pr_fs_decode_path2(cmd->tmp_pool, path,
588       FSIO_DECODE_FL_TELL_ERRORS);
589     if (decoded_path == NULL) {
590       int xerrno = errno;
591 
592       pr_log_debug(DEBUG8, "'%s' failed to decode properly: %s", path,
593         strerror(xerrno));
594       pr_response_add_err(R_550,
595         _("%s: Illegal character sequence in filename"), path);
596 
597       pr_cmd_set_errno(cmd, xerrno);
598       errno = xerrno;
599       return PR_ERROR(cmd);
600     }
601 
602     path = decoded_path;
603 
604     if (site_misc_check_filters(cmd, path) < 0) {
605       int xerrno = EPERM;
606 
607       pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
608 
609       pr_cmd_set_errno(cmd, xerrno);
610       errno = xerrno;
611       return PR_ERROR(cmd);
612     }
613 
614     path = dir_canonical_path(cmd->tmp_pool, path);
615     if (path == NULL) {
616       int xerrno = EINVAL;
617 
618       pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
619 
620       pr_cmd_set_errno(cmd, xerrno);
621       errno = xerrno;
622       return PR_ERROR(cmd);
623     }
624 
625     cmd_name = cmd->argv[0];
626     cmd->argv[0] = "SITE_MKDIR";
627     if (!dir_check_canon(cmd->tmp_pool, cmd, G_WRITE, path, NULL)) {
628       int xerrno = EPERM;
629 
630       cmd->argv[0] = cmd_name;
631 
632       pr_log_debug(DEBUG4, MOD_SITE_MISC_VERSION
633         ": %s command denied by <Limit>", (char *) cmd->argv[0]);
634       pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
635 
636       pr_cmd_set_errno(cmd, xerrno);
637       errno = xerrno;
638       return PR_ERROR(cmd);
639     }
640     cmd->argv[0] = cmd_name;
641 
642     if (site_misc_create_path(cmd->tmp_pool, path) < 0) {
643       int xerrno = errno;
644 
645       pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
646 
647       pr_cmd_set_errno(cmd, xerrno);
648       errno = xerrno;
649       return PR_ERROR(cmd);
650     }
651 
652     pr_response_add(R_200, _("SITE %s command successful"),
653       (char *) cmd->argv[1]);
654     return PR_HANDLED(cmd);
655   }
656 
657   if (strncasecmp(cmd->argv[1], "HELP", 5) == 0) {
658     pr_response_add(R_214, "MKDIR <sp> path");
659   }
660 
661   return PR_DECLINED(cmd);
662 }
663 
site_misc_rmdir(cmd_rec * cmd)664 MODRET site_misc_rmdir(cmd_rec *cmd) {
665   if (!site_misc_engine) {
666     return PR_DECLINED(cmd);
667   }
668 
669   if (cmd->argc < 2) {
670     pr_log_debug(DEBUG5, MOD_SITE_MISC_VERSION
671       "%s : wrong number of parameters (%d)", (char *) cmd->argv[0], cmd->argc);
672     return PR_DECLINED(cmd);
673   }
674 
675   if (strncasecmp(cmd->argv[1], "RMDIR", 6) == 0) {
676     register unsigned int i;
677     char *cmd_name, *decoded_path, *path = "";
678     unsigned char *authenticated;
679 
680     if (cmd->argc < 3)
681       return PR_DECLINED(cmd);
682 
683     authenticated = get_param_ptr(cmd->server->conf, "authenticated", FALSE);
684 
685     if (!authenticated ||
686         *authenticated == FALSE) {
687       pr_response_add_err(R_530, _("Please login with USER and PASS"));
688 
689       pr_cmd_set_errno(cmd, EPERM);
690       errno = EPERM;
691       return PR_ERROR(cmd);
692     }
693 
694     for (i = 2; i < cmd->argc; i++) {
695       path = pstrcat(cmd->tmp_pool, path, *path ? " " : "", cmd->argv[i], NULL);
696     }
697 
698     decoded_path = pr_fs_decode_path2(cmd->tmp_pool, path,
699       FSIO_DECODE_FL_TELL_ERRORS);
700     if (decoded_path == NULL) {
701       int xerrno = errno;
702 
703       pr_log_debug(DEBUG8, "'%s' failed to decode properly: %s", path,
704         strerror(xerrno));
705       pr_response_add_err(R_550,
706         _("%s: Illegal character sequence in filename"), path);
707 
708       pr_cmd_set_errno(cmd, xerrno);
709       errno = xerrno;
710       return PR_ERROR(cmd);
711     }
712 
713     path = dir_canonical_path(cmd->tmp_pool, decoded_path);
714     if (path == NULL) {
715       int xerrno = EINVAL;
716 
717       pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
718 
719       pr_cmd_set_errno(cmd, xerrno);
720       errno = xerrno;
721       return PR_ERROR(cmd);
722     }
723 
724     cmd_name = cmd->argv[0];
725     cmd->argv[0] = "SITE_RMDIR";
726     if (!dir_check_canon(cmd->tmp_pool, cmd, G_WRITE, path, NULL)) {
727       int xerrno = EPERM;
728 
729       cmd->argv[0] = cmd_name;
730 
731       pr_log_debug(DEBUG4, MOD_SITE_MISC_VERSION
732         ": %s command denied by <Limit>", (char *) cmd->argv[0]);
733       pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
734 
735       pr_cmd_set_errno(cmd, xerrno);
736       errno = xerrno;
737       return PR_ERROR(cmd);
738     }
739     cmd->argv[0] = cmd_name;
740 
741     if (site_misc_delete_path(cmd->tmp_pool, path) < 0) {
742       int xerrno = errno;
743 
744       pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
745 
746       pr_cmd_set_errno(cmd, xerrno);
747       errno = xerrno;
748       return PR_ERROR(cmd);
749     }
750 
751     pr_response_add(R_200, _("SITE %s command successful"),
752       (char *) cmd->argv[1]);
753     return PR_HANDLED(cmd);
754   }
755 
756   if (strncasecmp(cmd->argv[1], "HELP", 5) == 0) {
757     pr_response_add(R_214, "RMDIR <sp> path");
758   }
759 
760   return PR_DECLINED(cmd);
761 }
762 
site_misc_symlink(cmd_rec * cmd)763 MODRET site_misc_symlink(cmd_rec *cmd) {
764   if (!site_misc_engine) {
765     return PR_DECLINED(cmd);
766   }
767 
768   if (cmd->argc < 2) {
769     pr_log_debug(DEBUG5, MOD_SITE_MISC_VERSION
770       "%s : wrong number of parameters (%d)", (char *) cmd->argv[0], cmd->argc);
771     return PR_DECLINED(cmd);
772   }
773 
774   if (strncasecmp(cmd->argv[1], "SYMLINK", 8) == 0) {
775     struct stat st;
776     int res;
777     char *cmd_name, *decoded_path, *src, *dst;
778     unsigned char *authenticated;
779 
780     if (cmd->argc < 4)
781       return PR_DECLINED(cmd);
782 
783     authenticated = get_param_ptr(cmd->server->conf, "authenticated", FALSE);
784 
785     if (!authenticated ||
786         *authenticated == FALSE) {
787       pr_response_add_err(R_530, _("Please login with USER and PASS"));
788 
789       pr_cmd_set_errno(cmd, EPERM);
790       errno = EPERM;
791       return PR_ERROR(cmd);
792     }
793 
794     decoded_path = pr_fs_decode_path2(cmd->tmp_pool, cmd->argv[2],
795       FSIO_DECODE_FL_TELL_ERRORS);
796     if (decoded_path == NULL) {
797       int xerrno = errno;
798 
799       pr_log_debug(DEBUG8, "'%s' failed to decode properly: %s",
800         (char *) cmd->argv[2], strerror(xerrno));
801       pr_response_add_err(R_550,
802         _("%s: Illegal character sequence in filename"), (char *) cmd->argv[2]);
803 
804       pr_cmd_set_errno(cmd, xerrno);
805       errno = xerrno;
806       return PR_ERROR(cmd);
807     }
808 
809     src = dir_canonical_path(cmd->tmp_pool, decoded_path);
810     if (src == NULL) {
811       int xerrno = EINVAL;
812 
813       pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
814 
815       errno = xerrno;
816       return PR_ERROR(cmd);
817     }
818 
819     cmd_name = cmd->argv[0];
820     cmd->argv[0] = "SITE_SYMLINK";
821     if (!dir_check_canon(cmd->tmp_pool, cmd, G_READ, src, NULL)) {
822       int xerrno = EPERM;
823 
824       cmd->argv[0] = cmd_name;
825 
826       pr_log_debug(DEBUG4, MOD_SITE_MISC_VERSION
827         ": %s command denied by <Limit>", (char *) cmd->argv[0]);
828       pr_response_add_err(R_550, "%s: %s", (char *) cmd->argv[2],
829         strerror(xerrno));
830 
831       errno = xerrno;
832       return PR_ERROR(cmd);
833     }
834 
835     decoded_path = pr_fs_decode_path2(cmd->tmp_pool, cmd->argv[3],
836       FSIO_DECODE_FL_TELL_ERRORS);
837     if (decoded_path == NULL) {
838       int xerrno = errno;
839 
840       pr_log_debug(DEBUG8, "'%s' failed to decode properly: %s",
841         (char *) cmd->argv[3], strerror(xerrno));
842       pr_response_add_err(R_550,
843         _("%s: Illegal character sequence in filename"), (char *) cmd->argv[3]);
844 
845       pr_cmd_set_errno(cmd, xerrno);
846       errno = xerrno;
847       return PR_ERROR(cmd);
848     }
849 
850     dst = dir_canonical_path(cmd->tmp_pool, decoded_path);
851     if (dst == NULL) {
852       int xerrno = EINVAL;
853 
854       pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
855 
856       errno = xerrno;
857       return PR_ERROR(cmd);
858     }
859 
860     if (!dir_check_canon(cmd->tmp_pool, cmd, G_WRITE, dst, NULL)) {
861       int xerrno = EPERM;
862 
863       cmd->argv[0] = cmd_name;
864 
865       pr_log_debug(DEBUG4, MOD_SITE_MISC_VERSION
866         ": %s command denied by <Limit>", (char *) cmd->argv[0]);
867       pr_response_add_err(R_550, "%s: %s", (char *) cmd->argv[3],
868         strerror(xerrno));
869 
870       errno = xerrno;
871       return PR_ERROR(cmd);
872     }
873     cmd->argv[0] = cmd_name;
874 
875     if (site_misc_check_filters(cmd, dst) < 0) {
876       int xerrno = EPERM;
877 
878       pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
879 
880       errno = xerrno;
881       return PR_ERROR(cmd);
882     }
883 
884     /* Make sure the source path exists.  The symlink(2) man page suggests
885      * that the system call will do this, but experimentally (Mac OSX 10.4)
886      * I've seen symlink(2) happily link two names, neither of which exist
887      * in the filesystem.
888      */
889 
890     pr_fs_clear_cache2(src);
891     res = pr_fsio_stat(src, &st);
892     if (res < 0) {
893       int xerrno = errno;
894 
895       pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
896 
897       errno = xerrno;
898       return PR_ERROR(cmd);
899     }
900 
901     if (pr_fsio_symlink(src, dst) < 0) {
902       int xerrno = errno;
903 
904       pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
905 
906       errno = xerrno;
907       return PR_ERROR(cmd);
908     }
909 
910     pr_response_add(R_200, _("SITE %s command successful"),
911       (char *) cmd->argv[1]);
912     return PR_HANDLED(cmd);
913   }
914 
915   if (strncasecmp(cmd->argv[1], "HELP", 5) == 0) {
916     pr_response_add(R_214, "SYMLINK <sp> source <sp> destination");
917   }
918 
919   return PR_DECLINED(cmd);
920 }
921 
922 /* Handle: SITE UTIME mtime path-with-spaces */
site_misc_utime_mtime(cmd_rec * cmd)923 MODRET site_misc_utime_mtime(cmd_rec *cmd) {
924   register unsigned int i;
925   char *cmd_name, *decoded_path, *path = "";
926   size_t timestamp_len;
927   unsigned int year, month, day, hour, min, sec = 0;
928   struct timeval tvs[2];
929   struct stat st;
930 
931   /* Accept both 'YYYYMMDDhhmm' and 'YYYYMMDDhhmmss' formats. */
932   timestamp_len = strlen(cmd->argv[2]);
933   if (timestamp_len != 12 &&
934       timestamp_len != 14) {
935     int xerrno = EINVAL;
936 
937     pr_log_debug(DEBUG7, MOD_SITE_MISC_VERSION
938       ": wrong number of digits in timestamp argument '%s' (%lu)",
939       (char *) cmd->argv[2], (unsigned long) timestamp_len);
940     pr_response_add_err(R_500, "%s: %s", cmd->arg, strerror(xerrno));
941 
942     errno = xerrno;
943     return PR_ERROR(cmd);
944   }
945 
946   for (i = 3; i < cmd->argc; i++) {
947     path = pstrcat(cmd->tmp_pool, path, *path ? " " : "", cmd->argv[i], NULL);
948   }
949 
950   decoded_path = pr_fs_decode_path2(cmd->tmp_pool, path,
951     FSIO_DECODE_FL_TELL_ERRORS);
952   if (decoded_path == NULL) {
953     int xerrno = errno;
954 
955     pr_log_debug(DEBUG8, "'%s' failed to decode properly: %s", path,
956       strerror(xerrno));
957     pr_response_add_err(R_550, _("%s: Illegal character sequence in filename"),
958       path);
959 
960     pr_cmd_set_errno(cmd, xerrno);
961     errno = xerrno;
962     return PR_ERROR(cmd);
963   }
964 
965   if (pr_fsio_lstat(decoded_path, &st) == 0) {
966     if (S_ISLNK(st.st_mode)) {
967       char link_path[PR_TUNABLE_PATH_MAX];
968       int len;
969 
970       memset(link_path, '\0', sizeof(link_path));
971       len = dir_readlink(cmd->tmp_pool, decoded_path, link_path,
972         sizeof(link_path)-1, PR_DIR_READLINK_FL_HANDLE_REL_PATH);
973       if (len > 0) {
974         link_path[len] = '\0';
975         decoded_path = pstrdup(cmd->tmp_pool, link_path);
976       }
977     }
978   }
979 
980   path = dir_canonical_path(cmd->tmp_pool, decoded_path);
981   if (path == NULL) {
982     int xerrno = EINVAL;
983 
984     pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
985 
986     errno = xerrno;
987     return PR_ERROR(cmd);
988   }
989 
990   cmd_name = cmd->argv[0];
991   cmd->argv[0] = "SITE_UTIME";
992   if (!dir_check_canon(cmd->tmp_pool, cmd, G_WRITE, path, NULL)) {
993     int xerrno = EPERM;
994 
995     cmd->argv[0] = cmd_name;
996 
997     pr_log_debug(DEBUG4, MOD_SITE_MISC_VERSION
998       ": %s command denied by <Limit>", (char *) cmd->argv[0]);
999     pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
1000 
1001     errno = xerrno;
1002     return PR_ERROR(cmd);
1003   }
1004   cmd->argv[0] = cmd_name;
1005 
1006   if (site_misc_check_filters(cmd, path) < 0) {
1007     int xerrno = EPERM;
1008 
1009     pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
1010 
1011     errno = xerrno;
1012     return PR_ERROR(cmd);
1013   }
1014 
1015   if (site_misc_parsetime(cmd->argv[2], timestamp_len, &year, &month, &day,
1016       &hour, &min, &sec) < 0) {
1017     int xerrno = errno;
1018 
1019     pr_response_add_err(R_500, "%s: %s", cmd->arg, strerror(xerrno));
1020 
1021     errno = xerrno;
1022     return PR_ERROR(cmd);
1023   }
1024 
1025   tvs[0].tv_usec = tvs[1].tv_usec = 0;
1026   tvs[0].tv_sec = tvs[1].tv_sec = site_misc_mktime(year, month, day, hour,
1027     min, sec);
1028 
1029   if (pr_fsio_utimes_with_root(path, tvs) < 0) {
1030     int xerrno = errno;
1031 
1032     pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
1033 
1034     errno = xerrno;
1035     return PR_ERROR(cmd);
1036   }
1037 
1038   pr_response_add(R_200, _("SITE %s command successful"),
1039     (char *) cmd->argv[1]);
1040   return PR_HANDLED(cmd);
1041 }
1042 
1043 /* Handle: SITE UTIME path-with-spaces atime mtime ctime UTC */
site_misc_utime_atime_mtime_ctime(cmd_rec * cmd)1044 MODRET site_misc_utime_atime_mtime_ctime(cmd_rec *cmd) {
1045   register unsigned int i;
1046   char *cmd_name, *decoded_path, *path = "", *timestamp;
1047   size_t timestamp_len;
1048   unsigned int year, month, day, hour, min, sec = 0;
1049   time_t parsed_atime, parsed_mtime, parsed_ctime;
1050   struct timeval tvs[2];
1051 
1052   for (i = 2; i < cmd->argc-4; i++) {
1053     path = pstrcat(cmd->tmp_pool, path, *path ? " " : "", cmd->argv[i], NULL);
1054   }
1055 
1056   decoded_path = pr_fs_decode_path2(cmd->tmp_pool, path,
1057     FSIO_DECODE_FL_TELL_ERRORS);
1058   if (decoded_path == NULL) {
1059     int xerrno = errno;
1060 
1061     pr_log_debug(DEBUG8, "'%s' failed to decode properly: %s", path,
1062       strerror(xerrno));
1063     pr_response_add_err(R_550, _("%s: Illegal character sequence in filename"),
1064       path);
1065 
1066     pr_cmd_set_errno(cmd, xerrno);
1067     errno = xerrno;
1068     return PR_ERROR(cmd);
1069   }
1070 
1071   path = dir_canonical_path(cmd->tmp_pool, decoded_path);
1072   if (path == NULL) {
1073     int xerrno = EINVAL;
1074 
1075     pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
1076 
1077     errno = xerrno;
1078     return PR_ERROR(cmd);
1079   }
1080 
1081   cmd_name = cmd->argv[0];
1082   cmd->argv[0] = "SITE_UTIME";
1083   if (!dir_check_canon(cmd->tmp_pool, cmd, G_WRITE, path, NULL)) {
1084     int xerrno = EPERM;
1085 
1086     cmd->argv[0] = cmd_name;
1087 
1088     pr_log_debug(DEBUG4, MOD_SITE_MISC_VERSION
1089       ": %s command denied by <Limit>", (char *) cmd->argv[0]);
1090     pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
1091 
1092     errno = xerrno;
1093     return PR_ERROR(cmd);
1094   }
1095   cmd->argv[0] = cmd_name;
1096 
1097   if (site_misc_check_filters(cmd, path) < 0) {
1098     int xerrno = EPERM;
1099 
1100     pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
1101 
1102     errno = xerrno;
1103     return PR_ERROR(cmd);
1104   }
1105 
1106   /* Handle the atime. Accept both 'YYYYMMDDhhmm' and 'YYYYMMDDhhmmss'
1107    * formats.
1108    */
1109   timestamp = cmd->argv[cmd->argc-4];
1110   timestamp_len = strlen(timestamp);
1111   if (timestamp_len != 12 &&
1112       timestamp_len != 14) {
1113     int xerrno = EINVAL;
1114 
1115     pr_log_debug(DEBUG7, MOD_SITE_MISC_VERSION
1116       ": wrong number of digits in timestamp argument '%s' (%lu)",
1117       timestamp, (unsigned long) timestamp_len);
1118     pr_response_add_err(R_500, "%s: %s", cmd->arg, strerror(xerrno));
1119 
1120     errno = xerrno;
1121     return PR_ERROR(cmd);
1122   }
1123 
1124   if (site_misc_parsetime(timestamp, timestamp_len, &year, &month, &day,
1125       &hour, &min, &sec) < 0) {
1126     int xerrno = errno;
1127 
1128     pr_response_add_err(R_500, "%s: %s", cmd->arg, strerror(xerrno));
1129 
1130     errno = xerrno;
1131     return PR_ERROR(cmd);
1132   }
1133 
1134   parsed_atime = site_misc_mktime(year, month, day, hour, min, sec);
1135 
1136   /* Handle the mtime. Accept both 'YYYYMMDDhhmm' and 'YYYYMMDDhhmmss'
1137    * formats.
1138    */
1139 
1140   sec = 0;
1141   timestamp = cmd->argv[cmd->argc-3];
1142   timestamp_len = strlen(timestamp);
1143   if (timestamp_len != 12 &&
1144       timestamp_len != 14) {
1145     int xerrno = EINVAL;
1146 
1147     pr_log_debug(DEBUG7, MOD_SITE_MISC_VERSION
1148       ": wrong number of digits in timestamp argument '%s' (%lu)",
1149       timestamp, (unsigned long) timestamp_len);
1150     pr_response_add_err(R_500, "%s: %s", cmd->arg, strerror(xerrno));
1151 
1152     errno = xerrno;
1153     return PR_ERROR(cmd);
1154   }
1155 
1156   if (site_misc_parsetime(timestamp, timestamp_len, &year, &month, &day,
1157       &hour, &min, &sec) < 0) {
1158     int xerrno = errno;
1159 
1160     pr_response_add_err(R_500, "%s: %s", cmd->arg, strerror(xerrno));
1161 
1162     errno = xerrno;
1163     return PR_ERROR(cmd);
1164   }
1165 
1166   parsed_mtime = site_misc_mktime(year, month, day, hour, min, sec);
1167 
1168   /* Handle the ctime. Accept both 'YYYYMMDDhhmm' and 'YYYYMMDDhhmmss'
1169    * formats.
1170    */
1171 
1172   sec = 0;
1173   timestamp = cmd->argv[cmd->argc-2];
1174   timestamp_len = strlen(timestamp);
1175   if (timestamp_len != 12 &&
1176       timestamp_len != 14) {
1177     int xerrno = EINVAL;
1178 
1179     pr_log_debug(DEBUG7, MOD_SITE_MISC_VERSION
1180       ": wrong number of digits in timestamp argument '%s' (%lu)",
1181       timestamp, (unsigned long) timestamp_len);
1182     pr_response_add_err(R_500, "%s: %s", cmd->arg, strerror(xerrno));
1183 
1184     errno = xerrno;
1185     return PR_ERROR(cmd);
1186   }
1187 
1188   if (site_misc_parsetime(timestamp, timestamp_len, &year, &month, &day,
1189       &hour, &min, &sec) < 0) {
1190     int xerrno = errno;
1191 
1192     pr_response_add_err(R_500, "%s: %s", cmd->arg, strerror(xerrno));
1193 
1194     errno = xerrno;
1195     return PR_ERROR(cmd);
1196   }
1197 
1198   /* Unix filesystems typically do not allow changing/setting the creation
1199    * timestamp.  Thus we parse the timestamp provided by the client, but
1200    * do nothing but log it.
1201    */
1202   parsed_ctime = site_misc_mktime(year, month, day, hour, min, sec);
1203   pr_trace_msg("command", 9,
1204     "SITE UTIME command sent ctime timestamp of %lu secs",
1205     (unsigned long) parsed_ctime);
1206 
1207   tvs[0].tv_usec = tvs[1].tv_usec = 0;
1208   tvs[0].tv_sec = parsed_atime;
1209   tvs[1].tv_sec = parsed_mtime;
1210 
1211   if (pr_fsio_utimes_with_root(path, tvs) < 0) {
1212     int xerrno = errno;
1213 
1214     pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
1215 
1216     errno = xerrno;
1217     return PR_ERROR(cmd);
1218   }
1219 
1220   pr_response_add(R_200, _("SITE %s command successful"),
1221     (char *) cmd->argv[1]);
1222   return PR_HANDLED(cmd);
1223 }
1224 
site_misc_utime(cmd_rec * cmd)1225 MODRET site_misc_utime(cmd_rec *cmd) {
1226   if (!site_misc_engine) {
1227     return PR_DECLINED(cmd);
1228   }
1229 
1230   if (cmd->argc < 2) {
1231     pr_log_debug(DEBUG5, MOD_SITE_MISC_VERSION
1232       "%s : wrong number of parameters (%d)", (char *) cmd->argv[0], cmd->argc);
1233     return PR_DECLINED(cmd);
1234   }
1235 
1236   if (strncasecmp(cmd->argv[1], "UTIME", 6) == 0) {
1237     unsigned char *authenticated;
1238 
1239     authenticated = get_param_ptr(cmd->server->conf, "authenticated", FALSE);
1240     if (authenticated == NULL ||
1241         *authenticated == FALSE) {
1242       pr_response_add_err(R_530, _("Please login with USER and PASS"));
1243       errno = EACCES;
1244       return PR_ERROR(cmd);
1245     }
1246 
1247     /* Now try to determine whether we are dealing with the mtime-only
1248      * SITE UTIME variant (one timestamp only), or with the atime/mtime/ctime
1249      * variant (three timestamps).  What makes this trickier is making sure
1250      * to handle filenames that contain spaces.
1251      */
1252 
1253     if (cmd->argc < 4) {
1254       /* Not enough arguments for any SITE UTIME variant. */
1255       pr_log_debug(DEBUG9, MOD_SITE_MISC_VERSION
1256         ": SITE UTIME command has wrong number of parameters (%d), ignoring",
1257         cmd->argc);
1258       return PR_DECLINED(cmd);
1259     }
1260 
1261     /* If we have at least 7 parameters, AND the last parameter is "UTC"
1262      * (case-insensitive), then it's a candidate for the atime/mtime/ctime
1263      * variant.
1264      */
1265     if (cmd->argc >= 7 &&
1266         strncasecmp(cmd->argv[cmd->argc-1], "UTC", 4) == 0) {
1267       return site_misc_utime_atime_mtime_ctime(cmd);
1268     }
1269 
1270     return site_misc_utime_mtime(cmd);
1271   }
1272 
1273   if (strncasecmp(cmd->argv[1], "HELP", 5) == 0) {
1274     pr_response_add(R_214, "UTIME <sp> YYYYMMDDhhmm[ss] <sp> path");
1275   }
1276 
1277   return PR_DECLINED(cmd);
1278 }
1279 
1280 /* Event listeners
1281  */
1282 
site_misc_sess_reinit_ev(const void * event_data,void * user_data)1283 static void site_misc_sess_reinit_ev(const void *event_data, void *user_data) {
1284   int res;
1285 
1286   /* A HOST command changed the main_server pointer, reinitialize ourselves. */
1287 
1288   pr_event_unregister(&site_misc_module, "core.session-reinit",
1289     site_misc_sess_reinit_ev);
1290 
1291   site_misc_engine = TRUE;
1292   pr_feat_remove("SITE MKDIR");
1293   pr_feat_remove("SITE RMDIR");
1294   pr_feat_remove("SITE SYMLINK");
1295   pr_feat_remove("SITE UTIME");
1296 
1297   res = site_misc_sess_init();
1298   if (res < 0) {
1299     pr_session_disconnect(&site_misc_module,
1300       PR_SESS_DISCONNECT_SESSION_INIT_FAILED, NULL);
1301   }
1302 }
1303 
1304 /* Initialization functions
1305  */
1306 
site_misc_sess_init(void)1307 static int site_misc_sess_init(void) {
1308   config_rec *c;
1309 
1310   pr_event_register(&site_misc_module, "core.session-reinit",
1311     site_misc_sess_reinit_ev, NULL);
1312 
1313   c = find_config(main_server->conf, CONF_PARAM, "SiteMiscEngine", FALSE);
1314   if (c) {
1315     site_misc_engine = *((unsigned int *) c->argv[0]);
1316   }
1317 
1318   if (!site_misc_engine) {
1319     return 0;
1320   }
1321 
1322   /* Advertise support for these SITE commands */
1323   pr_feat_add("SITE MKDIR");
1324   pr_feat_add("SITE RMDIR");
1325   pr_feat_add("SITE SYMLINK");
1326   pr_feat_add("SITE UTIME");
1327 
1328   return 0;
1329 }
1330 
1331 /* Module API tables
1332  */
1333 
1334 static conftable site_misc_conftab[] = {
1335   { "SiteMiscEngine",	set_sitemiscengine,	NULL },
1336   { NULL }
1337 };
1338 
1339 static cmdtable site_misc_cmdtab[] = {
1340   { CMD, C_SITE, G_WRITE, site_misc_mkdir,	FALSE,	FALSE, CL_MISC },
1341   { CMD, C_SITE, G_WRITE, site_misc_rmdir,	FALSE,	FALSE, CL_MISC },
1342   { CMD, C_SITE, G_WRITE, site_misc_symlink,	FALSE,	FALSE, CL_MISC },
1343   { CMD, C_SITE, G_WRITE, site_misc_utime,	FALSE,	FALSE, CL_MISC },
1344   { 0, NULL }
1345 };
1346 
1347 module site_misc_module = {
1348   NULL, NULL,
1349 
1350   /* Module API version 2.0 */
1351   0x20,
1352 
1353   /* Module name */
1354   "site_misc",
1355 
1356   /* Module configuration handler table */
1357   site_misc_conftab,
1358 
1359   /* Module command handler table */
1360   site_misc_cmdtab,
1361 
1362   /* Module authentication handler table */
1363   NULL,
1364 
1365   /* Module initialization function */
1366   NULL,
1367 
1368   /* Session initialization function */
1369   site_misc_sess_init,
1370 
1371   /* Module version */
1372   MOD_SITE_MISC_VERSION
1373 };
1374