1186872Ssimon/* $NetBSD: ftpd.c,v 1.187 2008/09/13 03:30:35 lukem Exp $ */ 279968Sobrien 379968Sobrien/* 4133939Sobrien * Copyright (c) 1997-2004 The NetBSD Foundation, Inc. 579968Sobrien * All rights reserved. 679968Sobrien * 779968Sobrien * This code is derived from software contributed to The NetBSD Foundation 879968Sobrien * by 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 3979968Sobrien/* 4079968Sobrien * Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994 4179968Sobrien * The Regents of the University of California. All rights reserved. 4279968Sobrien * 4379968Sobrien * Redistribution and use in source and binary forms, with or without 4479968Sobrien * modification, are permitted provided that the following conditions 4579968Sobrien * are met: 4679968Sobrien * 1. Redistributions of source code must retain the above copyright 4779968Sobrien * notice, this list of conditions and the following disclaimer. 4879968Sobrien * 2. Redistributions in binary form must reproduce the above copyright 4979968Sobrien * notice, this list of conditions and the following disclaimer in the 5079968Sobrien * documentation and/or other materials provided with the distribution. 51133939Sobrien * 3. Neither the name of the University nor the names of its contributors 5279968Sobrien * may be used to endorse or promote products derived from this software 5379968Sobrien * without specific prior written permission. 5479968Sobrien * 5579968Sobrien * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 5679968Sobrien * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 5779968Sobrien * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 5879968Sobrien * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 5979968Sobrien * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 6079968Sobrien * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 6179968Sobrien * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 6279968Sobrien * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 6379968Sobrien * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 6479968Sobrien * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 6579968Sobrien * SUCH DAMAGE. 6679968Sobrien */ 6779968Sobrien 6879968Sobrien/* 6979968Sobrien * Copyright (C) 1997 and 1998 WIDE Project. 7079968Sobrien * All rights reserved. 7179968Sobrien * 7279968Sobrien * Redistribution and use in source and binary forms, with or without 7379968Sobrien * modification, are permitted provided that the following conditions 7479968Sobrien * are met: 7579968Sobrien * 1. Redistributions of source code must retain the above copyright 7679968Sobrien * notice, this list of conditions and the following disclaimer. 7779968Sobrien * 2. Redistributions in binary form must reproduce the above copyright 7879968Sobrien * notice, this list of conditions and the following disclaimer in the 7979968Sobrien * documentation and/or other materials provided with the distribution. 8079968Sobrien * 3. Neither the name of the project nor the names of its contributors 8179968Sobrien * may be used to endorse or promote products derived from this software 8279968Sobrien * without specific prior written permission. 8379968Sobrien * 8479968Sobrien * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND 8579968Sobrien * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 8679968Sobrien * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 8779968Sobrien * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE 8879968Sobrien * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 8979968Sobrien * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 9079968Sobrien * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 9179968Sobrien * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 9279968Sobrien * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 9379968Sobrien * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 9479968Sobrien * SUCH DAMAGE. 9579968Sobrien */ 9679968Sobrien 97108746Sobrien#include <sys/cdefs.h> 98108746Sobrien#ifndef lint 99108746Sobrien__COPYRIGHT( 100108746Sobrien"@(#) Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994\n\ 101108746Sobrien The Regents of the University of California. All rights reserved.\n"); 102108746Sobrien#endif /* not lint */ 103108746Sobrien 104108746Sobrien#ifndef lint 105108746Sobrien#if 0 106108746Sobrienstatic char sccsid[] = "@(#)ftpd.c 8.5 (Berkeley) 4/28/95"; 107108746Sobrien#else 108161770Sobrien__RCSID("$NetBSD: ftpd.c,v 1.176 2006/05/09 20:18:06 mrg Exp $"); 109108746Sobrien#endif 110161770Sobrien__FBSDID("$FreeBSD$"); 111108746Sobrien#endif /* not lint */ 112108746Sobrien 11379968Sobrien/* 11479968Sobrien * FTP server. 11579968Sobrien */ 116108746Sobrien#include <sys/param.h> 117108746Sobrien#include <sys/stat.h> 118108746Sobrien#include <sys/ioctl.h> 119108746Sobrien#include <sys/socket.h> 120108746Sobrien#include <sys/wait.h> 121108746Sobrien#include <sys/mman.h> 122108746Sobrien#include <sys/resource.h> 12379968Sobrien 124108746Sobrien#include <netinet/in.h> 125108746Sobrien#include <netinet/in_systm.h> 126108746Sobrien#include <netinet/ip.h> 127108746Sobrien 12879968Sobrien#define FTP_NAMES 129108746Sobrien#include <arpa/ftp.h> 130108746Sobrien#include <arpa/inet.h> 131108746Sobrien#include <arpa/telnet.h> 13279968Sobrien 133108746Sobrien#include <ctype.h> 134108746Sobrien#include <dirent.h> 135108746Sobrien#include <err.h> 136108746Sobrien#include <errno.h> 137108746Sobrien#include <fcntl.h> 138108746Sobrien#include <fnmatch.h> 139108746Sobrien#include <glob.h> 140108746Sobrien#include <grp.h> 141108746Sobrien#include <limits.h> 142108746Sobrien#include <netdb.h> 143108746Sobrien#include <pwd.h> 144161770Sobrien#include <poll.h> 145108746Sobrien#include <signal.h> 146108746Sobrien#include <stdarg.h> 147108746Sobrien#include <stdio.h> 148108746Sobrien#include <stdlib.h> 149108746Sobrien#include <string.h> 150108746Sobrien#include <syslog.h> 151108746Sobrien#include <time.h> 152108746Sobrien#include <tzfile.h> 153108746Sobrien#include <unistd.h> 154108746Sobrien#include <util.h> 155108746Sobrien#ifdef SUPPORT_UTMP 156108746Sobrien#include <utmp.h> 15779968Sobrien#endif 158108746Sobrien#ifdef SUPPORT_UTMPX 159108746Sobrien#include <utmpx.h> 160108746Sobrien#endif 16179968Sobrien#ifdef SKEY 16279968Sobrien#include <skey.h> 16379968Sobrien#endif 16479968Sobrien#ifdef KERBEROS5 16579968Sobrien#include <com_err.h> 16679968Sobrien#include <krb5/krb5.h> 16779968Sobrien#endif 168161770Sobrien 169161770Sobrien#ifdef LOGIN_CAP 170133939Sobrien#include <login_cap.h> 171133939Sobrien#endif 17279968Sobrien 173161770Sobrien#ifdef USE_PAM 174161770Sobrien#include <security/pam_appl.h> 175161770Sobrien#endif 176161770Sobrien 17779968Sobrien#define GLOBAL 17879968Sobrien#include "extern.h" 17979968Sobrien#include "pathnames.h" 18079968Sobrien#include "version.h" 18179968Sobrien 182161770Sobrien#include "nbsd_pidfile.h" 183161770Sobrien 184133939Sobrienvolatile sig_atomic_t transflag; 185133939Sobrienvolatile sig_atomic_t urgflag; 186133939Sobrien 18779968Sobrienint data; 188161770Sobrienint Dflag; 18979968Sobrienint sflag; 19079968Sobrienint stru; /* avoid C keyword */ 19179968Sobrienint mode; 19279968Sobrienint dataport; /* use specific data port */ 19379968Sobrienint dopidfile; /* maintain pid file */ 19479968Sobrienint doutmp; /* update utmp file */ 19579968Sobrienint dowtmp; /* update wtmp file */ 196133939Sobrienint doxferlog; /* syslog/write wu-ftpd style xferlog entries */ 197133939Sobrienint xferlogfd; /* fd to write wu-ftpd xferlog entries to */ 19879968Sobrienint dropprivs; /* if privileges should or have been dropped */ 19979968Sobrienint mapped; /* IPv4 connection on AF_INET6 socket */ 20079968Sobrienoff_t file_size; 20179968Sobrienoff_t byte_count; 20279968Sobrienstatic char ttyline[20]; 203161770Sobrien 204161770Sobrien#ifdef USE_PAM 205161770Sobrienstatic int auth_pam(struct passwd **, const char *); 206161770Sobrienpam_handle_t *pamh = NULL; 207161770Sobrien#endif 208161770Sobrien 209108746Sobrien#ifdef SUPPORT_UTMP 21079968Sobrienstatic struct utmp utmp; /* for utmp */ 211108746Sobrien#endif 212108746Sobrien#ifdef SUPPORT_UTMPX 213108746Sobrienstatic struct utmpx utmpx; /* for utmpx */ 214108746Sobrien#endif 21579968Sobrien 21679968Sobrienstatic const char *anondir = NULL; 21779968Sobrienstatic const char *confdir = _DEFAULT_CONFDIR; 21879968Sobrien 219133939Sobrienstatic char *curname; /* current USER name */ 220133939Sobrienstatic size_t curname_len; /* length of curname (include NUL) */ 221133939Sobrien 22279968Sobrien#if defined(KERBEROS) || defined(KERBEROS5) 22379968Sobrienint has_ccache = 0; 22479968Sobrienint notickets = 1; 22579968Sobrienchar *krbtkfile_env = NULL; 22679968Sobrienchar *tty = ttyline; 22779968Sobrienint login_krb5_forwardable_tgt = 0; 22879968Sobrien#endif 22979968Sobrien 23079968Sobrienint epsvall = 0; 23179968Sobrien 23279968Sobrien/* 23379968Sobrien * Timeout intervals for retrying connections 23479968Sobrien * to hosts that don't accept PORT cmds. This 23579968Sobrien * is a kludge, but given the problems with TCP... 23679968Sobrien */ 23779968Sobrien#define SWAITMAX 90 /* wait at most 90 seconds */ 23879968Sobrien#define SWAITINT 5 /* interval between retries */ 23979968Sobrien 24079968Sobrienint swaitmax = SWAITMAX; 24179968Sobrienint swaitint = SWAITINT; 24279968Sobrien 243108746Sobrienenum send_status { 244108746Sobrien SS_SUCCESS, 245133939Sobrien SS_ABORTED, /* transfer aborted */ 246108746Sobrien SS_NO_TRANSFER, /* no transfer made yet */ 247108746Sobrien SS_FILE_ERROR, /* file read error */ 248108746Sobrien SS_DATA_ERROR /* data send error */ 249108746Sobrien}; 250108746Sobrien 251108886Sobrien#ifdef USE_OPIE 252108886Sobrien#include <opie.h> 253108886Sobrienstatic struct opie opiedata; 254108886Sobrienstatic char opieprompt[OPIE_CHALLENGE_MAX+1]; 255108886Sobrienstatic int pwok; 256108886Sobrien#endif 257108886Sobrien 25879968Sobrienstatic int bind_pasv_addr(void); 25979968Sobrienstatic int checkuser(const char *, const char *, int, int, char **); 26079968Sobrienstatic int checkaccess(const char *); 26179968Sobrienstatic int checkpassword(const struct passwd *, const char *); 26279968Sobrienstatic void end_login(void); 26379968Sobrienstatic FILE *getdatasock(const char *); 26479968Sobrienstatic char *gunique(const char *); 265161770Sobrienstatic void login_utmp(const char *, const char *, const char *, 266161770Sobrien struct sockinet *); 26779968Sobrienstatic void logremotehost(struct sockinet *); 26879968Sobrienstatic void lostconn(int); 269133939Sobrienstatic void toolong(int); 270133939Sobrienstatic void sigquit(int); 271133939Sobrienstatic void sigurg(int); 272133939Sobrienstatic int handleoobcmd(void); 27379968Sobrienstatic int receive_data(FILE *, FILE *); 274108746Sobrienstatic int send_data(FILE *, FILE *, const struct stat *, int); 27579968Sobrienstatic struct passwd *sgetpwnam(const char *); 276108746Sobrienstatic int write_data(int, char *, size_t, off_t *, struct timeval *, 277108746Sobrien int); 278108746Sobrienstatic enum send_status 279108746Sobrien send_data_with_read(int, int, const struct stat *, int); 280108746Sobrienstatic enum send_status 281108746Sobrien send_data_with_mmap(int, int, const struct stat *, int); 282108746Sobrienstatic void logrusage(const struct rusage *, const struct rusage *); 283108746Sobrienstatic void logout_utmp(void); 28479968Sobrien 28579968Sobrienint main(int, char *[]); 28679968Sobrien 28779968Sobrien#if defined(KERBEROS) 28879968Sobrienint klogin(struct passwd *, char *, char *, char *); 28979968Sobrienvoid kdestroy(void); 29079968Sobrien#endif 29179968Sobrien#if defined(KERBEROS5) 29279968Sobrienint k5login(struct passwd *, char *, char *, char *); 29379968Sobrienvoid k5destroy(void); 29479968Sobrien#endif 29579968Sobrien 29679968Sobrienint 29779968Sobrienmain(int argc, char *argv[]) 29879968Sobrien{ 299161770Sobrien int ch, on = 1, tos, keepalive; 300161770Sobrien socklen_t addrlen; 30179968Sobrien#ifdef KERBEROS5 30279968Sobrien krb5_error_code kerror; 30379968Sobrien#endif 30479968Sobrien char *p; 305133939Sobrien const char *xferlogname = NULL; 306108746Sobrien long l; 307133939Sobrien struct sigaction sa; 308161770Sobrien sa_family_t af = AF_UNSPEC; 30979968Sobrien 31079968Sobrien connections = 1; 311161770Sobrien ftpd_debug = 0; 31279968Sobrien logging = 0; 31379968Sobrien pdata = -1; 314161770Sobrien Dflag = 0; 31579968Sobrien sflag = 0; 31679968Sobrien dataport = 0; 31779968Sobrien dopidfile = 1; /* default: DO use a pid file to count users */ 31879968Sobrien doutmp = 0; /* default: Do NOT log to utmp */ 31979968Sobrien dowtmp = 1; /* default: DO log to wtmp */ 32079968Sobrien doxferlog = 0; /* default: Do NOT syslog xferlog */ 321133939Sobrien xferlogfd = -1; /* default: Do NOT write xferlog file */ 32279968Sobrien dropprivs = 0; 32379968Sobrien mapped = 0; 32479968Sobrien usedefault = 1; 32579968Sobrien emailaddr = NULL; 32679968Sobrien hostname[0] = '\0'; 32779968Sobrien homedir[0] = '\0'; 32879968Sobrien gidcount = 0; 32979968Sobrien is_oob = 0; 33079968Sobrien version = FTPD_VERSION; 33179968Sobrien 33279968Sobrien /* 33379968Sobrien * LOG_NDELAY sets up the logging connection immediately, 33479968Sobrien * necessary for anonymous ftp's that chroot and can't do it later. 33579968Sobrien */ 336108746Sobrien openlog("ftpd", LOG_PID | LOG_NDELAY, LOG_FTP); 33779968Sobrien 338161770Sobrien while ((ch = getopt(argc, argv, 339161770Sobrien "46a:c:C:Dde:h:HlL:P:qQrst:T:uUvV:wWX")) != -1) { 34079968Sobrien switch (ch) { 341161770Sobrien case '4': 342161770Sobrien af = AF_INET; 343161770Sobrien break; 344161770Sobrien 345161770Sobrien case '6': 346161770Sobrien af = AF_INET6; 347161770Sobrien break; 348161770Sobrien 34979968Sobrien case 'a': 35079968Sobrien anondir = optarg; 35179968Sobrien break; 35279968Sobrien 35379968Sobrien case 'c': 35479968Sobrien confdir = optarg; 35579968Sobrien break; 35679968Sobrien 35779968Sobrien case 'C': 35879968Sobrien pw = sgetpwnam(optarg); 35979968Sobrien exit(checkaccess(optarg) ? 0 : 1); 36079968Sobrien /* NOTREACHED */ 36179968Sobrien 362161770Sobrien case 'D': 363161770Sobrien Dflag = 1; 364161770Sobrien break; 365161770Sobrien 36679968Sobrien case 'd': 36779968Sobrien case 'v': /* deprecated */ 368161770Sobrien ftpd_debug = 1; 36979968Sobrien break; 37079968Sobrien 37179968Sobrien case 'e': 37279968Sobrien emailaddr = optarg; 37379968Sobrien break; 37479968Sobrien 37579968Sobrien case 'h': 37679968Sobrien strlcpy(hostname, optarg, sizeof(hostname)); 37779968Sobrien break; 37879968Sobrien 37979968Sobrien case 'H': 38079968Sobrien if (gethostname(hostname, sizeof(hostname)) == -1) 38179968Sobrien hostname[0] = '\0'; 38279968Sobrien hostname[sizeof(hostname) - 1] = '\0'; 38379968Sobrien break; 38479968Sobrien 38579968Sobrien case 'l': 38679968Sobrien logging++; /* > 1 == extra logging */ 38779968Sobrien break; 38879968Sobrien 389133939Sobrien case 'L': 390133939Sobrien xferlogname = optarg; 391133939Sobrien break; 392133939Sobrien 39379968Sobrien case 'P': 394108746Sobrien errno = 0; 395108746Sobrien p = NULL; 396108746Sobrien l = strtol(optarg, &p, 10); 397108746Sobrien if (errno || *optarg == '\0' || *p != '\0' || 398108746Sobrien l < IPPORT_RESERVED || 399108746Sobrien l > IPPORT_ANONMAX) { 40079968Sobrien syslog(LOG_WARNING, "Invalid dataport %s", 40179968Sobrien optarg); 40279968Sobrien dataport = 0; 40379968Sobrien } 404108746Sobrien dataport = (int)l; 40579968Sobrien break; 40679968Sobrien 40779968Sobrien case 'q': 40879968Sobrien dopidfile = 1; 40979968Sobrien break; 41079968Sobrien 41179968Sobrien case 'Q': 41279968Sobrien dopidfile = 0; 41379968Sobrien break; 41479968Sobrien 41579968Sobrien case 'r': 41679968Sobrien dropprivs = 1; 41779968Sobrien break; 41879968Sobrien 41979968Sobrien case 's': 42079968Sobrien sflag = 1; 42179968Sobrien break; 42279968Sobrien 42379968Sobrien case 't': 42479968Sobrien case 'T': 42579968Sobrien syslog(LOG_WARNING, 42679968Sobrien "-%c has been deprecated in favour of ftpd.conf", 42779968Sobrien ch); 42879968Sobrien break; 42979968Sobrien 43079968Sobrien case 'u': 43179968Sobrien doutmp = 1; 43279968Sobrien break; 43379968Sobrien 43479968Sobrien case 'U': 43579968Sobrien doutmp = 0; 43679968Sobrien break; 43779968Sobrien 43879968Sobrien case 'V': 43979968Sobrien if (EMPTYSTR(optarg) || strcmp(optarg, "-") == 0) 44079968Sobrien version = NULL; 44179968Sobrien else 442161770Sobrien version = ftpd_strdup(optarg); 44379968Sobrien break; 44479968Sobrien 44579968Sobrien case 'w': 44679968Sobrien dowtmp = 1; 44779968Sobrien break; 44879968Sobrien 44979968Sobrien case 'W': 45079968Sobrien dowtmp = 0; 45179968Sobrien break; 45279968Sobrien 45379968Sobrien case 'X': 454133939Sobrien doxferlog |= 1; 45579968Sobrien break; 45679968Sobrien 45779968Sobrien default: 45879968Sobrien if (optopt == 'a' || optopt == 'C') 45979968Sobrien exit(1); 46079968Sobrien syslog(LOG_WARNING, "unknown flag -%c ignored", optopt); 46179968Sobrien break; 46279968Sobrien } 46379968Sobrien } 46479968Sobrien if (EMPTYSTR(confdir)) 46579968Sobrien confdir = _DEFAULT_CONFDIR; 46679968Sobrien 467161770Sobrien if (dowtmp) { 468161770Sobrien#ifdef SUPPORT_UTMPX 469161770Sobrien ftpd_initwtmpx(); 470161770Sobrien#endif 471161770Sobrien#ifdef SUPPORT_UTMP 472161770Sobrien ftpd_initwtmp(); 473161770Sobrien#endif 474161770Sobrien } 475133939Sobrien errno = 0; 476133939Sobrien l = sysconf(_SC_LOGIN_NAME_MAX); 477133939Sobrien if (l == -1 && errno != 0) { 478133939Sobrien syslog(LOG_ERR, "sysconf _SC_LOGIN_NAME_MAX: %m"); 479133939Sobrien exit(1); 480133939Sobrien } else if (l <= 0) { 481133939Sobrien syslog(LOG_WARNING, "using conservative LOGIN_NAME_MAX value"); 482133939Sobrien curname_len = _POSIX_LOGIN_NAME_MAX; 483133939Sobrien } else 484133939Sobrien curname_len = (size_t)l; 485133939Sobrien curname = malloc(curname_len); 486133939Sobrien if (curname == NULL) { 487133939Sobrien syslog(LOG_ERR, "malloc: %m"); 488133939Sobrien exit(1); 489133939Sobrien } 490133939Sobrien curname[0] = '\0'; 491133939Sobrien 492161770Sobrien if (Dflag) { 493161770Sobrien int error, fd, i, n, *socks; 494161770Sobrien struct pollfd *fds; 495161770Sobrien struct addrinfo hints, *res, *res0; 496161770Sobrien 497161770Sobrien if (daemon(1, 0) == -1) { 498161770Sobrien syslog(LOG_ERR, "failed to daemonize: %m"); 499161770Sobrien exit(1); 500161770Sobrien } 501161770Sobrien (void)memset(&sa, 0, sizeof(sa)); 502161770Sobrien sa.sa_handler = SIG_IGN; 503161770Sobrien sa.sa_flags = SA_NOCLDWAIT; 504161770Sobrien sigemptyset(&sa.sa_mask); 505161770Sobrien (void)sigaction(SIGCHLD, &sa, NULL); 506161770Sobrien 507161770Sobrien (void)memset(&hints, 0, sizeof(hints)); 508161770Sobrien hints.ai_flags = AI_PASSIVE; 509161770Sobrien hints.ai_family = af; 510161770Sobrien hints.ai_socktype = SOCK_STREAM; 511161770Sobrien error = getaddrinfo(NULL, "ftp", &hints, &res0); 512161770Sobrien if (error) { 513161770Sobrien syslog(LOG_ERR, "getaddrinfo: %s", gai_strerror(error)); 514161770Sobrien exit(1); 515161770Sobrien } 516161770Sobrien 517161770Sobrien for (n = 0, res = res0; res != NULL; res = res->ai_next) 518161770Sobrien n++; 519161770Sobrien if (n == 0) { 520161770Sobrien syslog(LOG_ERR, "no addresses available"); 521161770Sobrien exit(1); 522161770Sobrien } 523161770Sobrien socks = malloc(n * sizeof(int)); 524161770Sobrien fds = malloc(n * sizeof(struct pollfd)); 525161770Sobrien if (socks == NULL || fds == NULL) { 526161770Sobrien syslog(LOG_ERR, "malloc: %m"); 527161770Sobrien exit(1); 528161770Sobrien } 529161770Sobrien 530161770Sobrien for (n = 0, res = res0; res != NULL; res = res->ai_next) { 531161770Sobrien socks[n] = socket(res->ai_family, res->ai_socktype, 532161770Sobrien res->ai_protocol); 533161770Sobrien if (socks[n] == -1) 534161770Sobrien continue; 535161770Sobrien (void)setsockopt(socks[n], SOL_SOCKET, SO_REUSEADDR, 536161770Sobrien &on, sizeof(on)); 537161770Sobrien if (bind(socks[n], res->ai_addr, res->ai_addrlen) 538161770Sobrien == -1) { 539161770Sobrien (void)close(socks[n]); 540161770Sobrien continue; 541161770Sobrien } 542161770Sobrien if (listen(socks[n], 12) == -1) { 543161770Sobrien (void)close(socks[n]); 544161770Sobrien continue; 545161770Sobrien } 546161770Sobrien 547161770Sobrien fds[n].fd = socks[n]; 548161770Sobrien fds[n].events = POLLIN; 549161770Sobrien n++; 550161770Sobrien } 551161770Sobrien if (n == 0) { 552161770Sobrien syslog(LOG_ERR, "%m"); 553161770Sobrien exit(1); 554161770Sobrien } 555161770Sobrien freeaddrinfo(res0); 556161770Sobrien 557161770Sobrien if (pidfile(NULL) == -1) 558161770Sobrien syslog(LOG_ERR, "failed to write a pid file: %m"); 559161770Sobrien 560161770Sobrien for (;;) { 561161770Sobrien if (poll(fds, n, INFTIM) == -1) { 562161770Sobrien if (errno == EINTR) 563161770Sobrien continue; 564161770Sobrien syslog(LOG_ERR, "poll: %m"); 565161770Sobrien exit(1); 566161770Sobrien } 567161770Sobrien for (i = 0; i < n; i++) { 568161770Sobrien if (fds[i].revents & POLLIN) { 569161770Sobrien fd = accept(fds[i].fd, NULL, NULL); 570161770Sobrien if (fd == -1) { 571161770Sobrien syslog(LOG_ERR, "accept: %m"); 572161770Sobrien continue; 573161770Sobrien } 574161770Sobrien switch (fork()) { 575161770Sobrien case -1: 576161770Sobrien syslog(LOG_ERR, "fork: %m"); 577161770Sobrien break; 578161770Sobrien case 0: 579161770Sobrien goto child; 580161770Sobrien /* NOTREACHED */ 581161770Sobrien } 582161770Sobrien (void)close(fd); 583161770Sobrien } 584161770Sobrien } 585161770Sobrien } 586161770Sobrien child: 587161770Sobrien (void)dup2(fd, STDIN_FILENO); 588161770Sobrien (void)dup2(fd, STDOUT_FILENO); 589161770Sobrien (void)dup2(fd, STDERR_FILENO); 590161770Sobrien for (i = 0; i < n; i++) 591161770Sobrien (void)close(socks[i]); 592161770Sobrien } 593161770Sobrien 59479968Sobrien memset((char *)&his_addr, 0, sizeof(his_addr)); 59579968Sobrien addrlen = sizeof(his_addr.si_su); 59679968Sobrien if (getpeername(0, (struct sockaddr *)&his_addr.si_su, &addrlen) < 0) { 59779968Sobrien syslog(LOG_ERR, "getpeername (%s): %m",argv[0]); 59879968Sobrien exit(1); 59979968Sobrien } 60079968Sobrien his_addr.su_len = addrlen; 60179968Sobrien memset((char *)&ctrl_addr, 0, sizeof(ctrl_addr)); 60279968Sobrien addrlen = sizeof(ctrl_addr.si_su); 60379968Sobrien if (getsockname(0, (struct sockaddr *)&ctrl_addr, &addrlen) < 0) { 60479968Sobrien syslog(LOG_ERR, "getsockname (%s): %m",argv[0]); 60579968Sobrien exit(1); 60679968Sobrien } 60779968Sobrien ctrl_addr.su_len = addrlen; 60879968Sobrien#ifdef INET6 60979968Sobrien if (his_addr.su_family == AF_INET6 61079968Sobrien && IN6_IS_ADDR_V4MAPPED(&his_addr.su_6addr)) { 61179968Sobrien#if 1 61279968Sobrien /* 61379968Sobrien * IPv4 control connection arrived to AF_INET6 socket. 61479968Sobrien * I hate to do this, but this is the easiest solution. 61579968Sobrien * 61679968Sobrien * The assumption is untrue on SIIT environment. 61779968Sobrien */ 61879968Sobrien struct sockinet tmp_addr; 61979968Sobrien const int off = sizeof(struct in6_addr) - sizeof(struct in_addr); 62079968Sobrien 62179968Sobrien tmp_addr = his_addr; 62279968Sobrien memset(&his_addr, 0, sizeof(his_addr)); 62379968Sobrien his_addr.su_family = AF_INET; 62479968Sobrien his_addr.su_len = sizeof(his_addr.si_su.su_sin); 62579968Sobrien memcpy(&his_addr.su_addr, &tmp_addr.su_6addr.s6_addr[off], 62679968Sobrien sizeof(his_addr.su_addr)); 62779968Sobrien his_addr.su_port = tmp_addr.su_port; 62879968Sobrien 62979968Sobrien tmp_addr = ctrl_addr; 63079968Sobrien memset(&ctrl_addr, 0, sizeof(ctrl_addr)); 63179968Sobrien ctrl_addr.su_family = AF_INET; 63279968Sobrien ctrl_addr.su_len = sizeof(ctrl_addr.si_su.su_sin); 63379968Sobrien memcpy(&ctrl_addr.su_addr, &tmp_addr.su_6addr.s6_addr[off], 63479968Sobrien sizeof(ctrl_addr.su_addr)); 63579968Sobrien ctrl_addr.su_port = tmp_addr.su_port; 63679968Sobrien#else 63779968Sobrien while (fgets(line, sizeof(line), fd) != NULL) { 63879968Sobrien if ((cp = strchr(line, '\n')) != NULL) 63979968Sobrien *cp = '\0'; 64079968Sobrien reply(-530, "%s", line); 64179968Sobrien } 64279968Sobrien (void) fflush(stdout); 64379968Sobrien (void) fclose(fd); 64479968Sobrien reply(530, 64579968Sobrien "Connection from IPv4 mapped address is not supported."); 64679968Sobrien exit(0); 64779968Sobrien#endif 64879968Sobrien 64979968Sobrien mapped = 1; 65079968Sobrien } else 65179968Sobrien#endif /* INET6 */ 65279968Sobrien mapped = 0; 65379968Sobrien#ifdef IP_TOS 65479968Sobrien if (!mapped && his_addr.su_family == AF_INET) { 65579968Sobrien tos = IPTOS_LOWDELAY; 65679968Sobrien if (setsockopt(0, IPPROTO_IP, IP_TOS, (char *)&tos, 65779968Sobrien sizeof(int)) < 0) 65879968Sobrien syslog(LOG_WARNING, "setsockopt (IP_TOS): %m"); 65979968Sobrien } 66079968Sobrien#endif 66179968Sobrien /* if the hostname hasn't been given, attempt to determine it */ 66279968Sobrien if (hostname[0] == '\0') { 66379968Sobrien if (getnameinfo((struct sockaddr *)&ctrl_addr.si_su, 66479968Sobrien ctrl_addr.su_len, hostname, sizeof(hostname), NULL, 0, 0) 66579968Sobrien != 0) 66679968Sobrien (void)gethostname(hostname, sizeof(hostname)); 66779968Sobrien hostname[sizeof(hostname) - 1] = '\0'; 66879968Sobrien } 66979968Sobrien 67079968Sobrien /* set this here so klogin can use it... */ 67179968Sobrien (void)snprintf(ttyline, sizeof(ttyline), "ftp%d", getpid()); 67279968Sobrien 67379968Sobrien (void) freopen(_PATH_DEVNULL, "w", stderr); 67479968Sobrien 675133939Sobrien memset(&sa, 0, sizeof(sa)); 676133939Sobrien sa.sa_handler = SIG_DFL; 677133939Sobrien sa.sa_flags = SA_RESTART; 678133939Sobrien sigemptyset(&sa.sa_mask); 679133939Sobrien (void) sigaction(SIGCHLD, &sa, NULL); 680133939Sobrien 681133939Sobrien sa.sa_handler = sigquit; 682133939Sobrien sa.sa_flags = SA_RESTART; 683133939Sobrien sigfillset(&sa.sa_mask); /* block all sigs in these handlers */ 684133939Sobrien (void) sigaction(SIGHUP, &sa, NULL); 685133939Sobrien (void) sigaction(SIGINT, &sa, NULL); 686133939Sobrien (void) sigaction(SIGQUIT, &sa, NULL); 687133939Sobrien (void) sigaction(SIGTERM, &sa, NULL); 688133939Sobrien sa.sa_handler = lostconn; 689133939Sobrien (void) sigaction(SIGPIPE, &sa, NULL); 690133939Sobrien sa.sa_handler = toolong; 691133939Sobrien (void) sigaction(SIGALRM, &sa, NULL); 692133939Sobrien sa.sa_handler = sigurg; 693133939Sobrien (void) sigaction(SIGURG, &sa, NULL); 694133939Sobrien 69579968Sobrien /* Try to handle urgent data inline */ 69679968Sobrien#ifdef SO_OOBINLINE 69779968Sobrien if (setsockopt(0, SOL_SOCKET, SO_OOBINLINE, (char *)&on, sizeof(on)) < 0) 69879968Sobrien syslog(LOG_WARNING, "setsockopt: %m"); 69979968Sobrien#endif 70079968Sobrien /* Set keepalives on the socket to detect dropped connections. */ 70179968Sobrien#ifdef SO_KEEPALIVE 70279968Sobrien keepalive = 1; 70379968Sobrien if (setsockopt(0, SOL_SOCKET, SO_KEEPALIVE, (char *)&keepalive, 70479968Sobrien sizeof(int)) < 0) 70579968Sobrien syslog(LOG_WARNING, "setsockopt (SO_KEEPALIVE): %m"); 70679968Sobrien#endif 70779968Sobrien 70879968Sobrien#ifdef F_SETOWN 70979968Sobrien if (fcntl(fileno(stdin), F_SETOWN, getpid()) == -1) 71079968Sobrien syslog(LOG_WARNING, "fcntl F_SETOWN: %m"); 71179968Sobrien#endif 71279968Sobrien logremotehost(&his_addr); 71379968Sobrien /* 71479968Sobrien * Set up default state 71579968Sobrien */ 71679968Sobrien data = -1; 71779968Sobrien type = TYPE_A; 71879968Sobrien form = FORM_N; 71979968Sobrien stru = STRU_F; 72079968Sobrien mode = MODE_S; 72179968Sobrien tmpline[0] = '\0'; 72279968Sobrien hasyyerrored = 0; 72379968Sobrien 72479968Sobrien#ifdef KERBEROS5 72579968Sobrien kerror = krb5_init_context(&kcontext); 72679968Sobrien if (kerror) { 72779968Sobrien syslog(LOG_ERR, "%s when initializing Kerberos context", 72879968Sobrien error_message(kerror)); 72979968Sobrien exit(0); 73079968Sobrien } 73179968Sobrien#endif /* KERBEROS5 */ 73279968Sobrien 73379968Sobrien init_curclass(); 73479968Sobrien curclass.timeout = 300; /* 5 minutes, as per login(1) */ 73579968Sobrien curclass.type = CLASS_REAL; 73679968Sobrien 73779968Sobrien /* If logins are disabled, print out the message. */ 73879968Sobrien if (display_file(_PATH_NOLOGIN, 530)) { 73979968Sobrien reply(530, "System not available."); 74079968Sobrien exit(0); 74179968Sobrien } 742161770Sobrien (void)display_file(conffilename(_NAME_FTPWELCOME), 220); 74379968Sobrien /* reply(220,) must follow */ 74479968Sobrien if (EMPTYSTR(version)) 74579968Sobrien reply(220, "%s FTP server ready.", hostname); 74679968Sobrien else 74779968Sobrien reply(220, "%s FTP server (%s) ready.", hostname, version); 74879968Sobrien 749133939Sobrien if (xferlogname != NULL) { 750133939Sobrien xferlogfd = open(xferlogname, O_WRONLY | O_APPEND | O_CREAT, 751133939Sobrien 0660); 752133939Sobrien if (xferlogfd == -1) 753133939Sobrien syslog(LOG_WARNING, "open xferlog `%s': %m", 754133939Sobrien xferlogname); 755133939Sobrien else 756133939Sobrien doxferlog |= 2; 757133939Sobrien } 758133939Sobrien 75979968Sobrien ftp_loop(); 76079968Sobrien /* NOTREACHED */ 76179968Sobrien} 76279968Sobrien 76379968Sobrienstatic void 76479968Sobrienlostconn(int signo) 76579968Sobrien{ 76679968Sobrien 767161770Sobrien if (ftpd_debug) 76879968Sobrien syslog(LOG_DEBUG, "lost connection"); 76979968Sobrien dologout(1); 77079968Sobrien} 77179968Sobrien 772133939Sobrienstatic void 773133939Sobrientoolong(int signo) 774133939Sobrien{ 775133939Sobrien 776133939Sobrien /* XXXSIGRACE */ 777133939Sobrien reply(421, 778133939Sobrien "Timeout (" LLF " seconds): closing control connection.", 779133939Sobrien (LLT)curclass.timeout); 780133939Sobrien if (logging) 781133939Sobrien syslog(LOG_INFO, "User %s timed out after " LLF " seconds", 782133939Sobrien (pw ? pw->pw_name : "unknown"), (LLT)curclass.timeout); 783133939Sobrien dologout(1); 784133939Sobrien} 785133939Sobrien 786133939Sobrienstatic void 787133939Sobriensigquit(int signo) 788133939Sobrien{ 789133939Sobrien 790161770Sobrien if (ftpd_debug) 791133939Sobrien syslog(LOG_DEBUG, "got signal %d", signo); 792133939Sobrien dologout(1); 793133939Sobrien} 794133939Sobrien 795133939Sobrienstatic void 796133939Sobriensigurg(int signo) 797133939Sobrien{ 798133939Sobrien 799133939Sobrien urgflag = 1; 800133939Sobrien} 801133939Sobrien 802133939Sobrien 80379968Sobrien/* 80479968Sobrien * Save the result of a getpwnam. Used for USER command, since 80579968Sobrien * the data returned must not be clobbered by any other command 80679968Sobrien * (e.g., globbing). 80779968Sobrien */ 80879968Sobrienstatic struct passwd * 80979968Sobriensgetpwnam(const char *name) 81079968Sobrien{ 81179968Sobrien static struct passwd save; 81279968Sobrien struct passwd *p; 81379968Sobrien 81479968Sobrien if ((p = getpwnam(name)) == NULL) 81579968Sobrien return (p); 81679968Sobrien if (save.pw_name) { 81779968Sobrien free((char *)save.pw_name); 81879968Sobrien memset(save.pw_passwd, 0, strlen(save.pw_passwd)); 81979968Sobrien free((char *)save.pw_passwd); 82079968Sobrien free((char *)save.pw_gecos); 82179968Sobrien free((char *)save.pw_dir); 82279968Sobrien free((char *)save.pw_shell); 82379968Sobrien } 82479968Sobrien save = *p; 825161770Sobrien save.pw_name = ftpd_strdup(p->pw_name); 826161770Sobrien save.pw_passwd = ftpd_strdup(p->pw_passwd); 827161770Sobrien save.pw_gecos = ftpd_strdup(p->pw_gecos); 828161770Sobrien save.pw_dir = ftpd_strdup(p->pw_dir); 829161770Sobrien save.pw_shell = ftpd_strdup(p->pw_shell); 83079968Sobrien return (&save); 83179968Sobrien} 83279968Sobrien 83379968Sobrienstatic int login_attempts; /* number of failed login attempts */ 83492282Sobrienstatic int askpasswd; /* had USER command, ask for PASSwd */ 83592282Sobrienstatic int permitted; /* USER permitted */ 83679968Sobrien 83779968Sobrien/* 83879968Sobrien * USER command. 83979968Sobrien * Sets global passwd pointer pw if named account exists and is acceptable; 84079968Sobrien * sets askpasswd if a PASS command is expected. If logged in previously, 84179968Sobrien * need to reset state. If name is "ftp" or "anonymous", the name is not in 842161770Sobrien * _NAME_FTPUSERS, and ftp account exists, set guest and pw, then just return. 84379968Sobrien * If account doesn't exist, ask for passwd anyway. Otherwise, check user 84479968Sobrien * requesting login privileges. Disallow anyone who does not have a standard 84579968Sobrien * shell as returned by getusershell(). Disallow anyone mentioned in the file 846161770Sobrien * _NAME_FTPUSERS to allow people such as root and uucp to be avoided. 84779968Sobrien */ 84879968Sobrienvoid 84979968Sobrienuser(const char *name) 85079968Sobrien{ 851161770Sobrien char *class; 852161770Sobrien#ifdef LOGIN_CAP 853161770Sobrien login_cap_t *lc = NULL; 854133939Sobrien#endif 85592282Sobrien 85692282Sobrien class = NULL; 85779968Sobrien if (logged_in) { 85879968Sobrien switch (curclass.type) { 85979968Sobrien case CLASS_GUEST: 86079968Sobrien reply(530, "Can't change user from guest login."); 86179968Sobrien return; 86279968Sobrien case CLASS_CHROOT: 86379968Sobrien reply(530, "Can't change user from chroot user."); 86479968Sobrien return; 86579968Sobrien case CLASS_REAL: 86679968Sobrien if (dropprivs) { 86779968Sobrien reply(530, "Can't change user."); 86879968Sobrien return; 86979968Sobrien } 87079968Sobrien end_login(); 87179968Sobrien break; 87279968Sobrien default: 87379968Sobrien abort(); 87479968Sobrien } 87579968Sobrien } 87679968Sobrien 87779968Sobrien#if defined(KERBEROS) 87879968Sobrien kdestroy(); 87979968Sobrien#endif 88079968Sobrien#if defined(KERBEROS5) 88179968Sobrien k5destroy(); 88279968Sobrien#endif 88379968Sobrien 88479968Sobrien curclass.type = CLASS_REAL; 88592282Sobrien askpasswd = 0; 88692282Sobrien permitted = 0; 88792282Sobrien 88879968Sobrien if (strcmp(name, "ftp") == 0 || strcmp(name, "anonymous") == 0) { 88979968Sobrien /* need `pw' setup for checkaccess() and checkuser () */ 89079968Sobrien if ((pw = sgetpwnam("ftp")) == NULL) 89179968Sobrien reply(530, "User %s unknown.", name); 89279968Sobrien else if (! checkaccess("ftp") || ! checkaccess("anonymous")) 89379968Sobrien reply(530, "User %s access denied.", name); 89479968Sobrien else { 89579968Sobrien curclass.type = CLASS_GUEST; 89679968Sobrien askpasswd = 1; 89779968Sobrien reply(331, 89879968Sobrien "Guest login ok, type your name as password."); 89979968Sobrien } 90092282Sobrien if (!askpasswd) { 90192282Sobrien if (logging) 90292282Sobrien syslog(LOG_NOTICE, 90392282Sobrien "ANONYMOUS FTP LOGIN REFUSED FROM %s", 90492282Sobrien remotehost); 90592282Sobrien end_login(); 90692282Sobrien goto cleanup_user; 90792282Sobrien } 90892282Sobrien name = "ftp"; 90992282Sobrien } else 91092282Sobrien pw = sgetpwnam(name); 91179968Sobrien 912133939Sobrien strlcpy(curname, name, curname_len); 91379968Sobrien 91492282Sobrien /* check user in /etc/ftpusers, and setup class */ 915161770Sobrien permitted = checkuser(_NAME_FTPUSERS, curname, 1, 0, &class); 91692282Sobrien 91792282Sobrien /* check user in /etc/ftpchroot */ 918161770Sobrien lc = login_getpwclass(pw); 919161770Sobrien if (checkuser(_NAME_FTPCHROOT, curname, 0, 0, NULL) 920161770Sobrien#ifdef LOGIN_CAP /* Allow login.conf configuration as well */ 921133939Sobrien || login_getcapbool(lc, "ftp-chroot", 0) 922133939Sobrien#endif 923161770Sobrien ) { 92492282Sobrien if (curclass.type == CLASS_GUEST) { 92592282Sobrien syslog(LOG_NOTICE, 92692282Sobrien "Can't change guest user to chroot class; remove entry in %s", 927161770Sobrien _NAME_FTPCHROOT); 92892282Sobrien exit(1); 92992282Sobrien } 93092282Sobrien curclass.type = CLASS_CHROOT; 93192282Sobrien } 93292282Sobrien /* determine default class */ 93392282Sobrien if (class == NULL) { 93492282Sobrien switch (curclass.type) { 93592282Sobrien case CLASS_GUEST: 936161770Sobrien class = ftpd_strdup("guest"); 93792282Sobrien break; 93892282Sobrien case CLASS_CHROOT: 939161770Sobrien class = ftpd_strdup("chroot"); 94092282Sobrien break; 94192282Sobrien case CLASS_REAL: 942161770Sobrien class = ftpd_strdup("real"); 94392282Sobrien break; 94492282Sobrien default: 94592282Sobrien syslog(LOG_ERR, "unknown curclass.type %d; aborting", 94692282Sobrien curclass.type); 94792282Sobrien abort(); 94892282Sobrien } 94992282Sobrien } 95092282Sobrien /* parse ftpd.conf, setting up various parameters */ 95192282Sobrien parse_conf(class); 95292282Sobrien /* if not guest user, check for valid shell */ 95392282Sobrien if (pw == NULL) 95492282Sobrien permitted = 0; 95592282Sobrien else { 95692282Sobrien const char *cp, *shell; 95792282Sobrien 95892282Sobrien if ((shell = pw->pw_shell) == NULL || *shell == 0) 95992282Sobrien shell = _PATH_BSHELL; 96092282Sobrien while ((cp = getusershell()) != NULL) 96192282Sobrien if (strcmp(cp, shell) == 0) 96292282Sobrien break; 96392282Sobrien endusershell(); 96492282Sobrien if (cp == NULL && curclass.type != CLASS_GUEST) 96592282Sobrien permitted = 0; 96692282Sobrien } 96792282Sobrien 96892282Sobrien /* deny quickly (after USER not PASS) if requested */ 96992282Sobrien if (CURCLASS_FLAGS_ISSET(denyquick) && !permitted) { 97092282Sobrien reply(530, "User %s may not use FTP.", curname); 97192282Sobrien if (logging) 97292282Sobrien syslog(LOG_NOTICE, "FTP LOGIN REFUSED FROM %s, %s", 97392282Sobrien remotehost, curname); 97492282Sobrien end_login(); 97592282Sobrien goto cleanup_user; 97692282Sobrien } 97792282Sobrien 97892282Sobrien /* if haven't asked yet (i.e, not anon), ask now */ 97992282Sobrien if (!askpasswd) { 98092282Sobrien askpasswd = 1; 98179968Sobrien#ifdef SKEY 98292282Sobrien if (skey_haskey(curname) == 0) { 98392282Sobrien const char *myskey; 98479968Sobrien 98592282Sobrien myskey = skey_keyinfo(curname); 98692282Sobrien reply(331, "Password [ %s ] required for %s.", 98792282Sobrien myskey ? myskey : "error getting challenge", 98892282Sobrien curname); 98992282Sobrien } else 99079968Sobrien#endif 991108886Sobrien#ifdef USE_OPIE 992108886Sobrien if (opiechallenge(&opiedata, (char *)curname, opieprompt) == 993108886Sobrien 0) { 994108886Sobrien pwok = (pw != NULL) && 995108886Sobrien opieaccessfile(remotehost) && 996108886Sobrien opiealways(pw->pw_dir); 997108886Sobrien reply(331, "Response to %s %s for %s.", 998108886Sobrien opieprompt, pwok ? "requested" : "required", 999108886Sobrien curname); 1000108886Sobrien } else 1001108886Sobrien#endif 100292282Sobrien reply(331, "Password required for %s.", curname); 100392282Sobrien } 100479968Sobrien 100592282Sobrien cleanup_user: 1006133939Sobrien#ifdef LOGIN_CAP 1007133939Sobrien login_close(lc); 1008133939Sobrien#endif 100979968Sobrien /* 101079968Sobrien * Delay before reading passwd after first failed 101179968Sobrien * attempt to slow down passwd-guessing programs. 101279968Sobrien */ 101379968Sobrien if (login_attempts) 101479968Sobrien sleep((unsigned) login_attempts); 101592282Sobrien 101692282Sobrien if (class) 101792282Sobrien free(class); 101879968Sobrien} 101979968Sobrien 102079968Sobrien/* 102179968Sobrien * Determine whether something is to happen (allow access, chroot) 102279968Sobrien * for a user. Each line is a shell-style glob followed by 102379968Sobrien * `yes' or `no'. 102479968Sobrien * 102592282Sobrien * For backward compatibility, `allow' and `deny' are synonymns 102679968Sobrien * for `yes' and `no', respectively. 102779968Sobrien * 102879968Sobrien * Each glob is matched against the username in turn, and the first 102979968Sobrien * match found is used. If no match is found, the result is the 103079968Sobrien * argument `def'. If a match is found but without and explicit 103179968Sobrien * `yes'/`no', the result is the opposite of def. 103279968Sobrien * 103379968Sobrien * If the file doesn't exist at all, the result is the argument 103479968Sobrien * `nofile' 103579968Sobrien * 103679968Sobrien * Any line starting with `#' is considered a comment and ignored. 103779968Sobrien * 103879968Sobrien * Returns 0 if the user is denied, or 1 if they are allowed. 103979968Sobrien * 104079968Sobrien * NOTE: needs struct passwd *pw setup before use. 104179968Sobrien */ 104279968Sobrienstatic int 104379968Sobriencheckuser(const char *fname, const char *name, int def, int nofile, 104479968Sobrien char **retclass) 104579968Sobrien{ 104679968Sobrien FILE *fd; 104779968Sobrien int retval; 104892282Sobrien char *word, *perm, *class, *buf, *p; 104979968Sobrien size_t len, line; 105079968Sobrien 105179968Sobrien retval = def; 105279968Sobrien if (retclass != NULL) 105379968Sobrien *retclass = NULL; 105479968Sobrien if ((fd = fopen(conffilename(fname), "r")) == NULL) 105579968Sobrien return nofile; 105679968Sobrien 105779968Sobrien line = 0; 105879968Sobrien for (; 105979968Sobrien (buf = fparseln(fd, &len, &line, NULL, FPARSELN_UNESCCOMM | 1060108746Sobrien FPARSELN_UNESCCONT | FPARSELN_UNESCESC)) != NULL; 106179968Sobrien free(buf), buf = NULL) { 106292282Sobrien word = perm = class = NULL; 106379968Sobrien p = buf; 106479968Sobrien if (len < 1) 106579968Sobrien continue; 106679968Sobrien if (p[len - 1] == '\n') 106779968Sobrien p[--len] = '\0'; 106879968Sobrien if (EMPTYSTR(p)) 106979968Sobrien continue; 107079968Sobrien 107192282Sobrien NEXTWORD(p, word); 107279968Sobrien NEXTWORD(p, perm); 107379968Sobrien NEXTWORD(p, class); 107492282Sobrien if (EMPTYSTR(word)) 107579968Sobrien continue; 107679968Sobrien if (!EMPTYSTR(class)) { 107779968Sobrien if (strcasecmp(class, "all") == 0 || 107879968Sobrien strcasecmp(class, "none") == 0) { 107979968Sobrien syslog(LOG_WARNING, 108079968Sobrien "%s line %d: illegal user-defined class `%s' - skipping entry", 108179968Sobrien fname, (int)line, class); 108279968Sobrien continue; 108379968Sobrien } 108479968Sobrien } 108579968Sobrien 108679968Sobrien /* have a host specifier */ 108792282Sobrien if ((p = strchr(word, '@')) != NULL) { 108879968Sobrien unsigned long net, mask, addr; 108979968Sobrien int bits; 109079968Sobrien 109179968Sobrien *p++ = '\0'; 109279968Sobrien /* check against network or CIDR */ 1093161770Sobrien if (isdigit((unsigned char)*p) && 109479968Sobrien (bits = inet_net_pton(AF_INET, p, 109579968Sobrien &net, sizeof(net))) != -1) { 109679968Sobrien net = ntohl(net); 109779968Sobrien mask = 0xffffffffU << (32 - bits); 109879968Sobrien addr = ntohl(his_addr.su_addr.s_addr); 109979968Sobrien if ((addr & mask) != net) 110079968Sobrien continue; 110179968Sobrien 110279968Sobrien /* check against hostname glob */ 110392282Sobrien } else if (fnmatch(p, remotehost, FNM_CASEFOLD) != 0) 110479968Sobrien continue; 110579968Sobrien } 110679968Sobrien 110779968Sobrien /* have a group specifier */ 110892282Sobrien if ((p = strchr(word, ':')) != NULL) { 110979968Sobrien gid_t *groups, *ng; 111079968Sobrien int gsize, i, found; 111179968Sobrien 111292282Sobrien if (pw == NULL) 111392282Sobrien continue; /* no match for unknown user */ 111479968Sobrien *p++ = '\0'; 111579968Sobrien groups = NULL; 111679968Sobrien gsize = 16; 111779968Sobrien do { 111879968Sobrien ng = realloc(groups, gsize * sizeof(gid_t)); 111979968Sobrien if (ng == NULL) 112079968Sobrien fatal( 112179968Sobrien "Local resource failure: realloc"); 112279968Sobrien groups = ng; 112379968Sobrien } while (getgrouplist(pw->pw_name, pw->pw_gid, 112479968Sobrien groups, &gsize) == -1); 112579968Sobrien found = 0; 112679968Sobrien for (i = 0; i < gsize; i++) { 112779968Sobrien struct group *g; 112879968Sobrien 112979968Sobrien if ((g = getgrgid(groups[i])) == NULL) 113079968Sobrien continue; 113179968Sobrien if (fnmatch(p, g->gr_name, 0) == 0) { 113279968Sobrien found = 1; 113379968Sobrien break; 113479968Sobrien } 113579968Sobrien } 113679968Sobrien free(groups); 113779968Sobrien if (!found) 113879968Sobrien continue; 113979968Sobrien } 114079968Sobrien 114179968Sobrien /* check against username glob */ 114292282Sobrien if (fnmatch(word, name, 0) != 0) 114379968Sobrien continue; 114479968Sobrien 114579968Sobrien if (perm != NULL && 114679968Sobrien ((strcasecmp(perm, "allow") == 0) || 114779968Sobrien (strcasecmp(perm, "yes") == 0))) 114879968Sobrien retval = 1; 114979968Sobrien else if (perm != NULL && 115079968Sobrien ((strcasecmp(perm, "deny") == 0) || 115179968Sobrien (strcasecmp(perm, "no") == 0))) 115279968Sobrien retval = 0; 115379968Sobrien else 115479968Sobrien retval = !def; 115579968Sobrien if (!EMPTYSTR(class) && retclass != NULL) 1156161770Sobrien *retclass = ftpd_strdup(class); 115779968Sobrien free(buf); 115879968Sobrien break; 115979968Sobrien } 116079968Sobrien (void) fclose(fd); 116179968Sobrien return (retval); 116279968Sobrien} 116379968Sobrien 116479968Sobrien/* 116579968Sobrien * Check if user is allowed by /etc/ftpusers 116679968Sobrien * returns 1 for yes, 0 for no 116779968Sobrien * 116879968Sobrien * NOTE: needs struct passwd *pw setup (for checkuser()) 116979968Sobrien */ 117079968Sobrienstatic int 117179968Sobriencheckaccess(const char *name) 117279968Sobrien{ 117379968Sobrien 1174161770Sobrien return (checkuser(_NAME_FTPUSERS, name, 1, 0, NULL)); 117579968Sobrien} 117679968Sobrien 1177108746Sobrienstatic void 1178161770Sobrienlogin_utmp(const char *line, const char *name, const char *host, 1179161770Sobrien struct sockinet *haddr) 1180108746Sobrien{ 1181108746Sobrien#if defined(SUPPORT_UTMPX) || defined(SUPPORT_UTMP) 1182108746Sobrien struct timeval tv; 1183108746Sobrien (void)gettimeofday(&tv, NULL); 1184108746Sobrien#endif 1185108746Sobrien#ifdef SUPPORT_UTMPX 1186108746Sobrien if (doutmp) { 1187108746Sobrien (void)memset(&utmpx, 0, sizeof(utmpx)); 1188108746Sobrien utmpx.ut_tv = tv; 1189108746Sobrien utmpx.ut_pid = getpid(); 1190108746Sobrien utmpx.ut_id[0] = 'f'; 1191108746Sobrien utmpx.ut_id[1] = 't'; 1192108746Sobrien utmpx.ut_id[2] = 'p'; 1193108746Sobrien utmpx.ut_id[3] = '*'; 1194108746Sobrien utmpx.ut_type = USER_PROCESS; 1195108746Sobrien (void)strncpy(utmpx.ut_name, name, sizeof(utmpx.ut_name)); 1196108746Sobrien (void)strncpy(utmpx.ut_line, line, sizeof(utmpx.ut_line)); 1197108746Sobrien (void)strncpy(utmpx.ut_host, host, sizeof(utmpx.ut_host)); 1198161770Sobrien (void)memcpy(&utmpx.ut_ss, &haddr->si_su, haddr->su_len); 1199133939Sobrien ftpd_loginx(&utmpx); 1200108746Sobrien } 1201108746Sobrien if (dowtmp) 1202161770Sobrien ftpd_logwtmpx(line, name, host, haddr, 0, USER_PROCESS); 1203108746Sobrien#endif 1204108746Sobrien#ifdef SUPPORT_UTMP 1205108746Sobrien if (doutmp) { 1206108746Sobrien (void)memset(&utmp, 0, sizeof(utmp)); 1207108746Sobrien (void)time(&utmp.ut_time); 1208108746Sobrien (void)strncpy(utmp.ut_name, name, sizeof(utmp.ut_name)); 1209108746Sobrien (void)strncpy(utmp.ut_line, line, sizeof(utmp.ut_line)); 1210108746Sobrien (void)strncpy(utmp.ut_host, host, sizeof(utmp.ut_host)); 1211133939Sobrien ftpd_login(&utmp); 1212108746Sobrien } 1213108746Sobrien if (dowtmp) 1214133939Sobrien ftpd_logwtmp(line, name, host); 1215108746Sobrien#endif 1216108746Sobrien} 1217108746Sobrien 1218108746Sobrienstatic void 1219108746Sobrienlogout_utmp(void) 1220108746Sobrien{ 1221108746Sobrien#ifdef SUPPORT_UTMPX 1222161770Sobrien int okwtmpx = dowtmp; 1223108746Sobrien#endif 1224108746Sobrien#ifdef SUPPORT_UTMP 1225161770Sobrien int okwtmp = dowtmp; 1226108746Sobrien#endif 1227161770Sobrien if (logged_in) { 1228108746Sobrien#ifdef SUPPORT_UTMPX 1229161770Sobrien if (doutmp) 1230161770Sobrien okwtmpx &= ftpd_logoutx(ttyline, 0, DEAD_PROCESS); 1231161770Sobrien if (okwtmpx) 1232161770Sobrien ftpd_logwtmpx(ttyline, "", "", NULL, 0, DEAD_PROCESS); 1233108746Sobrien#endif 1234108746Sobrien#ifdef SUPPORT_UTMP 1235161770Sobrien if (doutmp) 1236161770Sobrien okwtmp &= ftpd_logout(ttyline); 1237161770Sobrien if (okwtmp) 1238133939Sobrien ftpd_logwtmp(ttyline, "", ""); 1239108746Sobrien#endif 1240108746Sobrien } 1241108746Sobrien} 1242108746Sobrien 124379968Sobrien/* 124479968Sobrien * Terminate login as previous user (if any), resetting state; 124579968Sobrien * used when USER command is given or login fails. 124679968Sobrien */ 124779968Sobrienstatic void 124879968Sobrienend_login(void) 124979968Sobrien{ 1250108886Sobrien#ifdef USE_PAM 1251108886Sobrien int e; 1252108886Sobrien#endif 1253108746Sobrien logout_utmp(); 125479968Sobrien show_chdir_messages(-1); /* flush chdir cache */ 125579968Sobrien if (pw != NULL && pw->pw_passwd != NULL) 125679968Sobrien memset(pw->pw_passwd, 0, strlen(pw->pw_passwd)); 125779968Sobrien pw = NULL; 125879968Sobrien logged_in = 0; 125992282Sobrien askpasswd = 0; 126092282Sobrien permitted = 0; 126179968Sobrien quietmessages = 0; 126279968Sobrien gidcount = 0; 126379968Sobrien curclass.type = CLASS_REAL; 126479968Sobrien (void) seteuid((uid_t)0); 1265161770Sobrien#ifdef LOGIN_CAP 1266223702Strasz setusercontext(NULL, getpwuid(0), 0, LOGIN_SETALL & ~(LOGIN_SETLOGIN | 1267223702Strasz LOGIN_SETUSER | LOGIN_SETGROUP | LOGIN_SETPATH | 1268223702Strasz LOGIN_SETENV)); 1269133939Sobrien#endif 1270108886Sobrien#ifdef USE_PAM 1271161770Sobrien if (pamh) { 1272161770Sobrien if ((e = pam_setcred(pamh, PAM_DELETE_CRED)) != PAM_SUCCESS) 1273161770Sobrien syslog(LOG_ERR, "pam_setcred: %s", 1274161770Sobrien pam_strerror(pamh, e)); 1275161770Sobrien if ((e = pam_close_session(pamh,0)) != PAM_SUCCESS) 1276161770Sobrien syslog(LOG_ERR, "pam_close_session: %s", 1277161770Sobrien pam_strerror(pamh, e)); 1278161770Sobrien if ((e = pam_end(pamh, e)) != PAM_SUCCESS) 1279161770Sobrien syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e)); 1280161770Sobrien pamh = NULL; 1281161770Sobrien } 1282108886Sobrien#endif 128379968Sobrien} 128479968Sobrien 128579968Sobrienvoid 128679968Sobrienpass(const char *passwd) 128779968Sobrien{ 1288161770Sobrien int rval; 1289161770Sobrien char root[MAXPATHLEN]; 1290161770Sobrien#ifdef LOGIN_CAP 1291161770Sobrien login_cap_t *lc = NULL; 1292133939Sobrien#endif 1293108886Sobrien#ifdef USE_PAM 1294161770Sobrien int e; 1295108886Sobrien#endif 129679968Sobrien if (logged_in || askpasswd == 0) { 129779968Sobrien reply(503, "Login with USER first."); 129879968Sobrien return; 129979968Sobrien } 130079968Sobrien askpasswd = 0; 130179968Sobrien if (curclass.type != CLASS_GUEST) { 130279968Sobrien /* "ftp" is the only account allowed with no password */ 130379968Sobrien if (pw == NULL) { 130479968Sobrien rval = 1; /* failure below */ 130579968Sobrien goto skip; 130679968Sobrien } 1307161770Sobrien#ifdef USE_PAM 1308161770Sobrien rval = auth_pam(&pw, passwd); 1309161770Sobrien#ifdef notdef 1310161770Sobrien /* If PAM fails, we proceed with other authentications */ 1311161770Sobrien if (rval >= 0) { 1312161770Sobrien#ifdef USE_OPIE 1313161770Sobrien opieunlock(); 1314161770Sobrien#endif 1315161770Sobrien goto skip; 1316161770Sobrien } 1317161770Sobrien#else 1318161770Sobrien /* If PAM fails, that's it */ 1319161770Sobrien goto skip; 1320161770Sobrien#endif 1321161770Sobrien#endif 132279968Sobrien#if defined(KERBEROS) 132379968Sobrien if (klogin(pw, "", hostname, (char *)passwd) == 0) { 132479968Sobrien rval = 0; 132579968Sobrien goto skip; 132679968Sobrien } 132779968Sobrien#endif 132879968Sobrien#if defined(KERBEROS5) 132979968Sobrien if (k5login(pw, "", hostname, (char *)passwd) == 0) { 133079968Sobrien rval = 0; 133179968Sobrien goto skip; 133279968Sobrien } 133379968Sobrien#endif 133479968Sobrien#ifdef SKEY 133579968Sobrien if (skey_haskey(pw->pw_name) == 0) { 133679968Sobrien char *p; 133779968Sobrien int r; 133879968Sobrien 1339161770Sobrien p = ftpd_strdup(passwd); 134079968Sobrien r = skey_passcheck(pw->pw_name, p); 134179968Sobrien free(p); 134279968Sobrien if (r != -1) { 134379968Sobrien rval = 0; 134479968Sobrien goto skip; 134579968Sobrien } 134679968Sobrien } 134779968Sobrien#endif 1348108886Sobrien#ifdef USE_OPIE 1349108886Sobrien if (opieverify(&opiedata, (char *)passwd) == 0) { 1350108886Sobrien /* OPIE says ok, check expire time */ 1351108886Sobrien if (pw->pw_expire && time(NULL) >= pw->pw_expire) 1352108886Sobrien rval = 2; 1353108886Sobrien else 1354108886Sobrien rval = 0; 1355108886Sobrien goto skip; 1356108886Sobrien } 1357108886Sobrien#endif 135879968Sobrien if (!sflag) 135979968Sobrien rval = checkpassword(pw, passwd); 136079968Sobrien else 136179968Sobrien rval = 1; 136279968Sobrien 136379968Sobrien skip: 136479968Sobrien 136579968Sobrien /* 136679968Sobrien * If rval > 0, the user failed the authentication check 136779968Sobrien * above. If rval == 0, either Kerberos or local 136879968Sobrien * authentication succeeded. 136979968Sobrien */ 137079968Sobrien if (rval) { 137179968Sobrien reply(530, "%s", rval == 2 ? "Password expired." : 137279968Sobrien "Login incorrect."); 137379968Sobrien if (logging) { 137479968Sobrien syslog(LOG_NOTICE, 137579968Sobrien "FTP LOGIN FAILED FROM %s", remotehost); 137679968Sobrien syslog(LOG_AUTHPRIV | LOG_NOTICE, 137779968Sobrien "FTP LOGIN FAILED FROM %s, %s", 137879968Sobrien remotehost, curname); 137979968Sobrien } 138079968Sobrien pw = NULL; 138179968Sobrien if (login_attempts++ >= 5) { 138279968Sobrien syslog(LOG_NOTICE, 138379968Sobrien "repeated login failures from %s", 138479968Sobrien remotehost); 138579968Sobrien exit(0); 138679968Sobrien } 138779968Sobrien return; 138879968Sobrien } 138979968Sobrien } 139079968Sobrien 139192282Sobrien /* password ok; check if anything else prevents login */ 139292282Sobrien if (! permitted) { 139379968Sobrien reply(530, "User %s may not use FTP.", pw->pw_name); 139479968Sobrien if (logging) 139579968Sobrien syslog(LOG_NOTICE, "FTP LOGIN REFUSED FROM %s, %s", 139679968Sobrien remotehost, pw->pw_name); 139779968Sobrien goto bad; 139879968Sobrien } 139979968Sobrien 140079968Sobrien login_attempts = 0; /* this time successful */ 140179968Sobrien if (setegid((gid_t)pw->pw_gid) < 0) { 140279968Sobrien reply(550, "Can't set gid."); 140379968Sobrien goto bad; 140479968Sobrien } 1405161770Sobrien#ifdef LOGIN_CAP 1406133939Sobrien if ((lc = login_getpwclass(pw)) != NULL) { 1407161770Sobrien#ifdef notyet 1408161770Sobrien char remote_ip[NI_MAXHOST]; 1409133939Sobrien 1410161770Sobrien if (getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len, 1411161770Sobrien remote_ip, sizeof(remote_ip) - 1, NULL, 0, 1412161770Sobrien NI_NUMERICHOST)) 1413161770Sobrien *remote_ip = 0; 1414133939Sobrien remote_ip[sizeof(remote_ip) - 1] = 0; 1415133939Sobrien if (!auth_hostok(lc, remotehost, remote_ip)) { 1416133939Sobrien syslog(LOG_INFO|LOG_AUTH, 1417133939Sobrien "FTP LOGIN FAILED (HOST) as %s: permission denied.", 1418133939Sobrien pw->pw_name); 1419161770Sobrien reply(530, "Permission denied."); 1420133939Sobrien pw = NULL; 1421133939Sobrien return; 1422133939Sobrien } 1423133939Sobrien if (!auth_timeok(lc, time(NULL))) { 1424161770Sobrien reply(530, "Login not available right now."); 1425133939Sobrien pw = NULL; 1426133939Sobrien return; 1427133939Sobrien } 1428161770Sobrien#endif 1429133939Sobrien } 1430161770Sobrien setsid(); 1431223702Strasz setusercontext(lc, pw, 0, LOGIN_SETALL & 1432223702Strasz ~(LOGIN_SETUSER | LOGIN_SETPATH | LOGIN_SETENV)); 1433161770Sobrien#else 143479968Sobrien (void) initgroups(pw->pw_name, pw->pw_gid); 143579968Sobrien /* cache groups for cmds.c::matchgroup() */ 1436161770Sobrien#endif 1437108886Sobrien#ifdef USE_PAM 1438133939Sobrien if (pamh) { 1439108886Sobrien if ((e = pam_open_session(pamh, 0)) != PAM_SUCCESS) { 1440108886Sobrien syslog(LOG_ERR, "pam_open_session: %s", 1441108886Sobrien pam_strerror(pamh, e)); 1442161770Sobrien } else if ((e = pam_setcred(pamh, PAM_ESTABLISH_CRED)) 1443161770Sobrien != PAM_SUCCESS) { 1444108886Sobrien syslog(LOG_ERR, "pam_setcred: %s", 1445108886Sobrien pam_strerror(pamh, e)); 1446108886Sobrien } 1447108886Sobrien } 1448108886Sobrien#endif 1449161770Sobrien gidcount = getgroups(0, NULL); 1450161770Sobrien if (gidlist) 1451161770Sobrien free(gidlist); 1452161770Sobrien gidlist = malloc(gidcount * sizeof *gidlist); 1453161770Sobrien gidcount = getgroups(gidcount, gidlist); 1454108886Sobrien 1455108746Sobrien /* open utmp/wtmp before chroot */ 1456161770Sobrien login_utmp(ttyline, pw->pw_name, remotehost, &his_addr); 145779968Sobrien 145879968Sobrien logged_in = 1; 145979968Sobrien 146079968Sobrien connections = 1; 146179968Sobrien if (dopidfile) 146279968Sobrien count_users(); 146379968Sobrien if (curclass.limit != -1 && connections > curclass.limit) { 146479968Sobrien if (! EMPTYSTR(curclass.limitfile)) 146579968Sobrien (void)display_file(conffilename(curclass.limitfile), 146679968Sobrien 530); 146779968Sobrien reply(530, 1468108746Sobrien "User %s access denied, connection limit of " LLF 1469108746Sobrien " reached.", 1470108746Sobrien pw->pw_name, (LLT)curclass.limit); 147179968Sobrien syslog(LOG_NOTICE, 1472108746Sobrien "Maximum connection limit of " LLF 1473108746Sobrien " for class %s reached, login refused for %s", 1474108746Sobrien (LLT)curclass.limit, curclass.classname, pw->pw_name); 147579968Sobrien goto bad; 147679968Sobrien } 147779968Sobrien 147879968Sobrien homedir[0] = '/'; 147979968Sobrien switch (curclass.type) { 148079968Sobrien case CLASS_GUEST: 148179968Sobrien /* 148279968Sobrien * We MUST do a chdir() after the chroot. Otherwise 148379968Sobrien * the old current directory will be accessible as "." 148479968Sobrien * outside the new root! 148579968Sobrien */ 148679968Sobrien format_path(root, 148779968Sobrien curclass.chroot ? curclass.chroot : 148879968Sobrien anondir ? anondir : 148979968Sobrien pw->pw_dir); 149079968Sobrien format_path(homedir, 149179968Sobrien curclass.homedir ? curclass.homedir : 149279968Sobrien "/"); 149379968Sobrien if (EMPTYSTR(homedir)) 149479968Sobrien homedir[0] = '/'; 149579968Sobrien if (EMPTYSTR(root) || chroot(root) < 0) { 149679968Sobrien syslog(LOG_NOTICE, 149779968Sobrien "GUEST user %s: can't chroot to %s: %m", 149879968Sobrien pw->pw_name, root); 149979968Sobrien goto bad_guest; 150079968Sobrien } 150179968Sobrien if (chdir(homedir) < 0) { 150279968Sobrien syslog(LOG_NOTICE, 150379968Sobrien "GUEST user %s: can't chdir to %s: %m", 150479968Sobrien pw->pw_name, homedir); 150579968Sobrien bad_guest: 150679968Sobrien reply(550, "Can't set guest privileges."); 150779968Sobrien goto bad; 150879968Sobrien } 150979968Sobrien break; 151079968Sobrien case CLASS_CHROOT: 151179968Sobrien format_path(root, 151279968Sobrien curclass.chroot ? curclass.chroot : 151379968Sobrien pw->pw_dir); 151479968Sobrien format_path(homedir, 151579968Sobrien curclass.homedir ? curclass.homedir : 151679968Sobrien "/"); 151779968Sobrien if (EMPTYSTR(homedir)) 151879968Sobrien homedir[0] = '/'; 151979968Sobrien if (EMPTYSTR(root) || chroot(root) < 0) { 152079968Sobrien syslog(LOG_NOTICE, 152179968Sobrien "CHROOT user %s: can't chroot to %s: %m", 152279968Sobrien pw->pw_name, root); 152379968Sobrien goto bad_chroot; 152479968Sobrien } 152579968Sobrien if (chdir(homedir) < 0) { 152679968Sobrien syslog(LOG_NOTICE, 152779968Sobrien "CHROOT user %s: can't chdir to %s: %m", 152879968Sobrien pw->pw_name, homedir); 152979968Sobrien bad_chroot: 153079968Sobrien reply(550, "Can't change root."); 153179968Sobrien goto bad; 153279968Sobrien } 153379968Sobrien break; 153479968Sobrien case CLASS_REAL: 1535161770Sobrien /* only chroot REAL if explicitly requested */ 153679968Sobrien if (! EMPTYSTR(curclass.chroot)) { 153779968Sobrien format_path(root, curclass.chroot); 153879968Sobrien if (EMPTYSTR(root) || chroot(root) < 0) { 153979968Sobrien syslog(LOG_NOTICE, 154079968Sobrien "REAL user %s: can't chroot to %s: %m", 154179968Sobrien pw->pw_name, root); 154279968Sobrien goto bad_chroot; 154379968Sobrien } 154479968Sobrien } 154579968Sobrien format_path(homedir, 154679968Sobrien curclass.homedir ? curclass.homedir : 154779968Sobrien pw->pw_dir); 154879968Sobrien if (EMPTYSTR(homedir) || chdir(homedir) < 0) { 154979968Sobrien if (chdir("/") < 0) { 155079968Sobrien syslog(LOG_NOTICE, 155179968Sobrien "REAL user %s: can't chdir to %s: %m", 155279968Sobrien pw->pw_name, 155379968Sobrien !EMPTYSTR(homedir) ? homedir : "/"); 155479968Sobrien reply(530, 155579968Sobrien "User %s: can't change directory to %s.", 155679968Sobrien pw->pw_name, 155779968Sobrien !EMPTYSTR(homedir) ? homedir : "/"); 155879968Sobrien goto bad; 155979968Sobrien } else { 156079968Sobrien reply(-230, 156179968Sobrien "No directory! Logging in with home=/"); 156279968Sobrien homedir[0] = '/'; 156379968Sobrien } 156479968Sobrien } 156579968Sobrien break; 156679968Sobrien } 1567161770Sobrien#ifndef LOGIN_CAP 1568133939Sobrien setsid(); 156979968Sobrien setlogin(pw->pw_name); 1570161770Sobrien#endif 157179968Sobrien if (dropprivs || 157279968Sobrien (curclass.type != CLASS_REAL && 157379968Sobrien ntohs(ctrl_addr.su_port) > IPPORT_RESERVED + 1)) { 157479968Sobrien dropprivs++; 157579968Sobrien if (setgid((gid_t)pw->pw_gid) < 0) { 157679968Sobrien reply(550, "Can't set gid."); 157779968Sobrien goto bad; 157879968Sobrien } 157979968Sobrien if (setuid((uid_t)pw->pw_uid) < 0) { 158079968Sobrien reply(550, "Can't set uid."); 158179968Sobrien goto bad; 158279968Sobrien } 158379968Sobrien } else { 158479968Sobrien if (seteuid((uid_t)pw->pw_uid) < 0) { 158579968Sobrien reply(550, "Can't set uid."); 158679968Sobrien goto bad; 158779968Sobrien } 158879968Sobrien } 1589108746Sobrien setenv("HOME", homedir, 1); 159079968Sobrien 159179968Sobrien if (curclass.type == CLASS_GUEST && passwd[0] == '-') 159279968Sobrien quietmessages = 1; 159379968Sobrien 159479968Sobrien /* 159579968Sobrien * Display a login message, if it exists. 159679968Sobrien * N.B. reply(230,) must follow the message. 159779968Sobrien */ 159892282Sobrien if (! EMPTYSTR(curclass.motd)) 159992282Sobrien (void)display_file(conffilename(curclass.motd), 230); 160079968Sobrien show_chdir_messages(230); 160179968Sobrien if (curclass.type == CLASS_GUEST) { 160279968Sobrien char *p; 160379968Sobrien 160479968Sobrien reply(230, "Guest login ok, access restrictions apply."); 160579968Sobrien#if HAVE_SETPROCTITLE 160679968Sobrien snprintf(proctitle, sizeof(proctitle), 160792282Sobrien "%s: anonymous/%s", remotehost, passwd); 160879968Sobrien setproctitle("%s", proctitle); 160979968Sobrien#endif /* HAVE_SETPROCTITLE */ 161079968Sobrien if (logging) 161179968Sobrien syslog(LOG_INFO, 161279968Sobrien "ANONYMOUS FTP LOGIN FROM %s, %s (class: %s, type: %s)", 161379968Sobrien remotehost, passwd, 161479968Sobrien curclass.classname, CURCLASSTYPE); 161579968Sobrien /* store guest password reply into pw_passwd */ 1616161770Sobrien REASSIGN(pw->pw_passwd, ftpd_strdup(passwd)); 161779968Sobrien for (p = pw->pw_passwd; *p; p++) 1618161770Sobrien if (!isgraph((unsigned char)*p)) 161979968Sobrien *p = '_'; 162079968Sobrien } else { 162179968Sobrien reply(230, "User %s logged in.", pw->pw_name); 162279968Sobrien#if HAVE_SETPROCTITLE 162379968Sobrien snprintf(proctitle, sizeof(proctitle), 162479968Sobrien "%s: %s", remotehost, pw->pw_name); 162579968Sobrien setproctitle("%s", proctitle); 162679968Sobrien#endif /* HAVE_SETPROCTITLE */ 162779968Sobrien if (logging) 162879968Sobrien syslog(LOG_INFO, 162979968Sobrien "FTP LOGIN FROM %s as %s (class: %s, type: %s)", 163079968Sobrien remotehost, pw->pw_name, 163179968Sobrien curclass.classname, CURCLASSTYPE); 163279968Sobrien } 1633161770Sobrien (void) umask(curclass.umask); 1634161770Sobrien#ifdef LOGIN_CAP 1635133939Sobrien login_close(lc); 1636133939Sobrien#endif 163792282Sobrien return; 163879968Sobrien 163979968Sobrien bad: 1640161770Sobrien#ifdef LOGIN_CAP 1641133939Sobrien login_close(lc); 1642133939Sobrien#endif 1643161770Sobrien /* Forget all about it... */ 164479968Sobrien end_login(); 164579968Sobrien} 164679968Sobrien 164779968Sobrienvoid 164879968Sobrienretrieve(char *argv[], const char *name) 164979968Sobrien{ 165079968Sobrien FILE *fin, *dout; 165179968Sobrien struct stat st; 165279968Sobrien int (*closefunc)(FILE *) = NULL; 1653108746Sobrien int dolog, sendrv, closerv, stderrfd, isconversion, isdata, isls; 165479968Sobrien struct timeval start, finish, td, *tdp; 1655108746Sobrien struct rusage rusage_before, rusage_after; 165679968Sobrien const char *dispname; 165792282Sobrien char *error; 165879968Sobrien 165979968Sobrien sendrv = closerv = stderrfd = -1; 1660108746Sobrien isconversion = isdata = isls = dolog = 0; 166179968Sobrien tdp = NULL; 166279968Sobrien dispname = name; 166379968Sobrien fin = dout = NULL; 166492282Sobrien error = NULL; 166579968Sobrien if (argv == NULL) { /* if not running a command ... */ 1666108746Sobrien dolog = 1; 166779968Sobrien isdata = 1; 166879968Sobrien fin = fopen(name, "r"); 166979968Sobrien closefunc = fclose; 167079968Sobrien if (fin == NULL) /* doesn't exist?; try a conversion */ 167179968Sobrien argv = do_conversion(name); 167279968Sobrien if (argv != NULL) { 167379968Sobrien isconversion++; 167479968Sobrien syslog(LOG_DEBUG, "get command: '%s' on '%s'", 167579968Sobrien argv[0], name); 167679968Sobrien } 167779968Sobrien } 167879968Sobrien if (argv != NULL) { 167979968Sobrien char temp[MAXPATHLEN]; 168079968Sobrien 168179968Sobrien if (strcmp(argv[0], INTERNAL_LS) == 0) { 168279968Sobrien isls = 1; 168379968Sobrien stderrfd = -1; 168479968Sobrien } else { 168579968Sobrien (void)snprintf(temp, sizeof(temp), "%s", TMPFILE); 168679968Sobrien stderrfd = mkstemp(temp); 168779968Sobrien if (stderrfd != -1) 168879968Sobrien (void)unlink(temp); 168979968Sobrien } 169079968Sobrien dispname = argv[0]; 169179968Sobrien fin = ftpd_popen(argv, "r", stderrfd); 169279968Sobrien closefunc = ftpd_pclose; 169379968Sobrien st.st_size = -1; 169479968Sobrien st.st_blksize = BUFSIZ; 169579968Sobrien } 169679968Sobrien if (fin == NULL) { 169779968Sobrien if (errno != 0) { 169879968Sobrien perror_reply(550, dispname); 1699108746Sobrien if (dolog) 170079968Sobrien logxfer("get", -1, name, NULL, NULL, 170179968Sobrien strerror(errno)); 170279968Sobrien } 170379968Sobrien goto cleanupretrieve; 170479968Sobrien } 170579968Sobrien byte_count = -1; 170679968Sobrien if (argv == NULL 170779968Sobrien && (fstat(fileno(fin), &st) < 0 || !S_ISREG(st.st_mode))) { 170892282Sobrien error = "Not a plain file"; 170992282Sobrien reply(550, "%s: %s.", dispname, error); 171079968Sobrien goto done; 171179968Sobrien } 171279968Sobrien if (restart_point) { 171379968Sobrien if (type == TYPE_A) { 171479968Sobrien off_t i; 171579968Sobrien int c; 171679968Sobrien 171779968Sobrien for (i = 0; i < restart_point; i++) { 171879968Sobrien if ((c=getc(fin)) == EOF) { 171992282Sobrien error = strerror(errno); 172079968Sobrien perror_reply(550, dispname); 172179968Sobrien goto done; 172279968Sobrien } 172379968Sobrien if (c == '\n') 172479968Sobrien i++; 172579968Sobrien } 172679968Sobrien } else if (lseek(fileno(fin), restart_point, SEEK_SET) < 0) { 172792282Sobrien error = strerror(errno); 172879968Sobrien perror_reply(550, dispname); 172979968Sobrien goto done; 173079968Sobrien } 173179968Sobrien } 173279968Sobrien dout = dataconn(dispname, st.st_size, "w"); 173379968Sobrien if (dout == NULL) 173479968Sobrien goto done; 173579968Sobrien 1736108746Sobrien (void)getrusage(RUSAGE_SELF, &rusage_before); 173779968Sobrien (void)gettimeofday(&start, NULL); 1738108746Sobrien sendrv = send_data(fin, dout, &st, isdata); 173979968Sobrien (void)gettimeofday(&finish, NULL); 1740108746Sobrien (void)getrusage(RUSAGE_SELF, &rusage_after); 174192282Sobrien closedataconn(dout); /* close now to affect timing stats */ 174279968Sobrien timersub(&finish, &start, &td); 174379968Sobrien tdp = &td; 174479968Sobrien done: 1745108746Sobrien if (dolog) { 174692282Sobrien logxfer("get", byte_count, name, NULL, tdp, error); 1747108746Sobrien if (tdp != NULL) 1748108746Sobrien logrusage(&rusage_before, &rusage_after); 1749108746Sobrien } 175079968Sobrien closerv = (*closefunc)(fin); 175179968Sobrien if (sendrv == 0) { 175292282Sobrien FILE *errf; 175379968Sobrien struct stat sb; 175479968Sobrien 175579968Sobrien if (!isls && argv != NULL && closerv != 0) { 175679968Sobrien reply(-226, 175779968Sobrien "Command returned an exit status of %d", 175879968Sobrien closerv); 175979968Sobrien if (isconversion) 176079968Sobrien syslog(LOG_WARNING, 176179968Sobrien "retrieve command: '%s' returned %d", 176279968Sobrien argv[0], closerv); 176379968Sobrien } 176479968Sobrien if (!isls && argv != NULL && stderrfd != -1 && 176579968Sobrien (fstat(stderrfd, &sb) == 0) && sb.st_size > 0 && 176692282Sobrien ((errf = fdopen(stderrfd, "r")) != NULL)) { 176779968Sobrien char *cp, line[LINE_MAX]; 176879968Sobrien 176979968Sobrien reply(-226, "Command error messages:"); 177092282Sobrien rewind(errf); 177192282Sobrien while (fgets(line, sizeof(line), errf) != NULL) { 177279968Sobrien if ((cp = strchr(line, '\n')) != NULL) 177379968Sobrien *cp = '\0'; 177479968Sobrien reply(0, " %s", line); 177579968Sobrien } 177679968Sobrien (void) fflush(stdout); 177792282Sobrien (void) fclose(errf); 177879968Sobrien /* a reply(226,) must follow */ 177979968Sobrien } 178079968Sobrien reply(226, "Transfer complete."); 178179968Sobrien } 178279968Sobrien cleanupretrieve: 178379968Sobrien if (stderrfd != -1) 178479968Sobrien (void)close(stderrfd); 178579968Sobrien if (isconversion) 178679968Sobrien free(argv); 178779968Sobrien} 178879968Sobrien 178979968Sobrienvoid 179092282Sobrienstore(const char *name, const char *fmode, int unique) 179179968Sobrien{ 179279968Sobrien FILE *fout, *din; 179379968Sobrien struct stat st; 179479968Sobrien int (*closefunc)(FILE *); 179579968Sobrien struct timeval start, finish, td, *tdp; 179692282Sobrien char *desc, *error; 179779968Sobrien 179879968Sobrien din = NULL; 179992282Sobrien desc = (*fmode == 'w') ? "put" : "append"; 180092282Sobrien error = NULL; 180179968Sobrien if (unique && stat(name, &st) == 0 && 180279968Sobrien (name = gunique(name)) == NULL) { 180379968Sobrien logxfer(desc, -1, name, NULL, NULL, 180479968Sobrien "cannot create unique file"); 180579968Sobrien goto cleanupstore; 180679968Sobrien } 180779968Sobrien 180879968Sobrien if (restart_point) 180992282Sobrien fmode = "r+"; 181092282Sobrien fout = fopen(name, fmode); 181179968Sobrien closefunc = fclose; 181279968Sobrien tdp = NULL; 181379968Sobrien if (fout == NULL) { 181479968Sobrien perror_reply(553, name); 181579968Sobrien logxfer(desc, -1, name, NULL, NULL, strerror(errno)); 181679968Sobrien goto cleanupstore; 181779968Sobrien } 181879968Sobrien byte_count = -1; 181979968Sobrien if (restart_point) { 182079968Sobrien if (type == TYPE_A) { 182179968Sobrien off_t i; 182279968Sobrien int c; 182379968Sobrien 182479968Sobrien for (i = 0; i < restart_point; i++) { 182579968Sobrien if ((c=getc(fout)) == EOF) { 182692282Sobrien error = strerror(errno); 182779968Sobrien perror_reply(550, name); 182879968Sobrien goto done; 182979968Sobrien } 183079968Sobrien if (c == '\n') 183179968Sobrien i++; 183279968Sobrien } 183379968Sobrien /* 183479968Sobrien * We must do this seek to "current" position 183579968Sobrien * because we are changing from reading to 183679968Sobrien * writing. 183779968Sobrien */ 183879968Sobrien if (fseek(fout, 0L, SEEK_CUR) < 0) { 183992282Sobrien error = strerror(errno); 184079968Sobrien perror_reply(550, name); 184179968Sobrien goto done; 184279968Sobrien } 184379968Sobrien } else if (lseek(fileno(fout), restart_point, SEEK_SET) < 0) { 184492282Sobrien error = strerror(errno); 184579968Sobrien perror_reply(550, name); 184679968Sobrien goto done; 184779968Sobrien } 184879968Sobrien } 184979968Sobrien din = dataconn(name, (off_t)-1, "r"); 185079968Sobrien if (din == NULL) 185179968Sobrien goto done; 185279968Sobrien (void)gettimeofday(&start, NULL); 185379968Sobrien if (receive_data(din, fout) == 0) { 185479968Sobrien if (unique) 185579968Sobrien reply(226, "Transfer complete (unique file name:%s).", 185679968Sobrien name); 185779968Sobrien else 185879968Sobrien reply(226, "Transfer complete."); 185979968Sobrien } 186079968Sobrien (void)gettimeofday(&finish, NULL); 186192282Sobrien closedataconn(din); /* close now to affect timing stats */ 186279968Sobrien timersub(&finish, &start, &td); 186379968Sobrien tdp = &td; 186479968Sobrien done: 186592282Sobrien logxfer(desc, byte_count, name, NULL, tdp, error); 186679968Sobrien (*closefunc)(fout); 186779968Sobrien cleanupstore: 186892282Sobrien ; 186979968Sobrien} 187079968Sobrien 187179968Sobrienstatic FILE * 187292282Sobriengetdatasock(const char *fmode) 187379968Sobrien{ 187479968Sobrien int on, s, t, tries; 187579968Sobrien in_port_t port; 187679968Sobrien 187779968Sobrien on = 1; 187879968Sobrien if (data >= 0) 187992282Sobrien return (fdopen(data, fmode)); 188079968Sobrien if (! dropprivs) 188179968Sobrien (void) seteuid((uid_t)0); 188279968Sobrien s = socket(ctrl_addr.su_family, SOCK_STREAM, 0); 188379968Sobrien if (s < 0) 188479968Sobrien goto bad; 188579968Sobrien if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, 188679968Sobrien (char *) &on, sizeof(on)) < 0) 188779968Sobrien goto bad; 188879968Sobrien if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, 188979968Sobrien (char *) &on, sizeof(on)) < 0) 189079968Sobrien goto bad; 189179968Sobrien /* anchor socket to avoid multi-homing problems */ 189279968Sobrien data_source = ctrl_addr; 189379968Sobrien /* 189479968Sobrien * By default source port for PORT connctions is 189579968Sobrien * ctrlport-1 (see RFC959 section 5.2). 189679968Sobrien * However, if privs have been dropped and that 189779968Sobrien * would be < IPPORT_RESERVED, use a random port 189879968Sobrien * instead. 189979968Sobrien */ 190079968Sobrien if (dataport) 190179968Sobrien port = dataport; 190279968Sobrien else 190379968Sobrien port = ntohs(ctrl_addr.su_port) - 1; 190479968Sobrien if (dropprivs && port < IPPORT_RESERVED) 190579968Sobrien port = 0; /* use random port */ 190679968Sobrien data_source.su_port = htons(port); 190779968Sobrien 190879968Sobrien for (tries = 1; ; tries++) { 190979968Sobrien if (bind(s, (struct sockaddr *)&data_source.si_su, 191079968Sobrien data_source.su_len) >= 0) 191179968Sobrien break; 191279968Sobrien if (errno != EADDRINUSE || tries > 10) 191379968Sobrien goto bad; 191479968Sobrien sleep(tries); 191579968Sobrien } 191679968Sobrien if (! dropprivs) 191779968Sobrien (void) seteuid((uid_t)pw->pw_uid); 191879968Sobrien#ifdef IP_TOS 191979968Sobrien if (!mapped && ctrl_addr.su_family == AF_INET) { 192079968Sobrien on = IPTOS_THROUGHPUT; 192179968Sobrien if (setsockopt(s, IPPROTO_IP, IP_TOS, (char *)&on, 192279968Sobrien sizeof(int)) < 0) 192379968Sobrien syslog(LOG_WARNING, "setsockopt (IP_TOS): %m"); 192479968Sobrien } 192579968Sobrien#endif 192692282Sobrien return (fdopen(s, fmode)); 192779968Sobrien bad: 192879968Sobrien /* Return the real value of errno (close may change it) */ 192979968Sobrien t = errno; 193079968Sobrien if (! dropprivs) 193179968Sobrien (void) seteuid((uid_t)pw->pw_uid); 193279968Sobrien (void) close(s); 193379968Sobrien errno = t; 193479968Sobrien return (NULL); 193579968Sobrien} 193679968Sobrien 193779968SobrienFILE * 193892282Sobriendataconn(const char *name, off_t size, const char *fmode) 193979968Sobrien{ 194079968Sobrien char sizebuf[32]; 194179968Sobrien FILE *file; 1942110245Sobrien int retry, tos, keepalive, conerrno; 194379968Sobrien 194479968Sobrien file_size = size; 194579968Sobrien byte_count = 0; 194679968Sobrien if (size != (off_t) -1) 194779968Sobrien (void)snprintf(sizebuf, sizeof(sizebuf), " (" LLF " byte%s)", 194879968Sobrien (LLT)size, PLURAL(size)); 194979968Sobrien else 195079968Sobrien sizebuf[0] = '\0'; 195179968Sobrien if (pdata >= 0) { 195279968Sobrien struct sockinet from; 1953161770Sobrien int s; 1954161770Sobrien socklen_t fromlen = sizeof(from.su_len); 195579968Sobrien 195679968Sobrien (void) alarm(curclass.timeout); 195779968Sobrien s = accept(pdata, (struct sockaddr *)&from.si_su, &fromlen); 195879968Sobrien (void) alarm(0); 195979968Sobrien if (s < 0) { 196079968Sobrien reply(425, "Can't open data connection."); 196179968Sobrien (void) close(pdata); 196279968Sobrien pdata = -1; 196379968Sobrien return (NULL); 196479968Sobrien } 196579968Sobrien (void) close(pdata); 196679968Sobrien pdata = s; 196779968Sobrien switch (from.su_family) { 196879968Sobrien case AF_INET: 196979968Sobrien#ifdef IP_TOS 197079968Sobrien if (!mapped) { 197179968Sobrien tos = IPTOS_THROUGHPUT; 197279968Sobrien (void) setsockopt(s, IPPROTO_IP, IP_TOS, 197379968Sobrien (char *)&tos, sizeof(int)); 197479968Sobrien } 197579968Sobrien break; 197679968Sobrien#endif 197779968Sobrien } 197879968Sobrien /* Set keepalives on the socket to detect dropped conns. */ 197979968Sobrien#ifdef SO_KEEPALIVE 198079968Sobrien keepalive = 1; 198179968Sobrien (void) setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, 198279968Sobrien (char *)&keepalive, sizeof(int)); 198379968Sobrien#endif 198479968Sobrien reply(150, "Opening %s mode data connection for '%s'%s.", 198579968Sobrien type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf); 198692282Sobrien return (fdopen(pdata, fmode)); 198779968Sobrien } 198879968Sobrien if (data >= 0) { 198979968Sobrien reply(125, "Using existing data connection for '%s'%s.", 199079968Sobrien name, sizebuf); 199179968Sobrien usedefault = 1; 199292282Sobrien return (fdopen(data, fmode)); 199379968Sobrien } 199479968Sobrien if (usedefault) 199579968Sobrien data_dest = his_addr; 199679968Sobrien usedefault = 1; 1997110245Sobrien retry = conerrno = 0; 1998110245Sobrien do { 1999110245Sobrien file = getdatasock(fmode); 2000110245Sobrien if (file == NULL) { 2001110245Sobrien char hbuf[NI_MAXHOST]; 2002110245Sobrien char pbuf[NI_MAXSERV]; 200379968Sobrien 2004110245Sobrien if (getnameinfo((struct sockaddr *)&data_source.si_su, 2005110245Sobrien data_source.su_len, hbuf, sizeof(hbuf), pbuf, 2006110245Sobrien sizeof(pbuf), NI_NUMERICHOST | NI_NUMERICSERV)) 2007110245Sobrien strlcpy(hbuf, "?", sizeof(hbuf)); 2008110245Sobrien reply(425, "Can't create data socket (%s,%s): %s.", 2009110245Sobrien hbuf, pbuf, strerror(errno)); 2010110245Sobrien return (NULL); 2011110245Sobrien } 2012110245Sobrien data = fileno(file); 2013110245Sobrien conerrno = 0; 2014110245Sobrien if (connect(data, (struct sockaddr *)&data_dest.si_su, 2015110245Sobrien data_dest.su_len) == 0) 2016110245Sobrien break; 2017110245Sobrien conerrno = errno; 2018110245Sobrien (void) fclose(file); 2019161770Sobrien file = NULL; 2020110245Sobrien data = -1; 2021110245Sobrien if (conerrno == EADDRINUSE) { 202279968Sobrien sleep((unsigned) swaitint); 202379968Sobrien retry += swaitint; 2024110245Sobrien } else { 2025110245Sobrien break; 202679968Sobrien } 2027110245Sobrien } while (retry <= swaitmax); 2028110245Sobrien if (conerrno != 0) { 202979968Sobrien perror_reply(425, "Can't build data connection"); 203079968Sobrien return (NULL); 203179968Sobrien } 203279968Sobrien reply(150, "Opening %s mode data connection for '%s'%s.", 203379968Sobrien type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf); 203479968Sobrien return (file); 203579968Sobrien} 203679968Sobrien 203779968Sobrienvoid 203879968Sobrienclosedataconn(FILE *fd) 203979968Sobrien{ 204079968Sobrien 204192282Sobrien if (fd == NULL) 204292282Sobrien return; 204392282Sobrien (void)fclose(fd); 204479968Sobrien data = -1; 204579968Sobrien if (pdata >= 0) 204679968Sobrien (void)close(pdata); 204779968Sobrien pdata = -1; 204879968Sobrien} 204979968Sobrien 2050108746Sobrienint 2051108746Sobrienwrite_data(int fd, char *buf, size_t size, off_t *bufrem, 2052108746Sobrien struct timeval *then, int isdata) 2053108746Sobrien{ 2054108746Sobrien struct timeval now, td; 2055108746Sobrien ssize_t c; 2056108746Sobrien 2057108746Sobrien while (size > 0) { 2058108746Sobrien c = size; 2059108746Sobrien if (curclass.writesize) { 2060108746Sobrien if (curclass.writesize < c) 2061108746Sobrien c = curclass.writesize; 2062108746Sobrien } 2063108746Sobrien if (curclass.rateget) { 2064108746Sobrien if (*bufrem < c) 2065108746Sobrien c = *bufrem; 2066108746Sobrien } 2067108746Sobrien (void) alarm(curclass.timeout); 2068108746Sobrien c = write(fd, buf, c); 2069108746Sobrien if (c <= 0) 2070108746Sobrien return (1); 2071108746Sobrien buf += c; 2072108746Sobrien size -= c; 2073108746Sobrien byte_count += c; 2074108746Sobrien if (isdata) { 2075108746Sobrien total_data_out += c; 2076108746Sobrien total_data += c; 2077108746Sobrien } 2078108746Sobrien total_bytes_out += c; 2079108746Sobrien total_bytes += c; 2080108746Sobrien if (curclass.rateget) { 2081108746Sobrien *bufrem -= c; 2082108746Sobrien if (*bufrem == 0) { 2083108746Sobrien (void)gettimeofday(&now, NULL); 2084108746Sobrien timersub(&now, then, &td); 2085108746Sobrien if (td.tv_sec == 0) { 2086108746Sobrien usleep(1000000 - td.tv_usec); 2087108746Sobrien (void)gettimeofday(then, NULL); 2088108746Sobrien } else 2089108746Sobrien *then = now; 2090108746Sobrien *bufrem = curclass.rateget; 2091108746Sobrien } 2092108746Sobrien } 2093108746Sobrien } 2094108746Sobrien return (0); 2095108746Sobrien} 2096108746Sobrien 2097108746Sobrienstatic enum send_status 2098108746Sobriensend_data_with_read(int filefd, int netfd, const struct stat *st, int isdata) 2099108746Sobrien{ 2100108746Sobrien struct timeval then; 2101108746Sobrien off_t bufrem; 2102108746Sobrien size_t readsize; 2103108746Sobrien char *buf; 2104108746Sobrien int c, error; 2105108746Sobrien 2106108746Sobrien if (curclass.readsize) 2107108746Sobrien readsize = curclass.readsize; 2108108746Sobrien else 2109108746Sobrien readsize = (size_t)st->st_blksize; 2110108746Sobrien if ((buf = malloc(readsize)) == NULL) { 2111108746Sobrien perror_reply(451, "Local resource failure: malloc"); 2112108746Sobrien return (SS_NO_TRANSFER); 2113108746Sobrien } 2114108746Sobrien 2115108746Sobrien if (curclass.rateget) { 2116108746Sobrien bufrem = curclass.rateget; 2117108746Sobrien (void)gettimeofday(&then, NULL); 2118108746Sobrien } 2119108746Sobrien while (1) { 2120108746Sobrien (void) alarm(curclass.timeout); 2121108746Sobrien c = read(filefd, buf, readsize); 2122108746Sobrien if (c == 0) 2123108746Sobrien error = SS_SUCCESS; 2124108746Sobrien else if (c < 0) 2125108746Sobrien error = SS_FILE_ERROR; 2126108746Sobrien else if (write_data(netfd, buf, c, &bufrem, &then, isdata)) 2127108746Sobrien error = SS_DATA_ERROR; 2128133939Sobrien else if (urgflag && handleoobcmd()) 2129133939Sobrien error = SS_ABORTED; 2130108746Sobrien else 2131108746Sobrien continue; 2132108746Sobrien 2133108746Sobrien free(buf); 2134108746Sobrien return (error); 2135108746Sobrien } 2136108746Sobrien} 2137108746Sobrien 2138108746Sobrienstatic enum send_status 2139108746Sobriensend_data_with_mmap(int filefd, int netfd, const struct stat *st, int isdata) 2140108746Sobrien{ 2141108746Sobrien struct timeval then; 2142108746Sobrien off_t bufrem, filesize, off, origoff; 2143108746Sobrien size_t mapsize, winsize; 2144108746Sobrien int error, sendbufsize, sendlowat; 2145108746Sobrien void *win; 2146108746Sobrien 2147108746Sobrien if (curclass.sendbufsize) { 2148108746Sobrien sendbufsize = curclass.sendbufsize; 2149108746Sobrien if (setsockopt(netfd, SOL_SOCKET, SO_SNDBUF, 2150108746Sobrien &sendbufsize, sizeof(int)) == -1) 2151108746Sobrien syslog(LOG_WARNING, "setsockopt(SO_SNDBUF, %d): %m", 2152108746Sobrien sendbufsize); 2153108746Sobrien } 2154108746Sobrien 2155108746Sobrien if (curclass.sendlowat) { 2156108746Sobrien sendlowat = curclass.sendlowat; 2157108746Sobrien if (setsockopt(netfd, SOL_SOCKET, SO_SNDLOWAT, 2158108746Sobrien &sendlowat, sizeof(int)) == -1) 2159108746Sobrien syslog(LOG_WARNING, "setsockopt(SO_SNDLOWAT, %d): %m", 2160108746Sobrien sendlowat); 2161108746Sobrien } 2162108746Sobrien 2163108746Sobrien winsize = curclass.mmapsize; 2164108746Sobrien filesize = st->st_size; 2165161770Sobrien if (ftpd_debug) 2166108746Sobrien syslog(LOG_INFO, "mmapsize = %ld, writesize = %ld", 2167108746Sobrien (long)winsize, (long)curclass.writesize); 2168108746Sobrien if (winsize == 0) 2169108746Sobrien goto try_read; 2170108746Sobrien 2171108746Sobrien off = lseek(filefd, (off_t)0, SEEK_CUR); 2172108746Sobrien if (off == -1) 2173108746Sobrien goto try_read; 2174108746Sobrien 2175108746Sobrien origoff = off; 2176108746Sobrien if (curclass.rateget) { 2177108746Sobrien bufrem = curclass.rateget; 2178108746Sobrien (void)gettimeofday(&then, NULL); 2179108746Sobrien } 2180108746Sobrien while (1) { 2181108746Sobrien mapsize = MIN(filesize - off, winsize); 2182108746Sobrien if (mapsize == 0) 2183108746Sobrien break; 2184108746Sobrien win = mmap(NULL, mapsize, PROT_READ, 2185108746Sobrien MAP_FILE|MAP_SHARED, filefd, off); 2186108746Sobrien if (win == MAP_FAILED) { 2187108746Sobrien if (off == origoff) 2188108746Sobrien goto try_read; 2189108746Sobrien return (SS_FILE_ERROR); 2190108746Sobrien } 2191108746Sobrien (void) madvise(win, mapsize, MADV_SEQUENTIAL); 2192108746Sobrien error = write_data(netfd, win, mapsize, &bufrem, &then, 2193108746Sobrien isdata); 2194108746Sobrien (void) madvise(win, mapsize, MADV_DONTNEED); 2195108746Sobrien munmap(win, mapsize); 2196133939Sobrien if (urgflag && handleoobcmd()) 2197133939Sobrien return (SS_ABORTED); 2198108746Sobrien if (error) 2199108746Sobrien return (SS_DATA_ERROR); 2200108746Sobrien off += mapsize; 2201108746Sobrien } 2202108746Sobrien return (SS_SUCCESS); 2203108746Sobrien 2204108746Sobrien try_read: 2205108746Sobrien return (send_data_with_read(filefd, netfd, st, isdata)); 2206108746Sobrien} 2207108746Sobrien 220879968Sobrien/* 220979968Sobrien * Tranfer the contents of "instr" to "outstr" peer using the appropriate 2210108746Sobrien * encapsulation of the data subject to Mode, Structure, and Type. 221179968Sobrien * 221279968Sobrien * NB: Form isn't handled. 221379968Sobrien */ 221479968Sobrienstatic int 2215108746Sobriensend_data(FILE *instr, FILE *outstr, const struct stat *st, int isdata) 221679968Sobrien{ 221779968Sobrien int c, filefd, netfd, rval; 221879968Sobrien 2219133939Sobrien urgflag = 0; 222079968Sobrien transflag = 1; 222179968Sobrien rval = -1; 222279968Sobrien 222379968Sobrien switch (type) { 222479968Sobrien 222579968Sobrien case TYPE_A: 222679968Sobrien /* XXXLUKEM: rate limit ascii send (get) */ 222779968Sobrien (void) alarm(curclass.timeout); 222879968Sobrien while ((c = getc(instr)) != EOF) { 2229133939Sobrien if (urgflag && handleoobcmd()) 2230133939Sobrien goto cleanup_send_data; 223179968Sobrien byte_count++; 223279968Sobrien if (c == '\n') { 223379968Sobrien if (ferror(outstr)) 223479968Sobrien goto data_err; 223579968Sobrien (void) putc('\r', outstr); 223679968Sobrien if (isdata) { 223779968Sobrien total_data_out++; 223879968Sobrien total_data++; 223979968Sobrien } 224079968Sobrien total_bytes_out++; 224179968Sobrien total_bytes++; 224279968Sobrien } 224379968Sobrien (void) putc(c, outstr); 224479968Sobrien if (isdata) { 224579968Sobrien total_data_out++; 224679968Sobrien total_data++; 224779968Sobrien } 224879968Sobrien total_bytes_out++; 224979968Sobrien total_bytes++; 225079968Sobrien if ((byte_count % 4096) == 0) 225179968Sobrien (void) alarm(curclass.timeout); 225279968Sobrien } 225379968Sobrien (void) alarm(0); 225479968Sobrien fflush(outstr); 225579968Sobrien if (ferror(instr)) 225679968Sobrien goto file_err; 225779968Sobrien if (ferror(outstr)) 225879968Sobrien goto data_err; 225979968Sobrien rval = 0; 226079968Sobrien goto cleanup_send_data; 226179968Sobrien 226279968Sobrien case TYPE_I: 226379968Sobrien case TYPE_L: 226479968Sobrien filefd = fileno(instr); 226579968Sobrien netfd = fileno(outstr); 2266108746Sobrien switch (send_data_with_mmap(filefd, netfd, st, isdata)) { 226779968Sobrien 2268108746Sobrien case SS_SUCCESS: 2269108746Sobrien break; 2270108746Sobrien 2271133939Sobrien case SS_ABORTED: 2272108746Sobrien case SS_NO_TRANSFER: 2273108746Sobrien goto cleanup_send_data; 2274108746Sobrien 2275108746Sobrien case SS_FILE_ERROR: 2276108746Sobrien goto file_err; 2277108746Sobrien 2278108746Sobrien case SS_DATA_ERROR: 2279108746Sobrien goto data_err; 228079968Sobrien } 228179968Sobrien rval = 0; 228279968Sobrien goto cleanup_send_data; 228379968Sobrien 228479968Sobrien default: 228579968Sobrien reply(550, "Unimplemented TYPE %d in send_data", type); 228679968Sobrien goto cleanup_send_data; 228779968Sobrien } 228879968Sobrien 228979968Sobrien data_err: 229079968Sobrien (void) alarm(0); 229179968Sobrien perror_reply(426, "Data connection"); 229279968Sobrien goto cleanup_send_data; 229379968Sobrien 229479968Sobrien file_err: 229579968Sobrien (void) alarm(0); 229679968Sobrien perror_reply(551, "Error on input file"); 2297133939Sobrien goto cleanup_send_data; 229879968Sobrien 229979968Sobrien cleanup_send_data: 230079968Sobrien (void) alarm(0); 230179968Sobrien transflag = 0; 2302133939Sobrien urgflag = 0; 230379968Sobrien if (isdata) { 230479968Sobrien total_files_out++; 230579968Sobrien total_files++; 230679968Sobrien } 230779968Sobrien total_xfers_out++; 230879968Sobrien total_xfers++; 230979968Sobrien return (rval); 231079968Sobrien} 231179968Sobrien 231279968Sobrien/* 231379968Sobrien * Transfer data from peer to "outstr" using the appropriate encapulation of 231479968Sobrien * the data subject to Mode, Structure, and Type. 231579968Sobrien * 231679968Sobrien * N.B.: Form isn't handled. 231779968Sobrien */ 231879968Sobrienstatic int 231979968Sobrienreceive_data(FILE *instr, FILE *outstr) 232079968Sobrien{ 232179968Sobrien int c, bare_lfs, netfd, filefd, rval; 232279968Sobrien off_t byteswritten; 2323161770Sobrien char *buf; 2324161770Sobrien size_t readsize; 2325133939Sobrien struct sigaction sa, sa_saved; 2326161770Sobrien struct stat st; 232779968Sobrien#ifdef __GNUC__ 232879968Sobrien (void) &bare_lfs; 232979968Sobrien#endif 233079968Sobrien 2331133939Sobrien memset(&sa, 0, sizeof(sa)); 2332133939Sobrien sigfillset(&sa.sa_mask); 2333133939Sobrien sa.sa_flags = SA_RESTART; 2334133939Sobrien sa.sa_handler = lostconn; 2335133939Sobrien (void) sigaction(SIGALRM, &sa, &sa_saved); 2336133939Sobrien 233779968Sobrien bare_lfs = 0; 2338133939Sobrien urgflag = 0; 233979968Sobrien transflag = 1; 234079968Sobrien rval = -1; 234179968Sobrien byteswritten = 0; 2342161770Sobrien buf = NULL; 234379968Sobrien 234479968Sobrien#define FILESIZECHECK(x) \ 234579968Sobrien do { \ 234679968Sobrien if (curclass.maxfilesize != -1 && \ 234779968Sobrien (x) > curclass.maxfilesize) { \ 234879968Sobrien errno = EFBIG; \ 234979968Sobrien goto file_err; \ 235079968Sobrien } \ 235179968Sobrien } while (0) 235279968Sobrien 235379968Sobrien switch (type) { 235479968Sobrien 235579968Sobrien case TYPE_I: 235679968Sobrien case TYPE_L: 235779968Sobrien netfd = fileno(instr); 235879968Sobrien filefd = fileno(outstr); 235979968Sobrien (void) alarm(curclass.timeout); 2360161770Sobrien if (curclass.readsize) 2361161770Sobrien readsize = curclass.readsize; 2362161770Sobrien else if (fstat(filefd, &st)) 2363161770Sobrien readsize = (size_t)st.st_blksize; 2364161770Sobrien else 2365161770Sobrien readsize = BUFSIZ; 2366161770Sobrien if ((buf = malloc(readsize)) == NULL) { 2367161770Sobrien perror_reply(451, "Local resource failure: malloc"); 2368161770Sobrien goto cleanup_recv_data; 2369161770Sobrien } 237079968Sobrien if (curclass.rateput) { 237179968Sobrien while (1) { 237279968Sobrien int d; 237379968Sobrien struct timeval then, now, td; 237479968Sobrien off_t bufrem; 237579968Sobrien 237679968Sobrien (void)gettimeofday(&then, NULL); 237779968Sobrien errno = c = d = 0; 237879968Sobrien for (bufrem = curclass.rateput; bufrem > 0; ) { 237979968Sobrien if ((c = read(netfd, buf, 2380161770Sobrien MIN(readsize, bufrem))) <= 0) 238179968Sobrien goto recvdone; 2382133939Sobrien if (urgflag && handleoobcmd()) 2383133939Sobrien goto cleanup_recv_data; 238479968Sobrien FILESIZECHECK(byte_count + c); 238579968Sobrien if ((d = write(filefd, buf, c)) != c) 238679968Sobrien goto file_err; 238779968Sobrien (void) alarm(curclass.timeout); 238879968Sobrien bufrem -= c; 238979968Sobrien byte_count += c; 239079968Sobrien total_data_in += c; 239179968Sobrien total_data += c; 239279968Sobrien total_bytes_in += c; 239379968Sobrien total_bytes += c; 239479968Sobrien } 239579968Sobrien (void)gettimeofday(&now, NULL); 239679968Sobrien timersub(&now, &then, &td); 239779968Sobrien if (td.tv_sec == 0) 239879968Sobrien usleep(1000000 - td.tv_usec); 239979968Sobrien } 240079968Sobrien } else { 2401161770Sobrien while ((c = read(netfd, buf, readsize)) > 0) { 2402133939Sobrien if (urgflag && handleoobcmd()) 2403133939Sobrien goto cleanup_recv_data; 240479968Sobrien FILESIZECHECK(byte_count + c); 240579968Sobrien if (write(filefd, buf, c) != c) 240679968Sobrien goto file_err; 240779968Sobrien (void) alarm(curclass.timeout); 240879968Sobrien byte_count += c; 240979968Sobrien total_data_in += c; 241079968Sobrien total_data += c; 241179968Sobrien total_bytes_in += c; 241279968Sobrien total_bytes += c; 241379968Sobrien } 241479968Sobrien } 241579968Sobrien recvdone: 241679968Sobrien if (c < 0) 241779968Sobrien goto data_err; 241879968Sobrien rval = 0; 241979968Sobrien goto cleanup_recv_data; 242079968Sobrien 242179968Sobrien case TYPE_E: 242279968Sobrien reply(553, "TYPE E not implemented."); 242379968Sobrien goto cleanup_recv_data; 242479968Sobrien 242579968Sobrien case TYPE_A: 242679968Sobrien (void) alarm(curclass.timeout); 242779968Sobrien /* XXXLUKEM: rate limit ascii receive (put) */ 242879968Sobrien while ((c = getc(instr)) != EOF) { 2429133939Sobrien if (urgflag && handleoobcmd()) 2430133939Sobrien goto cleanup_recv_data; 243179968Sobrien byte_count++; 243279968Sobrien total_data_in++; 243379968Sobrien total_data++; 243479968Sobrien total_bytes_in++; 243579968Sobrien total_bytes++; 243679968Sobrien if ((byte_count % 4096) == 0) 243779968Sobrien (void) alarm(curclass.timeout); 243879968Sobrien if (c == '\n') 243979968Sobrien bare_lfs++; 244079968Sobrien while (c == '\r') { 244179968Sobrien if (ferror(outstr)) 244279968Sobrien goto data_err; 244379968Sobrien if ((c = getc(instr)) != '\n') { 244479968Sobrien byte_count++; 244579968Sobrien total_data_in++; 244679968Sobrien total_data++; 244779968Sobrien total_bytes_in++; 244879968Sobrien total_bytes++; 244979968Sobrien if ((byte_count % 4096) == 0) 245079968Sobrien (void) alarm(curclass.timeout); 245179968Sobrien byteswritten++; 245279968Sobrien FILESIZECHECK(byteswritten); 245379968Sobrien (void) putc ('\r', outstr); 245479968Sobrien if (c == '\0' || c == EOF) 245579968Sobrien goto contin2; 245679968Sobrien } 245779968Sobrien } 245879968Sobrien byteswritten++; 245979968Sobrien FILESIZECHECK(byteswritten); 246079968Sobrien (void) putc(c, outstr); 246179968Sobrien contin2: ; 246279968Sobrien } 246379968Sobrien (void) alarm(0); 246479968Sobrien fflush(outstr); 246579968Sobrien if (ferror(instr)) 246679968Sobrien goto data_err; 246779968Sobrien if (ferror(outstr)) 246879968Sobrien goto file_err; 246979968Sobrien if (bare_lfs) { 247079968Sobrien reply(-226, 247179968Sobrien "WARNING! %d bare linefeeds received in ASCII mode", 247279968Sobrien bare_lfs); 247379968Sobrien reply(0, "File may not have transferred correctly."); 247479968Sobrien } 247579968Sobrien rval = 0; 247679968Sobrien goto cleanup_recv_data; 247779968Sobrien 247879968Sobrien default: 247979968Sobrien reply(550, "Unimplemented TYPE %d in receive_data", type); 248079968Sobrien goto cleanup_recv_data; 248179968Sobrien } 248279968Sobrien#undef FILESIZECHECK 248379968Sobrien 248479968Sobrien data_err: 248579968Sobrien (void) alarm(0); 248679968Sobrien perror_reply(426, "Data Connection"); 248779968Sobrien goto cleanup_recv_data; 248879968Sobrien 248979968Sobrien file_err: 249079968Sobrien (void) alarm(0); 249179968Sobrien perror_reply(452, "Error writing file"); 249279968Sobrien goto cleanup_recv_data; 249379968Sobrien 249479968Sobrien cleanup_recv_data: 249579968Sobrien (void) alarm(0); 2496133939Sobrien (void) sigaction(SIGALRM, &sa_saved, NULL); 2497161770Sobrien if (buf) 2498161770Sobrien free(buf); 249979968Sobrien transflag = 0; 2500133939Sobrien urgflag = 0; 250179968Sobrien total_files_in++; 250279968Sobrien total_files++; 250379968Sobrien total_xfers_in++; 250479968Sobrien total_xfers++; 250579968Sobrien return (rval); 250679968Sobrien} 250779968Sobrien 250879968Sobrienvoid 250979968Sobrienstatcmd(void) 251079968Sobrien{ 251179968Sobrien struct sockinet *su = NULL; 251279968Sobrien static char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; 2513108746Sobrien u_char *a, *p; 251479968Sobrien int ispassive, af; 251579968Sobrien off_t otbi, otbo, otb; 251679968Sobrien 251779968Sobrien a = p = (u_char *)NULL; 251879968Sobrien 251979968Sobrien reply(-211, "%s FTP server status:", hostname); 252079968Sobrien reply(0, "Version: %s", EMPTYSTR(version) ? "<suppressed>" : version); 252179968Sobrien hbuf[0] = '\0'; 252279968Sobrien if (!getnameinfo((struct sockaddr *)&his_addr.si_su, his_addr.su_len, 252379968Sobrien hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST) 252479968Sobrien && strcmp(remotehost, hbuf) != 0) 252579968Sobrien reply(0, "Connected to %s (%s)", remotehost, hbuf); 252679968Sobrien else 252779968Sobrien reply(0, "Connected to %s", remotehost); 252879968Sobrien 252979968Sobrien if (logged_in) { 253079968Sobrien if (curclass.type == CLASS_GUEST) 253179968Sobrien reply(0, "Logged in anonymously"); 253279968Sobrien else 253379968Sobrien reply(0, "Logged in as %s%s", pw->pw_name, 253479968Sobrien curclass.type == CLASS_CHROOT ? " (chroot)" : ""); 253579968Sobrien } else if (askpasswd) 253679968Sobrien reply(0, "Waiting for password"); 253779968Sobrien else 253879968Sobrien reply(0, "Waiting for user name"); 253979968Sobrien cprintf(stdout, " TYPE: %s", typenames[type]); 254079968Sobrien if (type == TYPE_A || type == TYPE_E) 254179968Sobrien cprintf(stdout, ", FORM: %s", formnames[form]); 254279968Sobrien if (type == TYPE_L) { 254379968Sobrien#if NBBY == 8 254479968Sobrien cprintf(stdout, " %d", NBBY); 254579968Sobrien#else 254679968Sobrien /* XXX: `bytesize' needs to be defined in this case */ 254779968Sobrien cprintf(stdout, " %d", bytesize); 254879968Sobrien#endif 254979968Sobrien } 255079968Sobrien cprintf(stdout, "; STRUcture: %s; transfer MODE: %s\r\n", 255179968Sobrien strunames[stru], modenames[mode]); 255279968Sobrien ispassive = 0; 255379968Sobrien if (data != -1) { 2554108746Sobrien reply(0, "Data connection open"); 255579968Sobrien su = NULL; 255679968Sobrien } else if (pdata != -1) { 255779968Sobrien reply(0, "in Passive mode"); 255879968Sobrien if (curclass.advertise.su_len != 0) 255979968Sobrien su = &curclass.advertise; 256079968Sobrien else 256179968Sobrien su = &pasv_addr; 256279968Sobrien ispassive = 1; 256379968Sobrien goto printaddr; 256479968Sobrien } else if (usedefault == 0) { 2565161770Sobrien su = (struct sockinet *)&data_dest; 2566161770Sobrien 256779968Sobrien if (epsvall) { 256879968Sobrien reply(0, "EPSV only mode (EPSV ALL)"); 256979968Sobrien goto epsvonly; 257079968Sobrien } 257179968Sobrien printaddr: 257279968Sobrien /* PASV/PORT */ 257379968Sobrien if (su->su_family == AF_INET) { 257479968Sobrien a = (u_char *) &su->su_addr; 257579968Sobrien p = (u_char *) &su->su_port; 257679968Sobrien#define UC(b) (((int) b) & 0xff) 257779968Sobrien reply(0, "%s (%d,%d,%d,%d,%d,%d)", 257879968Sobrien ispassive ? "PASV" : "PORT" , 257979968Sobrien UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]), 258079968Sobrien UC(p[0]), UC(p[1])); 258179968Sobrien } 258279968Sobrien 258379968Sobrien /* LPSV/LPRT */ 258479968Sobrien { 258592282Sobrien int alen, i; 258679968Sobrien 258779968Sobrien alen = 0; 258879968Sobrien switch (su->su_family) { 258979968Sobrien case AF_INET: 259079968Sobrien a = (u_char *) &su->su_addr; 259179968Sobrien p = (u_char *) &su->su_port; 259279968Sobrien alen = sizeof(su->su_addr); 259379968Sobrien af = 4; 259479968Sobrien break; 259579968Sobrien#ifdef INET6 259679968Sobrien case AF_INET6: 259779968Sobrien a = (u_char *) &su->su_6addr; 259879968Sobrien p = (u_char *) &su->su_port; 259979968Sobrien alen = sizeof(su->su_6addr); 260079968Sobrien af = 6; 260179968Sobrien break; 260279968Sobrien#endif 260379968Sobrien default: 260479968Sobrien af = 0; 260579968Sobrien break; 260679968Sobrien } 260779968Sobrien if (af) { 260879968Sobrien cprintf(stdout, " %s (%d,%d", 260979968Sobrien ispassive ? "LPSV" : "LPRT", af, alen); 261079968Sobrien for (i = 0; i < alen; i++) 261179968Sobrien cprintf(stdout, ",%d", UC(a[i])); 261279968Sobrien cprintf(stdout, ",%d,%d,%d)\r\n", 261379968Sobrien 2, UC(p[0]), UC(p[1])); 261479968Sobrien#undef UC 261579968Sobrien } 261679968Sobrien } 261779968Sobrien 261879968Sobrien /* EPRT/EPSV */ 261979968Sobrien epsvonly: 262079968Sobrien af = af2epsvproto(su->su_family); 262179968Sobrien hbuf[0] = '\0'; 262279968Sobrien if (af > 0) { 262379968Sobrien struct sockinet tmp; 262479968Sobrien 262579968Sobrien tmp = *su; 262679968Sobrien#ifdef INET6 262779968Sobrien if (tmp.su_family == AF_INET6) 262879968Sobrien tmp.su_scope_id = 0; 262979968Sobrien#endif 263079968Sobrien if (getnameinfo((struct sockaddr *)&tmp.si_su, 263179968Sobrien tmp.su_len, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), 263279968Sobrien NI_NUMERICHOST | NI_NUMERICSERV) == 0) 263379968Sobrien reply(0, "%s (|%d|%s|%s|)", 263479968Sobrien ispassive ? "EPSV" : "EPRT", 263579968Sobrien af, hbuf, sbuf); 263679968Sobrien } 263779968Sobrien } else 263879968Sobrien reply(0, "No data connection"); 263979968Sobrien 264079968Sobrien if (logged_in) { 264179968Sobrien reply(0, 264279968Sobrien "Data sent: " LLF " byte%s in " LLF " file%s", 264379968Sobrien (LLT)total_data_out, PLURAL(total_data_out), 264479968Sobrien (LLT)total_files_out, PLURAL(total_files_out)); 264579968Sobrien reply(0, 264679968Sobrien "Data received: " LLF " byte%s in " LLF " file%s", 264779968Sobrien (LLT)total_data_in, PLURAL(total_data_in), 264879968Sobrien (LLT)total_files_in, PLURAL(total_files_in)); 264979968Sobrien reply(0, 265079968Sobrien "Total data: " LLF " byte%s in " LLF " file%s", 265179968Sobrien (LLT)total_data, PLURAL(total_data), 265279968Sobrien (LLT)total_files, PLURAL(total_files)); 265379968Sobrien } 265479968Sobrien otbi = total_bytes_in; 265579968Sobrien otbo = total_bytes_out; 265679968Sobrien otb = total_bytes; 265779968Sobrien reply(0, "Traffic sent: " LLF " byte%s in " LLF " transfer%s", 265879968Sobrien (LLT)otbo, PLURAL(otbo), 265979968Sobrien (LLT)total_xfers_out, PLURAL(total_xfers_out)); 266079968Sobrien reply(0, "Traffic received: " LLF " byte%s in " LLF " transfer%s", 266179968Sobrien (LLT)otbi, PLURAL(otbi), 266279968Sobrien (LLT)total_xfers_in, PLURAL(total_xfers_in)); 266379968Sobrien reply(0, "Total traffic: " LLF " byte%s in " LLF " transfer%s", 266479968Sobrien (LLT)otb, PLURAL(otb), 266579968Sobrien (LLT)total_xfers, PLURAL(total_xfers)); 266679968Sobrien 266792282Sobrien if (logged_in && !CURCLASS_FLAGS_ISSET(private)) { 266879968Sobrien struct ftpconv *cp; 266979968Sobrien 267079968Sobrien reply(0, "%s", ""); 267179968Sobrien reply(0, "Class: %s, type: %s", 267279968Sobrien curclass.classname, CURCLASSTYPE); 267379968Sobrien reply(0, "Check PORT/LPRT commands: %sabled", 267479968Sobrien CURCLASS_FLAGS_ISSET(checkportcmd) ? "en" : "dis"); 267579968Sobrien if (! EMPTYSTR(curclass.display)) 267679968Sobrien reply(0, "Display file: %s", curclass.display); 267779968Sobrien if (! EMPTYSTR(curclass.notify)) 267879968Sobrien reply(0, "Notify fileglob: %s", curclass.notify); 2679108746Sobrien reply(0, "Idle timeout: " LLF ", maximum timeout: " LLF, 2680108746Sobrien (LLT)curclass.timeout, (LLT)curclass.maxtimeout); 268179968Sobrien reply(0, "Current connections: %d", connections); 268279968Sobrien if (curclass.limit == -1) 268379968Sobrien reply(0, "Maximum connections: unlimited"); 268479968Sobrien else 2685108746Sobrien reply(0, "Maximum connections: " LLF, 2686108746Sobrien (LLT)curclass.limit); 268779968Sobrien if (curclass.limitfile) 268879968Sobrien reply(0, "Connection limit exceeded message file: %s", 268992282Sobrien conffilename(curclass.limitfile)); 269079968Sobrien if (! EMPTYSTR(curclass.chroot)) 269179968Sobrien reply(0, "Chroot format: %s", curclass.chroot); 269292282Sobrien reply(0, "Deny bad ftpusers(5) quickly: %sabled", 269392282Sobrien CURCLASS_FLAGS_ISSET(denyquick) ? "en" : "dis"); 269479968Sobrien if (! EMPTYSTR(curclass.homedir)) 269579968Sobrien reply(0, "Homedir format: %s", curclass.homedir); 269679968Sobrien if (curclass.maxfilesize == -1) 269779968Sobrien reply(0, "Maximum file size: unlimited"); 269879968Sobrien else 269979968Sobrien reply(0, "Maximum file size: " LLF, 270079968Sobrien (LLT)curclass.maxfilesize); 270179968Sobrien if (! EMPTYSTR(curclass.motd)) 270292282Sobrien reply(0, "MotD file: %s", conffilename(curclass.motd)); 270379968Sobrien reply(0, 270479968Sobrien "Modify commands (CHMOD, DELE, MKD, RMD, RNFR, UMASK): %sabled", 270579968Sobrien CURCLASS_FLAGS_ISSET(modify) ? "en" : "dis"); 270679968Sobrien reply(0, "Upload commands (APPE, STOR, STOU): %sabled", 270779968Sobrien CURCLASS_FLAGS_ISSET(upload) ? "en" : "dis"); 270879968Sobrien reply(0, "Sanitize file names: %sabled", 270979968Sobrien CURCLASS_FLAGS_ISSET(sanenames) ? "en" : "dis"); 271079968Sobrien reply(0, "PASV/LPSV/EPSV connections: %sabled", 271179968Sobrien CURCLASS_FLAGS_ISSET(passive) ? "en" : "dis"); 271279968Sobrien if (curclass.advertise.su_len != 0) { 271379968Sobrien char buf[50]; /* big enough for IPv6 address */ 271479968Sobrien const char *bp; 271579968Sobrien 271679968Sobrien bp = inet_ntop(curclass.advertise.su_family, 271779968Sobrien (void *)&curclass.advertise.su_addr, 271879968Sobrien buf, sizeof(buf)); 271979968Sobrien if (bp != NULL) 272079968Sobrien reply(0, "PASV advertise address: %s", bp); 272179968Sobrien } 272279968Sobrien if (curclass.portmin && curclass.portmax) 2723108746Sobrien reply(0, "PASV port range: " LLF " - " LLF, 2724108746Sobrien (LLT)curclass.portmin, (LLT)curclass.portmax); 272579968Sobrien if (curclass.rateget) 272679968Sobrien reply(0, "Rate get limit: " LLF " bytes/sec", 272779968Sobrien (LLT)curclass.rateget); 272879968Sobrien else 272979968Sobrien reply(0, "Rate get limit: disabled"); 273079968Sobrien if (curclass.rateput) 273179968Sobrien reply(0, "Rate put limit: " LLF " bytes/sec", 273279968Sobrien (LLT)curclass.rateput); 273379968Sobrien else 273479968Sobrien reply(0, "Rate put limit: disabled"); 2735108746Sobrien if (curclass.mmapsize) 2736108746Sobrien reply(0, "Mmap size: " LLF, (LLT)curclass.mmapsize); 2737108746Sobrien else 2738108746Sobrien reply(0, "Mmap size: disabled"); 2739108746Sobrien if (curclass.readsize) 2740108746Sobrien reply(0, "Read size: " LLF, (LLT)curclass.readsize); 2741108746Sobrien else 2742108746Sobrien reply(0, "Read size: default"); 2743108746Sobrien if (curclass.writesize) 2744108746Sobrien reply(0, "Write size: " LLF, (LLT)curclass.writesize); 2745108746Sobrien else 2746108746Sobrien reply(0, "Write size: default"); 2747161770Sobrien if (curclass.recvbufsize) 2748161770Sobrien reply(0, "Receive buffer size: " LLF, 2749161770Sobrien (LLT)curclass.recvbufsize); 2750161770Sobrien else 2751161770Sobrien reply(0, "Receive buffer size: default"); 2752108746Sobrien if (curclass.sendbufsize) 2753108746Sobrien reply(0, "Send buffer size: " LLF, 2754108746Sobrien (LLT)curclass.sendbufsize); 2755108746Sobrien else 2756108746Sobrien reply(0, "Send buffer size: default"); 2757108746Sobrien if (curclass.sendlowat) 2758108746Sobrien reply(0, "Send low water mark: " LLF, 2759108746Sobrien (LLT)curclass.sendlowat); 2760108746Sobrien else 2761108746Sobrien reply(0, "Send low water mark: default"); 276279968Sobrien reply(0, "Umask: %.04o", curclass.umask); 276379968Sobrien for (cp = curclass.conversions; cp != NULL; cp=cp->next) { 276479968Sobrien if (cp->suffix == NULL || cp->types == NULL || 276579968Sobrien cp->command == NULL) 276679968Sobrien continue; 276779968Sobrien reply(0, "Conversion: %s [%s] disable: %s, command: %s", 276879968Sobrien cp->suffix, cp->types, cp->disable, cp->command); 276979968Sobrien } 277079968Sobrien } 277179968Sobrien 277279968Sobrien reply(211, "End of status"); 277379968Sobrien} 277479968Sobrien 277579968Sobrienvoid 277679968Sobrienfatal(const char *s) 277779968Sobrien{ 277879968Sobrien 277979968Sobrien reply(451, "Error in server: %s\n", s); 278079968Sobrien reply(221, "Closing connection due to server error."); 278179968Sobrien dologout(0); 278279968Sobrien /* NOTREACHED */ 278379968Sobrien} 278479968Sobrien 278579968Sobrien/* 278679968Sobrien * reply() -- 278779968Sobrien * depending on the value of n, display fmt with a trailing CRLF and 278879968Sobrien * prefix of: 278979968Sobrien * n < -1 prefix the message with abs(n) + "-" (initial line) 279079968Sobrien * n == 0 prefix the message with 4 spaces (middle lines) 279179968Sobrien * n > 0 prefix the message with n + " " (final line) 279279968Sobrien */ 279379968Sobrienvoid 279479968Sobrienreply(int n, const char *fmt, ...) 279579968Sobrien{ 2796133939Sobrien char msg[MAXPATHLEN * 2 + 100]; 2797133939Sobrien size_t b; 2798133939Sobrien va_list ap; 279979968Sobrien 280079968Sobrien b = 0; 280179968Sobrien if (n == 0) 2802133939Sobrien b = snprintf(msg, sizeof(msg), " "); 280379968Sobrien else if (n < 0) 2804133939Sobrien b = snprintf(msg, sizeof(msg), "%d-", -n); 280579968Sobrien else 2806133939Sobrien b = snprintf(msg, sizeof(msg), "%d ", n); 2807133939Sobrien va_start(ap, fmt); 2808133939Sobrien vsnprintf(msg + b, sizeof(msg) - b, fmt, ap); 280992282Sobrien va_end(ap); 2810133939Sobrien cprintf(stdout, "%s\r\n", msg); 281179968Sobrien (void)fflush(stdout); 2812161770Sobrien if (ftpd_debug) 2813133939Sobrien syslog(LOG_DEBUG, "<--- %s", msg); 281479968Sobrien} 281579968Sobrien 281679968Sobrienstatic void 281779968Sobrienlogremotehost(struct sockinet *who) 281879968Sobrien{ 281979968Sobrien 282079968Sobrien if (getnameinfo((struct sockaddr *)&who->si_su, 282179968Sobrien who->su_len, remotehost, sizeof(remotehost), NULL, 0, 0)) 282279968Sobrien strlcpy(remotehost, "?", sizeof(remotehost)); 282379968Sobrien 282479968Sobrien#if HAVE_SETPROCTITLE 282579968Sobrien snprintf(proctitle, sizeof(proctitle), "%s: connected", remotehost); 282679968Sobrien setproctitle("%s", proctitle); 282779968Sobrien#endif /* HAVE_SETPROCTITLE */ 282879968Sobrien if (logging) 282979968Sobrien syslog(LOG_INFO, "connection from %s to %s", 283079968Sobrien remotehost, hostname); 283179968Sobrien} 283279968Sobrien 283379968Sobrien/* 283479968Sobrien * Record logout in wtmp file and exit with supplied status. 2835133939Sobrien * NOTE: because this is called from signal handlers it cannot 2836133939Sobrien * use stdio (or call other functions that use stdio). 283779968Sobrien */ 283879968Sobrienvoid 283979968Sobriendologout(int status) 284079968Sobrien{ 284179968Sobrien /* 284279968Sobrien * Prevent reception of SIGURG from resulting in a resumption 284379968Sobrien * back to the main program loop. 284479968Sobrien */ 284579968Sobrien transflag = 0; 2846108746Sobrien logout_utmp(); 284779968Sobrien if (logged_in) { 284879968Sobrien#ifdef KERBEROS 284979968Sobrien if (!notickets && krbtkfile_env) 285079968Sobrien unlink(krbtkfile_env); 285179968Sobrien#endif 285279968Sobrien } 285379968Sobrien /* beware of flushing buffers after a SIGPIPE */ 2854133939Sobrien if (xferlogfd != -1) 2855133939Sobrien close(xferlogfd); 285679968Sobrien _exit(status); 285779968Sobrien} 285879968Sobrien 285979968Sobrienvoid 286079968Sobrienabor(void) 286179968Sobrien{ 286279968Sobrien 2863133939Sobrien if (!transflag) 2864133939Sobrien return; 286579968Sobrien tmpline[0] = '\0'; 286679968Sobrien is_oob = 0; 286779968Sobrien reply(426, "Transfer aborted. Data connection closed."); 286879968Sobrien reply(226, "Abort successful"); 2869133939Sobrien transflag = 0; /* flag that the transfer has aborted */ 287079968Sobrien} 287179968Sobrien 287279968Sobrienvoid 287379968Sobrienstatxfer(void) 287479968Sobrien{ 287579968Sobrien 2876133939Sobrien if (!transflag) 2877133939Sobrien return; 287879968Sobrien tmpline[0] = '\0'; 287979968Sobrien is_oob = 0; 288079968Sobrien if (file_size != (off_t) -1) 288179968Sobrien reply(213, 288279968Sobrien "Status: " LLF " of " LLF " byte%s transferred", 288379968Sobrien (LLT)byte_count, (LLT)file_size, 288479968Sobrien PLURAL(byte_count)); 288579968Sobrien else 288679968Sobrien reply(213, "Status: " LLF " byte%s transferred", 288779968Sobrien (LLT)byte_count, PLURAL(byte_count)); 288879968Sobrien} 288979968Sobrien 2890133939Sobrien/* 2891133939Sobrien * Call when urgflag != 0 to handle Out Of Band commands. 2892133939Sobrien * Returns non zero if the OOB command aborted the transfer 2893133939Sobrien * by setting transflag to 0. (c.f., "ABOR"). 2894133939Sobrien */ 2895133939Sobrienstatic int 2896133939Sobrienhandleoobcmd() 289779968Sobrien{ 289879968Sobrien char *cp; 2899186872Ssimon int ret; 290079968Sobrien 2901133939Sobrien if (!urgflag) 2902133939Sobrien return (0); 2903133939Sobrien urgflag = 0; 290479968Sobrien /* only process if transfer occurring */ 290579968Sobrien if (!transflag) 2906133939Sobrien return (0); 290779968Sobrien cp = tmpline; 2908186872Ssimon ret = getline(cp, sizeof(tmpline)-1, stdin); 2909186872Ssimon if (ret == -1) { 291079968Sobrien reply(221, "You could at least say goodbye."); 291179968Sobrien dologout(0); 2912186872Ssimon } else if (ret == -2) { 2913186872Ssimon /* Ignore truncated command */ 2914186872Ssimon /* XXX: abort xfer with "500 command too long", & return 1 ? */ 2915186872Ssimon return 0; 291679968Sobrien } 2917133939Sobrien /* 2918133939Sobrien * Manually parse OOB commands, because we can't 2919133939Sobrien * recursively call the yacc parser... 2920133939Sobrien */ 2921133939Sobrien if (strcasecmp(cp, "ABOR\r\n") == 0) { 2922133939Sobrien abor(); 2923133939Sobrien } else if (strcasecmp(cp, "STAT\r\n") == 0) { 2924133939Sobrien statxfer(); 2925133939Sobrien } else { 2926133939Sobrien /* XXX: error with "500 unknown command" ? */ 2927133939Sobrien } 2928133939Sobrien return (transflag == 0); 292979968Sobrien} 293079968Sobrien 293179968Sobrienstatic int 293279968Sobrienbind_pasv_addr(void) 293379968Sobrien{ 293479968Sobrien static int passiveport; 293579968Sobrien int port, len; 293679968Sobrien 293779968Sobrien len = pasv_addr.su_len; 293879968Sobrien if (curclass.portmin == 0 && curclass.portmax == 0) { 293979968Sobrien pasv_addr.su_port = 0; 294079968Sobrien return (bind(pdata, (struct sockaddr *)&pasv_addr.si_su, len)); 294179968Sobrien } 294279968Sobrien 294379968Sobrien if (passiveport == 0) { 294479968Sobrien srand(getpid()); 294579968Sobrien passiveport = rand() % (curclass.portmax - curclass.portmin) 294679968Sobrien + curclass.portmin; 294779968Sobrien } 294879968Sobrien 294979968Sobrien port = passiveport; 295079968Sobrien while (1) { 295179968Sobrien port++; 295279968Sobrien if (port > curclass.portmax) 295379968Sobrien port = curclass.portmin; 295479968Sobrien else if (port == passiveport) { 295579968Sobrien errno = EAGAIN; 295679968Sobrien return (-1); 295779968Sobrien } 295879968Sobrien pasv_addr.su_port = htons(port); 295979968Sobrien if (bind(pdata, (struct sockaddr *)&pasv_addr.si_su, len) == 0) 296079968Sobrien break; 296179968Sobrien if (errno != EADDRINUSE) 296279968Sobrien return (-1); 296379968Sobrien } 296479968Sobrien passiveport = port; 296579968Sobrien return (0); 296679968Sobrien} 296779968Sobrien 296879968Sobrien/* 296979968Sobrien * Note: a response of 425 is not mentioned as a possible response to 297079968Sobrien * the PASV command in RFC959. However, it has been blessed as 297179968Sobrien * a legitimate response by Jon Postel in a telephone conversation 297279968Sobrien * with Rick Adams on 25 Jan 89. 297379968Sobrien */ 297479968Sobrienvoid 297579968Sobrienpassive(void) 297679968Sobrien{ 2977161770Sobrien socklen_t len, recvbufsize; 297879968Sobrien char *p, *a; 297979968Sobrien 298079968Sobrien if (pdata >= 0) 298179968Sobrien close(pdata); 298279968Sobrien pdata = socket(AF_INET, SOCK_STREAM, 0); 298379968Sobrien if (pdata < 0 || !logged_in) { 298479968Sobrien perror_reply(425, "Can't open passive connection"); 298579968Sobrien return; 298679968Sobrien } 298779968Sobrien pasv_addr = ctrl_addr; 298879968Sobrien 298979968Sobrien if (bind_pasv_addr() < 0) 299079968Sobrien goto pasv_error; 299179968Sobrien len = pasv_addr.su_len; 299279968Sobrien if (getsockname(pdata, (struct sockaddr *) &pasv_addr.si_su, &len) < 0) 299379968Sobrien goto pasv_error; 299479968Sobrien pasv_addr.su_len = len; 2995161770Sobrien if (curclass.recvbufsize) { 2996161770Sobrien recvbufsize = curclass.recvbufsize; 2997161770Sobrien if (setsockopt(pdata, SOL_SOCKET, SO_RCVBUF, &recvbufsize, 2998161770Sobrien sizeof(int)) == -1) 2999161770Sobrien syslog(LOG_WARNING, "setsockopt(SO_RCVBUF, %d): %m", 3000161770Sobrien recvbufsize); 3001161770Sobrien } 300279968Sobrien if (listen(pdata, 1) < 0) 300379968Sobrien goto pasv_error; 300479968Sobrien if (curclass.advertise.su_len != 0) 300579968Sobrien a = (char *) &curclass.advertise.su_addr; 300679968Sobrien else 300779968Sobrien a = (char *) &pasv_addr.su_addr; 300879968Sobrien p = (char *) &pasv_addr.su_port; 300979968Sobrien 301079968Sobrien#define UC(b) (((int) b) & 0xff) 301179968Sobrien 301279968Sobrien reply(227, "Entering Passive Mode (%d,%d,%d,%d,%d,%d)", UC(a[0]), 301379968Sobrien UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1])); 301479968Sobrien return; 301579968Sobrien 301679968Sobrien pasv_error: 301779968Sobrien (void) close(pdata); 301879968Sobrien pdata = -1; 301979968Sobrien perror_reply(425, "Can't open passive connection"); 302079968Sobrien return; 302179968Sobrien} 302279968Sobrien 302379968Sobrien/* 302479968Sobrien * convert protocol identifier to/from AF 302579968Sobrien */ 302679968Sobrienint 302779968Sobrienlpsvproto2af(int proto) 302879968Sobrien{ 302979968Sobrien 303079968Sobrien switch (proto) { 303179968Sobrien case 4: 303279968Sobrien return AF_INET; 303379968Sobrien#ifdef INET6 303479968Sobrien case 6: 303579968Sobrien return AF_INET6; 303679968Sobrien#endif 303779968Sobrien default: 303879968Sobrien return -1; 303979968Sobrien } 304079968Sobrien} 304179968Sobrien 304279968Sobrienint 304379968Sobrienaf2lpsvproto(int af) 304479968Sobrien{ 304579968Sobrien 304679968Sobrien switch (af) { 304779968Sobrien case AF_INET: 304879968Sobrien return 4; 304979968Sobrien#ifdef INET6 305079968Sobrien case AF_INET6: 305179968Sobrien return 6; 305279968Sobrien#endif 305379968Sobrien default: 305479968Sobrien return -1; 305579968Sobrien } 305679968Sobrien} 305779968Sobrien 305879968Sobrienint 305979968Sobrienepsvproto2af(int proto) 306079968Sobrien{ 306179968Sobrien 306279968Sobrien switch (proto) { 306379968Sobrien case 1: 306479968Sobrien return AF_INET; 306579968Sobrien#ifdef INET6 306679968Sobrien case 2: 306779968Sobrien return AF_INET6; 306879968Sobrien#endif 306979968Sobrien default: 307079968Sobrien return -1; 307179968Sobrien } 307279968Sobrien} 307379968Sobrien 307479968Sobrienint 307579968Sobrienaf2epsvproto(int af) 307679968Sobrien{ 307779968Sobrien 307879968Sobrien switch (af) { 307979968Sobrien case AF_INET: 308079968Sobrien return 1; 308179968Sobrien#ifdef INET6 308279968Sobrien case AF_INET6: 308379968Sobrien return 2; 308479968Sobrien#endif 308579968Sobrien default: 308679968Sobrien return -1; 308779968Sobrien } 308879968Sobrien} 308979968Sobrien 309079968Sobrien/* 309179968Sobrien * 228 Entering Long Passive Mode (af, hal, h1, h2, h3,..., pal, p1, p2...) 309279968Sobrien * 229 Entering Extended Passive Mode (|||port|) 309379968Sobrien */ 309479968Sobrienvoid 309579968Sobrienlong_passive(char *cmd, int pf) 309679968Sobrien{ 3097161770Sobrien socklen_t len; 309879968Sobrien char *p, *a; 309979968Sobrien 310079968Sobrien if (!logged_in) { 310179968Sobrien syslog(LOG_NOTICE, "long passive but not logged in"); 310279968Sobrien reply(503, "Login with USER first."); 310379968Sobrien return; 310479968Sobrien } 310579968Sobrien 310679968Sobrien if (pf != PF_UNSPEC && ctrl_addr.su_family != pf) { 310779968Sobrien /* 310879968Sobrien * XXX: only EPRT/EPSV ready clients will understand this 310979968Sobrien */ 311079968Sobrien if (strcmp(cmd, "EPSV") != 0) 311179968Sobrien reply(501, "Network protocol mismatch"); /*XXX*/ 311279968Sobrien else 311379968Sobrien epsv_protounsupp("Network protocol mismatch"); 311479968Sobrien 311579968Sobrien return; 311679968Sobrien } 311779968Sobrien 311879968Sobrien if (pdata >= 0) 311979968Sobrien close(pdata); 312079968Sobrien pdata = socket(ctrl_addr.su_family, SOCK_STREAM, 0); 312179968Sobrien if (pdata < 0) { 312279968Sobrien perror_reply(425, "Can't open passive connection"); 312379968Sobrien return; 312479968Sobrien } 312579968Sobrien pasv_addr = ctrl_addr; 312679968Sobrien if (bind_pasv_addr() < 0) 312779968Sobrien goto pasv_error; 312879968Sobrien len = pasv_addr.su_len; 312979968Sobrien if (getsockname(pdata, (struct sockaddr *) &pasv_addr.si_su, &len) < 0) 313079968Sobrien goto pasv_error; 313179968Sobrien pasv_addr.su_len = len; 313279968Sobrien if (listen(pdata, 1) < 0) 313379968Sobrien goto pasv_error; 313479968Sobrien p = (char *) &pasv_addr.su_port; 313579968Sobrien 313679968Sobrien#define UC(b) (((int) b) & 0xff) 313779968Sobrien 313879968Sobrien if (strcmp(cmd, "LPSV") == 0) { 313979968Sobrien struct sockinet *advert; 314079968Sobrien 314179968Sobrien if (curclass.advertise.su_len != 0) 314279968Sobrien advert = &curclass.advertise; 314379968Sobrien else 314479968Sobrien advert = &pasv_addr; 314579968Sobrien switch (advert->su_family) { 314679968Sobrien case AF_INET: 314779968Sobrien a = (char *) &advert->su_addr; 314879968Sobrien reply(228, 314979968Sobrien "Entering Long Passive Mode (%d,%d,%d,%d,%d,%d,%d,%d,%d)", 315079968Sobrien 4, 4, UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]), 315179968Sobrien 2, UC(p[0]), UC(p[1])); 315279968Sobrien return; 315379968Sobrien#ifdef INET6 315479968Sobrien case AF_INET6: 315579968Sobrien a = (char *) &advert->su_6addr; 315679968Sobrien reply(228, 315779968Sobrien "Entering Long Passive Mode (%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)", 315879968Sobrien 6, 16, 315979968Sobrien UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]), 316079968Sobrien UC(a[4]), UC(a[5]), UC(a[6]), UC(a[7]), 316179968Sobrien UC(a[8]), UC(a[9]), UC(a[10]), UC(a[11]), 316279968Sobrien UC(a[12]), UC(a[13]), UC(a[14]), UC(a[15]), 316379968Sobrien 2, UC(p[0]), UC(p[1])); 316479968Sobrien return; 316579968Sobrien#endif 316679968Sobrien } 316779968Sobrien#undef UC 316879968Sobrien } else if (strcmp(cmd, "EPSV") == 0) { 316979968Sobrien switch (pasv_addr.su_family) { 317079968Sobrien case AF_INET: 317179968Sobrien#ifdef INET6 317279968Sobrien case AF_INET6: 317379968Sobrien#endif 317479968Sobrien reply(229, "Entering Extended Passive Mode (|||%d|)", 317579968Sobrien ntohs(pasv_addr.su_port)); 317679968Sobrien return; 317779968Sobrien } 317879968Sobrien } else { 317979968Sobrien /* more proper error code? */ 318079968Sobrien } 318179968Sobrien 318279968Sobrien pasv_error: 318379968Sobrien (void) close(pdata); 318479968Sobrien pdata = -1; 318579968Sobrien perror_reply(425, "Can't open passive connection"); 318679968Sobrien return; 318779968Sobrien} 318879968Sobrien 318979968Sobrienint 319079968Sobrienextended_port(const char *arg) 319179968Sobrien{ 319279968Sobrien char *tmp = NULL; 319379968Sobrien char *result[3]; 319479968Sobrien char *p, *q; 319579968Sobrien char delim; 319679968Sobrien struct addrinfo hints; 319779968Sobrien struct addrinfo *res = NULL; 319879968Sobrien int i; 319979968Sobrien unsigned long proto; 320079968Sobrien 3201161770Sobrien tmp = ftpd_strdup(arg); 320279968Sobrien p = tmp; 320379968Sobrien delim = p[0]; 320479968Sobrien p++; 320579968Sobrien memset(result, 0, sizeof(result)); 320679968Sobrien for (i = 0; i < 3; i++) { 320779968Sobrien q = strchr(p, delim); 320879968Sobrien if (!q || *q != delim) 320979968Sobrien goto parsefail; 321079968Sobrien *q++ = '\0'; 321179968Sobrien result[i] = p; 321279968Sobrien p = q; 321379968Sobrien } 321479968Sobrien 321579968Sobrien /* some more sanity checks */ 3216108746Sobrien errno = 0; 321779968Sobrien p = NULL; 321879968Sobrien (void)strtoul(result[2], &p, 10); 3219108746Sobrien if (errno || !*result[2] || *p) 322079968Sobrien goto parsefail; 3221108746Sobrien errno = 0; 322279968Sobrien p = NULL; 322379968Sobrien proto = strtoul(result[0], &p, 10); 3224108746Sobrien if (errno || !*result[0] || *p) 322579968Sobrien goto protounsupp; 322679968Sobrien 322779968Sobrien memset(&hints, 0, sizeof(hints)); 322879968Sobrien hints.ai_family = epsvproto2af((int)proto); 322979968Sobrien if (hints.ai_family < 0) 323079968Sobrien goto protounsupp; 323179968Sobrien hints.ai_socktype = SOCK_STREAM; 323279968Sobrien hints.ai_flags = AI_NUMERICHOST; 323379968Sobrien if (getaddrinfo(result[1], result[2], &hints, &res)) 323479968Sobrien goto parsefail; 323579968Sobrien if (res->ai_next) 323679968Sobrien goto parsefail; 323779968Sobrien if (sizeof(data_dest) < res->ai_addrlen) 323879968Sobrien goto parsefail; 323979968Sobrien memcpy(&data_dest.si_su, res->ai_addr, res->ai_addrlen); 324079968Sobrien data_dest.su_len = res->ai_addrlen; 324179968Sobrien#ifdef INET6 324279968Sobrien if (his_addr.su_family == AF_INET6 && 324379968Sobrien data_dest.su_family == AF_INET6) { 324479968Sobrien /* XXX: more sanity checks! */ 324579968Sobrien data_dest.su_scope_id = his_addr.su_scope_id; 324679968Sobrien } 324779968Sobrien#endif 324879968Sobrien 324979968Sobrien if (tmp != NULL) 325079968Sobrien free(tmp); 325179968Sobrien if (res) 325279968Sobrien freeaddrinfo(res); 325379968Sobrien return 0; 325479968Sobrien 325579968Sobrien parsefail: 325679968Sobrien reply(500, "Invalid argument, rejected."); 325779968Sobrien usedefault = 1; 325879968Sobrien if (tmp != NULL) 325979968Sobrien free(tmp); 326079968Sobrien if (res) 326179968Sobrien freeaddrinfo(res); 326279968Sobrien return -1; 326379968Sobrien 326479968Sobrien protounsupp: 326579968Sobrien epsv_protounsupp("Protocol not supported"); 326679968Sobrien usedefault = 1; 326779968Sobrien if (tmp != NULL) 326879968Sobrien free(tmp); 326979968Sobrien return -1; 327079968Sobrien} 327179968Sobrien 327279968Sobrien/* 327379968Sobrien * 522 Protocol not supported (proto,...) 327479968Sobrien * as we assume address family for control and data connections are the same, 327579968Sobrien * we do not return the list of address families we support - instead, we 327679968Sobrien * return the address family of the control connection. 327779968Sobrien */ 327879968Sobrienvoid 327979968Sobrienepsv_protounsupp(const char *message) 328079968Sobrien{ 328179968Sobrien int proto; 328279968Sobrien 328379968Sobrien proto = af2epsvproto(ctrl_addr.su_family); 328479968Sobrien if (proto < 0) 328579968Sobrien reply(501, "%s", message); /* XXX */ 328679968Sobrien else 328779968Sobrien reply(522, "%s, use (%d)", message, proto); 328879968Sobrien} 328979968Sobrien 329079968Sobrien/* 329179968Sobrien * Generate unique name for file with basename "local". 329279968Sobrien * The file named "local" is already known to exist. 329379968Sobrien * Generates failure reply on error. 329479968Sobrien * 329579968Sobrien * XXX: this function should under go changes similar to 329679968Sobrien * the mktemp(3)/mkstemp(3) changes. 329779968Sobrien */ 329879968Sobrienstatic char * 329979968Sobriengunique(const char *local) 330079968Sobrien{ 330179968Sobrien static char new[MAXPATHLEN]; 330279968Sobrien struct stat st; 330379968Sobrien char *cp; 330479968Sobrien int count; 330579968Sobrien 330679968Sobrien cp = strrchr(local, '/'); 330779968Sobrien if (cp) 330879968Sobrien *cp = '\0'; 330979968Sobrien if (stat(cp ? local : ".", &st) < 0) { 331079968Sobrien perror_reply(553, cp ? local : "."); 331179968Sobrien return (NULL); 331279968Sobrien } 331379968Sobrien if (cp) 331479968Sobrien *cp = '/'; 331579968Sobrien for (count = 1; count < 100; count++) { 331679968Sobrien (void)snprintf(new, sizeof(new) - 1, "%s.%d", local, count); 331779968Sobrien if (stat(new, &st) < 0) 331879968Sobrien return (new); 331979968Sobrien } 332079968Sobrien reply(452, "Unique file name cannot be created."); 332179968Sobrien return (NULL); 332279968Sobrien} 332379968Sobrien 332479968Sobrien/* 332579968Sobrien * Format and send reply containing system error number. 332679968Sobrien */ 332779968Sobrienvoid 332879968Sobrienperror_reply(int code, const char *string) 332979968Sobrien{ 333079968Sobrien int save_errno; 333179968Sobrien 333279968Sobrien save_errno = errno; 333379968Sobrien reply(code, "%s: %s.", string, strerror(errno)); 333479968Sobrien errno = save_errno; 333579968Sobrien} 333679968Sobrien 333779968Sobrienstatic char *onefile[] = { 333879968Sobrien "", 333979968Sobrien 0 334079968Sobrien}; 334179968Sobrien 334279968Sobrienvoid 334379968Sobriensend_file_list(const char *whichf) 334479968Sobrien{ 334579968Sobrien struct stat st; 334679968Sobrien DIR *dirp = NULL; 334779968Sobrien struct dirent *dir; 334879968Sobrien FILE *dout = NULL; 3349133939Sobrien char **dirlist, *dirname, *p; 3350133939Sobrien char *notglob = NULL; 335179968Sobrien int simple = 0; 335279968Sobrien int freeglob = 0; 335379968Sobrien glob_t gl; 335479968Sobrien 335579968Sobrien#ifdef __GNUC__ 335679968Sobrien (void) &dout; 335779968Sobrien (void) &dirlist; 335879968Sobrien (void) &simple; 335979968Sobrien (void) &freeglob; 336079968Sobrien#endif 3361133939Sobrien urgflag = 0; 336279968Sobrien 336379968Sobrien p = NULL; 336479968Sobrien if (strpbrk(whichf, "~{[*?") != NULL) { 336579968Sobrien int flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE|GLOB_LIMIT; 336679968Sobrien 336779968Sobrien memset(&gl, 0, sizeof(gl)); 336879968Sobrien freeglob = 1; 336979968Sobrien if (glob(whichf, flags, 0, &gl)) { 3370161770Sobrien reply(450, "Not found"); 3371133939Sobrien goto cleanup_send_file_list; 337279968Sobrien } else if (gl.gl_pathc == 0) { 337379968Sobrien errno = ENOENT; 3374161770Sobrien perror_reply(450, whichf); 3375133939Sobrien goto cleanup_send_file_list; 337679968Sobrien } 337779968Sobrien dirlist = gl.gl_pathv; 337879968Sobrien } else { 3379161770Sobrien notglob = ftpd_strdup(whichf); 338092282Sobrien onefile[0] = notglob; 338179968Sobrien dirlist = onefile; 338279968Sobrien simple = 1; 338379968Sobrien } 338479968Sobrien /* XXX: } for vi sm */ 338579968Sobrien 338679968Sobrien while ((dirname = *dirlist++) != NULL) { 338779968Sobrien int trailingslash = 0; 338879968Sobrien 338979968Sobrien if (stat(dirname, &st) < 0) { 339079968Sobrien /* 339179968Sobrien * If user typed "ls -l", etc, and the client 339279968Sobrien * used NLST, do what the user meant. 339379968Sobrien */ 339479968Sobrien /* XXX: nuke this support? */ 339579968Sobrien if (dirname[0] == '-' && *dirlist == NULL && 339679968Sobrien transflag == 0) { 339779968Sobrien char *argv[] = { INTERNAL_LS, "", NULL }; 339879968Sobrien 339979968Sobrien argv[1] = dirname; 340079968Sobrien retrieve(argv, dirname); 3401133939Sobrien goto cleanup_send_file_list; 340279968Sobrien } 3403161770Sobrien perror_reply(450, whichf); 340479968Sobrien goto cleanup_send_file_list; 340579968Sobrien } 340679968Sobrien 340779968Sobrien if (S_ISREG(st.st_mode)) { 340879968Sobrien /* 340979968Sobrien * XXXRFC: 341079968Sobrien * should we follow RFC959 and not work 341179968Sobrien * for non directories? 341279968Sobrien */ 341379968Sobrien if (dout == NULL) { 341479968Sobrien dout = dataconn("file list", (off_t)-1, "w"); 341579968Sobrien if (dout == NULL) 3416133939Sobrien goto cleanup_send_file_list; 3417133939Sobrien transflag = 1; 341879968Sobrien } 341979968Sobrien cprintf(dout, "%s%s\n", dirname, 342079968Sobrien type == TYPE_A ? "\r" : ""); 342179968Sobrien continue; 342279968Sobrien } else if (!S_ISDIR(st.st_mode)) 342379968Sobrien continue; 342479968Sobrien 342579968Sobrien if (dirname[strlen(dirname) - 1] == '/') 342679968Sobrien trailingslash++; 342779968Sobrien 342879968Sobrien if ((dirp = opendir(dirname)) == NULL) 342979968Sobrien continue; 343079968Sobrien 343179968Sobrien while ((dir = readdir(dirp)) != NULL) { 343279968Sobrien char nbuf[MAXPATHLEN]; 343379968Sobrien 3434133939Sobrien if (urgflag && handleoobcmd()) 3435133939Sobrien goto cleanup_send_file_list; 3436133939Sobrien 343779968Sobrien if (ISDOTDIR(dir->d_name) || ISDOTDOTDIR(dir->d_name)) 343879968Sobrien continue; 343979968Sobrien 344079968Sobrien (void)snprintf(nbuf, sizeof(nbuf), "%s%s%s", dirname, 344179968Sobrien trailingslash ? "" : "/", dir->d_name); 344279968Sobrien 344379968Sobrien /* 344479968Sobrien * We have to do a stat to ensure it's 344579968Sobrien * not a directory or special file. 344679968Sobrien */ 344779968Sobrien /* 344879968Sobrien * XXXRFC: 344979968Sobrien * should we follow RFC959 and filter out 345079968Sobrien * non files ? lukem - NO!, or not until 345179968Sobrien * our ftp client uses MLS{T,D} for completion. 345279968Sobrien */ 345379968Sobrien if (simple || (stat(nbuf, &st) == 0 && 345479968Sobrien S_ISREG(st.st_mode))) { 345579968Sobrien if (dout == NULL) { 345679968Sobrien dout = dataconn("file list", (off_t)-1, 345779968Sobrien "w"); 345879968Sobrien if (dout == NULL) 3459133939Sobrien goto cleanup_send_file_list; 3460133939Sobrien transflag = 1; 346179968Sobrien } 346279968Sobrien p = nbuf; 346379968Sobrien if (nbuf[0] == '.' && nbuf[1] == '/') 346479968Sobrien p = &nbuf[2]; 346579968Sobrien cprintf(dout, "%s%s\n", p, 346679968Sobrien type == TYPE_A ? "\r" : ""); 346779968Sobrien } 346879968Sobrien } 346979968Sobrien (void) closedir(dirp); 347079968Sobrien } 347179968Sobrien 347279968Sobrien if (dout == NULL) 3473161770Sobrien reply(450, "No files found."); 347479968Sobrien else if (ferror(dout) != 0) 3475161770Sobrien perror_reply(451, "Data connection"); 347679968Sobrien else 347779968Sobrien reply(226, "Transfer complete."); 347879968Sobrien 347979968Sobrien cleanup_send_file_list: 3480133939Sobrien closedataconn(dout); 348179968Sobrien transflag = 0; 3482133939Sobrien urgflag = 0; 348379968Sobrien total_xfers++; 348479968Sobrien total_xfers_out++; 348592282Sobrien if (notglob) 348692282Sobrien free(notglob); 348779968Sobrien if (freeglob) 348879968Sobrien globfree(&gl); 348979968Sobrien} 349079968Sobrien 349179968Sobrienchar * 349279968Sobrienconffilename(const char *s) 349379968Sobrien{ 349479968Sobrien static char filename[MAXPATHLEN]; 349579968Sobrien 349679968Sobrien if (*s == '/') 349779968Sobrien strlcpy(filename, s, sizeof(filename)); 349879968Sobrien else 349979968Sobrien (void)snprintf(filename, sizeof(filename), "%s/%s", confdir ,s); 350079968Sobrien return (filename); 350179968Sobrien} 350279968Sobrien 350379968Sobrien/* 350479968Sobrien * logxfer -- 350579968Sobrien * if logging > 1, then based on the arguments, syslog a message: 350679968Sobrien * if bytes != -1 "<command> <file1> = <bytes> bytes" 350779968Sobrien * else if file2 != NULL "<command> <file1> <file2>" 350879968Sobrien * else "<command> <file1>" 350979968Sobrien * if elapsed != NULL, append "in xxx.yyy seconds" 351079968Sobrien * if error != NULL, append ": " + error 351179968Sobrien * 3512108746Sobrien * if doxferlog != 0, bytes != -1, and command is "get", "put", 3513133939Sobrien * or "append", syslog and/or write a wu-ftpd style xferlog entry 351479968Sobrien */ 351579968Sobrienvoid 351679968Sobrienlogxfer(const char *command, off_t bytes, const char *file1, const char *file2, 3517108746Sobrien const struct timeval *elapsed, const char *error) 351879968Sobrien{ 3519161770Sobrien char buf[MAXPATHLEN * 2 + 100]; 3520161770Sobrien char realfile1[MAXPATHLEN], realfile2[MAXPATHLEN]; 352179968Sobrien const char *r1, *r2; 352279968Sobrien char direction; 352379968Sobrien size_t len; 352479968Sobrien time_t now; 352579968Sobrien 352679968Sobrien if (logging <=1 && !doxferlog) 352779968Sobrien return; 352879968Sobrien 352979968Sobrien r1 = r2 = NULL; 3530161770Sobrien if ((r1 = realpath(file1, realfile1)) == NULL) 353179968Sobrien r1 = file1; 353279968Sobrien if (file2 != NULL) 3533161770Sobrien if ((r2 = realpath(file2, realfile2)) == NULL) 353479968Sobrien r2 = file2; 353579968Sobrien 353679968Sobrien /* 353779968Sobrien * syslog command 353879968Sobrien */ 353979968Sobrien if (logging > 1) { 354079968Sobrien len = snprintf(buf, sizeof(buf), "%s %s", command, r1); 354179968Sobrien if (bytes != (off_t)-1) 354279968Sobrien len += snprintf(buf + len, sizeof(buf) - len, 354379968Sobrien " = " LLF " byte%s", (LLT) bytes, PLURAL(bytes)); 354479968Sobrien else if (r2 != NULL) 354579968Sobrien len += snprintf(buf + len, sizeof(buf) - len, 354679968Sobrien " %s", r2); 354779968Sobrien if (elapsed != NULL) 354879968Sobrien len += snprintf(buf + len, sizeof(buf) - len, 354979968Sobrien " in %ld.%.03d seconds", elapsed->tv_sec, 355079968Sobrien (int)(elapsed->tv_usec / 1000)); 355179968Sobrien if (error != NULL) 355279968Sobrien len += snprintf(buf + len, sizeof(buf) - len, 355379968Sobrien ": %s", error); 355479968Sobrien syslog(LOG_INFO, "%s", buf); 355579968Sobrien } 355679968Sobrien 355779968Sobrien /* 355879968Sobrien * syslog wu-ftpd style log entry, prefixed with "xferlog: " 355979968Sobrien */ 356092282Sobrien if (!doxferlog || bytes == -1) 356179968Sobrien return; 356279968Sobrien 356379968Sobrien if (strcmp(command, "get") == 0) 356479968Sobrien direction = 'o'; 356579968Sobrien else if (strcmp(command, "put") == 0 || strcmp(command, "append") == 0) 356679968Sobrien direction = 'i'; 356779968Sobrien else 356879968Sobrien return; 356979968Sobrien 357079968Sobrien time(&now); 3571133939Sobrien len = snprintf(buf, sizeof(buf), 3572133939Sobrien "%.24s %ld %s " LLF " %s %c %s %c %c %s FTP 0 * %c\n", 357379968Sobrien 357479968Sobrien/* 3575133939Sobrien * XXX: wu-ftpd puts ' (send)' or ' (recv)' in the syslog message, and removes 357679968Sobrien * the full date. This may be problematic for accurate log parsing, 357779968Sobrien * given that syslog messages don't contain the full date. 357879968Sobrien */ 357979968Sobrien ctime(&now), 358079968Sobrien elapsed == NULL ? 0 : elapsed->tv_sec + (elapsed->tv_usec > 0), 358179968Sobrien remotehost, 358292282Sobrien (LLT) bytes, 358379968Sobrien r1, 358479968Sobrien type == TYPE_A ? 'a' : 'b', 358579968Sobrien "_", /* XXX: take conversions into account? */ 358679968Sobrien direction, 358779968Sobrien 358879968Sobrien curclass.type == CLASS_GUEST ? 'a' : 358979968Sobrien curclass.type == CLASS_CHROOT ? 'g' : 359079968Sobrien curclass.type == CLASS_REAL ? 'r' : '?', 359179968Sobrien 359279968Sobrien curclass.type == CLASS_GUEST ? pw->pw_passwd : pw->pw_name, 359379968Sobrien error != NULL ? 'i' : 'c' 359479968Sobrien ); 3595133939Sobrien 3596133939Sobrien if ((doxferlog & 2) && xferlogfd != -1) 3597133939Sobrien write(xferlogfd, buf, len); 3598133939Sobrien if ((doxferlog & 1)) { 3599133939Sobrien buf[len-1] = '\n'; /* strip \n from syslog message */ 3600133939Sobrien syslog(LOG_INFO, "xferlog: %s", buf); 3601133939Sobrien } 360279968Sobrien} 360379968Sobrien 360479968Sobrien/* 3605108746Sobrien * Log the resource usage. 3606108746Sobrien * 3607108746Sobrien * XXX: more resource usage to logging? 3608108746Sobrien */ 3609108746Sobrienvoid 3610108746Sobrienlogrusage(const struct rusage *rusage_before, 3611108746Sobrien const struct rusage *rusage_after) 3612108746Sobrien{ 3613108746Sobrien struct timeval usrtime, systime; 3614108746Sobrien 3615108746Sobrien if (logging <= 1) 3616108746Sobrien return; 3617108746Sobrien 3618108746Sobrien timersub(&rusage_after->ru_utime, &rusage_before->ru_utime, &usrtime); 3619108746Sobrien timersub(&rusage_after->ru_stime, &rusage_before->ru_stime, &systime); 3620108746Sobrien syslog(LOG_INFO, "%ld.%.03du %ld.%.03ds %ld+%ldio %ldpf+%ldw", 3621108746Sobrien usrtime.tv_sec, (int)(usrtime.tv_usec / 1000), 3622108746Sobrien systime.tv_sec, (int)(systime.tv_usec / 1000), 3623108746Sobrien rusage_after->ru_inblock - rusage_before->ru_inblock, 3624108746Sobrien rusage_after->ru_oublock - rusage_before->ru_oublock, 3625108746Sobrien rusage_after->ru_majflt - rusage_before->ru_majflt, 3626108746Sobrien rusage_after->ru_nswap - rusage_before->ru_nswap); 3627108746Sobrien} 3628108746Sobrien 3629108746Sobrien/* 363079968Sobrien * Determine if `password' is valid for user given in `pw'. 363179968Sobrien * Returns 2 if password expired, 1 if otherwise failed, 0 if ok 363279968Sobrien */ 363379968Sobrienint 363492282Sobriencheckpassword(const struct passwd *pwent, const char *password) 363579968Sobrien{ 363679968Sobrien char *orig, *new; 3637161770Sobrien time_t change, expire, now; 363879968Sobrien 3639161770Sobrien change = expire = 0; 364092282Sobrien if (pwent == NULL) 364179968Sobrien return 1; 364279968Sobrien 3643161770Sobrien time(&now); 364492282Sobrien orig = pwent->pw_passwd; /* save existing password */ 364592282Sobrien expire = pwent->pw_expire; 3646161770Sobrien change = (pwent->pw_change == _PASSWORD_CHGNOW)? now : pwent->pw_change; 364779968Sobrien 364879968Sobrien if (orig[0] == '\0') /* don't allow empty passwords */ 364979968Sobrien return 1; 365079968Sobrien 365179968Sobrien new = crypt(password, orig); /* encrypt given password */ 365279968Sobrien if (strcmp(new, orig) != 0) /* compare */ 365379968Sobrien return 1; 365479968Sobrien 3655161770Sobrien if ((expire && now >= expire) || (change && now >= change)) 365679968Sobrien return 2; /* check if expired */ 365779968Sobrien 365879968Sobrien return 0; /* OK! */ 365979968Sobrien} 366079968Sobrien 366179968Sobrienchar * 3662161770Sobrienftpd_strdup(const char *s) 366379968Sobrien{ 366479968Sobrien char *new = strdup(s); 366579968Sobrien 366679968Sobrien if (new == NULL) 366779968Sobrien fatal("Local resource failure: malloc"); 366879968Sobrien /* NOTREACHED */ 366979968Sobrien return (new); 367079968Sobrien} 367179968Sobrien 367279968Sobrien/* 367379968Sobrien * As per fprintf(), but increment total_bytes and total_bytes_out, 367479968Sobrien * by the appropriate amount. 367579968Sobrien */ 367679968Sobrienvoid 367779968Sobriencprintf(FILE *fd, const char *fmt, ...) 367879968Sobrien{ 367979968Sobrien off_t b; 368079968Sobrien va_list ap; 368179968Sobrien 368279968Sobrien va_start(ap, fmt); 368379968Sobrien b = vfprintf(fd, fmt, ap); 368492282Sobrien va_end(ap); 368579968Sobrien total_bytes += b; 368679968Sobrien total_bytes_out += b; 368779968Sobrien} 3688161770Sobrien 3689161770Sobrien#ifdef USE_PAM 3690161770Sobrien/* 3691161770Sobrien * the following code is stolen from imap-uw PAM authentication module and 3692161770Sobrien * login.c 3693161770Sobrien */ 3694161770Sobrien#define COPY_STRING(s) (s ? strdup(s) : NULL) 3695161770Sobrien 3696161770Sobrienstruct cred_t { 3697161770Sobrien const char *uname; /* user name */ 3698161770Sobrien const char *pass; /* password */ 3699161770Sobrien}; 3700161770Sobrientypedef struct cred_t cred_t; 3701161770Sobrien 3702161770Sobrienstatic int 3703161770Sobrienauth_conv(int num_msg, const struct pam_message **msg, 3704161770Sobrien struct pam_response **resp, void *appdata) 3705161770Sobrien{ 3706161770Sobrien int i; 3707161770Sobrien cred_t *cred = (cred_t *) appdata; 3708161770Sobrien struct pam_response *myreply; 3709161770Sobrien 3710161770Sobrien myreply = calloc(num_msg, sizeof *myreply); 3711161770Sobrien if (myreply == NULL) 3712161770Sobrien return PAM_BUF_ERR; 3713161770Sobrien 3714161770Sobrien for (i = 0; i < num_msg; i++) { 3715161770Sobrien switch (msg[i]->msg_style) { 3716161770Sobrien case PAM_PROMPT_ECHO_ON: /* assume want user name */ 3717161770Sobrien myreply[i].resp_retcode = PAM_SUCCESS; 3718161770Sobrien myreply[i].resp = COPY_STRING(cred->uname); 3719161770Sobrien /* PAM frees resp. */ 3720161770Sobrien break; 3721161770Sobrien case PAM_PROMPT_ECHO_OFF: /* assume want password */ 3722161770Sobrien myreply[i].resp_retcode = PAM_SUCCESS; 3723161770Sobrien myreply[i].resp = COPY_STRING(cred->pass); 3724161770Sobrien /* PAM frees resp. */ 3725161770Sobrien break; 3726161770Sobrien case PAM_TEXT_INFO: 3727161770Sobrien case PAM_ERROR_MSG: 3728161770Sobrien myreply[i].resp_retcode = PAM_SUCCESS; 3729161770Sobrien myreply[i].resp = NULL; 3730161770Sobrien break; 3731161770Sobrien default: /* unknown message style */ 3732161770Sobrien free(myreply); 3733161770Sobrien return PAM_CONV_ERR; 3734161770Sobrien } 3735161770Sobrien } 3736161770Sobrien 3737161770Sobrien *resp = myreply; 3738161770Sobrien return PAM_SUCCESS; 3739161770Sobrien} 3740161770Sobrien 3741161770Sobrien/* 3742161770Sobrien * Attempt to authenticate the user using PAM. Returns 0 if the user is 3743161770Sobrien * authenticated, or 1 if not authenticated. If some sort of PAM system 3744161770Sobrien * error occurs (e.g., the "/etc/pam.conf" file is missing) then this 3745161770Sobrien * function returns -1. This can be used as an indication that we should 3746161770Sobrien * fall back to a different authentication mechanism. 3747161770Sobrien */ 3748161770Sobrienstatic int 3749161770Sobrienauth_pam(struct passwd **ppw, const char *pwstr) 3750161770Sobrien{ 3751161770Sobrien const char *tmpl_user; 3752161770Sobrien const void *item; 3753161770Sobrien int rval; 3754161770Sobrien int e; 3755161770Sobrien cred_t auth_cred = { (*ppw)->pw_name, pwstr }; 3756161770Sobrien struct pam_conv conv = { &auth_conv, &auth_cred }; 3757161770Sobrien 3758161770Sobrien e = pam_start("ftpd", (*ppw)->pw_name, &conv, &pamh); 3759161770Sobrien if (e != PAM_SUCCESS) { 3760161770Sobrien /* 3761161770Sobrien * In OpenPAM, it's OK to pass NULL to pam_strerror() 3762161770Sobrien * if context creation has failed in the first place. 3763161770Sobrien */ 3764161770Sobrien syslog(LOG_ERR, "pam_start: %s", pam_strerror(NULL, e)); 3765161770Sobrien return -1; 3766161770Sobrien } 3767161770Sobrien 3768161770Sobrien e = pam_set_item(pamh, PAM_RHOST, remotehost); 3769161770Sobrien if (e != PAM_SUCCESS) { 3770161770Sobrien syslog(LOG_ERR, "pam_set_item(PAM_RHOST): %s", 3771161770Sobrien pam_strerror(pamh, e)); 3772161770Sobrien if ((e = pam_end(pamh, e)) != PAM_SUCCESS) { 3773161770Sobrien syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e)); 3774161770Sobrien } 3775161770Sobrien pamh = NULL; 3776161770Sobrien return -1; 3777161770Sobrien } 3778161770Sobrien 3779161770Sobrien e = pam_authenticate(pamh, 0); 3780161770Sobrien switch (e) { 3781161770Sobrien case PAM_SUCCESS: 3782161770Sobrien /* 3783161770Sobrien * With PAM we support the concept of a "template" 3784161770Sobrien * user. The user enters a login name which is 3785161770Sobrien * authenticated by PAM, usually via a remote service 3786161770Sobrien * such as RADIUS or TACACS+. If authentication 3787161770Sobrien * succeeds, a different but related "template" name 3788161770Sobrien * is used for setting the credentials, shell, and 3789161770Sobrien * home directory. The name the user enters need only 3790161770Sobrien * exist on the remote authentication server, but the 3791161770Sobrien * template name must be present in the local password 3792161770Sobrien * database. 3793161770Sobrien * 3794161770Sobrien * This is supported by two various mechanisms in the 3795161770Sobrien * individual modules. However, from the application's 3796161770Sobrien * point of view, the template user is always passed 3797161770Sobrien * back as a changed value of the PAM_USER item. 3798161770Sobrien */ 3799161770Sobrien if ((e = pam_get_item(pamh, PAM_USER, &item)) == 3800161770Sobrien PAM_SUCCESS) { 3801161770Sobrien tmpl_user = (const char *) item; 3802161770Sobrien if (strcmp((*ppw)->pw_name, tmpl_user) != 0) 3803161770Sobrien *ppw = sgetpwnam(tmpl_user); 3804161770Sobrien } else 3805161770Sobrien syslog(LOG_ERR, "Couldn't get PAM_USER: %s", 3806161770Sobrien pam_strerror(pamh, e)); 3807161770Sobrien rval = 0; 3808161770Sobrien break; 3809161770Sobrien 3810161770Sobrien case PAM_AUTH_ERR: 3811161770Sobrien case PAM_USER_UNKNOWN: 3812161770Sobrien case PAM_MAXTRIES: 3813161770Sobrien rval = 1; 3814161770Sobrien break; 3815161770Sobrien 3816161770Sobrien default: 3817161770Sobrien syslog(LOG_ERR, "pam_authenticate: %s", pam_strerror(pamh, e)); 3818161770Sobrien rval = -1; 3819161770Sobrien break; 3820161770Sobrien } 3821161770Sobrien 3822161770Sobrien if (rval == 0) { 3823161770Sobrien e = pam_acct_mgmt(pamh, 0); 3824161770Sobrien if (e != PAM_SUCCESS) { 3825161770Sobrien syslog(LOG_ERR, "pam_acct_mgmt: %s", 3826161770Sobrien pam_strerror(pamh, e)); 3827161770Sobrien rval = 1; 3828161770Sobrien } 3829161770Sobrien } 3830161770Sobrien 3831161770Sobrien if (rval != 0) { 3832161770Sobrien if ((e = pam_end(pamh, e)) != PAM_SUCCESS) { 3833161770Sobrien syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e)); 3834161770Sobrien } 3835161770Sobrien pamh = NULL; 3836161770Sobrien } 3837161770Sobrien return rval; 3838161770Sobrien} 3839161770Sobrien 3840161770Sobrien#endif /* USE_PAM */ 3841