1 /* $NetBSD: conf.c,v 1.61 2008/06/09 00:33:39 lukem Exp $ */
4 * Copyright (c) 1997-2009 The NetBSD Foundation, Inc.
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Simon Burge and Luke Mewburn.
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
32 #include <sys/cdefs.h>
34 __RCSID("$NetBSD: conf.c,v 1.61 2008/06/09 00:33:39 lukem Exp $");
37 #include <sys/types.h>
38 #include <sys/param.h>
39 #include <sys/socket.h>
51 #include <stringlist.h>
58 #include <krb5/krb5.h>
62 #include "pathnames.h"
64 static char *strend(const char *, char *);
65 static int filetypematch(char *, int);
69 #define DEFAULT_LIMIT -1 /* unlimited connections */
70 #define DEFAULT_MAXFILESIZE -1 /* unlimited file size */
71 #define DEFAULT_MAXTIMEOUT 7200 /* 2 hours */
72 #define DEFAULT_TIMEOUT 900 /* 15 minutes */
73 #define DEFAULT_UMASK 027 /* rw-r----- */
76 * Initialise curclass to an `empty' state
81 struct ftpconv
*conv
, *cnext
;
83 for (conv
= curclass
.conversions
; conv
!= NULL
; conv
= cnext
) {
84 REASSIGN(conv
->suffix
, NULL
);
85 REASSIGN(conv
->types
, NULL
);
86 REASSIGN(conv
->disable
, NULL
);
87 REASSIGN(conv
->command
, NULL
);
92 memset((char *)&curclass
.advertise
, 0, sizeof(curclass
.advertise
));
93 curclass
.advertise
.su_len
= 0; /* `not used' */
94 REASSIGN(curclass
.chroot
, NULL
);
95 REASSIGN(curclass
.classname
, NULL
);
96 curclass
.conversions
= NULL
;
97 REASSIGN(curclass
.display
, NULL
);
98 REASSIGN(curclass
.homedir
, NULL
);
99 curclass
.limit
= DEFAULT_LIMIT
;
100 REASSIGN(curclass
.limitfile
, NULL
);
101 curclass
.maxfilesize
= DEFAULT_MAXFILESIZE
;
102 curclass
.maxrateget
= 0;
103 curclass
.maxrateput
= 0;
104 curclass
.maxtimeout
= DEFAULT_MAXTIMEOUT
;
105 REASSIGN(curclass
.motd
, ftpd_strdup(_NAME_FTPLOGINMESG
));
106 REASSIGN(curclass
.notify
, NULL
);
107 curclass
.portmin
= 0;
108 curclass
.portmax
= 0;
109 curclass
.rateget
= 0;
110 curclass
.rateput
= 0;
111 curclass
.timeout
= DEFAULT_TIMEOUT
;
112 /* curclass.type is set elsewhere */
113 curclass
.umask
= DEFAULT_UMASK
;
114 curclass
.mmapsize
= 0;
115 curclass
.readsize
= 0;
116 curclass
.writesize
= 0;
117 curclass
.sendbufsize
= 0;
118 curclass
.sendlowat
= 0;
120 CURCLASS_FLAGS_SET(checkportcmd
);
121 CURCLASS_FLAGS_CLR(denyquick
);
122 CURCLASS_FLAGS_CLR(hidesymlinks
);
123 CURCLASS_FLAGS_SET(modify
);
124 CURCLASS_FLAGS_SET(passive
);
125 CURCLASS_FLAGS_CLR(private);
126 CURCLASS_FLAGS_CLR(sanenames
);
127 CURCLASS_FLAGS_SET(upload
);
131 * Parse the configuration file, looking for the named class, and
132 * define curclass to contain the appropriate settings.
135 parse_conf(const char *findclass
)
142 char *endp
, errbuf
[100];
143 char *class, *word
, *arg
, *template;
146 struct ftpconv
*conv
, *cnext
;
149 REASSIGN(curclass
.classname
, ftpd_strdup(findclass
));
150 /* set more guest defaults */
151 if (strcasecmp(findclass
, "guest") == 0) {
152 CURCLASS_FLAGS_CLR(modify
);
153 curclass
.umask
= 0707;
156 infile
= conffilename(_NAME_FTPDCONF
);
157 if ((f
= fopen(infile
, "r")) == NULL
)
163 (buf
= fparseln(f
, &len
, &line
, NULL
, FPARSELN_UNESCCOMM
|
164 FPARSELN_UNESCCONT
| FPARSELN_UNESCESC
)) != NULL
;
170 if (p
[len
- 1] == '\n')
178 if (EMPTYSTR(word
) || EMPTYSTR(class))
180 if (strcasecmp(class, "none") == 0)
182 if (! (strcasecmp(class, findclass
) == 0 ||
183 (template != NULL
&& strcasecmp(class, template) == 0) ||
185 strcasecmp(class, "all") == 0) )
188 #define CONF_FLAG(Field) \
191 (!EMPTYSTR(arg) && strcasecmp(arg, "off") == 0)) \
192 CURCLASS_FLAGS_CLR(Field); \
194 CURCLASS_FLAGS_SET(Field); \
197 #define CONF_STRING(Field) \
199 if (none || EMPTYSTR(arg)) \
202 arg = ftpd_strdup(arg); \
203 REASSIGN(curclass.Field, arg); \
206 #define CONF_LL(Field,Arg,Min,Max) \
208 if (none || EMPTYSTR(Arg)) \
210 llval = strsuftollx(#Field, Arg, Min, Max, \
211 errbuf, sizeof(errbuf)); \
213 syslog(LOG_WARNING, "%s line %d: %s", \
214 infile, (int)line, errbuf); \
217 curclass.Field = llval; \
223 } else if ((strcasecmp(word
, "advertise") == 0)
224 || (strcasecmp(word
, "advertize") == 0)) {
225 struct addrinfo hints
, *res
;
228 memset((char *)&curclass
.advertise
, 0,
229 sizeof(curclass
.advertise
));
230 curclass
.advertise
.su_len
= 0;
231 if (none
|| EMPTYSTR(arg
))
234 memset(&hints
, 0, sizeof(hints
));
236 * only get addresses of the family
237 * that we're listening on
239 hints
.ai_family
= ctrl_addr
.su_family
;
240 hints
.ai_socktype
= SOCK_STREAM
;
241 error
= getaddrinfo(arg
, "0", &hints
, &res
);
243 syslog(LOG_WARNING
, "%s line %d: %s",
244 infile
, (int)line
, gai_strerror(error
));
252 "%s line %d: multiple addresses returned for `%s'; please be more specific",
253 infile
, (int)line
, arg
);
254 goto advertiseparsefail
;
256 if (sizeof(curclass
.advertise
) < res
->ai_addrlen
|| (
258 res
->ai_family
!= AF_INET6
&&
260 res
->ai_family
!= AF_INET
)) {
262 "%s line %d: unsupported protocol %d for `%s'",
263 infile
, (int)line
, res
->ai_family
, arg
);
264 goto advertiseparsefail
;
266 memcpy(&curclass
.advertise
, res
->ai_addr
,
268 curclass
.advertise
.su_len
= res
->ai_addrlen
;
271 } else if (strcasecmp(word
, "checkportcmd") == 0) {
272 CONF_FLAG(checkportcmd
);
274 } else if (strcasecmp(word
, "chroot") == 0) {
277 } else if (strcasecmp(word
, "classtype") == 0) {
278 if (!none
&& !EMPTYSTR(arg
)) {
279 if (strcasecmp(arg
, "GUEST") == 0)
280 curclass
.type
= CLASS_GUEST
;
281 else if (strcasecmp(arg
, "CHROOT") == 0)
282 curclass
.type
= CLASS_CHROOT
;
283 else if (strcasecmp(arg
, "REAL") == 0)
284 curclass
.type
= CLASS_REAL
;
287 "%s line %d: unknown class type `%s'",
288 infile
, (int)line
, arg
);
293 } else if (strcasecmp(word
, "conversion") == 0) {
294 char *suffix
, *types
, *disable
, *convcmd
;
298 "%s line %d: %s requires a suffix",
299 infile
, (int)line
, word
);
300 continue; /* need a suffix */
303 NEXTWORD(p
, disable
);
306 convcmd
+= strspn(convcmd
, " \t");
307 suffix
= ftpd_strdup(arg
);
308 if (none
|| EMPTYSTR(types
) ||
309 EMPTYSTR(disable
) || EMPTYSTR(convcmd
)) {
314 types
= ftpd_strdup(types
);
315 disable
= ftpd_strdup(disable
);
316 convcmd
= ftpd_strdup(convcmd
);
318 for (conv
= curclass
.conversions
; conv
!= NULL
;
320 if (strcmp(conv
->suffix
, suffix
) == 0)
324 conv
= (struct ftpconv
*)
325 calloc(1, sizeof(struct ftpconv
));
327 syslog(LOG_WARNING
, "can't malloc");
331 for (cnext
= curclass
.conversions
;
332 cnext
!= NULL
; cnext
= cnext
->next
)
333 if (cnext
->next
== NULL
)
338 curclass
.conversions
= conv
;
340 REASSIGN(conv
->suffix
, suffix
);
341 REASSIGN(conv
->types
, types
);
342 REASSIGN(conv
->disable
, disable
);
343 REASSIGN(conv
->command
, convcmd
);
345 } else if (strcasecmp(word
, "denyquick") == 0) {
346 CONF_FLAG(denyquick
);
348 } else if (strcasecmp(word
, "display") == 0) {
349 CONF_STRING(display
);
351 } else if (strcasecmp(word
, "hidesymlinks") == 0) {
352 CONF_FLAG(hidesymlinks
);
354 } else if (strcasecmp(word
, "homedir") == 0) {
355 CONF_STRING(homedir
);
357 } else if (strcasecmp(word
, "limit") == 0) {
358 curclass
.limit
= DEFAULT_LIMIT
;
359 REASSIGN(curclass
.limitfile
, NULL
);
360 CONF_LL(limit
, arg
, -1, LLTMAX
);
361 REASSIGN(curclass
.limitfile
,
362 EMPTYSTR(p
) ? NULL
: ftpd_strdup(p
));
364 } else if (strcasecmp(word
, "maxfilesize") == 0) {
365 curclass
.maxfilesize
= DEFAULT_MAXFILESIZE
;
366 CONF_LL(maxfilesize
, arg
, -1, LLTMAX
);
368 } else if (strcasecmp(word
, "maxtimeout") == 0) {
369 curclass
.maxtimeout
= DEFAULT_MAXTIMEOUT
;
370 CONF_LL(maxtimeout
, arg
,
371 MIN(30, curclass
.timeout
), LLTMAX
);
373 } else if (strcasecmp(word
, "mmapsize") == 0) {
374 curclass
.mmapsize
= 0;
375 CONF_LL(mmapsize
, arg
, 0, SSIZE_MAX
);
377 } else if (strcasecmp(word
, "readsize") == 0) {
378 curclass
.readsize
= 0;
379 CONF_LL(readsize
, arg
, 0, SSIZE_MAX
);
381 } else if (strcasecmp(word
, "writesize") == 0) {
382 curclass
.writesize
= 0;
383 CONF_LL(writesize
, arg
, 0, SSIZE_MAX
);
385 } else if (strcasecmp(word
, "recvbufsize") == 0) {
386 curclass
.recvbufsize
= 0;
387 CONF_LL(recvbufsize
, arg
, 0, INT_MAX
);
389 } else if (strcasecmp(word
, "sendbufsize") == 0) {
390 curclass
.sendbufsize
= 0;
391 CONF_LL(sendbufsize
, arg
, 0, INT_MAX
);
393 } else if (strcasecmp(word
, "sendlowat") == 0) {
394 curclass
.sendlowat
= 0;
395 CONF_LL(sendlowat
, arg
, 0, INT_MAX
);
397 } else if (strcasecmp(word
, "modify") == 0) {
400 } else if (strcasecmp(word
, "motd") == 0) {
403 } else if (strcasecmp(word
, "notify") == 0) {
406 } else if (strcasecmp(word
, "passive") == 0) {
409 } else if (strcasecmp(word
, "portrange") == 0) {
410 long minport
, maxport
;
412 curclass
.portmin
= 0;
413 curclass
.portmax
= 0;
414 if (none
|| EMPTYSTR(arg
))
418 "%s line %d: missing maxport argument",
422 minport
= strsuftollx("minport", arg
, IPPORT_RESERVED
,
423 IPPORT_ANONMAX
, errbuf
, sizeof(errbuf
));
425 syslog(LOG_WARNING
, "%s line %d: %s",
426 infile
, (int)line
, errbuf
);
429 maxport
= strsuftollx("maxport", p
, IPPORT_RESERVED
,
430 IPPORT_ANONMAX
, errbuf
, sizeof(errbuf
));
432 syslog(LOG_WARNING
, "%s line %d: %s",
433 infile
, (int)line
, errbuf
);
436 if (minport
>= maxport
) {
438 "%s line %d: minport %ld >= maxport %ld",
439 infile
, (int)line
, minport
, maxport
);
442 curclass
.portmin
= (int)minport
;
443 curclass
.portmax
= (int)maxport
;
445 } else if (strcasecmp(word
, "private") == 0) {
448 } else if (strcasecmp(word
, "rateget") == 0) {
449 curclass
.maxrateget
= curclass
.rateget
= 0;
450 CONF_LL(rateget
, arg
, 0, LLTMAX
);
451 curclass
.maxrateget
= curclass
.rateget
;
453 } else if (strcasecmp(word
, "rateput") == 0) {
454 curclass
.maxrateput
= curclass
.rateput
= 0;
455 CONF_LL(rateput
, arg
, 0, LLTMAX
);
456 curclass
.maxrateput
= curclass
.rateput
;
458 } else if (strcasecmp(word
, "sanenames") == 0) {
459 CONF_FLAG(sanenames
);
461 } else if (strcasecmp(word
, "timeout") == 0) {
462 curclass
.timeout
= DEFAULT_TIMEOUT
;
463 CONF_LL(timeout
, arg
, 30, curclass
.maxtimeout
);
465 } else if (strcasecmp(word
, "template") == 0) {
468 REASSIGN(template, EMPTYSTR(arg
) ? NULL
: ftpd_strdup(arg
));
470 } else if (strcasecmp(word
, "umask") == 0) {
471 unsigned long fumask
;
473 curclass
.umask
= DEFAULT_UMASK
;
474 if (none
|| EMPTYSTR(arg
))
478 fumask
= strtoul(arg
, &endp
, 8);
479 if (errno
|| *arg
== '\0' || *endp
!= '\0' ||
482 "%s line %d: invalid umask %s",
483 infile
, (int)line
, arg
);
486 curclass
.umask
= (mode_t
)fumask
;
488 } else if (strcasecmp(word
, "upload") == 0) {
490 if (! CURCLASS_FLAGS_ISSET(upload
))
491 CURCLASS_FLAGS_CLR(modify
);
495 "%s line %d: unknown directive '%s'",
496 infile
, (int)line
, word
);
502 REASSIGN(template, NULL
);
507 * Show file listed in curclass.display first time in, and list all the
508 * files named in curclass.notify in the current directory.
509 * Send back responses with the prefix `code' + "-".
510 * If code == -1, flush the internal cache of directory names and return.
513 show_chdir_messages(int code
)
515 static StringList
*slist
= NULL
;
522 char curwd
[MAXPATHLEN
];
535 /* Setup list for directory cache */
539 syslog(LOG_WARNING
, "can't allocate memory for stringlist");
543 /* Check if this directory has already been visited */
544 if (getcwd(curwd
, sizeof(curwd
) - 1) == NULL
) {
545 syslog(LOG_WARNING
, "can't getcwd: %s", strerror(errno
));
548 if (sl_find(slist
, curwd
) != NULL
)
551 cp
= ftpd_strdup(curwd
);
552 if (sl_add(slist
, cp
) == -1)
553 syslog(LOG_WARNING
, "can't add `%s' to stringlist", cp
);
555 /* First check for a display file */
556 (void)display_file(curclass
.display
, code
);
558 /* Now see if there are any notify files */
559 if (EMPTYSTR(curclass
.notify
))
562 memset(&gl
, 0, sizeof(gl
));
563 if (glob(curclass
.notify
, GLOB_BRACE
|GLOB_LIMIT
, NULL
, &gl
) != 0
564 || gl
.gl_matchc
== 0) {
569 for (rlist
= gl
.gl_pathv
; *rlist
!= NULL
; rlist
++) {
570 if (stat(*rlist
, &st
) != 0)
572 if (!S_ISREG(st
.st_mode
))
576 reply(-code
, "%s", "");
579 reply(-code
, "Please read the file %s", *rlist
);
581 age
= 365 * t
->tm_year
+ t
->tm_yday
;
582 t
= localtime(&then
);
583 age
-= 365 * t
->tm_year
+ t
->tm_yday
;
584 reply(-code
, " it was last modified on %.24s - %d day%s ago",
585 ctime(&then
), age
, PLURAL(age
));
591 display_file(const char *file
, int code
)
595 char curwd
[MAXPATHLEN
];
606 if ((f
= fopen(file
, "r")) == NULL
)
608 reply(-code
, "%s", "");
611 (buf
= fparseln(f
, &len
, NULL
, "\0\0\0", 0)) != NULL
; free(buf
)) {
613 if (buf
[len
- 1] == '\n')
615 cprintf(stdout
, " ");
617 for (p
= buf
; *p
; p
++) {
623 cprintf(stdout
, "%s",
625 curclass
.classname
: "<unknown>");
629 if (getcwd(curwd
, sizeof(curwd
)-1)
636 cprintf(stdout
, "%s", curwd
);
640 if (! EMPTYSTR(emailaddr
))
641 cprintf(stdout
, "%s",
646 cprintf(stdout
, "%s", hostname
);
650 if (curclass
.limit
== -1) {
651 cprintf(stdout
, "unlimited");
655 (LLT
)curclass
.limit
);
656 lastnum
= curclass
.limit
;
661 cprintf(stdout
, "%d", connections
);
662 lastnum
= connections
;
666 cprintf(stdout
, "%s", remotehost
);
671 cprintf(stdout
, "s");
676 cprintf(stdout
, "S");
681 cprintf(stdout
, "%.24s", ctime(&now
));
685 cprintf(stdout
, "%s",
686 pw
? pw
->pw_name
: "<unknown>");
697 cprintf(stdout
, "\r\n");
700 (void)fflush(stdout
);
706 * Parse src, expanding '%' escapes, into dst (which must be at least
710 format_path(char *dst
, const char *src
)
719 for (p
= src
; *p
&& len
< MAXPATHLEN
; p
++) {
725 len
+= strlcpy(dst
+ len
, curclass
.classname
,
730 len
+= strlcpy(dst
+ len
, pw
->pw_dir
,
735 len
+= strlcpy(dst
+ len
, pw
->pw_name
,
747 if (len
< MAXPATHLEN
)
749 dst
[MAXPATHLEN
- 1] = '\0';
753 * Find s2 at the end of s1. If found, return a string up to (but
754 * not including) s2, otherwise returns NULL.
757 strend(const char *s1
, char *s2
)
759 static char buf
[MAXPATHLEN
];
767 if (l2
>= l1
|| l1
>= sizeof(buf
))
770 strlcpy(buf
, s1
, sizeof(buf
));
771 start
= buf
+ (l1
- l2
);
773 if (strcmp(start
, s2
) == 0) {
781 filetypematch(char *types
, int mode
)
783 for ( ; types
[0] != '\0'; types
++)
798 * Look for a conversion. If we succeed, return a pointer to the
799 * command to execute for the conversion.
801 * The command is stored in a static array so there's no memory
802 * leak problems, and not too much to change in ftpd.c. This
803 * routine doesn't need to be re-entrant unless we start using a
804 * multi-threaded ftpd, and that's not likely for a while...
807 do_conversion(const char *fname
)
820 for (cp
= curclass
.conversions
; cp
!= NULL
; cp
= cp
->next
) {
821 if (cp
->suffix
== NULL
) {
823 "cp->suffix==NULL in conv list; SHOULDN'T HAPPEN!");
826 if ((base
= strend(fname
, cp
->suffix
)) == NULL
)
828 if (cp
->types
== NULL
|| cp
->disable
== NULL
||
832 if (strcmp(cp
->disable
, ".") != 0 &&
833 stat(cp
->disable
, &st
) == 0)
835 /* Does the base exist? */
836 if (stat(base
, &st
) < 0)
838 /* Is the file type ok */
839 if (!filetypematch(cp
->types
, st
.st_mode
))
841 break; /* "We have a winner!" */
844 /* If we got through the list, no conversion */
846 goto cleanup_do_conv
;
848 /* Split up command into an argv */
849 if ((sl
= sl_init()) == NULL
)
850 goto cleanup_do_conv
;
851 cmd
= ftpd_strdup(cp
->command
);
855 if (strcmp(lp
, "%s") == 0)
857 if (sl_add(sl
, ftpd_strdup(lp
)) == -1)
858 goto cleanup_do_conv
;
861 if (sl_add(sl
, NULL
) == -1)
862 goto cleanup_do_conv
;
863 argv
= (const char **)sl
->sl_str
;
877 * Count the number of current connections, reading from
878 * /var/run/ftpd.pids-<class>
879 * Does a kill -0 on each pid in that file, and only counts
880 * processes that exist (or frees the slot if it doesn't).
881 * Adds getpid() to the first free slot. Truncates the file
889 size_t i
, last
, count
;
895 (void)strlcpy(fn
, _PATH_CLASSPIDS
, sizeof(fn
));
896 (void)strlcat(fn
, curclass
.classname
, sizeof(fn
));
903 fl
.l_whence
= SEEK_SET
;
905 if ((fd
= open(fn
, O_RDWR
| O_CREAT
, 0600)) == -1)
907 if (fcntl(fd
, F_SETLK
, &fl
) == -1)
909 if (fstat(fd
, &sb
) == -1)
911 if ((pids
= malloc(sb
.st_size
+ sizeof(pid_t
))) == NULL
)
913 /* XXX: implement a better read loop */
914 scount
= read(fd
, pids
, sb
.st_size
);
915 if (scount
== -1 || scount
!= sb
.st_size
|| scount
< 0)
917 count
= (size_t)scount
/ sizeof(pid_t
);
920 for (i
= 0; i
< count
; i
++) {
923 if (kill(pids
[i
], 0) == -1 && errno
!= EPERM
) {
939 count
= (last
+ 1) * sizeof(pid_t
);
940 if (lseek(fd
, 0, SEEK_SET
) == -1)
942 /* XXX: implement a better write loop */
943 scount
= write(fd
, pids
, count
);
944 if (scount
== -1 || (size_t)scount
!= count
)
946 (void)ftruncate(fd
, count
);
950 (void)fcntl(fd
, F_SETLK
, &fl
);
952 REASSIGN(pids
, NULL
);