Coverage Report

Created: 2026-02-23 06:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/proftpd/modules/mod_site.c
Line
Count
Source
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, 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
/* "SITE" commands module for ProFTPD. */
27
28
#include "conf.h"
29
30
modret_t *site_dispatch(cmd_rec *cmd);
31
32
static struct {
33
  char *cmd;
34
  char *syntax;
35
  int implemented;
36
} _help[] = {
37
  { "HELP", "[<sp> site-command]",      TRUE },
38
  { "CHGRP",  "<sp> group <sp> pathname",   TRUE },
39
  { "CHMOD",  "<sp> mode <sp> pathname",    TRUE },
40
  { NULL, NULL,         FALSE }
41
};
42
43
0
static char *full_cmd(cmd_rec *cmd) {
44
0
  register unsigned int i;
45
0
  char *res = "";
46
0
  size_t reslen = 0;
47
48
0
  for (i = 0; i < cmd->argc; i++) {
49
0
    res = pstrcat(cmd->tmp_pool, res, cmd->argv[i], " ", NULL);
50
0
  }
51
52
0
  reslen = strlen(res);
53
0
  while (reslen >= 1 &&
54
0
         res[reslen-1] == ' ') {
55
0
    res[reslen-1] = '\0';
56
0
    reslen--;
57
0
  }
58
59
0
  return res;
60
0
}
61
62
0
MODRET site_chgrp(cmd_rec *cmd) {
63
0
  int res;
64
0
  gid_t gid;
65
0
  char *path = NULL, *tmp = NULL, *arg = "";
66
0
  register unsigned int i = 0;
67
0
#ifdef PR_USE_REGEX
68
0
  pr_regex_t *pre;
69
0
#endif
70
71
0
  if (cmd->argc < 3) {
72
0
    pr_response_add_err(R_500, _("'SITE %s' not understood"), full_cmd(cmd));
73
0
    return NULL;
74
0
  }
75
76
  /* Construct the target file name by concatenating all the parameters after
77
   * the mode, separating them with spaces.
78
   */
79
0
  for (i = 2; i <= cmd->argc-1; i++) {
80
0
    char *decoded_path;
81
82
0
    decoded_path = pr_fs_decode_path2(cmd->tmp_pool, cmd->argv[i],
83
0
      FSIO_DECODE_FL_TELL_ERRORS);
84
0
    if (decoded_path == NULL) {
85
0
      int xerrno = errno;
86
87
0
      pr_log_debug(DEBUG8, "'%s' failed to decode properly: %s",
88
0
        (char *) cmd->argv[i], strerror(xerrno));
89
0
      pr_response_add_err(R_550,
90
0
        _("SITE %s: Illegal character sequence in command"),
91
0
        (char *) cmd->argv[1]);
92
93
0
      pr_cmd_set_errno(cmd, xerrno);
94
0
      errno = xerrno;
95
0
      return PR_ERROR(cmd);
96
0
    }
97
98
0
    arg = pstrcat(cmd->tmp_pool, arg, *arg ? " " : "", decoded_path, NULL);
99
0
  }
100
101
0
#ifdef PR_USE_REGEX
102
0
  pre = get_param_ptr(CURRENT_CONF, "PathAllowFilter", FALSE);
103
0
  if (pre != NULL &&
104
0
      pr_regexp_exec(pre, arg, 0, NULL, 0, 0, 0) != 0) {
105
0
    pr_log_pri(PR_LOG_NOTICE, "'%s %s' denied by PathAllowFilter",
106
0
      (char *) cmd->argv[0], arg);
107
0
    pr_response_add_err(R_550, _("%s: Forbidden filename"), cmd->arg);
108
109
0
    pr_cmd_set_errno(cmd, EPERM);
110
0
    errno = EPERM;
111
0
    return PR_ERROR(cmd);
112
0
  }
113
114
0
  pre = get_param_ptr(CURRENT_CONF, "PathDenyFilter", FALSE);
115
0
  if (pre != NULL &&
116
0
      pr_regexp_exec(pre, arg, 0, NULL, 0, 0, 0) == 0) {
117
0
    pr_log_pri(PR_LOG_NOTICE, "'%s %s' denied by PathDenyFilter",
118
0
      (char *) cmd->argv[0], arg);
119
0
    pr_response_add_err(R_550, _("%s: Forbidden filename"), cmd->arg);
120
121
0
    pr_cmd_set_errno(cmd, EPERM);
122
0
    errno = EPERM;
123
0
    return PR_ERROR(cmd);
124
0
  }
125
0
#endif
126
127
0
  pr_fs_clear_cache2(arg);
128
0
  path = dir_realpath(cmd->tmp_pool, arg);
129
0
  if (path == NULL) {
130
0
    int xerrno = errno;
131
132
0
    pr_response_add_err(R_550, "%s: %s", arg, strerror(xerrno));
133
134
0
    pr_cmd_set_errno(cmd, xerrno);
135
0
    errno = xerrno;
136
0
    return PR_ERROR(cmd);
137
0
  }
138
139
  /* Map the given group argument, if a string, to a GID.  If already a
140
   * number, pass through as is.
141
   */
142
0
  gid = strtoul(cmd->argv[1], &tmp, 10);
143
144
0
  if (tmp && *tmp) {
145
146
    /* Try the parameter as a group name. */
147
0
    gid = pr_auth_name2gid(cmd->tmp_pool, cmd->argv[1]);
148
0
    if (gid == (gid_t) -1) {
149
0
      int xerrno = EINVAL;
150
151
0
      pr_log_debug(DEBUG9,
152
0
        "SITE CHGRP: Unable to resolve group name '%s' to GID",
153
0
        (char *) cmd->argv[1]);
154
0
      pr_response_add_err(R_550, "%s: %s", arg, strerror(xerrno));
155
156
0
      pr_cmd_set_errno(cmd, xerrno);
157
0
      errno = xerrno;
158
0
      return PR_ERROR(cmd);
159
0
    }
160
0
  }
161
162
0
  res = core_chgrp(cmd, path, (uid_t) -1, gid);
163
0
  if (res < 0) {
164
0
    int xerrno = errno;
165
166
0
    (void) pr_trace_msg("fileperms", 1, "%s, user '%s' (UID %s, GID %s): "
167
0
      "error chown'ing '%s' to GID %s: %s", (char *) cmd->argv[0], session.user,
168
0
      pr_uid2str(cmd->tmp_pool, session.uid),
169
0
      pr_gid2str(cmd->tmp_pool, session.gid), path,
170
0
      pr_gid2str(cmd->tmp_pool, gid), strerror(xerrno));
171
172
0
    pr_response_add_err(R_550, "%s: %s", arg, strerror(xerrno));
173
174
0
    pr_cmd_set_errno(cmd, xerrno);
175
0
    errno = xerrno;
176
0
    return PR_ERROR(cmd);
177
0
  }
178
179
0
  pr_response_add(R_200, _("SITE %s command successful"),
180
0
    (char *) cmd->argv[0]);
181
0
  return PR_HANDLED(cmd);
182
0
}
183
184
0
MODRET site_chmod(cmd_rec *cmd) {
185
0
  int res;
186
0
  mode_t mode = 0;
187
0
  char *dir, *endp, *mode_str, *tmp, *arg = "";
188
0
  struct stat st;
189
0
  register unsigned int i = 0;
190
0
#ifdef PR_USE_REGEX
191
0
  pr_regex_t *pre;
192
0
#endif
193
194
0
  if (cmd->argc < 3) {
195
0
    pr_response_add_err(R_500, _("'SITE %s' not understood"), full_cmd(cmd));
196
0
    return NULL;
197
0
  }
198
199
  /* Construct the target file name by concatenating all the parameters after
200
   * the mode, separating them with spaces.
201
   */
202
0
  for (i = 2; i <= cmd->argc-1; i++) {
203
0
    char *decoded_path;
204
205
0
    decoded_path = pr_fs_decode_path2(cmd->tmp_pool, cmd->argv[i],
206
0
      FSIO_DECODE_FL_TELL_ERRORS);
207
0
    if (decoded_path == NULL) {
208
0
      int xerrno = errno;
209
210
0
      pr_log_debug(DEBUG8, "'%s' failed to decode properly: %s",
211
0
        (char *) cmd->argv[i], strerror(xerrno));
212
0
      pr_response_add_err(R_550,
213
0
        _("SITE %s: Illegal character sequence in command"),
214
0
        (char *) cmd->argv[1]);
215
216
0
      pr_cmd_set_errno(cmd, xerrno);
217
0
      errno = xerrno;
218
0
      return PR_ERROR(cmd);
219
0
    }
220
221
0
    arg = pstrcat(cmd->tmp_pool, arg, *arg ? " " : "", decoded_path, NULL);
222
0
  }
223
224
0
#ifdef PR_USE_REGEX
225
0
  pre = get_param_ptr(CURRENT_CONF, "PathAllowFilter", FALSE);
226
0
  if (pre != NULL &&
227
0
      pr_regexp_exec(pre, arg, 0, NULL, 0, 0, 0) != 0) {
228
0
    pr_log_pri(PR_LOG_NOTICE, "'%s %s %s' denied by PathAllowFilter",
229
0
      (char *) cmd->argv[0], (char *) cmd->argv[1], arg);
230
0
    pr_response_add_err(R_550, _("%s: Forbidden filename"), cmd->arg);
231
232
0
    pr_cmd_set_errno(cmd, EPERM);
233
0
    errno = EPERM;
234
0
    return PR_ERROR(cmd);
235
0
  }
236
237
0
  pre = get_param_ptr(CURRENT_CONF, "PathDenyFilter", FALSE);
238
0
  if (pre != NULL &&
239
0
      pr_regexp_exec(pre, arg, 0, NULL, 0, 0, 0) == 0) {
240
0
    pr_log_pri(PR_LOG_NOTICE, "'%s %s %s' denied by PathDenyFilter",
241
0
      (char *) cmd->argv[0], (char *) cmd->argv[1], arg);
242
0
    pr_response_add_err(R_550, _("%s: Forbidden filename"), cmd->arg);
243
244
0
    pr_cmd_set_errno(cmd, EPERM);
245
0
    errno = EPERM;
246
0
    return PR_ERROR(cmd);
247
0
  }
248
0
#endif
249
250
0
  pr_fs_clear_cache2(arg);
251
0
  dir = dir_realpath(cmd->tmp_pool, arg);
252
0
  if (dir == NULL) {
253
0
    int xerrno = errno;
254
255
0
    pr_response_add_err(R_550, "%s: %s", arg, strerror(xerrno));
256
257
0
    pr_cmd_set_errno(cmd, xerrno);
258
0
    errno = xerrno;
259
0
    return PR_ERROR(cmd);
260
0
  }
261
262
  /* If the first character isn't '0', prepend it and attempt conversion.
263
   * This will fail if the chmod is a symbolic, but takes care of the
264
   * case where an octal number is sent without the leading '0'.
265
   */
266
0
  mode_str = cmd->argv[1];
267
0
  if (mode_str[0] != '0') {
268
0
    tmp = pstrcat(cmd->tmp_pool, "0", mode_str, NULL);
269
270
0
  } else {
271
0
    tmp = mode_str;
272
0
  }
273
274
0
  mode = strtol(tmp, &endp, 0);
275
0
  if (endp && *endp) {
276
    /* It's not an absolute number, try symbolic */
277
0
    char *cp = mode_str;
278
0
    int mask = 0, mode_op = 0, curr_mode = 0, curr_umask = umask(0);
279
0
    int invalid = 0;
280
0
    char *who, *how, *what;
281
282
0
    umask(curr_umask);
283
0
    mode = 0;
284
285
0
    if (pr_fsio_stat(dir, &st) != -1) {
286
0
      curr_mode = st.st_mode;
287
0
    }
288
289
0
    while (TRUE) {
290
0
      pr_signals_handle();
291
292
0
      who = pstrdup(cmd->tmp_pool, cp);
293
294
0
      tmp = strpbrk(who, "+-=");
295
0
      if (tmp != NULL) {
296
0
        how = pstrdup(cmd->tmp_pool, tmp);
297
0
        if (*how != '=') {
298
0
          mode = curr_mode;
299
0
        }
300
301
0
        *tmp = '\0';
302
303
0
      } else {
304
0
        invalid++;
305
0
        break;
306
0
      }
307
308
0
      tmp = strpbrk(how, "rwxXstugo");
309
0
      if (tmp != NULL) {
310
0
        what = pstrdup(cmd->tmp_pool, tmp);
311
0
        *tmp = '\0';
312
313
0
      } else {
314
0
        invalid++;
315
0
        break;
316
0
      }
317
318
0
      cp = what;
319
0
      while (cp) {
320
0
        switch (*who) {
321
0
          case 'u':
322
0
            mask = 0077;
323
0
            break;
324
325
0
          case 'g':
326
0
            mask = 0707;
327
0
            break;
328
329
0
          case 'o':
330
0
            mask = 0770;
331
0
            break;
332
333
0
          case 'a':
334
0
            mask = 0000;
335
0
            break;
336
337
0
          case '\0':
338
0
            mask = curr_umask;
339
0
            break;
340
341
0
          default:
342
0
            invalid++;
343
0
            break;
344
0
        }
345
346
0
        if (invalid) {
347
0
          break;
348
0
        }
349
350
0
        switch (*how) {
351
0
          case '+':
352
0
          case '-':
353
0
          case '=':
354
0
            break;
355
356
0
          default:
357
0
            invalid++;
358
0
        }
359
360
0
        if (invalid) {
361
0
          break;
362
0
        }
363
364
0
        switch (*cp) {
365
0
          case 'r':
366
0
            mode_op |= (S_IRUSR|S_IRGRP|S_IROTH);
367
0
            break;
368
369
0
          case 'w':
370
0
            mode_op |= (S_IWUSR|S_IWGRP|S_IWOTH);
371
0
            break;
372
373
0
          case 'x':
374
0
            mode_op |= (S_IXUSR|S_IXGRP|S_IXOTH);
375
0
            break;
376
377
          /* 'X' not implemented */
378
0
          case 's':
379
            /* setuid */
380
0
            mode_op |= S_ISUID;
381
0
            break;
382
383
0
          case 't':
384
            /* sticky */
385
0
            mode_op |= S_ISVTX;
386
0
            break;
387
388
0
          case 'o':
389
0
            mode_op |= (curr_mode & S_IRWXO);
390
0
            mode_op |= ((curr_mode & S_IRWXO) << 3);
391
0
            mode_op |= ((curr_mode & S_IRWXO) << 6);
392
0
            break;
393
394
0
          case 'g':
395
0
            mode_op |= ((curr_mode & S_IRWXG) >> 3);
396
0
            mode_op |= (curr_mode & S_IRWXG);
397
0
            mode_op |= ((curr_mode & S_IRWXG) << 3);
398
0
            break;
399
400
0
          case 'u':
401
0
            mode_op |= ((curr_mode & S_IRWXU) >> 6);
402
0
            mode_op |= ((curr_mode & S_IRWXU) >> 3);
403
0
            mode_op |= (curr_mode & S_IRWXU);
404
0
            break;
405
406
0
          case '\0':
407
            /* Apply the mode and move on */
408
0
            switch (*how) {
409
0
              case '+':
410
0
              case '=':
411
0
                mode |= (mode_op & ~mask);
412
0
                break;
413
414
0
              case '-':
415
0
                mode &= ~(mode_op & ~mask);
416
0
                break;
417
0
            }
418
419
0
            mode_op = 0;
420
0
            if (*who && *(who+1)) {
421
0
              who++;
422
0
              cp = what;
423
0
              continue;
424
0
            }
425
426
0
            cp = NULL;
427
0
            break;
428
429
0
          default:
430
0
            invalid++;
431
0
        }
432
433
0
        if (invalid) {
434
0
          break;
435
0
        }
436
437
0
        if (cp) {
438
0
          cp++;
439
0
        }
440
0
      }
441
0
      break;
442
0
    }
443
444
0
    if (invalid) {
445
0
      pr_response_add_err(R_550, _("'%s': invalid mode"), (char *) cmd->argv[1]);
446
447
0
      pr_cmd_set_errno(cmd, EINVAL);
448
0
      errno = EINVAL;
449
0
      return PR_ERROR(cmd);
450
0
    }
451
0
  }
452
453
0
  res = core_chmod(cmd, dir, mode);
454
0
  if (res < 0) {
455
0
    int xerrno = errno;
456
457
0
    (void) pr_trace_msg("fileperms", 1, "%s, user '%s' (UID %s, GID %s): "
458
0
      "error chmod'ing '%s' to %04o: %s", (char *) cmd->argv[0], session.user,
459
0
      pr_uid2str(cmd->tmp_pool, session.uid),
460
0
      pr_gid2str(cmd->tmp_pool, session.gid), dir, (unsigned int) mode,
461
0
      strerror(xerrno));
462
463
0
    pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
464
465
0
    pr_cmd_set_errno(cmd, xerrno);
466
0
    errno = xerrno;
467
0
    return PR_ERROR(cmd);
468
0
  }
469
470
0
  pr_response_add(R_200, _("SITE %s command successful"),
471
0
    (char *) cmd->argv[0]);
472
0
  return PR_HANDLED(cmd);
473
0
}
474
475
0
MODRET site_help(cmd_rec *cmd) {
476
0
  register unsigned int i = 0;
477
478
  /* Have to support 'HELP SITE' as well as 'SITE HELP'.  Most clients expect
479
   * the former (it's mentioned in RFC959), whereas the latter is more
480
   * syntactically correct.
481
   */
482
483
0
  if (!dir_check_limits(cmd, session.dir_config, "SITE_HELP", FALSE)) {
484
0
    int xerrno = EACCES;
485
486
0
    pr_log_debug(DEBUG8, "SITE HELP denied by <Limit> configuration");
487
488
    /* Returning 501 is the best we can do.  It would be nicer if RFC959 allowed
489
     * 550 as a possible response.
490
     */
491
0
    pr_response_add_err(R_501, "%s: %s", (char *) cmd->argv[0],
492
0
      strerror(xerrno));
493
494
0
    pr_cmd_set_errno(cmd, xerrno);
495
0
    errno = xerrno;
496
0
    return PR_ERROR(cmd);
497
0
  }
498
499
0
  if (cmd->argc == 1 || (cmd->argc == 2 &&
500
0
      ((strcasecmp(cmd->argv[0], "SITE") == 0 &&
501
0
        strcasecmp(cmd->argv[1], "HELP") == 0) ||
502
0
       (strcasecmp(cmd->argv[0], "HELP") == 0 &&
503
0
        strcasecmp(cmd->argv[1], "SITE") == 0)))) {
504
505
0
    for (i = 0; _help[i].cmd; i++) {
506
0
      if (_help[i].implemented) {
507
0
        pr_response_add(i != 0 ? R_DUP : R_214, "%s", _help[i].cmd);
508
509
0
      } else {
510
0
        pr_response_add(i != 0 ? R_DUP : R_214, "%s",
511
0
          pstrcat(cmd->pool, _help[i].cmd, "*", NULL));
512
0
      }
513
0
    }
514
515
0
  } else {
516
0
    char *arg, *cp = NULL;
517
518
0
    arg = cmd->argv[1];
519
0
    for (cp = arg; *cp; cp++) {
520
0
      if (PR_ISALPHA((int) *cp)) {
521
0
        *cp = toupper((int) *cp);
522
0
      }
523
0
    }
524
525
0
    for (i = 0; _help[i].cmd; i++) {
526
0
      if (strcasecmp(arg, _help[i].cmd) == 0) {
527
0
        pr_response_add(R_214, _("Syntax: SITE %s %s"),
528
0
          (char *) cmd->argv[1], _help[i].syntax);
529
0
        return PR_HANDLED(cmd);
530
0
      }
531
0
    }
532
533
0
    pr_response_add_err(R_502, _("Unknown command 'SITE %s'"), cmd->arg);
534
0
    pr_cmd_set_errno(cmd, ENOSYS);
535
0
    errno = ENOSYS;
536
0
    return PR_ERROR(cmd);
537
0
  }
538
539
0
  return PR_HANDLED(cmd);
540
0
}
541
542
/* The site_commands table is local only, and not registered with our
543
 * module.
544
 */
