1161764Sobrien/* $NetBSD: conf.c,v 1.57 2006/02/01 14:20:12 christos Exp $ */ 279968Sobrien 379968Sobrien/*- 4161764Sobrien * Copyright (c) 1997-2005 The NetBSD Foundation, Inc. 579968Sobrien * All rights reserved. 679968Sobrien * 779968Sobrien * This code is derived from software contributed to The NetBSD Foundation 879968Sobrien * by Simon Burge and Luke Mewburn. 979968Sobrien * 1079968Sobrien * Redistribution and use in source and binary forms, with or without 1179968Sobrien * modification, are permitted provided that the following conditions 1279968Sobrien * are met: 1379968Sobrien * 1. Redistributions of source code must retain the above copyright 1479968Sobrien * notice, this list of conditions and the following disclaimer. 1579968Sobrien * 2. Redistributions in binary form must reproduce the above copyright 1679968Sobrien * notice, this list of conditions and the following disclaimer in the 1779968Sobrien * documentation and/or other materials provided with the distribution. 1879968Sobrien * 3. All advertising materials mentioning features or use of this software 1979968Sobrien * must display the following acknowledgement: 2079968Sobrien * This product includes software developed by the NetBSD 2179968Sobrien * Foundation, Inc. and its contributors. 2279968Sobrien * 4. Neither the name of The NetBSD Foundation nor the names of its 2379968Sobrien * contributors may be used to endorse or promote products derived 2479968Sobrien * from this software without specific prior written permission. 2579968Sobrien * 2679968Sobrien * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 2779968Sobrien * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 2879968Sobrien * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 2979968Sobrien * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 3079968Sobrien * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 3179968Sobrien * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 3279968Sobrien * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 3379968Sobrien * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 3479968Sobrien * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 3579968Sobrien * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 3679968Sobrien * POSSIBILITY OF SUCH DAMAGE. 3779968Sobrien */ 3879968Sobrien 39108746Sobrien#include <sys/cdefs.h> 40108746Sobrien#ifndef lint 41161764Sobrien__RCSID("$NetBSD: conf.c,v 1.57 2006/02/01 14:20:12 christos Exp $"); 42108746Sobrien#endif /* not lint */ 4379968Sobrien 44108746Sobrien#include <sys/types.h> 45108746Sobrien#include <sys/param.h> 46108746Sobrien#include <sys/socket.h> 47108746Sobrien#include <sys/stat.h> 48108746Sobrien 49108746Sobrien#include <ctype.h> 50108746Sobrien#include <errno.h> 51108746Sobrien#include <fcntl.h> 52108746Sobrien#include <glob.h> 53108746Sobrien#include <netdb.h> 54108746Sobrien#include <signal.h> 55108746Sobrien#include <stdio.h> 56108746Sobrien#include <stdlib.h> 57108746Sobrien#include <string.h> 58108746Sobrien#include <stringlist.h> 59108746Sobrien#include <syslog.h> 60108746Sobrien#include <time.h> 61108746Sobrien#include <unistd.h> 62108746Sobrien#include <util.h> 63108746Sobrien 64108746Sobrien#ifdef KERBEROS5 65108746Sobrien#include <krb5/krb5.h> 66108746Sobrien#endif 67108746Sobrien 6879968Sobrien#include "extern.h" 6979968Sobrien#include "pathnames.h" 7079968Sobrien 7179968Sobrienstatic char *strend(const char *, char *); 7279968Sobrienstatic int filetypematch(char *, int); 7379968Sobrien 7479968Sobrien 7579968Sobrien /* class defaults */ 7679968Sobrien#define DEFAULT_LIMIT -1 /* unlimited connections */ 7779968Sobrien#define DEFAULT_MAXFILESIZE -1 /* unlimited file size */ 7879968Sobrien#define DEFAULT_MAXTIMEOUT 7200 /* 2 hours */ 7979968Sobrien#define DEFAULT_TIMEOUT 900 /* 15 minutes */ 80161764Sobrien#define DEFAULT_UMASK 027 /* rw-r----- */ 8179968Sobrien 8279968Sobrien/* 8379968Sobrien * Initialise curclass to an `empty' state 8479968Sobrien */ 8579968Sobrienvoid 8679968Sobrieninit_curclass(void) 8779968Sobrien{ 8879968Sobrien struct ftpconv *conv, *cnext; 8979968Sobrien 9079968Sobrien for (conv = curclass.conversions; conv != NULL; conv = cnext) { 9179968Sobrien REASSIGN(conv->suffix, NULL); 9279968Sobrien REASSIGN(conv->types, NULL); 9379968Sobrien REASSIGN(conv->disable, NULL); 9479968Sobrien REASSIGN(conv->command, NULL); 9579968Sobrien cnext = conv->next; 9679968Sobrien free(conv); 9779968Sobrien } 9879968Sobrien 9979968Sobrien memset((char *)&curclass.advertise, 0, sizeof(curclass.advertise)); 10079968Sobrien curclass.advertise.su_len = 0; /* `not used' */ 10179968Sobrien REASSIGN(curclass.chroot, NULL); 10279968Sobrien REASSIGN(curclass.classname, NULL); 10379968Sobrien curclass.conversions = NULL; 10479968Sobrien REASSIGN(curclass.display, NULL); 10579968Sobrien REASSIGN(curclass.homedir, NULL); 10679968Sobrien curclass.limit = DEFAULT_LIMIT; 10779968Sobrien REASSIGN(curclass.limitfile, NULL); 10879968Sobrien curclass.maxfilesize = DEFAULT_MAXFILESIZE; 10979968Sobrien curclass.maxrateget = 0; 11079968Sobrien curclass.maxrateput = 0; 11179968Sobrien curclass.maxtimeout = DEFAULT_MAXTIMEOUT; 112161764Sobrien REASSIGN(curclass.motd, ftpd_strdup(_NAME_FTPLOGINMESG)); 11379968Sobrien REASSIGN(curclass.notify, NULL); 11479968Sobrien curclass.portmin = 0; 11579968Sobrien curclass.portmax = 0; 11679968Sobrien curclass.rateget = 0; 11779968Sobrien curclass.rateput = 0; 11879968Sobrien curclass.timeout = DEFAULT_TIMEOUT; 11979968Sobrien /* curclass.type is set elsewhere */ 12079968Sobrien curclass.umask = DEFAULT_UMASK; 121108746Sobrien curclass.mmapsize = 0; 122108746Sobrien curclass.readsize = 0; 123108746Sobrien curclass.writesize = 0; 124108746Sobrien curclass.sendbufsize = 0; 125108746Sobrien curclass.sendlowat = 0; 12679968Sobrien 12779968Sobrien CURCLASS_FLAGS_SET(checkportcmd); 12892282Sobrien CURCLASS_FLAGS_CLR(denyquick); 129161764Sobrien CURCLASS_FLAGS_CLR(hidesymlinks); 13079968Sobrien CURCLASS_FLAGS_SET(modify); 13179968Sobrien CURCLASS_FLAGS_SET(passive); 13292282Sobrien CURCLASS_FLAGS_CLR(private); 13379968Sobrien CURCLASS_FLAGS_CLR(sanenames); 13479968Sobrien CURCLASS_FLAGS_SET(upload); 13579968Sobrien} 13679968Sobrien 13779968Sobrien/* 13879968Sobrien * Parse the configuration file, looking for the named class, and 13979968Sobrien * define curclass to contain the appropriate settings. 14079968Sobrien */ 14179968Sobrienvoid 14279968Sobrienparse_conf(const char *findclass) 14379968Sobrien{ 14479968Sobrien FILE *f; 14579968Sobrien char *buf, *p; 14679968Sobrien size_t len; 14779968Sobrien LLT llval; 14879968Sobrien int none, match; 149108746Sobrien char *endp, errbuf[100]; 15079968Sobrien char *class, *word, *arg, *template; 15179968Sobrien const char *infile; 15279968Sobrien size_t line; 15379968Sobrien struct ftpconv *conv, *cnext; 15479968Sobrien 15579968Sobrien init_curclass(); 156161764Sobrien REASSIGN(curclass.classname, ftpd_strdup(findclass)); 15779968Sobrien /* set more guest defaults */ 15879968Sobrien if (strcasecmp(findclass, "guest") == 0) { 15979968Sobrien CURCLASS_FLAGS_CLR(modify); 16079968Sobrien curclass.umask = 0707; 16179968Sobrien } 16279968Sobrien 163161764Sobrien infile = conffilename(_NAME_FTPDCONF); 16479968Sobrien if ((f = fopen(infile, "r")) == NULL) 16579968Sobrien return; 16679968Sobrien 16779968Sobrien line = 0; 16879968Sobrien template = NULL; 16979968Sobrien for (; 17079968Sobrien (buf = fparseln(f, &len, &line, NULL, FPARSELN_UNESCCOMM | 171108746Sobrien FPARSELN_UNESCCONT | FPARSELN_UNESCESC)) != NULL; 17279968Sobrien free(buf)) { 17379968Sobrien none = match = 0; 17479968Sobrien p = buf; 17579968Sobrien if (len < 1) 17679968Sobrien continue; 17779968Sobrien if (p[len - 1] == '\n') 17879968Sobrien p[--len] = '\0'; 17979968Sobrien if (EMPTYSTR(p)) 18079968Sobrien continue; 18179968Sobrien 18279968Sobrien NEXTWORD(p, word); 18379968Sobrien NEXTWORD(p, class); 18479968Sobrien NEXTWORD(p, arg); 18579968Sobrien if (EMPTYSTR(word) || EMPTYSTR(class)) 18679968Sobrien continue; 18779968Sobrien if (strcasecmp(class, "none") == 0) 18879968Sobrien none = 1; 18979968Sobrien if (! (strcasecmp(class, findclass) == 0 || 19079968Sobrien (template != NULL && strcasecmp(class, template) == 0) || 19179968Sobrien none || 19279968Sobrien strcasecmp(class, "all") == 0) ) 19379968Sobrien continue; 19479968Sobrien 195108746Sobrien#define CONF_FLAG(Field) \ 196108746Sobrien do { \ 197108746Sobrien if (none || \ 198108746Sobrien (!EMPTYSTR(arg) && strcasecmp(arg, "off") == 0)) \ 199108746Sobrien CURCLASS_FLAGS_CLR(Field); \ 200108746Sobrien else \ 201108746Sobrien CURCLASS_FLAGS_SET(Field); \ 20279968Sobrien } while (0) 20379968Sobrien 204108746Sobrien#define CONF_STRING(Field) \ 205108746Sobrien do { \ 206108746Sobrien if (none || EMPTYSTR(arg)) \ 207108746Sobrien arg = NULL; \ 208108746Sobrien else \ 209161764Sobrien arg = ftpd_strdup(arg); \ 210108746Sobrien REASSIGN(curclass.Field, arg); \ 21179968Sobrien } while (0) 21279968Sobrien 213108746Sobrien#define CONF_LL(Field,Arg,Min,Max) \ 214108746Sobrien do { \ 215108746Sobrien if (none || EMPTYSTR(Arg)) \ 216108746Sobrien goto nextline; \ 217108746Sobrien llval = strsuftollx(#Field, Arg, Min, Max, \ 218108746Sobrien errbuf, sizeof(errbuf)); \ 219108746Sobrien if (errbuf[0]) { \ 220108746Sobrien syslog(LOG_WARNING, "%s line %d: %s", \ 221108746Sobrien infile, (int)line, errbuf); \ 222108746Sobrien goto nextline; \ 223108746Sobrien } \ 224108746Sobrien curclass.Field = llval; \ 225108746Sobrien } while(0) 22679968Sobrien 22779968Sobrien if (0) { 22879968Sobrien /* no-op */ 22979968Sobrien 23092282Sobrien } else if ((strcasecmp(word, "advertise") == 0) 23192282Sobrien || (strcasecmp(word, "advertize") == 0)) { 23279968Sobrien struct addrinfo hints, *res; 23379968Sobrien int error; 23479968Sobrien 23579968Sobrien memset((char *)&curclass.advertise, 0, 23679968Sobrien sizeof(curclass.advertise)); 23779968Sobrien curclass.advertise.su_len = 0; 23879968Sobrien if (none || EMPTYSTR(arg)) 23979968Sobrien continue; 24079968Sobrien res = NULL; 24179968Sobrien memset(&hints, 0, sizeof(hints)); 24279968Sobrien /* 24379968Sobrien * only get addresses of the family 24479968Sobrien * that we're listening on 24579968Sobrien */ 24679968Sobrien hints.ai_family = ctrl_addr.su_family; 24779968Sobrien hints.ai_socktype = SOCK_STREAM; 24879968Sobrien error = getaddrinfo(arg, "0", &hints, &res); 24979968Sobrien if (error) { 25079968Sobrien syslog(LOG_WARNING, "%s line %d: %s", 25179968Sobrien infile, (int)line, gai_strerror(error)); 25279968Sobrien advertiseparsefail: 25379968Sobrien if (res) 25479968Sobrien freeaddrinfo(res); 25579968Sobrien continue; 25679968Sobrien } 25779968Sobrien if (res->ai_next) { 25879968Sobrien syslog(LOG_WARNING, 25979968Sobrien "%s line %d: multiple addresses returned for `%s'; please be more specific", 26079968Sobrien infile, (int)line, arg); 26179968Sobrien goto advertiseparsefail; 26279968Sobrien } 26379968Sobrien if (sizeof(curclass.advertise) < res->ai_addrlen || ( 26479968Sobrien#ifdef INET6 26579968Sobrien res->ai_family != AF_INET6 && 26679968Sobrien#endif 26779968Sobrien res->ai_family != AF_INET)) { 26879968Sobrien syslog(LOG_WARNING, 26979968Sobrien "%s line %d: unsupported protocol %d for `%s'", 27079968Sobrien infile, (int)line, res->ai_family, arg); 27179968Sobrien goto advertiseparsefail; 27279968Sobrien } 27379968Sobrien memcpy(&curclass.advertise, res->ai_addr, 27479968Sobrien res->ai_addrlen); 27579968Sobrien curclass.advertise.su_len = res->ai_addrlen; 27679968Sobrien freeaddrinfo(res); 27779968Sobrien 27879968Sobrien } else if (strcasecmp(word, "checkportcmd") == 0) { 27979968Sobrien CONF_FLAG(checkportcmd); 28079968Sobrien 28179968Sobrien } else if (strcasecmp(word, "chroot") == 0) { 28279968Sobrien CONF_STRING(chroot); 28379968Sobrien 28479968Sobrien } else if (strcasecmp(word, "classtype") == 0) { 28579968Sobrien if (!none && !EMPTYSTR(arg)) { 28679968Sobrien if (strcasecmp(arg, "GUEST") == 0) 28779968Sobrien curclass.type = CLASS_GUEST; 28879968Sobrien else if (strcasecmp(arg, "CHROOT") == 0) 28979968Sobrien curclass.type = CLASS_CHROOT; 29079968Sobrien else if (strcasecmp(arg, "REAL") == 0) 29179968Sobrien curclass.type = CLASS_REAL; 29279968Sobrien else { 29379968Sobrien syslog(LOG_WARNING, 29479968Sobrien "%s line %d: unknown class type `%s'", 29579968Sobrien infile, (int)line, arg); 29679968Sobrien continue; 29779968Sobrien } 29879968Sobrien } 29979968Sobrien 30079968Sobrien } else if (strcasecmp(word, "conversion") == 0) { 30179968Sobrien char *suffix, *types, *disable, *convcmd; 30279968Sobrien 30379968Sobrien if (EMPTYSTR(arg)) { 30479968Sobrien syslog(LOG_WARNING, 30579968Sobrien "%s line %d: %s requires a suffix", 30679968Sobrien infile, (int)line, word); 30779968Sobrien continue; /* need a suffix */ 30879968Sobrien } 30979968Sobrien NEXTWORD(p, types); 31079968Sobrien NEXTWORD(p, disable); 31179968Sobrien convcmd = p; 31279968Sobrien if (convcmd) 31379968Sobrien convcmd += strspn(convcmd, " \t"); 314161764Sobrien suffix = ftpd_strdup(arg); 31579968Sobrien if (none || EMPTYSTR(types) || 31679968Sobrien EMPTYSTR(disable) || EMPTYSTR(convcmd)) { 31779968Sobrien types = NULL; 31879968Sobrien disable = NULL; 31979968Sobrien convcmd = NULL; 32079968Sobrien } else { 321161764Sobrien types = ftpd_strdup(types); 322161764Sobrien disable = ftpd_strdup(disable); 323161764Sobrien convcmd = ftpd_strdup(convcmd); 32479968Sobrien } 32579968Sobrien for (conv = curclass.conversions; conv != NULL; 32679968Sobrien conv = conv->next) { 32779968Sobrien if (strcmp(conv->suffix, suffix) == 0) 32879968Sobrien break; 32979968Sobrien } 33079968Sobrien if (conv == NULL) { 33179968Sobrien conv = (struct ftpconv *) 33279968Sobrien calloc(1, sizeof(struct ftpconv)); 33379968Sobrien if (conv == NULL) { 33479968Sobrien syslog(LOG_WARNING, "can't malloc"); 33579968Sobrien continue; 33679968Sobrien } 33779968Sobrien conv->next = NULL; 33879968Sobrien for (cnext = curclass.conversions; 33979968Sobrien cnext != NULL; cnext = cnext->next) 34079968Sobrien if (cnext->next == NULL) 34179968Sobrien break; 34279968Sobrien if (cnext != NULL) 34379968Sobrien cnext->next = conv; 34479968Sobrien else 34579968Sobrien curclass.conversions = conv; 34679968Sobrien } 34779968Sobrien REASSIGN(conv->suffix, suffix); 34879968Sobrien REASSIGN(conv->types, types); 34979968Sobrien REASSIGN(conv->disable, disable); 35079968Sobrien REASSIGN(conv->command, convcmd); 35179968Sobrien 35292282Sobrien } else if (strcasecmp(word, "denyquick") == 0) { 35392282Sobrien CONF_FLAG(denyquick); 35492282Sobrien 35579968Sobrien } else if (strcasecmp(word, "display") == 0) { 35679968Sobrien CONF_STRING(display); 35779968Sobrien 358161764Sobrien } else if (strcasecmp(word, "hidesymlinks") == 0) { 359161764Sobrien CONF_FLAG(hidesymlinks); 360161764Sobrien 36179968Sobrien } else if (strcasecmp(word, "homedir") == 0) { 36279968Sobrien CONF_STRING(homedir); 36379968Sobrien 36479968Sobrien } else if (strcasecmp(word, "limit") == 0) { 36579968Sobrien curclass.limit = DEFAULT_LIMIT; 36679968Sobrien REASSIGN(curclass.limitfile, NULL); 367108746Sobrien CONF_LL(limit, arg, -1, LLTMAX); 36879968Sobrien REASSIGN(curclass.limitfile, 369161764Sobrien EMPTYSTR(p) ? NULL : ftpd_strdup(p)); 37079968Sobrien 37179968Sobrien } else if (strcasecmp(word, "maxfilesize") == 0) { 37279968Sobrien curclass.maxfilesize = DEFAULT_MAXFILESIZE; 373108746Sobrien CONF_LL(maxfilesize, arg, -1, LLTMAX); 37479968Sobrien 37579968Sobrien } else if (strcasecmp(word, "maxtimeout") == 0) { 37679968Sobrien curclass.maxtimeout = DEFAULT_MAXTIMEOUT; 377108746Sobrien CONF_LL(maxtimeout, arg, 378108746Sobrien MIN(30, curclass.timeout), LLTMAX); 37979968Sobrien 380108746Sobrien } else if (strcasecmp(word, "mmapsize") == 0) { 381108746Sobrien curclass.mmapsize = 0; 382108746Sobrien CONF_LL(mmapsize, arg, 0, LLTMAX); 383108746Sobrien 384108746Sobrien } else if (strcasecmp(word, "readsize") == 0) { 385108746Sobrien curclass.readsize = 0; 386108746Sobrien CONF_LL(readsize, arg, 0, LLTMAX); 387108746Sobrien 388108746Sobrien } else if (strcasecmp(word, "writesize") == 0) { 389108746Sobrien curclass.writesize = 0; 390108746Sobrien CONF_LL(writesize, arg, 0, LLTMAX); 391108746Sobrien 392161764Sobrien } else if (strcasecmp(word, "recvbufsize") == 0) { 393161764Sobrien curclass.recvbufsize = 0; 394161764Sobrien CONF_LL(recvbufsize, arg, 0, LLTMAX); 395161764Sobrien 396108746Sobrien } else if (strcasecmp(word, "sendbufsize") == 0) { 397108746Sobrien curclass.sendbufsize = 0; 398108746Sobrien CONF_LL(sendbufsize, arg, 0, LLTMAX); 399108746Sobrien 400108746Sobrien } else if (strcasecmp(word, "sendlowat") == 0) { 401108746Sobrien curclass.sendlowat = 0; 402108746Sobrien CONF_LL(sendlowat, arg, 0, LLTMAX); 403108746Sobrien 40479968Sobrien } else if (strcasecmp(word, "modify") == 0) { 40579968Sobrien CONF_FLAG(modify); 40679968Sobrien 40779968Sobrien } else if (strcasecmp(word, "motd") == 0) { 40879968Sobrien CONF_STRING(motd); 40979968Sobrien 41079968Sobrien } else if (strcasecmp(word, "notify") == 0) { 41179968Sobrien CONF_STRING(notify); 41279968Sobrien 41379968Sobrien } else if (strcasecmp(word, "passive") == 0) { 41479968Sobrien CONF_FLAG(passive); 41579968Sobrien 41679968Sobrien } else if (strcasecmp(word, "portrange") == 0) { 417108746Sobrien long minport, maxport; 41879968Sobrien 41979968Sobrien curclass.portmin = 0; 42079968Sobrien curclass.portmax = 0; 42179968Sobrien if (none || EMPTYSTR(arg)) 42279968Sobrien continue; 423108746Sobrien if (EMPTYSTR(p)) { 42479968Sobrien syslog(LOG_WARNING, 42579968Sobrien "%s line %d: missing maxport argument", 42679968Sobrien infile, (int)line); 42779968Sobrien continue; 42879968Sobrien } 429108746Sobrien minport = strsuftollx("minport", arg, IPPORT_RESERVED, 430108746Sobrien IPPORT_ANONMAX, errbuf, sizeof(errbuf)); 431108746Sobrien if (errbuf[0]) { 432108746Sobrien syslog(LOG_WARNING, "%s line %d: %s", 433108746Sobrien infile, (int)line, errbuf); 43479968Sobrien continue; 43579968Sobrien } 436108746Sobrien maxport = strsuftollx("maxport", p, IPPORT_RESERVED, 437108746Sobrien IPPORT_ANONMAX, errbuf, sizeof(errbuf)); 438108746Sobrien if (errbuf[0]) { 439108746Sobrien syslog(LOG_WARNING, "%s line %d: %s", 440108746Sobrien infile, (int)line, errbuf); 44179968Sobrien continue; 44279968Sobrien } 44379968Sobrien if (minport >= maxport) { 44479968Sobrien syslog(LOG_WARNING, 445108746Sobrien "%s line %d: minport %ld >= maxport %ld", 44679968Sobrien infile, (int)line, minport, maxport); 44779968Sobrien continue; 44879968Sobrien } 449108746Sobrien curclass.portmin = (int)minport; 450108746Sobrien curclass.portmax = (int)maxport; 45179968Sobrien 45292282Sobrien } else if (strcasecmp(word, "private") == 0) { 45392282Sobrien CONF_FLAG(private); 45492282Sobrien 45579968Sobrien } else if (strcasecmp(word, "rateget") == 0) { 456108746Sobrien curclass.maxrateget = curclass.rateget = 0; 457108746Sobrien CONF_LL(rateget, arg, 0, LLTMAX); 458108746Sobrien curclass.maxrateget = curclass.rateget; 45979968Sobrien 46079968Sobrien } else if (strcasecmp(word, "rateput") == 0) { 461108746Sobrien curclass.maxrateput = curclass.rateput = 0; 462108746Sobrien CONF_LL(rateput, arg, 0, LLTMAX); 463108746Sobrien curclass.maxrateput = curclass.rateput; 46479968Sobrien 46579968Sobrien } else if (strcasecmp(word, "sanenames") == 0) { 46679968Sobrien CONF_FLAG(sanenames); 46779968Sobrien 46879968Sobrien } else if (strcasecmp(word, "timeout") == 0) { 46979968Sobrien curclass.timeout = DEFAULT_TIMEOUT; 470108746Sobrien CONF_LL(timeout, arg, 30, curclass.maxtimeout); 47179968Sobrien 47279968Sobrien } else if (strcasecmp(word, "template") == 0) { 47379968Sobrien if (none) 47479968Sobrien continue; 475161764Sobrien REASSIGN(template, EMPTYSTR(arg) ? NULL : ftpd_strdup(arg)); 47679968Sobrien 47779968Sobrien } else if (strcasecmp(word, "umask") == 0) { 478108746Sobrien u_long fumask; 47979968Sobrien 48079968Sobrien curclass.umask = DEFAULT_UMASK; 48179968Sobrien if (none || EMPTYSTR(arg)) 48279968Sobrien continue; 483108746Sobrien errno = 0; 484108746Sobrien endp = NULL; 485108746Sobrien fumask = strtoul(arg, &endp, 8); 486108746Sobrien if (errno || *arg == '\0' || *endp != '\0' || 487108746Sobrien fumask > 0777) { 48879968Sobrien syslog(LOG_WARNING, 48979968Sobrien "%s line %d: invalid umask %s", 49079968Sobrien infile, (int)line, arg); 49179968Sobrien continue; 49279968Sobrien } 493108746Sobrien curclass.umask = (mode_t)fumask; 49479968Sobrien 49579968Sobrien } else if (strcasecmp(word, "upload") == 0) { 49679968Sobrien CONF_FLAG(upload); 49779968Sobrien if (! CURCLASS_FLAGS_ISSET(upload)) 49879968Sobrien CURCLASS_FLAGS_CLR(modify); 49979968Sobrien 50079968Sobrien } else { 50179968Sobrien syslog(LOG_WARNING, 50279968Sobrien "%s line %d: unknown directive '%s'", 50379968Sobrien infile, (int)line, word); 50479968Sobrien continue; 50579968Sobrien } 506108746Sobrien nextline: 507108746Sobrien ; 50879968Sobrien } 50979968Sobrien REASSIGN(template, NULL); 51079968Sobrien fclose(f); 51179968Sobrien} 51279968Sobrien 51379968Sobrien/* 51479968Sobrien * Show file listed in curclass.display first time in, and list all the 51579968Sobrien * files named in curclass.notify in the current directory. 51679968Sobrien * Send back responses with the prefix `code' + "-". 51779968Sobrien * If code == -1, flush the internal cache of directory names and return. 51879968Sobrien */ 51979968Sobrienvoid 52079968Sobrienshow_chdir_messages(int code) 52179968Sobrien{ 52279968Sobrien static StringList *slist = NULL; 52379968Sobrien 52479968Sobrien struct stat st; 52579968Sobrien struct tm *t; 52679968Sobrien glob_t gl; 52779968Sobrien time_t now, then; 52879968Sobrien int age; 52992282Sobrien char curwd[MAXPATHLEN]; 53079968Sobrien char *cp, **rlist; 53179968Sobrien 53279968Sobrien if (code == -1) { 53379968Sobrien if (slist != NULL) 53479968Sobrien sl_free(slist, 1); 53579968Sobrien slist = NULL; 53679968Sobrien return; 53779968Sobrien } 53879968Sobrien 53979968Sobrien if (quietmessages) 54079968Sobrien return; 54179968Sobrien 54279968Sobrien /* Setup list for directory cache */ 54379968Sobrien if (slist == NULL) 54479968Sobrien slist = sl_init(); 54579968Sobrien if (slist == NULL) { 54679968Sobrien syslog(LOG_WARNING, "can't allocate memory for stringlist"); 54779968Sobrien return; 54879968Sobrien } 54979968Sobrien 55079968Sobrien /* Check if this directory has already been visited */ 55192282Sobrien if (getcwd(curwd, sizeof(curwd) - 1) == NULL) { 55279968Sobrien syslog(LOG_WARNING, "can't getcwd: %s", strerror(errno)); 55379968Sobrien return; 55479968Sobrien } 55592282Sobrien if (sl_find(slist, curwd) != NULL) 55679968Sobrien return; 55779968Sobrien 558161764Sobrien cp = ftpd_strdup(curwd); 55979968Sobrien if (sl_add(slist, cp) == -1) 56079968Sobrien syslog(LOG_WARNING, "can't add `%s' to stringlist", cp); 56179968Sobrien 56279968Sobrien /* First check for a display file */ 56379968Sobrien (void)display_file(curclass.display, code); 56479968Sobrien 56579968Sobrien /* Now see if there are any notify files */ 56679968Sobrien if (EMPTYSTR(curclass.notify)) 56779968Sobrien return; 56879968Sobrien 56992282Sobrien memset(&gl, 0, sizeof(gl)); 570108746Sobrien if (glob(curclass.notify, GLOB_BRACE|GLOB_LIMIT, NULL, &gl) != 0 57179968Sobrien || gl.gl_matchc == 0) { 57279968Sobrien globfree(&gl); 57379968Sobrien return; 57479968Sobrien } 57579968Sobrien time(&now); 57679968Sobrien for (rlist = gl.gl_pathv; *rlist != NULL; rlist++) { 57779968Sobrien if (stat(*rlist, &st) != 0) 57879968Sobrien continue; 57979968Sobrien if (!S_ISREG(st.st_mode)) 58079968Sobrien continue; 58179968Sobrien then = st.st_mtime; 58279968Sobrien if (code != 0) { 58379968Sobrien reply(-code, "%s", ""); 58479968Sobrien code = 0; 58579968Sobrien } 58679968Sobrien reply(-code, "Please read the file %s", *rlist); 58779968Sobrien t = localtime(&now); 58879968Sobrien age = 365 * t->tm_year + t->tm_yday; 58979968Sobrien t = localtime(&then); 59079968Sobrien age -= 365 * t->tm_year + t->tm_yday; 59179968Sobrien reply(-code, " it was last modified on %.24s - %d day%s ago", 59279968Sobrien ctime(&then), age, PLURAL(age)); 59379968Sobrien } 59479968Sobrien globfree(&gl); 59579968Sobrien} 59679968Sobrien 59779968Sobrienint 59879968Sobriendisplay_file(const char *file, int code) 59979968Sobrien{ 60079968Sobrien FILE *f; 60192282Sobrien char *buf, *p; 60292282Sobrien char curwd[MAXPATHLEN]; 60379968Sobrien size_t len; 60479968Sobrien off_t lastnum; 60579968Sobrien time_t now; 60679968Sobrien 60779968Sobrien lastnum = 0; 60879968Sobrien if (quietmessages) 60979968Sobrien return (0); 61079968Sobrien 61179968Sobrien if (EMPTYSTR(file)) 61279968Sobrien return(0); 61379968Sobrien if ((f = fopen(file, "r")) == NULL) 61479968Sobrien return (0); 61579968Sobrien reply(-code, "%s", ""); 61679968Sobrien 61779968Sobrien for (; 61879968Sobrien (buf = fparseln(f, &len, NULL, "\0\0\0", 0)) != NULL; free(buf)) { 61979968Sobrien if (len > 0) 62079968Sobrien if (buf[len - 1] == '\n') 62179968Sobrien buf[--len] = '\0'; 62279968Sobrien cprintf(stdout, " "); 62379968Sobrien 62479968Sobrien for (p = buf; *p; p++) { 62579968Sobrien if (*p == '%') { 62679968Sobrien p++; 62779968Sobrien switch (*p) { 62879968Sobrien 62979968Sobrien case 'c': 63079968Sobrien cprintf(stdout, "%s", 63179968Sobrien curclass.classname ? 63279968Sobrien curclass.classname : "<unknown>"); 63379968Sobrien break; 63479968Sobrien 63579968Sobrien case 'C': 63692282Sobrien if (getcwd(curwd, sizeof(curwd)-1) 63792282Sobrien == NULL){ 63879968Sobrien syslog(LOG_WARNING, 63979968Sobrien "can't getcwd: %s", 64079968Sobrien strerror(errno)); 64179968Sobrien continue; 64279968Sobrien } 64392282Sobrien cprintf(stdout, "%s", curwd); 64479968Sobrien break; 64579968Sobrien 64679968Sobrien case 'E': 64779968Sobrien if (! EMPTYSTR(emailaddr)) 64879968Sobrien cprintf(stdout, "%s", 64979968Sobrien emailaddr); 65079968Sobrien break; 65179968Sobrien 65279968Sobrien case 'L': 65379968Sobrien cprintf(stdout, "%s", hostname); 65479968Sobrien break; 65579968Sobrien 65679968Sobrien case 'M': 65779968Sobrien if (curclass.limit == -1) { 65879968Sobrien cprintf(stdout, "unlimited"); 65979968Sobrien lastnum = 0; 66079968Sobrien } else { 661108746Sobrien cprintf(stdout, LLF, 662108746Sobrien (LLT)curclass.limit); 66379968Sobrien lastnum = curclass.limit; 66479968Sobrien } 66579968Sobrien break; 66679968Sobrien 66779968Sobrien case 'N': 66879968Sobrien cprintf(stdout, "%d", connections); 66979968Sobrien lastnum = connections; 67079968Sobrien break; 67179968Sobrien 67279968Sobrien case 'R': 67379968Sobrien cprintf(stdout, "%s", remotehost); 67479968Sobrien break; 67579968Sobrien 67679968Sobrien case 's': 67779968Sobrien if (lastnum != 1) 67879968Sobrien cprintf(stdout, "s"); 67979968Sobrien break; 68079968Sobrien 68179968Sobrien case 'S': 68279968Sobrien if (lastnum != 1) 68379968Sobrien cprintf(stdout, "S"); 68479968Sobrien break; 68579968Sobrien 68679968Sobrien case 'T': 68779968Sobrien now = time(NULL); 68879968Sobrien cprintf(stdout, "%.24s", ctime(&now)); 68979968Sobrien break; 69079968Sobrien 69179968Sobrien case 'U': 69279968Sobrien cprintf(stdout, "%s", 69379968Sobrien pw ? pw->pw_name : "<unknown>"); 69479968Sobrien break; 69579968Sobrien 69679968Sobrien case '%': 69779968Sobrien CPUTC('%', stdout); 69879968Sobrien break; 69979968Sobrien 70079968Sobrien } 70179968Sobrien } else 70279968Sobrien CPUTC(*p, stdout); 70379968Sobrien } 70479968Sobrien cprintf(stdout, "\r\n"); 70579968Sobrien } 70679968Sobrien 70779968Sobrien (void)fflush(stdout); 70879968Sobrien (void)fclose(f); 70979968Sobrien return (1); 71079968Sobrien} 71179968Sobrien 71279968Sobrien/* 71379968Sobrien * Parse src, expanding '%' escapes, into dst (which must be at least 71479968Sobrien * MAXPATHLEN long). 71579968Sobrien */ 71679968Sobrienvoid 71779968Sobrienformat_path(char *dst, const char *src) 71879968Sobrien{ 71979968Sobrien size_t len; 72079968Sobrien const char *p; 72179968Sobrien 72279968Sobrien dst[0] = '\0'; 72379968Sobrien len = 0; 72479968Sobrien if (src == NULL) 72579968Sobrien return; 72679968Sobrien for (p = src; *p && len < MAXPATHLEN; p++) { 72779968Sobrien if (*p == '%') { 72879968Sobrien p++; 72979968Sobrien switch (*p) { 73079968Sobrien 73179968Sobrien case 'c': 73279968Sobrien len += strlcpy(dst + len, curclass.classname, 73379968Sobrien MAXPATHLEN - len); 73479968Sobrien break; 73579968Sobrien 73679968Sobrien case 'd': 73779968Sobrien len += strlcpy(dst + len, pw->pw_dir, 73879968Sobrien MAXPATHLEN - len); 73979968Sobrien break; 74079968Sobrien 74179968Sobrien case 'u': 74279968Sobrien len += strlcpy(dst + len, pw->pw_name, 74379968Sobrien MAXPATHLEN - len); 74479968Sobrien break; 74579968Sobrien 74679968Sobrien case '%': 74779968Sobrien dst[len++] = '%'; 74879968Sobrien break; 74979968Sobrien 75079968Sobrien } 75179968Sobrien } else 75279968Sobrien dst[len++] = *p; 75379968Sobrien } 75479968Sobrien if (len < MAXPATHLEN) 75579968Sobrien dst[len] = '\0'; 75679968Sobrien dst[MAXPATHLEN - 1] = '\0'; 75779968Sobrien} 75879968Sobrien 75979968Sobrien/* 76079968Sobrien * Find s2 at the end of s1. If found, return a string up to (but 76179968Sobrien * not including) s2, otherwise returns NULL. 76279968Sobrien */ 76379968Sobrienstatic char * 76479968Sobrienstrend(const char *s1, char *s2) 76579968Sobrien{ 76679968Sobrien static char buf[MAXPATHLEN]; 76779968Sobrien 76879968Sobrien char *start; 76979968Sobrien size_t l1, l2; 77079968Sobrien 77179968Sobrien l1 = strlen(s1); 77279968Sobrien l2 = strlen(s2); 77379968Sobrien 77492282Sobrien if (l2 >= l1 || l1 >= sizeof(buf)) 77579968Sobrien return(NULL); 77679968Sobrien 77779968Sobrien strlcpy(buf, s1, sizeof(buf)); 77879968Sobrien start = buf + (l1 - l2); 77979968Sobrien 78079968Sobrien if (strcmp(start, s2) == 0) { 78179968Sobrien *start = '\0'; 78279968Sobrien return(buf); 78379968Sobrien } else 78479968Sobrien return(NULL); 78579968Sobrien} 78679968Sobrien 78779968Sobrienstatic int 78879968Sobrienfiletypematch(char *types, int mode) 78979968Sobrien{ 79079968Sobrien for ( ; types[0] != '\0'; types++) 79179968Sobrien switch (*types) { 79279968Sobrien case 'd': 79379968Sobrien if (S_ISDIR(mode)) 79479968Sobrien return(1); 79579968Sobrien break; 79679968Sobrien case 'f': 79779968Sobrien if (S_ISREG(mode)) 79879968Sobrien return(1); 79979968Sobrien break; 80079968Sobrien } 80179968Sobrien return(0); 80279968Sobrien} 80379968Sobrien 80479968Sobrien/* 80579968Sobrien * Look for a conversion. If we succeed, return a pointer to the 80679968Sobrien * command to execute for the conversion. 80779968Sobrien * 80879968Sobrien * The command is stored in a static array so there's no memory 80979968Sobrien * leak problems, and not too much to change in ftpd.c. This 81079968Sobrien * routine doesn't need to be re-entrant unless we start using a 81179968Sobrien * multi-threaded ftpd, and that's not likely for a while... 81279968Sobrien */ 81379968Sobrienchar ** 81479968Sobriendo_conversion(const char *fname) 81579968Sobrien{ 81679968Sobrien struct ftpconv *cp; 81779968Sobrien struct stat st; 81879968Sobrien int o_errno; 81979968Sobrien char *base = NULL; 82079968Sobrien char *cmd, *p, *lp, **argv; 82179968Sobrien StringList *sl; 82279968Sobrien 82379968Sobrien o_errno = errno; 82479968Sobrien sl = NULL; 82579968Sobrien cmd = NULL; 82679968Sobrien for (cp = curclass.conversions; cp != NULL; cp = cp->next) { 82779968Sobrien if (cp->suffix == NULL) { 82879968Sobrien syslog(LOG_WARNING, 82979968Sobrien "cp->suffix==NULL in conv list; SHOULDN'T HAPPEN!"); 83079968Sobrien continue; 83179968Sobrien } 83279968Sobrien if ((base = strend(fname, cp->suffix)) == NULL) 83379968Sobrien continue; 83479968Sobrien if (cp->types == NULL || cp->disable == NULL || 83579968Sobrien cp->command == NULL) 83679968Sobrien continue; 83779968Sobrien /* Is it enabled? */ 83879968Sobrien if (strcmp(cp->disable, ".") != 0 && 83979968Sobrien stat(cp->disable, &st) == 0) 84079968Sobrien continue; 84179968Sobrien /* Does the base exist? */ 84279968Sobrien if (stat(base, &st) < 0) 84379968Sobrien continue; 84479968Sobrien /* Is the file type ok */ 84579968Sobrien if (!filetypematch(cp->types, st.st_mode)) 84679968Sobrien continue; 84779968Sobrien break; /* "We have a winner!" */ 84879968Sobrien } 84979968Sobrien 85079968Sobrien /* If we got through the list, no conversion */ 85179968Sobrien if (cp == NULL) 85279968Sobrien goto cleanup_do_conv; 85379968Sobrien 85479968Sobrien /* Split up command into an argv */ 85579968Sobrien if ((sl = sl_init()) == NULL) 85679968Sobrien goto cleanup_do_conv; 857161764Sobrien cmd = ftpd_strdup(cp->command); 85879968Sobrien p = cmd; 85979968Sobrien while (p) { 86079968Sobrien NEXTWORD(p, lp); 86179968Sobrien if (strcmp(lp, "%s") == 0) 86279968Sobrien lp = base; 863161764Sobrien if (sl_add(sl, ftpd_strdup(lp)) == -1) 86479968Sobrien goto cleanup_do_conv; 86579968Sobrien } 86679968Sobrien 86779968Sobrien if (sl_add(sl, NULL) == -1) 86879968Sobrien goto cleanup_do_conv; 86979968Sobrien argv = sl->sl_str; 87079968Sobrien free(cmd); 87179968Sobrien free(sl); 87279968Sobrien return(argv); 87379968Sobrien 87479968Sobrien cleanup_do_conv: 87579968Sobrien if (sl) 87679968Sobrien sl_free(sl, 1); 87779968Sobrien free(cmd); 87879968Sobrien errno = o_errno; 87979968Sobrien return(NULL); 88079968Sobrien} 88179968Sobrien 88279968Sobrien/* 88379968Sobrien * Count the number of current connections, reading from 88479968Sobrien * /var/run/ftpd.pids-<class> 88579968Sobrien * Does a kill -0 on each pid in that file, and only counts 88679968Sobrien * processes that exist (or frees the slot if it doesn't). 88779968Sobrien * Adds getpid() to the first free slot. Truncates the file 88879968Sobrien * if possible. 88979968Sobrien */ 89079968Sobrienvoid 89179968Sobriencount_users(void) 89279968Sobrien{ 89379968Sobrien char fn[MAXPATHLEN]; 89479968Sobrien int fd, i, last; 89579968Sobrien size_t count; 89679968Sobrien pid_t *pids, mypid; 89779968Sobrien struct stat sb; 89879968Sobrien 89979968Sobrien (void)strlcpy(fn, _PATH_CLASSPIDS, sizeof(fn)); 90079968Sobrien (void)strlcat(fn, curclass.classname, sizeof(fn)); 90179968Sobrien pids = NULL; 90279968Sobrien connections = 1; 90379968Sobrien 90479968Sobrien if ((fd = open(fn, O_RDWR | O_CREAT, 0600)) == -1) 90579968Sobrien return; 90679968Sobrien if (lockf(fd, F_TLOCK, 0) == -1) 90779968Sobrien goto cleanup_count; 90879968Sobrien if (fstat(fd, &sb) == -1) 90979968Sobrien goto cleanup_count; 91079968Sobrien if ((pids = malloc(sb.st_size + sizeof(pid_t))) == NULL) 91179968Sobrien goto cleanup_count; 91279968Sobrien count = read(fd, pids, sb.st_size); 91379968Sobrien if (count < 0 || count != sb.st_size) 91479968Sobrien goto cleanup_count; 91579968Sobrien count /= sizeof(pid_t); 91679968Sobrien mypid = getpid(); 91779968Sobrien last = 0; 91879968Sobrien for (i = 0; i < count; i++) { 91979968Sobrien if (pids[i] == 0) 92079968Sobrien continue; 92179968Sobrien if (kill(pids[i], 0) == -1 && errno != EPERM) { 92279968Sobrien if (mypid != 0) { 92379968Sobrien pids[i] = mypid; 92479968Sobrien mypid = 0; 92579968Sobrien last = i; 92679968Sobrien } 92779968Sobrien } else { 92879968Sobrien connections++; 92979968Sobrien last = i; 93079968Sobrien } 93179968Sobrien } 93279968Sobrien if (mypid != 0) { 93379968Sobrien if (pids[last] != 0) 93479968Sobrien last++; 93579968Sobrien pids[last] = mypid; 93679968Sobrien } 93779968Sobrien count = (last + 1) * sizeof(pid_t); 93879968Sobrien if (lseek(fd, 0, SEEK_SET) == -1) 93979968Sobrien goto cleanup_count; 94079968Sobrien if (write(fd, pids, count) == -1) 94179968Sobrien goto cleanup_count; 94279968Sobrien (void)ftruncate(fd, count); 94379968Sobrien 94479968Sobrien cleanup_count: 94579968Sobrien if (lseek(fd, 0, SEEK_SET) != -1) 94679968Sobrien (void)lockf(fd, F_ULOCK, 0); 94779968Sobrien close(fd); 94879968Sobrien REASSIGN(pids, NULL); 94979968Sobrien} 950