1 /* $NetBSD: conf.c,v 1.64 2012/11/04 20:46:46 christos 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.64 2012/11/04 20:46:46 christos Exp $");
37 #include <sys/types.h>
38 #include <sys/param.h>
39 #include <sys/socket.h>
52 #include <stringlist.h>
59 #include <krb5/krb5.h>
63 #include "pathnames.h"
65 static char *strend(const char *, char *);
66 static int filetypematch(char *, int);
70 #define DEFAULT_LIMIT -1 /* unlimited connections */
71 #define DEFAULT_MAXFILESIZE -1 /* unlimited file size */
72 #define DEFAULT_MAXTIMEOUT 7200 /* 2 hours */
73 #define DEFAULT_TIMEOUT 900 /* 15 minutes */
74 #define DEFAULT_UMASK 027 /* rw-r----- */
77 * Initialise curclass to an `empty' state
82 struct ftpconv
*conv
, *cnext
;
84 for (conv
= curclass
.conversions
; conv
!= NULL
; conv
= cnext
) {
85 REASSIGN(conv
->suffix
, NULL
);
86 REASSIGN(conv
->types
, NULL
);
87 REASSIGN(conv
->disable
, NULL
);
88 REASSIGN(conv
->command
, NULL
);
93 memset((char *)&curclass
.advertise
, 0, sizeof(curclass
.advertise
));
94 curclass
.advertise
.su_len
= 0; /* `not used' */
95 REASSIGN(curclass
.chroot
, NULL
);
96 REASSIGN(curclass
.classname
, NULL
);
97 curclass
.conversions
= NULL
;
98 REASSIGN(curclass
.display
, NULL
);
99 REASSIGN(curclass
.homedir
, NULL
);
100 curclass
.limit
= DEFAULT_LIMIT
;
101 REASSIGN(curclass
.limitfile
, NULL
);
102 curclass
.maxfilesize
= DEFAULT_MAXFILESIZE
;
103 curclass
.maxrateget
= 0;
104 curclass
.maxrateput
= 0;
105 curclass
.maxtimeout
= DEFAULT_MAXTIMEOUT
;
106 REASSIGN(curclass
.motd
, ftpd_strdup(_NAME_FTPLOGINMESG
));
107 REASSIGN(curclass
.notify
, NULL
);
108 curclass
.portmin
= 0;
109 curclass
.portmax
= 0;
110 curclass
.rateget
= 0;
111 curclass
.rateput
= 0;
112 curclass
.timeout
= DEFAULT_TIMEOUT
;
113 /* curclass.type is set elsewhere */
114 curclass
.umask
= DEFAULT_UMASK
;
115 curclass
.mmapsize
= 0;
116 curclass
.readsize
= 0;
117 curclass
.writesize
= 0;
118 curclass
.sendbufsize
= 0;
119 curclass
.sendlowat
= 0;
121 CURCLASS_FLAGS_SET(checkportcmd
);
122 CURCLASS_FLAGS_CLR(denyquick
);
123 CURCLASS_FLAGS_CLR(hidesymlinks
);
124 CURCLASS_FLAGS_SET(modify
);
125 CURCLASS_FLAGS_SET(passive
);
126 CURCLASS_FLAGS_CLR(private);
127 CURCLASS_FLAGS_CLR(sanenames
);
128 CURCLASS_FLAGS_SET(upload
);
132 * Parse the configuration file, looking for the named class, and
133 * define curclass to contain the appropriate settings.
136 parse_conf(const char *findclass
)
143 char *endp
, errbuf
[100];
144 char *class, *word
, *arg
, *template;
147 struct ftpconv
*conv
, *cnext
;
150 REASSIGN(curclass
.classname
, ftpd_strdup(findclass
));
151 /* set more guest defaults */
152 if (strcasecmp(findclass
, "guest") == 0) {
153 CURCLASS_FLAGS_CLR(modify
);
154 curclass
.umask
= 0707;
157 infile
= conffilename(_NAME_FTPDCONF
);
158 if ((f
= fopen(infile
, "r")) == NULL
)
164 (buf
= fparseln(f
, &len
, &line
, NULL
, FPARSELN_UNESCCOMM
|
165 FPARSELN_UNESCCONT
| FPARSELN_UNESCESC
)) != NULL
;
171 if (p
[len
- 1] == '\n')
179 if (EMPTYSTR(word
) || EMPTYSTR(class))
181 if (strcasecmp(class, "none") == 0)
183 if (! (strcasecmp(class, findclass
) == 0 ||
184 (template != NULL
&& strcasecmp(class, template) == 0) ||
186 strcasecmp(class, "all") == 0) )
189 #define CONF_FLAG(Field) \
192 (!EMPTYSTR(arg) && strcasecmp(arg, "off") == 0)) \
193 CURCLASS_FLAGS_CLR(Field); \
195 CURCLASS_FLAGS_SET(Field); \
198 #define CONF_STRING(Field) \
200 if (none || EMPTYSTR(arg)) \
203 arg = ftpd_strdup(arg); \
204 REASSIGN(curclass.Field, arg); \
207 #define CONF_LL(Field,Arg,Min,Max) \
209 if (none || EMPTYSTR(Arg)) \
211 llval = strsuftollx(#Field, Arg, Min, Max, \
212 errbuf, sizeof(errbuf)); \
214 syslog(LOG_WARNING, "%s line %d: %s", \
215 infile, (int)line, errbuf); \
218 curclass.Field = llval; \
224 } else if ((strcasecmp(word
, "advertise") == 0)
225 || (strcasecmp(word
, "advertize") == 0)) {
226 struct addrinfo hints
, *res
;
229 memset((char *)&curclass
.advertise
, 0,
230 sizeof(curclass
.advertise
));
231 curclass
.advertise
.su_len
= 0;
232 if (none
|| EMPTYSTR(arg
))
235 memset(&hints
, 0, sizeof(hints
));
237 * only get addresses of the family
238 * that we're listening on
240 hints
.ai_family
= ctrl_addr
.su_family
;
241 hints
.ai_socktype
= SOCK_STREAM
;
242 error
= getaddrinfo(arg
, "0", &hints
, &res
);
244 syslog(LOG_WARNING
, "%s line %d: %s",
245 infile
, (int)line
, gai_strerror(error
));
253 "%s line %d: multiple addresses returned for `%s'; please be more specific",
254 infile
, (int)line
, arg
);
255 goto advertiseparsefail
;
257 if (sizeof(curclass
.advertise
) < res
->ai_addrlen
|| (
259 res
->ai_family
!= AF_INET6
&&
261 res
->ai_family
!= AF_INET
)) {
263 "%s line %d: unsupported protocol %d for `%s'",
264 infile
, (int)line
, res
->ai_family
, arg
);
265 goto advertiseparsefail
;
267 memcpy(&curclass
.advertise
, res
->ai_addr
,
269 curclass
.advertise
.su_len
= res
->ai_addrlen
;
272 } else if (strcasecmp(word
, "checkportcmd") == 0) {
273 CONF_FLAG(checkportcmd
);
275 } else if (strcasecmp(word
, "chroot") == 0) {
278 } else if (strcasecmp(word
, "classtype") == 0) {
279 if (!none
&& !EMPTYSTR(arg
)) {
280 if (strcasecmp(arg
, "GUEST") == 0)
281 curclass
.type
= CLASS_GUEST
;
282 else if (strcasecmp(arg
, "CHROOT") == 0)
283 curclass
.type
= CLASS_CHROOT
;
284 else if (strcasecmp(arg
, "REAL") == 0)
285 curclass
.type
= CLASS_REAL
;
288 "%s line %d: unknown class type `%s'",
289 infile
, (int)line
, arg
);
294 } else if (strcasecmp(word
, "conversion") == 0) {
295 char *suffix
, *types
, *disable
, *convcmd
;
299 "%s line %d: %s requires a suffix",
300 infile
, (int)line
, word
);
301 continue; /* need a suffix */
304 NEXTWORD(p
, disable
);
307 convcmd
+= strspn(convcmd
, " \t");
308 suffix
= ftpd_strdup(arg
);
309 if (none
|| EMPTYSTR(types
) ||
310 EMPTYSTR(disable
) || EMPTYSTR(convcmd
)) {
315 types
= ftpd_strdup(types
);
316 disable
= ftpd_strdup(disable
);
317 convcmd
= ftpd_strdup(convcmd
);
319 for (conv
= curclass
.conversions
; conv
!= NULL
;
321 if (strcmp(conv
->suffix
, suffix
) == 0)
325 conv
= (struct ftpconv
*)
326 calloc(1, sizeof(struct ftpconv
));
328 syslog(LOG_WARNING
, "can't malloc");
332 for (cnext
= curclass
.conversions
;
333 cnext
!= NULL
; cnext
= cnext
->next
)
334 if (cnext
->next
== NULL
)
339 curclass
.conversions
= conv
;
341 REASSIGN(conv
->suffix
, suffix
);
342 REASSIGN(conv
->types
, types
);
343 REASSIGN(conv
->disable
, disable
);
344 REASSIGN(conv
->command
, convcmd
);
346 } else if (strcasecmp(word
, "denyquick") == 0) {
347 CONF_FLAG(denyquick
);
349 } else if (strcasecmp(word
, "display") == 0) {
350 CONF_STRING(display
);
352 } else if (strcasecmp(word
, "hidesymlinks") == 0) {
353 CONF_FLAG(hidesymlinks
);
355 } else if (strcasecmp(word
, "homedir") == 0) {
356 CONF_STRING(homedir
);
358 } else if (strcasecmp(word
, "limit") == 0) {
359 curclass
.limit
= DEFAULT_LIMIT
;
360 REASSIGN(curclass
.limitfile
, NULL
);
361 CONF_LL(limit
, arg
, -1, LLTMAX
);
362 REASSIGN(curclass
.limitfile
,
363 EMPTYSTR(p
) ? NULL
: ftpd_strdup(p
));
365 } else if (strcasecmp(word
, "maxfilesize") == 0) {
366 curclass
.maxfilesize
= DEFAULT_MAXFILESIZE
;
367 CONF_LL(maxfilesize
, arg
, -1, LLTMAX
);
369 } else if (strcasecmp(word
, "maxtimeout") == 0) {
370 curclass
.maxtimeout
= DEFAULT_MAXTIMEOUT
;
371 CONF_LL(maxtimeout
, arg
,
372 MIN(30, curclass
.timeout
), LLTMAX
);
374 } else if (strcasecmp(word
, "mmapsize") == 0) {
375 curclass
.mmapsize
= 0;
376 CONF_LL(mmapsize
, arg
, 0, SSIZE_MAX
);
378 } else if (strcasecmp(word
, "readsize") == 0) {
379 curclass
.readsize
= 0;
380 CONF_LL(readsize
, arg
, 0, SSIZE_MAX
);
382 } else if (strcasecmp(word
, "writesize") == 0) {
383 curclass
.writesize
= 0;
384 CONF_LL(writesize
, arg
, 0, SSIZE_MAX
);
386 } else if (strcasecmp(word
, "recvbufsize") == 0) {
387 curclass
.recvbufsize
= 0;
388 CONF_LL(recvbufsize
, arg
, 0, INT_MAX
);
390 } else if (strcasecmp(word
, "sendbufsize") == 0) {
391 curclass
.sendbufsize
= 0;
392 CONF_LL(sendbufsize
, arg
, 0, INT_MAX
);
394 } else if (strcasecmp(word
, "sendlowat") == 0) {
395 curclass
.sendlowat
= 0;
396 CONF_LL(sendlowat
, arg
, 0, INT_MAX
);
398 } else if (strcasecmp(word
, "modify") == 0) {
401 } else if (strcasecmp(word
, "motd") == 0) {
404 } else if (strcasecmp(word
, "notify") == 0) {
407 } else if (strcasecmp(word
, "passive") == 0) {
410 } else if (strcasecmp(word
, "portrange") == 0) {
411 long minport
, maxport
;
413 curclass
.portmin
= 0;
414 curclass
.portmax
= 0;
415 if (none
|| EMPTYSTR(arg
))
419 "%s line %d: missing maxport argument",
423 minport
= strsuftollx("minport", arg
, IPPORT_RESERVED
,
424 IPPORT_ANONMAX
, errbuf
, sizeof(errbuf
));
426 syslog(LOG_WARNING
, "%s line %d: %s",
427 infile
, (int)line
, errbuf
);
430 maxport
= strsuftollx("maxport", p
, IPPORT_RESERVED
,
431 IPPORT_ANONMAX
, errbuf
, sizeof(errbuf
));
433 syslog(LOG_WARNING
, "%s line %d: %s",
434 infile
, (int)line
, errbuf
);
437 if (minport
>= maxport
) {
439 "%s line %d: minport %ld >= maxport %ld",
440 infile
, (int)line
, minport
, maxport
);
443 curclass
.portmin
= (int)minport
;
444 curclass
.portmax
= (int)maxport
;
446 } else if (strcasecmp(word
, "private") == 0) {
449 } else if (strcasecmp(word
, "rateget") == 0) {
450 curclass
.maxrateget
= curclass
.rateget
= 0;
451 CONF_LL(rateget
, arg
, 0, LLTMAX
);
452 curclass
.maxrateget
= curclass
.rateget
;
454 } else if (strcasecmp(word
, "rateput") == 0) {
455 curclass
.maxrateput
= curclass
.rateput
= 0;
456 CONF_LL(rateput
, arg
, 0, LLTMAX
);
457 curclass
.maxrateput
= curclass
.rateput
;
459 } else if (strcasecmp(word
, "sanenames") == 0) {
460 CONF_FLAG(sanenames
);
462 } else if (strcasecmp(word
, "timeout") == 0) {
463 curclass
.timeout
= DEFAULT_TIMEOUT
;
464 CONF_LL(timeout
, arg
, 30, curclass
.maxtimeout
);
466 } else if (strcasecmp(word
, "template") == 0) {
469 REASSIGN(template, EMPTYSTR(arg
) ? NULL
: ftpd_strdup(arg
));
471 } else if (strcasecmp(word
, "umask") == 0) {
472 unsigned long fumask
;
474 curclass
.umask
= DEFAULT_UMASK
;
475 if (none
|| EMPTYSTR(arg
))
479 fumask
= strtoul(arg
, &endp
, 8);
480 if (errno
|| *arg
== '\0' || *endp
!= '\0' ||
483 "%s line %d: invalid umask %s",
484 infile
, (int)line
, arg
);
487 curclass
.umask
= (mode_t
)fumask
;
489 } else if (strcasecmp(word
, "upload") == 0) {
491 if (! CURCLASS_FLAGS_ISSET(upload
))
492 CURCLASS_FLAGS_CLR(modify
);
496 "%s line %d: unknown directive '%s'",
497 infile
, (int)line
, word
);
503 REASSIGN(template, NULL
);
508 * Show file listed in curclass.display first time in, and list all the
509 * files named in curclass.notify in the current directory.
510 * Send back responses with the prefix `code' + "-".
511 * If code == -1, flush the internal cache of directory names and return.
514 show_chdir_messages(int code
)
516 static StringList
*slist
= NULL
;
523 char curwd
[MAXPATHLEN
];
536 /* Setup list for directory cache */
540 syslog(LOG_WARNING
, "can't allocate memory for stringlist");
544 /* Check if this directory has already been visited */
545 if (getcwd(curwd
, sizeof(curwd
) - 1) == NULL
) {
546 syslog(LOG_WARNING
, "can't getcwd: %s", strerror(errno
));
549 if (sl_find(slist
, curwd
) != NULL
)
552 cp
= ftpd_strdup(curwd
);
553 if (sl_add(slist
, cp
) == -1)
554 syslog(LOG_WARNING
, "can't add `%s' to stringlist", cp
);
556 /* First check for a display file */
557 (void)display_file(curclass
.display
, code
);
559 /* Now see if there are any notify files */
560 if (EMPTYSTR(curclass
.notify
))
563 memset(&gl
, 0, sizeof(gl
));
564 if (glob(curclass
.notify
, GLOB_BRACE
|GLOB_LIMIT
, NULL
, &gl
) != 0
565 || gl
.gl_matchc
== 0) {
570 for (rlist
= gl
.gl_pathv
; *rlist
!= NULL
; rlist
++) {
571 if (stat(*rlist
, &st
) != 0)
573 if (!S_ISREG(st
.st_mode
))
577 reply(-code
, "%s", "");
580 reply(-code
, "Please read the file %s", *rlist
);
582 age
= 365 * t
->tm_year
+ t
->tm_yday
;
583 t
= localtime(&then
);
584 age
-= 365 * t
->tm_year
+ t
->tm_yday
;
585 reply(-code
, " it was last modified on %.24s - %d day%s ago",
586 ctime(&then
), age
, PLURAL(age
));
592 display_file(const char *file
, int code
)
596 char curwd
[MAXPATHLEN
];
607 if ((f
= fopen(file
, "r")) == NULL
)
609 reply(-code
, "%s", "");
612 (buf
= fparseln(f
, &len
, NULL
, "\0\0\0", 0)) != NULL
; free(buf
)) {
614 if (buf
[len
- 1] == '\n')
616 cprintf(stdout
, " ");
618 for (p
= buf
; *p
; p
++) {
624 cprintf(stdout
, "%s",
626 curclass
.classname
: "<unknown>");
630 if (getcwd(curwd
, sizeof(curwd
)-1)
637 cprintf(stdout
, "%s", curwd
);
641 if (! EMPTYSTR(emailaddr
))
642 cprintf(stdout
, "%s",
647 cprintf(stdout
, "%s", hostname
);
651 if (curclass
.limit
== -1) {
652 cprintf(stdout
, "unlimited");
656 (LLT
)curclass
.limit
);
657 lastnum
= curclass
.limit
;
662 cprintf(stdout
, "%d", connections
);
663 lastnum
= connections
;
667 cprintf(stdout
, "%s", remotehost
);
672 cprintf(stdout
, "s");
677 cprintf(stdout
, "S");
682 cprintf(stdout
, "%.24s", ctime(&now
));
686 cprintf(stdout
, "%s",
687 pw
? pw
->pw_name
: "<unknown>");
698 cprintf(stdout
, "\r\n");
701 (void)fflush(stdout
);
707 * Parse src, expanding '%' escapes, into dst (which must be at least
711 format_path(char *dst
, const char *src
)
720 for (p
= src
; *p
&& len
< MAXPATHLEN
; p
++) {
726 len
+= strlcpy(dst
+ len
, curclass
.classname
,
731 len
+= strlcpy(dst
+ len
, pw
->pw_dir
,
736 len
+= strlcpy(dst
+ len
, pw
->pw_name
,
748 if (len
< MAXPATHLEN
)
750 dst
[MAXPATHLEN
- 1] = '\0';
754 * Find s2 at the end of s1. If found, return a string up to (but
755 * not including) s2, otherwise returns NULL.
758 strend(const char *s1
, char *s2
)
760 static char buf
[MAXPATHLEN
];
768 if (l2
>= l1
|| l1
>= sizeof(buf
))
771 strlcpy(buf
, s1
, sizeof(buf
));
772 start
= buf
+ (l1
- l2
);
774 if (strcmp(start
, s2
) == 0) {
782 filetypematch(char *types
, int mode
)
784 for ( ; types
[0] != '\0'; types
++)
799 * Look for a conversion. If we succeed, return a pointer to the
800 * command to execute for the conversion.
802 * The command is stored in a static array so there's no memory
803 * leak problems, and not too much to change in ftpd.c. This
804 * routine doesn't need to be re-entrant unless we start using a
805 * multi-threaded ftpd, and that's not likely for a while...
808 do_conversion(const char *fname
)
821 for (cp
= curclass
.conversions
; cp
!= NULL
; cp
= cp
->next
) {
822 if (cp
->suffix
== NULL
) {
824 "cp->suffix==NULL in conv list; SHOULDN'T HAPPEN!");
827 if ((base
= strend(fname
, cp
->suffix
)) == NULL
)
829 if (cp
->types
== NULL
|| cp
->disable
== NULL
||
833 if (strcmp(cp
->disable
, ".") != 0 &&
834 stat(cp
->disable
, &st
) == 0)
836 /* Does the base exist? */
837 if (stat(base
, &st
) < 0)
839 /* Is the file type ok */
840 if (!filetypematch(cp
->types
, st
.st_mode
))
842 break; /* "We have a winner!" */
845 /* If we got through the list, no conversion */
847 goto cleanup_do_conv
;
849 /* Split up command into an argv */
850 if ((sl
= sl_init()) == NULL
)
851 goto cleanup_do_conv
;
852 cmd
= ftpd_strdup(cp
->command
);
856 if (strcmp(lp
, "%s") == 0)
858 if (sl_add(sl
, ftpd_strdup(lp
)) == -1)
859 goto cleanup_do_conv
;
862 if (sl_add(sl
, NULL
) == -1)
863 goto cleanup_do_conv
;
867 return (void *)(intptr_t)argv
;
878 * Count the number of current connections, reading from
879 * /var/run/ftpd.pids-<class>
880 * Does a kill -0 on each pid in that file, and only counts
881 * processes that exist (or frees the slot if it doesn't).
882 * Adds getpid() to the first free slot. Truncates the file
890 size_t i
, last
, count
;
896 (void)strlcpy(fn
, _PATH_CLASSPIDS
, sizeof(fn
));
897 (void)strlcat(fn
, curclass
.classname
, sizeof(fn
));
904 fl
.l_whence
= SEEK_SET
;
906 if ((fd
= open(fn
, O_RDWR
| O_CREAT
, 0600)) == -1)
908 if (fcntl(fd
, F_SETLK
, &fl
) == -1)
910 if (fstat(fd
, &sb
) == -1)
912 if ((pids
= malloc(sb
.st_size
+ sizeof(pid_t
))) == NULL
)
914 /* XXX: implement a better read loop */
915 scount
= read(fd
, pids
, sb
.st_size
);
916 if (scount
== -1 || scount
!= sb
.st_size
|| scount
< 0)
918 count
= (size_t)scount
/ sizeof(pid_t
);
921 for (i
= 0; i
< count
; i
++) {
924 if (kill(pids
[i
], 0) == -1 && errno
!= EPERM
) {
940 count
= (last
+ 1) * sizeof(pid_t
);
941 if (lseek(fd
, 0, SEEK_SET
) == -1)
943 /* XXX: implement a better write loop */
944 scount
= write(fd
, pids
, count
);
945 if (scount
== -1 || (size_t)scount
!= count
)
947 (void)ftruncate(fd
, count
);
951 (void)fcntl(fd
, F_SETLK
, &fl
);
953 REASSIGN(pids
, NULL
);