545
546
static cmdtable site_commands[] = {
547
  { CMD, "HELP",  G_NONE,   site_help,  FALSE,  FALSE },
548
  { CMD, "CHGRP", G_NONE,   site_chgrp, TRUE, FALSE },
549
  { CMD, "CHMOD", G_NONE,   site_chmod, TRUE, FALSE },
550
  { 0, NULL }
551
};
552
553
0
modret_t *site_dispatch(cmd_rec *cmd) {
554
0
  register unsigned int i = 0;
555
556
0
  if (!cmd->argc) {
557
0
    pr_response_add_err(R_500, _("'SITE' requires parameters"));
558
559
0
    pr_cmd_set_errno(cmd, EINVAL);
560
0
    errno = EINVAL;
561
0
    return PR_ERROR(cmd);
562
0
  }
563
564
0
  for (i = 0; site_commands[i].command; i++) {
565
0
    if (strcmp(cmd->argv[0], site_commands[i].command) == 0) {
566
0
      if (site_commands[i].requires_auth && cmd_auth_chk &&
567
0
          !cmd_auth_chk(cmd)) {
568
0
        pr_response_send(R_530, _("Please login with USER and PASS"));
569
570
0
        pr_cmd_set_errno(cmd, EPERM);
571
0
        errno = EPERM;
572
0
        return PR_ERROR(cmd);
573
0
      }
574
575
0
      return site_commands[i].handler(cmd);
576
0
    }
577
0
  }
578
579
0
  pr_response_add_err(R_500, _("'SITE %s' not understood"),
580
0
    (char *) cmd->argv[0]);
581
582
0
  pr_cmd_set_errno(cmd, EINVAL);
583
0
  errno = EINVAL;
584
0
  return PR_ERROR(cmd);
585
0
}
586
587
/* Command handlers
588
 */
589
590
0
MODRET site_pre_cmd(cmd_rec *cmd) {
591
0
  if (cmd->argc > 1 &&
592
0
      strcasecmp(cmd->argv[1], "help") == 0) {
593
0
    pr_response_add(R_214,
594
0
      _("The following SITE commands are recognized (* =>'s unimplemented)"));
595
0
  }
596
597
0
  return PR_DECLINED(cmd);
598
0
}
599
600
0
MODRET site_cmd(cmd_rec *cmd) {
601
0
  char *cp = NULL;
602
0
  cmd_rec *tmpcmd = NULL;
603
604
  /* Make a copy of the cmd structure for passing to pr_module_call(). */
605
0
  tmpcmd = pcalloc(cmd->pool, sizeof(cmd_rec));
606
0
  memcpy(tmpcmd, cmd, sizeof(cmd_rec));
607
608
0
  tmpcmd->argc--;
609
0
  tmpcmd->argv++;
610
611
0
  if (tmpcmd->argc) {
612
0
    for (cp = tmpcmd->argv[0]; *cp; cp++) {
613
0
      if (PR_ISALPHA((int) *cp)) {
614
0
        *cp = toupper((int) *cp);
615
0
      }
616
0
    }
617
0
  }
618
619
0
  tmpcmd->notes = cmd->notes;
620
621
0
  return site_dispatch(tmpcmd);
622
0
}
623
624
0
MODRET site_post_cmd(cmd_rec *cmd) {
625
0
  if (cmd->argc > 1 &&
626
0
      strcasecmp(cmd->argv[1], "help") == 0) {
627
0
    pr_response_add(R_214, _("Direct comments to %s"),
628
0
      (cmd->server->ServerAdmin ? cmd->server->ServerAdmin : "ftp-admin"));
629
0
  }
630
631
0
  return PR_DECLINED(cmd);
632
0
}
633
634
/* Initialization routines
635
 */
636
637
0
static int site_init(void) {
638
  /* Add the commands handled by this module to the HELP list. */
639
0
  pr_help_add(C_SITE, _("<sp> string"), TRUE);
640
641
0
  return 0;
642
0
}
643
644
/* Module API tables
645
 */
646
647
static cmdtable site_cmdtab[] = {
648
  { PRE_CMD,  C_SITE, G_NONE, site_pre_cmd,   FALSE,  FALSE },
649
  { CMD,      C_SITE, G_NONE, site_cmd,       FALSE,  FALSE,  CL_MISC },
650
  { POST_CMD, C_SITE, G_NONE, site_post_cmd,  FALSE,  FALSE },
651
  { 0, NULL }
652
};
653
654
module site_module = {
655
  NULL, NULL,
656
657
  /* Module API version */
658
  0x20,
659
660
  /* Module name */
661
  "site",
662
663
  /* Module configuration table */
664
  NULL,
665
666
  /* Module command handler table */
667
  site_cmdtab,
668
669
  /* Module auth handler table */
670
  NULL,
671
672
  /* Module initialization function */
673
  site_init,
674
675
  /* Session initialization function */
676
  NULL
677
};