opieftpd.c revision 39012
122347Spst/* opieftpd.c: Main program for an FTP daemon. 222347Spst 329964Sache%%% portions-copyright-cmetz-96 429964SachePortions of this software are Copyright 1996-1997 by Craig Metz, All Rights 522347SpstReserved. The Inner Net License Version 2 applies to these portions of 622347Spstthe software. 722347SpstYou should have received a copy of the license with this software. If 822347Spstyou didn't get a copy, you may request one from <license@inner.net>. 922347Spst 1022347SpstPortions of this software are Copyright 1995 by Randall Atkinson and Dan 1122347SpstMcDonald, All Rights Reserved. All Rights under this copyright are assigned 1222347Spstto the U.S. Naval Research Laboratory (NRL). The NRL Copyright Notice and 1322347SpstLicense Agreement applies to this software. 1422347Spst 1522347Spst History: 1622347Spst 1729964Sache Modified by cmetz for OPIE 2.31. Merged in some 4.4BSD-Lite changes. 1829964Sache Merged in a security fix to BSD-derived ftpds. 1922347Spst Modified by cmetz for OPIE 2.3. Fixed the filename at the top. 2022347Spst Moved LS_COMMAND here. 2122347Spst Modified by cmetz for OPIE 2.2. Use FUNCTION definition et al. 2222347Spst Removed useless strings (I don't think that removing the 2322347Spst ucb copyright one is a problem -- please let me know if 2422347Spst I'm wrong). Changed default CMASK to 077. Removed random 2522347Spst comments. Use ANSI stdargs for reply/lreply if we can, 2622347Spst added stdargs version of reply/lreply. Don't declare the 2722347Spst tos variable unless IP_TOS defined. Include stdargs headers 2822347Spst early. More headers ifdefed. Made everything static. 2922347Spst Got rid of gethostname() call and use of hostname. Pared 3022347Spst down status response for places where header files frequently 3122347Spst cause trouble. Made logging of user logins (ala -l) 3222347Spst non-optional. Moved reply()/lrepy(). Fixed some prototypes. 3322347Spst Modified at NRL for OPIE 2.1. Added declaration of envp. Discard 3422347Spst result of opiechallenge (allows access control to work). 3522347Spst Added patches for AIX. Symbol changes for autoconf. 3622347Spst Modified at NRL for OPIE 2.01. Changed password lookup handling 3722347Spst to avoid problems with drain-bamaged shadow password packages. 3822347Spst Properly handle internal state for anonymous FTP. Unlock 3922347Spst user accounts properly if login fails because of /etc/shells. 4022347Spst Make sure to close syslog by function to avoid problems with 4122347Spst drain bamaged syslog implementations. 4222347Spst Modified at NRL for OPIE 2.0. 4322347Spst Originally from BSD Net/2. 4422347Spst 4522347Spst There is some really, really ugly code in here. 4622347Spst*/ 4722347Spst/* 4822347Spst * Copyright (c) 1985, 1988, 1990 Regents of the University of California. 4922347Spst * All rights reserved. 5022347Spst * 5122347Spst * Redistribution and use in source and binary forms, with or without 5222347Spst * modification, are permitted provided that the following conditions 5322347Spst * are met: 5422347Spst * 1. Redistributions of source code must retain the above copyright 5522347Spst * notice, this list of conditions and the following disclaimer. 5622347Spst * 2. Redistributions in binary form must reproduce the above copyright 5722347Spst * notice, this list of conditions and the following disclaimer in the 5822347Spst * documentation and/or other materials provided with the distribution. 5922347Spst * 3. All advertising materials mentioning features or use of this software 6022347Spst * must display the following acknowledgement: 6122347Spst * This product includes software developed by the University of 6222347Spst * California, Berkeley and its contributors. 6322347Spst * 4. Neither the name of the University nor the names of its contributors 6422347Spst * may be used to endorse or promote products derived from this software 6522347Spst * without specific prior written permission. 6622347Spst * 6722347Spst * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 6822347Spst * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 6922347Spst * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 7022347Spst * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 7122347Spst * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 7222347Spst * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 7322347Spst * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 7422347Spst * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 7522347Spst * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 7622347Spst * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 7722347Spst * SUCH DAMAGE. 7822347Spst */ 7922347Spst 8022347Spst#include "opie_cfg.h" 8122347Spst 8222347Spst#if HAVE_ANSISTDARG 8322347Spst#include <stdarg.h> 8422347Spst#endif /* HAVE_ANSISTDARG */ 8522347Spst 8622347Spst/* 8722347Spst * FTP server. 8822347Spst */ 8922347Spst 9022347Spst#if HAVE_SYS_PARAM_H 9122347Spst#include <sys/param.h> 9222347Spst#endif /* HAVE_SYS_PARAM_H */ 9322347Spst#include <sys/stat.h> 9422347Spst/* #include <sys/ioctl.h> */ 9522347Spst#include <sys/socket.h> 9622347Spst#include <sys/wait.h> 9722347Spst#ifdef SYS_FCNTL_H 9822347Spst#include <sys/fcntl.h> 9922347Spst#else 10022347Spst#include <fcntl.h> 10122347Spst#endif /* SYS_FCNTL_H */ 10222347Spst#include <sys/types.h> 10322347Spst 10422347Spst#include <netinet/in.h> 10522347Spst#include <netinet/in_systm.h> 10622347Spst#include <netinet/ip.h> 10722347Spst 10822347Spst#define FTP_NAMES 10922347Spst#include <arpa/ftp.h> 11022347Spst#include <arpa/inet.h> 11122347Spst#include <arpa/telnet.h> 11222347Spst 11322347Spst#include <signal.h> 11422347Spst#include <dirent.h> 11522347Spst#include <fcntl.h> 11622347Spst#if HAVE_TIME_H 11722347Spst#include <time.h> 11822347Spst#endif /* HAVE_TIME_H */ 11922347Spst#if HAVE_PWD_H 12022347Spst#include <pwd.h> 12122347Spst#endif /* HAVE_PWD_H */ 12222347Spst#include <setjmp.h> 12322347Spst#include <netdb.h> 12422347Spst#include <errno.h> 12522347Spst#include <syslog.h> 12622347Spst#if HAVE_UNISTD_H 12722347Spst#include <unistd.h> 12822347Spst#endif /* HAVE_UNISTD_H */ 12922347Spst#include <stdio.h> 13022347Spst#include <ctype.h> 13122347Spst#include <stdlib.h> 13222347Spst#include <string.h> 13322347Spst#include <grp.h> 13422347Spst 13522347Spst#include "opie.h" 13622347Spst 13722347Spst#if HAVE_SHADOW_H 13822347Spst#include <shadow.h> 13922347Spst#endif /* HAVE_SHADOW_H */ 14022347Spst 14122347Spst#if HAVE_CRYPT_H 14222347Spst#include <crypt.h> 14322347Spst#endif /* HAVE_CRYPT_H */ 14422347Spst 14522347Spst#if HAVE_SYS_UTSNAME_H 14622347Spst#include <sys/utsname.h> 14722347Spst#endif /* HAVE_SYS_UTSNAME_H */ 14822347Spst 14922347Spst#ifdef _AIX 15022347Spst#include <sys/id.h> 15122347Spst#include <sys/priv.h> 15222347Spst#endif /* _AIX */ 15322347Spst 15422347Spst#ifdef IP_TOS 15522347Spst#ifndef IPTOS_THROUGHPUT 15622347Spst#undef IP_TOS 15722347Spst#endif /* !IPTOS_THROUGHPUT */ 15822347Spst#ifndef IPTOS_LOWDELAY 15922347Spst#undef IP_TOS 16022347Spst#endif /* !IPTOS_LOWDELAY */ 16122347Spst#endif /* IP_TOS */ 16222347Spst 16322347Spstextern int errno; 16422347Spstextern char *home; /* pointer to home directory for glob */ 16522347Spstextern FILE *ftpd_popen __P((char *, char *)); 16622347Spstextern int ftpd_pclose __P((FILE *)); 16722347Spstextern char cbuf[]; 16822347Spstextern off_t restart_point; 16922347Spst 17022347Spststatic struct sockaddr_in ctrl_addr; 17122347Spststatic struct sockaddr_in data_source; 17222347Spststruct sockaddr_in data_dest; 17322347Spststruct sockaddr_in his_addr; 17422347Spststatic struct sockaddr_in pasv_addr; 17522347Spst 17622347Spststatic int data; 17722347Spstjmp_buf errcatch; 17822347Spststatic jmp_buf urgcatch; 17922347Spstint logged_in; 18022347Spststruct passwd *pw; 18122347Spstint debug; 18222347Spstint timeout = 900; /* timeout after 15 minutes of inactivity */ 18322347Spstint maxtimeout = 7200; /* don't allow idle time to be set beyond 2 hours */ 18422347Spst 18522347Spst#if DOANONYMOUS 18622347Spststatic int guest; 18722347Spst#endif /* DOANONYMOUS */ 18822347Spstint type; 18922347Spstint form; 19022347Spststatic int stru; /* avoid C keyword */ 19122347Spststatic int mode; 19222347Spstint usedefault = 1; /* for data transfers */ 19322347Spstint pdata = -1; /* for passive mode */ 19422347Spststatic int transflag; 19522347Spststatic off_t file_size; 19622347Spststatic off_t byte_count; 19722347Spst 19822347Spst#if (!defined(CMASK) || CMASK == 0) 19922347Spst#undef CMASK 20022347Spst#define CMASK 077 20122347Spst#endif 20222347Spst 20322347Spststatic int defumask = CMASK; /* default umask value */ 20422347Spstchar tmpline[7]; 20522347Spstchar remotehost[MAXHOSTNAMELEN]; 20622347Spst 20722347Spst/* 20822347Spst * Timeout intervals for retrying connections 20922347Spst * to hosts that don't accept PORT cmds. This 21022347Spst * is a kludge, but given the problems with TCP... 21122347Spst */ 21222347Spst#define SWAITMAX 90 /* wait at most 90 seconds */ 21322347Spst#define SWAITINT 5 /* interval between retries */ 21422347Spst 21522347Spststatic int swaitmax = SWAITMAX; 21622347Spststatic int swaitint = SWAITINT; 21722347Spst 21822347Spst#if DOTITLE 21922347Spststatic char **Argv = NULL; /* pointer to argument vector */ 22022347Spststatic char *LastArgv = NULL; /* end of argv */ 22122347Spststatic char proctitle[BUFSIZ]; /* initial part of title */ 22222347Spst#endif /* DOTITLE */ 22322347Spst 22422347Spststatic int af_pwok = 0, pwok = 0; 22522347Spststatic struct opie opiestate; 22622347Spst 22722347SpstVOIDRET perror_reply __P((int, char *)); 22822347SpstVOIDRET dologout __P((int)); 22922347Spstchar *getline __P((char *, int, FILE *)); 23022347SpstVOIDRET upper __P((char *)); 23122347Spst 23222347Spststatic VOIDRET lostconn __P((int)); 23329964Sachestatic VOIDRET myoob __P((int)); 23422347Spststatic FILE *getdatasock __P((char *)); 23522347Spststatic FILE *dataconn __P((char *, off_t, char *)); 23622347Spststatic int checkuser __P((char *)); 23722347Spststatic VOIDRET end_login __P((void)); 23822347Spststatic VOIDRET send_data __P((FILE *, FILE *, off_t)); 23922347Spststatic int receive_data __P((FILE *, FILE *)); 24022347Spststatic char *gunique __P((char *)); 24122347Spststatic char *sgetsave __P((char *)); 24222347Spst 24329964Sacheint opielogwtmp __P((char *, char *, char *)); 24422347Spst 24522347Spstint fclose __P((FILE *)); 24622347Spst 24722347Spst#ifdef HAVE_ANSISTDARG 24822347SpstVOIDRET reply FUNCTION((stdarg is ANSI only), int n AND char *fmt AND ...) 24922347Spst{ 25022347Spst va_list ap; 25122347Spst char buffer[1024]; 25222347Spst 25322347Spst va_start(ap, fmt); 25422347Spst vsprintf(buffer, fmt, ap); 25522347Spst va_end(ap); 25622347Spst 25722347Spst printf("%d %s\r\n", n, buffer); 25822347Spst fflush(stdout); 25922347Spst if (debug) 26022347Spst syslog(LOG_DEBUG, "<--- %d %s", n, buffer); 26122347Spst} 26222347Spst#else /* HAVE_ANSISTDARG */ 26322347SpstVOIDRET reply FUNCTION((n, fmt, p0, p1, p2, p3, p4, p5), int n AND char *fmt AND int p0 AND int p1 AND int p2 AND int p3 AND int p4 AND int p5) 26422347Spst{ 26522347Spst printf("%d ", n); 26622347Spst printf(fmt, p0, p1, p2, p3, p4, p5); 26722347Spst printf("\r\n"); 26822347Spst fflush(stdout); 26922347Spst if (debug) { 27022347Spst syslog(LOG_DEBUG, "<--- %d ", n); 27122347Spst syslog(LOG_DEBUG, fmt, p0, p1, p2, p3, p4, p5); 27222347Spst } 27322347Spst} 27422347Spst#endif /* HAVE_ANSISTDARG */ 27522347Spst 27622347Spst#ifdef HAVE_ANSISTDARG 27722347SpstVOIDRET lreply FUNCTION((stdarg is ANSI only), int n AND char *fmt AND ...) 27822347Spst{ 27922347Spst va_list ap; 28022347Spst char buffer[1024]; 28122347Spst 28222347Spst va_start(ap, fmt); 28322347Spst vsprintf(buffer, fmt, ap); 28422347Spst va_end(ap); 28522347Spst 28622347Spst printf("%d- %s\r\n", n, buffer); 28722347Spst fflush(stdout); 28822347Spst if (debug) 28922347Spst syslog(LOG_DEBUG, "<--- %d- %s", n, buffer); 29022347Spst} 29122347Spst#else /* HAVE_ANSISTDARG */ 29222347SpstVOIDRET lreply FUNCTION((n, fmt, p0, p1, p2, p3, p4, p5), int n AND char *fmt AND int p0 AND int p1 AND int p2 AND int p3 AND int p4 AND int p5) 29322347Spst{ 29422347Spst printf("%d- ", n); 29522347Spst printf(fmt, p0, p1, p2, p3, p4, p5); 29622347Spst printf("\r\n"); 29722347Spst fflush(stdout); 29822347Spst if (debug) { 29922347Spst syslog(LOG_DEBUG, "<--- %d- ", n); 30022347Spst syslog(LOG_DEBUG, fmt, p0, p1, p2, p3, p4, p5); 30122347Spst } 30222347Spst} 30322347Spst#endif /* HAVE_ANSISTDARG */ 30422347Spst 30529964SacheVOIDRET enable_signalling FUNCTION_NOARGS 30629964Sache{ 30729964Sache signal(SIGPIPE, lostconn); 30829964Sache if ((int)signal(SIGURG, myoob) < 0) 30929964Sache syslog(LOG_ERR, "signal: %m"); 31029964Sache} 31129964Sache 31229964SacheVOIDRET disable_signalling FUNCTION_NOARGS 31329964Sache{ 31429964Sache signal(SIGPIPE, SIG_IGN); 31529964Sache if ((int)signal(SIGURG, SIG_IGN) < 0) 31629964Sache syslog(LOG_ERR, "signal: %m"); 31729964Sache} 31829964Sache 31922347Spststatic VOIDRET lostconn FUNCTION((input), int input) 32022347Spst{ 32122347Spst if (debug) 32222347Spst syslog(LOG_DEBUG, "lost connection"); 32322347Spst dologout(-1); 32422347Spst} 32522347Spst 32622347Spststatic char ttyline[20]; 32722347Spst 32822347Spst/* 32922347Spst * Helper function for sgetpwnam(). 33022347Spst */ 33122347Spststatic char *sgetsave FUNCTION((s), char *s) 33222347Spst{ 33322347Spst char *new = malloc((unsigned) strlen(s) + 1); 33422347Spst 33522347Spst if (new == NULL) { 33622347Spst perror_reply(421, "Local resource failure: malloc"); 33722347Spst dologout(1); 33822347Spst /* NOTREACHED */ 33922347Spst } 34022347Spst strcpy(new, s); 34122347Spst return (new); 34222347Spst} 34322347Spst 34422347Spst/* 34522347Spst * Save the result of a getpwnam. Used for USER command, since 34622347Spst * the data returned must not be clobbered by any other command 34722347Spst * (e.g., globbing). 34822347Spst */ 34922347Spststatic struct passwd *sgetpwnam FUNCTION((name), char *name) 35022347Spst{ 35122347Spst static struct passwd save; 35222347Spst register struct passwd *p; 35322347Spst 35422347Spst#if HAVE_SHADOW 35522347Spst struct spwd *spwd; 35622347Spst#endif /* HAVE_SHADOW */ 35722347Spst 35822347Spst if ((p = getpwnam(name)) == NULL) 35922347Spst return (p); 36022347Spst 36122347Spst#if HAVE_SHADOW 36222347Spst if ((spwd = getspnam(name)) == NULL) 36322347Spst return NULL; 36422347Spst 36522347Spst endspent(); 36622347Spst 36722347Spst p->pw_passwd = spwd->sp_pwdp; 36822347Spst#endif /* HAVE_SHADOW */ 36922347Spst 37022347Spst endpwent(); 37122347Spst 37222347Spst if (save.pw_name) { 37322347Spst free(save.pw_name); 37422347Spst free(save.pw_passwd); 37522347Spst free(save.pw_gecos); 37622347Spst free(save.pw_dir); 37722347Spst free(save.pw_shell); 37822347Spst } 37922347Spst save = *p; 38022347Spst save.pw_name = sgetsave(p->pw_name); 38122347Spst save.pw_passwd = sgetsave(p->pw_passwd); 38222347Spst save.pw_gecos = sgetsave(p->pw_gecos); 38322347Spst save.pw_dir = sgetsave(p->pw_dir); 38422347Spst save.pw_shell = sgetsave(p->pw_shell); 38522347Spst return (&save); 38622347Spst} 38722347Spst 38822347Spstint login_attempts; /* number of failed login attempts */ 38922347Spstint askpasswd; /* had user command, ask for passwd */ 39022347Spst 39122347Spst/* 39222347Spst * USER command. 39322347Spst * Sets global passwd pointer pw if named account exists and is acceptable; 39422347Spst * sets askpasswd if a PASS command is expected. If logged in previously, 39522347Spst * need to reset state. If name is "ftp" or "anonymous", the name is not in 39622347Spst * _PATH_FTPUSERS, and ftp account exists, set guest and pw, then just return. 39722347Spst * If account doesn't exist, ask for passwd anyway. Otherwise, check user 39822347Spst * requesting login privileges. Disallow anyone who does not have a standard 39922347Spst * shell as returned by getusershell(). Disallow anyone mentioned in the file 40022347Spst * _PATH_FTPUSERS to allow people such as root and uucp to be avoided. 40122347Spst */ 40222347Spstint user FUNCTION((name), char *name) 40322347Spst{ 40422347Spst register char *cp; 40522347Spst char *shell; 40622347Spst 40722347Spst if (logged_in) { 40822347Spst#if DOANONYMOUS 40922347Spst if (guest) { 41022347Spst reply(530, "Can't change user from guest login."); 41122347Spst return -1; 41222347Spst } 41322347Spst#endif /* DOANONMOUS */ 41422347Spst end_login(); 41522347Spst } 41622347Spst askpasswd = 1; 41722347Spst#if DOANONYMOUS 41822347Spst guest = 0; 41922347Spst if (!strcmp(name, "ftp") || !strcmp(name, "anonymous")) 42022347Spst if (!checkuser("ftp") && !checkuser("anonymous")) 42122347Spst if ((pw = sgetpwnam("ftp")) != NULL) { 42222347Spst guest = 1; 42322347Spst askpasswd = 1; 42429964Sache reply(331, "Guest login ok, send your e-mail address as your password."); 42529964Sache syslog(LOG_INFO, "Anonymous FTP connection made from host %s.", remotehost); 42622347Spst return 0; 42722347Spst } 42822347Spst#endif /* DOANONYMOUS */ 42922347Spst if (pw = sgetpwnam(name)) { 43022347Spst if ((shell = pw->pw_shell) == NULL || *shell == 0) 43122347Spst shell = _PATH_BSHELL; 43222347Spst while ((cp = getusershell()) != NULL) 43322347Spst if (!strcmp(cp, shell)) 43422347Spst break; 43522347Spst endusershell(); 43629964Sache if (cp == NULL || checkuser(name) || ((pw->pw_passwd[0] == '*') || (pw->pw_passwd[0] == '#'))) { 43722347Spst#if DEBUG 43822347Spst if (!cp) 43922347Spst syslog(LOG_DEBUG, "Couldn't find %s in the list of valid shells.", pw->pw_shell); 44022347Spst if (checkuser(name)) 44122347Spst syslog(LOG_DEBUG, "checkuser failed - user in /etc/ftpusers?"); 44222347Spst if (((pw->pw_passwd[0] == '*') || (pw->pw_passwd[0] == '#'))) 44322347Spst syslog(LOG_DEBUG, "Login disabled: pw_passwd == %s", pw->pw_passwd); 44422347Spst#endif /* DEBUG */ 44522347Spst pw = (struct passwd *) NULL; 44622347Spst askpasswd = -1; 44722347Spst } 44822347Spst } 44922347Spst { 45022347Spst char prompt[OPIE_CHALLENGE_MAX + 1]; 45122347Spst 45222347Spst opiechallenge(&opiestate, name, prompt); 45322347Spst 45422347Spst if (askpasswd == -1) { 45522347Spst syslog(LOG_WARNING, "Invalid FTP user name %s attempted from %s.", name, remotehost); 45622347Spst pwok = 0; 45722347Spst } else 45822347Spst pwok = af_pwok && opiealways(pw->pw_dir); 45922347Spst 46022347Spst#if NEW_PROMPTS 46122347Spst reply(331, "Response to %s %s for %s.", prompt, 46222347Spst#else /* NEW_PROMPTS */ 46322347Spst reply(331, "OTP response %s %s for %s.", prompt, 46422347Spst#endif /* NEW_PROMPTS */ 46522347Spst pwok ? "requested" : "required", name); 46622347Spst } 46722347Spst /* Delay before reading passwd after first failed attempt to slow down 46822347Spst passwd-guessing programs. */ 46922347Spst if (login_attempts) 47022347Spst sleep((unsigned) login_attempts); 47122347Spst 47222347Spst return 0; 47322347Spst} 47422347Spst 47522347Spst/* 47622347Spst * Check if a user is in the file _PATH_FTPUSERS 47722347Spst */ 47822347Spststatic int checkuser FUNCTION((name), char *name) 47922347Spst{ 48022347Spst register FILE *fd; 48122347Spst register char *p; 48222347Spst char line[BUFSIZ]; 48322347Spst 48422347Spst if ((fd = fopen(_PATH_FTPUSERS, "r")) != NULL) { 48522347Spst while (fgets(line, sizeof(line), fd) != NULL) 48622347Spst if ((p = strchr(line, '\n')) != NULL) { 48722347Spst *p = '\0'; 48822347Spst if (line[0] == '#') 48922347Spst continue; 49029964Sache if (!strcmp(line, name)) { 49129964Sache fclose(fd); 49222347Spst return (1); 49329964Sache } 49422347Spst } 49522347Spst fclose(fd); 49622347Spst } 49722347Spst return (0); 49822347Spst} 49922347Spst 50022347Spst/* 50122347Spst * Terminate login as previous user, if any, resetting state; 50222347Spst * used when USER command is given or login fails. 50322347Spst */ 50422347Spststatic VOIDRET end_login FUNCTION_NOARGS 50522347Spst{ 50629964Sache disable_signalling(); 50722347Spst if (seteuid((uid_t) 0)) 50822347Spst syslog(LOG_ERR, "Can't set euid"); 50922347Spst if (logged_in) 51029964Sache opielogwtmp(ttyline, "", ""); 51122347Spst pw = NULL; 51222347Spst logged_in = 0; 51322347Spst#if DOANONYMOUS 51422347Spst guest = 0; 51522347Spst#endif /* DOANONYMOUS */ 51629964Sache enable_signalling(); 51722347Spst} 51822347Spst 51922347SpstVOIDRET pass FUNCTION((passwd), char *passwd) 52022347Spst{ 52122347Spst int legit = askpasswd + 1, i; 52222347Spst 52322347Spst if (logged_in || askpasswd == 0) { 52422347Spst reply(503, "Login with USER first."); 52522347Spst return; 52622347Spst } 52722347Spst askpasswd = 0; 52822347Spst 52922347Spst#if DOANONYMOUS 53022347Spst if (!guest) { /* "ftp" is only account allowed no password */ 53122347Spst#endif /* DOANONYMOUS */ 53222347Spst i = opieverify(&opiestate, passwd); 53322347Spst if (legit && i && pwok) 53422347Spst i = strcmp(crypt(passwd, pw->pw_passwd), pw->pw_passwd); 53522347Spst if (!legit || i) { 53622347Spst reply(530, "Login incorrect."); 53722347Spst pw = NULL; 53822347Spst if (login_attempts++ >= 5) { 53922347Spst syslog(LOG_WARNING, 54022347Spst "Repeated login failures for user %s from %s", 54122347Spst pw->pw_name, remotehost); 54222347Spst exit(0); 54322347Spst } 54422347Spst return; 54522347Spst } 54622347Spst#if DOANONYMOUS 54729964Sache } else 54829964Sache if ((passwd[0] <= ' ') || checkuser(passwd)) { 54929964Sache reply(530, "No identity, no service."); 55029964Sache syslog(LOG_DEBUG, "Bogus address: %s", passwd); 55129964Sache exit(0); 55229964Sache } 55322347Spst#endif /* DOANONYMOUS */ 55422347Spst login_attempts = 0; /* this time successful */ 55529964Sache if (setegid((gid_t) pw->pw_gid) < 0) { 55629964Sache reply(550, "Can't set gid."); 55729964Sache syslog(LOG_DEBUG, "gid = %d, errno = %s(%d)", pw->pw_gid, strerror(errno), errno); 55829964Sache return; 55929964Sache } 56022347Spst initgroups(pw->pw_name, pw->pw_gid); 56122347Spst 56222347Spst /* open wtmp before chroot */ 56322347Spst sprintf(ttyline, "ftp%d", getpid()); 56429964Sache opielogwtmp(ttyline, pw->pw_name, remotehost); 56522347Spst logged_in = 1; 56622347Spst 56722347Spst#if DOANONYMOUS 56822347Spst if (guest) { 56922347Spst /* We MUST do a chdir() after the chroot. Otherwise the old current 57022347Spst directory will be accessible as "." outside the new root! */ 57122347Spst if (chroot(pw->pw_dir) < 0 || chdir("/") < 0) { 57222347Spst reply(550, "Can't set guest privileges."); 57322347Spst goto bad; 57422347Spst } 57522347Spst } else 57622347Spst#endif /* DOANONYMOUS */ 57722347Spst if (chdir(pw->pw_dir) < 0) { 57822347Spst if (chdir("/") < 0) { 57922347Spst reply(530, "User %s: can't change directory to %s.", 58022347Spst pw->pw_name, pw->pw_dir); 58122347Spst goto bad; 58222347Spst } else 58322347Spst lreply(230, "No directory! Logging in with home=/"); 58422347Spst } 58522347Spst/* This patch was contributed by an OPIE user. We don't know what it 58622347Spst does, exactly. It may or may not work. */ 58722347Spst#ifdef _AIX 58822347Spst { 58922347Spst priv_t priv; 59022347Spst priv.pv_priv[0] = 0; 59122347Spst priv.pv_priv[1] = 0; 59222347Spst setgroups(NULL, NULL); 59322347Spst if (setpriv(PRIV_SET|PRIV_INHERITED|PRIV_EFFECTIVE|PRIV_BEQUEATH, 59422347Spst &priv, sizeof(priv_t)) < 0 || 59522347Spst setgidx(ID_REAL|ID_EFFECTIVE, (gid_t)pw->pw_gid) < 0 || 59622347Spst setuidx(ID_REAL|ID_EFFECTIVE, (uid_t)pw->pw_uid) < 0 || 59722347Spst seteuid((uid_t)pw->pw_uid) < 0) { 59822347Spst reply(550, "Can't set uid (_AIX3)."); 59922347Spst goto bad; 60022347Spst } 60122347Spst } 60222347Spst#else /* _AIX */ 60322347Spst if (seteuid((uid_t) pw->pw_uid) < 0) { 60422347Spst reply(550, "Can't set uid."); 60522347Spst goto bad; 60622347Spst } 60722347Spst#endif /* _AIX */ 60829964Sache /* 60929964Sache * Display a login message, if it exists. 61029964Sache * N.B. reply(230,) must follow the message. 61129964Sache */ 61229964Sache { 61329964Sache FILE *fd; 61429964Sache 61529964Sache if ((fd = fopen(_PATH_FTPLOGINMESG, "r")) != NULL) { 61629964Sache char *cp, line[128]; 61729964Sache 61829964Sache while (fgets(line, sizeof(line), fd) != NULL) { 61929964Sache if ((cp = strchr(line, '\n')) != NULL) 62029964Sache *cp = '\0'; 62129964Sache lreply(230, "%s", line); 62229964Sache } 62329964Sache (void) fflush(stdout); 62429964Sache (void) fclose(fd); 62529964Sache } 62629964Sache } 62722347Spst#if DOANONYMOUS 62822347Spst if (guest) { 62922347Spst reply(230, "Guest login ok, access restrictions apply."); 63022347Spst#if DOTITLE 63139012Simp snprintf(proctitle, sizeof(proctitle), "%s: anonymous/%s", remotehost, 63239012Simp passwd); 63322347Spst setproctitle(proctitle); 63422347Spst#endif /* DOTITLE */ 63522347Spst syslog(LOG_NOTICE, "ANONYMOUS FTP login from %s with ID %s", 63622347Spst remotehost, passwd); 63722347Spst } else 63822347Spst#endif /* DOANONYMOUS */ 63922347Spst { 64022347Spst reply(230, "User %s logged in.", pw->pw_name); 64122347Spst 64222347Spst#if DOTITLE 64339012Simp snprintf(proctitle, sizeof(proctitle), "%s: %s", remotehost, pw->pw_name); 64422347Spst setproctitle(proctitle); 64522347Spst#endif /* DOTITLE */ 64629964Sache syslog(LOG_INFO, "FTP login from %s with user name %s", remotehost, pw->pw_name); 64722347Spst } 64822347Spst home = pw->pw_dir; /* home dir for globbing */ 64922347Spst umask(defumask); 65022347Spst return; 65122347Spst 65222347Spstbad: 65322347Spst /* Forget all about it... */ 65422347Spst end_login(); 65522347Spst} 65622347Spst 65722347SpstVOIDRET retrieve FUNCTION((cmd, name), char *cmd AND char *name) 65822347Spst{ 65922347Spst FILE *fin, *dout; 66022347Spst struct stat st; 66122347Spst int (*closefunc) (); 66222347Spst 66322347Spst if (cmd == 0) { 66422347Spst fin = fopen(name, "r"), closefunc = fclose; 66522347Spst st.st_size = 0; 66622347Spst } else { 66722347Spst char line[BUFSIZ]; 66822347Spst 66939012Simp snprintf(line, sizeof(line), cmd, name); 67039012Simp name = line; 67122347Spst fin = ftpd_popen(line, "r"), closefunc = ftpd_pclose; 67222347Spst st.st_size = -1; 67322347Spst#if HAVE_ST_BLKSIZE 67422347Spst st.st_blksize = BUFSIZ; 67522347Spst#endif /* HAVE_ST_BLKSIZE */ 67622347Spst } 67722347Spst if (fin == NULL) { 67822347Spst if (errno != 0) 67922347Spst perror_reply(550, name); 68022347Spst return; 68122347Spst } 68222347Spst if (cmd == 0 && 68322347Spst (fstat(fileno(fin), &st) < 0 || (st.st_mode & S_IFMT) != S_IFREG)) { 68422347Spst reply(550, "%s: not a plain file.", name); 68522347Spst goto done; 68622347Spst } 68722347Spst if (restart_point) { 68822347Spst if (type == TYPE_A) { 68922347Spst register int i, n, c; 69022347Spst 69122347Spst n = restart_point; 69222347Spst i = 0; 69322347Spst while (i++ < n) { 69422347Spst if ((c = getc(fin)) == EOF) { 69522347Spst perror_reply(550, name); 69622347Spst goto done; 69722347Spst } 69822347Spst if (c == '\n') 69922347Spst i++; 70022347Spst } 70122347Spst } else 70222347Spst if (lseek(fileno(fin), restart_point, SEEK_SET /* L_SET */ ) < 0) { 70322347Spst perror_reply(550, name); 70422347Spst goto done; 70522347Spst } 70622347Spst } 70722347Spst dout = dataconn(name, st.st_size, "w"); 70822347Spst if (dout == NULL) 70922347Spst goto done; 71022347Spst#if HAVE_ST_BLKSIZE 71122347Spst send_data(fin, dout, st.st_blksize); 71222347Spst#else /* HAVE_ST_BLKSIZE */ 71322347Spst send_data(fin, dout, BUFSIZ); 71422347Spst#endif /* HAVE_ST_BLKSIZE */ 71522347Spst fclose(dout); 71622347Spst data = -1; 71722347Spst pdata = -1; 71822347Spstdone: 71922347Spst (*closefunc) (fin); 72022347Spst} 72122347Spst 72222347SpstVOIDRET store FUNCTION((name, mode, unique), char *name AND char *mode AND int unique) 72322347Spst{ 72422347Spst FILE *fout, *din; 72522347Spst struct stat st; 72622347Spst int (*closefunc) (); 72722347Spst 72822347Spst if (unique && stat(name, &st) == 0 && 72922347Spst (name = gunique(name)) == NULL) 73022347Spst return; 73122347Spst 73222347Spst if (restart_point) 73322347Spst mode = "r+w"; 73422347Spst fout = fopen(name, mode); 73522347Spst closefunc = fclose; 73622347Spst if (fout == NULL) { 73722347Spst perror_reply(553, name); 73822347Spst return; 73922347Spst } 74022347Spst if (restart_point) { 74122347Spst if (type == TYPE_A) { 74222347Spst register int i, n, c; 74322347Spst 74422347Spst n = restart_point; 74522347Spst i = 0; 74622347Spst while (i++ < n) { 74722347Spst if ((c = getc(fout)) == EOF) { 74822347Spst perror_reply(550, name); 74922347Spst goto done; 75022347Spst } 75122347Spst if (c == '\n') 75222347Spst i++; 75322347Spst } 75422347Spst /* We must do this seek to "current" position because we are changing 75522347Spst from reading to writing. */ 75622347Spst if (fseek(fout, 0L, SEEK_CUR /* L_INCR */ ) < 0) { 75722347Spst perror_reply(550, name); 75822347Spst goto done; 75922347Spst } 76022347Spst } else 76122347Spst if (lseek(fileno(fout), restart_point, SEEK_SET /* L_SET */ ) < 0) { 76222347Spst perror_reply(550, name); 76322347Spst goto done; 76422347Spst } 76522347Spst } 76622347Spst din = dataconn(name, (off_t) - 1, "r"); 76722347Spst if (din == NULL) 76822347Spst goto done; 76922347Spst if (receive_data(din, fout) == 0) { 77022347Spst if (unique) 77122347Spst reply(226, "Transfer complete (unique file name:%s).", 77222347Spst name); 77322347Spst else 77422347Spst reply(226, "Transfer complete."); 77522347Spst } 77622347Spst fclose(din); 77722347Spst data = -1; 77822347Spst pdata = -1; 77922347Spstdone: 78022347Spst (*closefunc) (fout); 78122347Spst} 78222347Spst 78322347Spststatic FILE *getdatasock FUNCTION((mode), char *mode) 78422347Spst{ 78522347Spst int s, on = 1, tries; 78622347Spst 78722347Spst if (data >= 0) 78822347Spst return (fdopen(data, mode)); 78929964Sache disable_signalling(); 79022347Spst if (seteuid((uid_t) 0)) 79122347Spst syslog(LOG_ERR, "Can't set euid"); 79222347Spst s = socket(AF_INET, SOCK_STREAM, 0); 79322347Spst if (s < 0) 79422347Spst goto bad; 79522347Spst if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, 79622347Spst (char *) &on, sizeof(on)) < 0) 79722347Spst goto bad; 79822347Spst /* anchor socket to avoid multi-homing problems */ 79922347Spst data_source.sin_family = AF_INET; 80022347Spst data_source.sin_addr = ctrl_addr.sin_addr; 80122347Spst for (tries = 1;; tries++) { 80222347Spst if (bind(s, (struct sockaddr *) & data_source, 80322347Spst sizeof(data_source)) >= 0) 80422347Spst break; 80522347Spst if (errno != EADDRINUSE || tries > 10) 80622347Spst goto bad; 80722347Spst sleep(tries); 80822347Spst } 80922347Spst if (seteuid((uid_t) pw->pw_uid)) 81022347Spst syslog(LOG_ERR, "Can't set euid"); 81129964Sache enable_signalling(); 81222347Spst#ifdef IP_TOS 81322347Spst on = IPTOS_THROUGHPUT; 81422347Spst if (setsockopt(s, IPPROTO_IP, IP_TOS, (char *) &on, sizeof(int)) < 0) 81522347Spst syslog(LOG_WARNING, "setsockopt (IP_TOS): %m"); 81622347Spst#endif 81722347Spst return (fdopen(s, mode)); 81822347Spstbad: 81929964Sache { 82029964Sache int t = errno; 82129964Sache 82222347Spst if (seteuid((uid_t) pw->pw_uid)) 82322347Spst syslog(LOG_ERR, "Can't set euid"); 82429964Sache enable_signalling(); 82522347Spst close(s); 82629964Sache 82729964Sache errno = t; 82829964Sache } 82922347Spst return (NULL); 83022347Spst} 83122347Spst 83222347Spststatic FILE *dataconn FUNCTION((name, size, mode), char *name AND off_t size AND char *mode) 83322347Spst{ 83422347Spst char sizebuf[32]; 83522347Spst FILE *file; 83622347Spst int retry = 0; 83722347Spst#ifdef IP_TOS 83822347Spst int tos; 83922347Spst#endif /* IP_TOS */ 84022347Spst 84122347Spst file_size = size; 84222347Spst byte_count = 0; 84322347Spst if (size != (off_t) - 1) 84439012Simp snprintf(sizebuf, sizeof(sizebuf), " (%ld bytes)", size); 84522347Spst else 84622347Spst strcpy(sizebuf, ""); 84722347Spst if (pdata >= 0) { 84822347Spst struct sockaddr_in from; 84922347Spst int s, fromlen = sizeof(from); 85022347Spst 85122347Spst s = accept(pdata, (struct sockaddr *) & from, &fromlen); 85222347Spst if (s < 0) { 85322347Spst reply(425, "Can't open data connection."); 85422347Spst close(pdata); 85522347Spst pdata = -1; 85622347Spst return (NULL); 85722347Spst } 85822347Spst close(pdata); 85922347Spst pdata = s; 86022347Spst#ifdef IP_TOS 86122347Spst tos = IPTOS_LOWDELAY; 86222347Spst setsockopt(s, IPPROTO_IP, IP_TOS, (char *) &tos, 86322347Spst sizeof(int)); 86422347Spst 86522347Spst#endif 86622347Spst reply(150, "Opening %s mode data connection for %s%s.", 86722347Spst type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf); 86822347Spst return (fdopen(pdata, mode)); 86922347Spst } 87022347Spst if (data >= 0) { 87122347Spst reply(125, "Using existing data connection for %s%s.", 87222347Spst name, sizebuf); 87322347Spst usedefault = 1; 87422347Spst return (fdopen(data, mode)); 87522347Spst } 87622347Spst if (usedefault) 87722347Spst data_dest = his_addr; 87822347Spst usedefault = 1; 87922347Spst file = getdatasock(mode); 88022347Spst if (file == NULL) { 88122347Spst reply(425, "Can't create data socket (%s,%d): %s.", 88222347Spst inet_ntoa(data_source.sin_addr), 88322347Spst ntohs(data_source.sin_port), strerror(errno)); 88422347Spst return (NULL); 88522347Spst } 88622347Spst data = fileno(file); 88722347Spst while (connect(data, (struct sockaddr *) & data_dest, 88822347Spst sizeof(data_dest)) < 0) { 88922347Spst if (errno == EADDRINUSE && retry < swaitmax) { 89022347Spst sleep((unsigned) swaitint); 89122347Spst retry += swaitint; 89222347Spst continue; 89322347Spst } 89422347Spst perror_reply(425, "Can't build data connection"); 89522347Spst fclose(file); 89622347Spst data = -1; 89722347Spst return (NULL); 89822347Spst } 89922347Spst reply(150, "Opening %s mode data connection for %s%s.", 90022347Spst type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf); 90122347Spst return (file); 90222347Spst} 90322347Spst 90422347Spst/* 90522347Spst * Tranfer the contents of "instr" to 90622347Spst * "outstr" peer using the appropriate 90722347Spst * encapsulation of the data subject 90822347Spst * to Mode, Structure, and Type. 90922347Spst * 91022347Spst * NB: Form isn't handled. 91122347Spst */ 91222347Spststatic VOIDRET send_data FUNCTION((instr, outstr, blksize), FILE *instr AND FILE *outstr AND off_t blksize) 91322347Spst{ 91422347Spst register int c, cnt; 91522347Spst register char *buf; 91622347Spst int netfd, filefd; 91722347Spst 91822347Spst transflag++; 91922347Spst if (setjmp(urgcatch)) { 92022347Spst transflag = 0; 92122347Spst return; 92222347Spst } 92322347Spst switch (type) { 92422347Spst 92522347Spst case TYPE_A: 92622347Spst while ((c = getc(instr)) != EOF) { 92722347Spst byte_count++; 92822347Spst if (c == '\n') { 92922347Spst if (ferror(outstr)) 93022347Spst goto data_err; 93122347Spst putc('\r', outstr); 93222347Spst } 93322347Spst putc(c, outstr); 93422347Spst } 93522347Spst fflush(outstr); 93622347Spst transflag = 0; 93722347Spst if (ferror(instr)) 93822347Spst goto file_err; 93922347Spst if (ferror(outstr)) 94022347Spst goto data_err; 94122347Spst reply(226, "Transfer complete."); 94222347Spst return; 94322347Spst 94422347Spst case TYPE_I: 94522347Spst case TYPE_L: 94622347Spst if ((buf = malloc((u_int) blksize)) == NULL) { 94722347Spst transflag = 0; 94822347Spst perror_reply(451, "Local resource failure: malloc"); 94922347Spst return; 95022347Spst } 95122347Spst netfd = fileno(outstr); 95222347Spst filefd = fileno(instr); 95322347Spst while ((cnt = read(filefd, buf, (u_int) blksize)) > 0 && 95422347Spst write(netfd, buf, cnt) == cnt) 95522347Spst byte_count += cnt; 95622347Spst transflag = 0; 95722347Spst free(buf); 95822347Spst if (cnt != 0) { 95922347Spst if (cnt < 0) 96022347Spst goto file_err; 96122347Spst goto data_err; 96222347Spst } 96322347Spst reply(226, "Transfer complete."); 96422347Spst return; 96522347Spst default: 96622347Spst transflag = 0; 96722347Spst reply(550, "Unimplemented TYPE %d in send_data", type); 96822347Spst return; 96922347Spst } 97022347Spst 97122347Spstdata_err: 97222347Spst transflag = 0; 97322347Spst perror_reply(426, "Data connection"); 97422347Spst return; 97522347Spst 97622347Spstfile_err: 97722347Spst transflag = 0; 97822347Spst perror_reply(551, "Error on input file"); 97922347Spst} 98022347Spst 98122347Spst/* 98222347Spst * Transfer data from peer to 98322347Spst * "outstr" using the appropriate 98422347Spst * encapulation of the data subject 98522347Spst * to Mode, Structure, and Type. 98622347Spst * 98722347Spst * N.B.: Form isn't handled. 98822347Spst */ 98922347Spststatic int receive_data FUNCTION((instr, outstr), FILE *instr AND FILE *outstr) 99022347Spst{ 99122347Spst register int c; 99222347Spst int cnt, bare_lfs = 0; 99322347Spst char buf[BUFSIZ]; 99422347Spst 99522347Spst transflag++; 99622347Spst if (setjmp(urgcatch)) { 99722347Spst transflag = 0; 99822347Spst return (-1); 99922347Spst } 100022347Spst switch (type) { 100122347Spst 100222347Spst case TYPE_I: 100322347Spst case TYPE_L: 100422347Spst while ((cnt = read(fileno(instr), buf, sizeof buf)) > 0) { 100522347Spst if (write(fileno(outstr), buf, cnt) != cnt) 100622347Spst goto file_err; 100722347Spst byte_count += cnt; 100822347Spst } 100922347Spst if (cnt < 0) 101022347Spst goto data_err; 101122347Spst transflag = 0; 101222347Spst return (0); 101322347Spst 101422347Spst case TYPE_E: 101522347Spst reply(553, "TYPE E not implemented."); 101622347Spst transflag = 0; 101722347Spst return (-1); 101822347Spst 101922347Spst case TYPE_A: 102022347Spst while ((c = getc(instr)) != EOF) { 102122347Spst byte_count++; 102222347Spst if (c == '\n') 102322347Spst bare_lfs++; 102422347Spst while (c == '\r') { 102522347Spst if (ferror(outstr)) 102622347Spst goto data_err; 102722347Spst if ((c = getc(instr)) != '\n') { 102822347Spst putc('\r', outstr); 102922347Spst if (c == '\0' || c == EOF) 103022347Spst goto contin2; 103122347Spst } 103222347Spst } 103322347Spst putc(c, outstr); 103422347Spst contin2:; 103522347Spst } 103622347Spst fflush(outstr); 103722347Spst if (ferror(instr)) 103822347Spst goto data_err; 103922347Spst if (ferror(outstr)) 104022347Spst goto file_err; 104122347Spst transflag = 0; 104222347Spst if (bare_lfs) { 104322347Spst lreply(230, "WARNING! %d bare linefeeds received in ASCII mode", bare_lfs); 104422347Spst printf(" File may not have transferred correctly.\r\n"); 104522347Spst } 104622347Spst return (0); 104722347Spst default: 104822347Spst reply(550, "Unimplemented TYPE %d in receive_data", type); 104922347Spst transflag = 0; 105022347Spst return (-1); 105122347Spst } 105222347Spst 105322347Spstdata_err: 105422347Spst transflag = 0; 105522347Spst perror_reply(426, "Data Connection"); 105622347Spst return (-1); 105722347Spst 105822347Spstfile_err: 105922347Spst transflag = 0; 106022347Spst perror_reply(452, "Error writing file"); 106122347Spst return (-1); 106222347Spst} 106322347Spst 106422347SpstVOIDRET statfilecmd FUNCTION((filename), char *filename) 106522347Spst{ 106622347Spst char line[BUFSIZ]; 106722347Spst FILE *fin; 106822347Spst int c; 106922347Spst 107022347Spst#if HAVE_LS_G_FLAG 107139012Simp snprintf(line, sizeof(line), "%s %s", "/bin/ls -lgA", filename); 107222347Spst#else /* HAVE_LS_G_FLAG */ 107339012Simp snprintf(line, sizeof(line), "%s %s", "/bin/ls -lA", filename); 107422347Spst#endif /* HAVE_LS_G_FLAG */ 107522347Spst fin = ftpd_popen(line, "r"); 107622347Spst lreply(211, "status of %s:", filename); 107722347Spst while ((c = getc(fin)) != EOF) { 107822347Spst if (c == '\n') { 107922347Spst if (ferror(stdout)) { 108022347Spst perror_reply(421, "control connection"); 108122347Spst ftpd_pclose(fin); 108222347Spst dologout(1); 108322347Spst /* NOTREACHED */ 108422347Spst } 108522347Spst if (ferror(fin)) { 108622347Spst perror_reply(551, filename); 108722347Spst ftpd_pclose(fin); 108822347Spst return; 108922347Spst } 109022347Spst putc('\r', stdout); 109122347Spst } 109222347Spst putc(c, stdout); 109322347Spst } 109422347Spst ftpd_pclose(fin); 109522347Spst reply(211, "End of Status"); 109622347Spst} 109722347Spst 109822347SpstVOIDRET statcmd FUNCTION_NOARGS 109922347Spst{ 110022347Spst/* COMMENTED OUT STUFF BECAUSE THINGS BROKE ON SUNOS. */ 110122347Spst struct sockaddr_in *sin; 110222347Spst u_char *a, *p; 110322347Spst 110422347Spst lreply(211, "FTP server status:"); 110522347Spst printf(" \r\n"); 110622347Spst printf(" Connected to %s", remotehost); 110722347Spst if (!isdigit(remotehost[0])) 110822347Spst printf(" (%s)", inet_ntoa(his_addr.sin_addr)); 110922347Spst printf("\r\n"); 111022347Spst if (logged_in) { 111122347Spst#if DOANONYMOUS 111222347Spst if (guest) 111322347Spst printf(" Logged in anonymously\r\n"); 111422347Spst else 111522347Spst#endif /* DOANONYMOUS */ 111622347Spst printf(" Logged in as %s\r\n", pw->pw_name); 111722347Spst } else 111822347Spst if (askpasswd) 111922347Spst printf(" Waiting for password\r\n"); 112022347Spst else 112122347Spst printf(" Waiting for user name\r\n"); 112222347Spst if (data != -1) 112322347Spst printf(" Data connection open\r\n"); 112422347Spst else 112522347Spst if (pdata != -1) { 112622347Spst printf(" in Passive mode"); 112722347Spst sin = &pasv_addr; 112822347Spst goto printaddr; 112922347Spst } else 113022347Spst if (usedefault == 0) { 113122347Spst printf(" PORT"); 113222347Spst sin = &data_dest; 113322347Spst printaddr: 113422347Spst a = (u_char *) & sin->sin_addr; 113522347Spst p = (u_char *) & sin->sin_port; 113622347Spst#define UC(b) (((int) b) & 0xff) 113722347Spst printf(" (%d,%d,%d,%d,%d,%d)\r\n", UC(a[0]), 113822347Spst UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1])); 113922347Spst#undef UC 114022347Spst } else 114122347Spst printf(" No data connection\r\n"); 114222347Spst reply(211, "End of status"); 114322347Spst} 114422347Spst 114522347SpstVOIDRET opiefatal FUNCTION((s), char *s) 114622347Spst{ 114722347Spst reply(451, "Error in server: %s\n", s); 114822347Spst reply(221, "Closing connection due to server error."); 114922347Spst dologout(0); 115022347Spst /* NOTREACHED */ 115122347Spst} 115222347Spst 115322347Spststatic VOIDRET ack FUNCTION((s), char *s) 115422347Spst{ 115522347Spst reply(250, "%s command successful.", s); 115622347Spst} 115722347Spst 115822347SpstVOIDRET nack FUNCTION((s), char *s) 115922347Spst{ 116022347Spst reply(502, "%s command not implemented.", s); 116122347Spst} 116222347Spst 116322347SpstVOIDRET yyerror FUNCTION((s), char *s) 116422347Spst{ 116522347Spst char *cp; 116622347Spst 116722347Spst if (cp = strchr(cbuf, '\n')) 116822347Spst *cp = '\0'; 116922347Spst reply(500, "'%s': command not understood.", cbuf); 117022347Spst} 117122347Spst 117222347SpstVOIDRET delete FUNCTION((name), char *name) 117322347Spst{ 117422347Spst struct stat st; 117522347Spst 117622347Spst if (stat(name, &st) < 0) { 117722347Spst perror_reply(550, name); 117822347Spst return; 117922347Spst } 118022347Spst if ((st.st_mode & S_IFMT) == S_IFDIR) { 118122347Spst if (rmdir(name) < 0) { 118222347Spst perror_reply(550, name); 118322347Spst return; 118422347Spst } 118522347Spst goto done; 118622347Spst } 118722347Spst if (unlink(name) < 0) { 118822347Spst perror_reply(550, name); 118922347Spst return; 119022347Spst } 119122347Spstdone: 119222347Spst ack("DELE"); 119322347Spst} 119422347Spst 119522347SpstVOIDRET cwd FUNCTION((path), char *path) 119622347Spst{ 119722347Spst if (chdir(path) < 0) 119822347Spst perror_reply(550, path); 119922347Spst else 120022347Spst ack("CWD"); 120122347Spst} 120222347Spst 120322347SpstVOIDRET makedir FUNCTION((name), char *name) 120422347Spst{ 120522347Spst if (mkdir(name, 0777) < 0) 120622347Spst perror_reply(550, name); 120722347Spst else 120822347Spst reply(257, "MKD command successful."); 120922347Spst} 121022347Spst 121122347SpstVOIDRET removedir FUNCTION((name), char *name) 121222347Spst{ 121322347Spst if (rmdir(name) < 0) 121422347Spst perror_reply(550, name); 121522347Spst else 121622347Spst ack("RMD"); 121722347Spst} 121822347Spst 121922347SpstVOIDRET pwd FUNCTION_NOARGS 122022347Spst{ 122122347Spst char path[MAXPATHLEN + 1]; 122222347Spst 122322347Spst if (getcwd(path, MAXPATHLEN) == (char *) NULL) 122422347Spst reply(550, "%s.", path); 122522347Spst else 122622347Spst reply(257, "\"%s\" is current directory.", path); 122722347Spst} 122822347Spst 122922347Spstchar *renamefrom FUNCTION((name), char *name) 123022347Spst{ 123122347Spst struct stat st; 123222347Spst 123322347Spst if (stat(name, &st) < 0) { 123422347Spst perror_reply(550, name); 123522347Spst return ((char *) 0); 123622347Spst } 123722347Spst reply(350, "File exists, ready for destination name"); 123822347Spst return (name); 123922347Spst} 124022347Spst 124122347SpstVOIDRET renamecmd FUNCTION((from, to), char *from AND char *to) 124222347Spst{ 124322347Spst if (rename(from, to) < 0) 124422347Spst perror_reply(550, "rename"); 124522347Spst else 124622347Spst ack("RNTO"); 124722347Spst} 124822347Spst 124922347Spststatic VOIDRET dolog FUNCTION((sin), struct sockaddr_in *sin) 125022347Spst{ 125122347Spst struct hostent *hp = gethostbyaddr((char *) &sin->sin_addr, 125222347Spst sizeof(struct in_addr), AF_INET); 125322347Spst time_t t, time(); 125422347Spst 125522347Spst if (hp) 125622347Spst strncpy(remotehost, hp->h_name, sizeof(remotehost)); 125722347Spst else 125822347Spst strncpy(remotehost, inet_ntoa(sin->sin_addr), sizeof(remotehost)); 125939012Simp remotehost[sizeof(remotehost) - 1] = '\0'; 126022347Spst#if DOTITLE 126139012Simp snprintf(proctitle, sizeof(proctitle), "%s: connected", remotehost); 126222347Spst setproctitle(proctitle); 126322347Spst#endif /* DOTITLE */ 126422347Spst 126522347Spst t = time((time_t *) 0); 126622347Spst syslog(LOG_INFO, "connection from %s at %s", 126722347Spst remotehost, ctime(&t)); 126822347Spst} 126922347Spst 127022347Spst/* 127122347Spst * Record logout in wtmp file 127222347Spst * and exit with supplied status. 127322347Spst */ 127422347SpstVOIDRET dologout FUNCTION((status), int status) 127522347Spst{ 127629964Sache disable_signalling(); 127722347Spst if (logged_in) { 127822347Spst if (seteuid((uid_t) 0)) 127922347Spst syslog(LOG_ERR, "Can't set euid"); 128029964Sache opielogwtmp(ttyline, "", ""); 128122347Spst } 128222347Spst /* beware of flushing buffers after a SIGPIPE */ 128322347Spst _exit(status); 128422347Spst} 128522347Spst 128622347Spststatic VOIDRET myoob FUNCTION((input), int input) 128722347Spst{ 128822347Spst char *cp; 128922347Spst 129022347Spst /* only process if transfer occurring */ 129122347Spst if (!transflag) 129222347Spst return; 129322347Spst cp = tmpline; 129422347Spst if (getline(cp, 7, stdin) == NULL) { 129522347Spst reply(221, "You could at least say goodbye."); 129622347Spst dologout(0); 129722347Spst } 129822347Spst upper(cp); 129922347Spst if (strcmp(cp, "ABOR\r\n") == 0) { 130022347Spst tmpline[0] = '\0'; 130122347Spst reply(426, "Transfer aborted. Data connection closed."); 130222347Spst reply(226, "Abort successful"); 130322347Spst longjmp(urgcatch, 1); 130422347Spst } 130522347Spst if (strcmp(cp, "STAT\r\n") == 0) { 130622347Spst if (file_size != (off_t) - 1) 130722347Spst reply(213, "Status: %lu of %lu bytes transferred", 130822347Spst byte_count, file_size); 130922347Spst else 131022347Spst reply(213, "Status: %lu bytes transferred", byte_count); 131122347Spst } 131222347Spst} 131322347Spst 131422347Spst/* 131522347Spst * Note: a response of 425 is not mentioned as a possible response to 131622347Spst * the PASV command in RFC959. However, it has been blessed as 131722347Spst * a legitimate response by Jon Postel in a telephone conversation 131822347Spst * with Rick Adams on 25 Jan 89. 131922347Spst */ 132022347SpstVOIDRET passive FUNCTION_NOARGS 132122347Spst{ 132222347Spst int len; 132322347Spst register char *p, *a; 132422347Spst 132522347Spst pdata = socket(AF_INET, SOCK_STREAM, 0); 132622347Spst if (pdata < 0) { 132722347Spst perror_reply(425, "Can't open passive connection"); 132822347Spst return; 132922347Spst } 133022347Spst pasv_addr = ctrl_addr; 133122347Spst pasv_addr.sin_port = 0; 133222347Spst if (seteuid((uid_t) 0)) 133322347Spst syslog(LOG_ERR, "Can't set euid"); 133422347Spst if (bind(pdata, (struct sockaddr *) & pasv_addr, sizeof(pasv_addr)) < 0) { 133522347Spst seteuid((uid_t) pw->pw_uid); 133622347Spst goto pasv_error; 133722347Spst } 133822347Spst if (seteuid((uid_t) pw->pw_uid)) 133922347Spst syslog(LOG_ERR, "Can't set euid"); 134022347Spst len = sizeof(pasv_addr); 134122347Spst if (getsockname(pdata, (struct sockaddr *) & pasv_addr, &len) < 0) 134222347Spst goto pasv_error; 134322347Spst if (listen(pdata, 1) < 0) 134422347Spst goto pasv_error; 134522347Spst a = (char *) &pasv_addr.sin_addr; 134622347Spst p = (char *) &pasv_addr.sin_port; 134722347Spst 134822347Spst#define UC(b) (((int) b) & 0xff) 134922347Spst 135022347Spst reply(227, "Entering Passive Mode (%d,%d,%d,%d,%d,%d)", UC(a[0]), 135122347Spst UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1])); 135222347Spst return; 135322347Spst 135422347Spstpasv_error: 135522347Spst close(pdata); 135622347Spst pdata = -1; 135722347Spst perror_reply(425, "Can't open passive connection"); 135822347Spst return; 135922347Spst} 136022347Spst 136122347Spst/* 136222347Spst * Generate unique name for file with basename "local". 136322347Spst * The file named "local" is already known to exist. 136422347Spst * Generates failure reply on error. 136522347Spst */ 136622347Spststatic char *gunique FUNCTION((local), char *local) 136722347Spst{ 136829964Sache static char new[MAXPATHLEN+1]; 136922347Spst struct stat st; 137022347Spst char *cp = strrchr(local, '/'); 137122347Spst int count = 0; 137222347Spst 137322347Spst if (cp) 137422347Spst *cp = '\0'; 137522347Spst if (stat(cp ? local : ".", &st) < 0) { 137622347Spst perror_reply(553, cp ? local : "."); 137722347Spst return ((char *) 0); 137822347Spst } 137922347Spst if (cp) 138022347Spst *cp = '/'; 138122347Spst strcpy(new, local); 138222347Spst cp = new + strlen(new); 138322347Spst *cp++ = '.'; 138422347Spst for (count = 1; count < 100; count++) { 138539012Simp snprintf(cp, sizeof(new) - (cp - new), "%d", count); 138622347Spst if (stat(new, &st) < 0) 138722347Spst return (new); 138822347Spst } 138922347Spst reply(452, "Unique file name cannot be created."); 139022347Spst return ((char *) 0); 139122347Spst} 139222347Spst 139322347Spst/* 139422347Spst * Format and send reply containing system error number. 139522347Spst */ 139622347SpstVOIDRET perror_reply FUNCTION((code, string), int code AND char *string) 139722347Spst{ 139822347Spst reply(code, "%s: %s.", string, strerror(errno)); 139922347Spst} 140022347Spst 140122347Spststatic char *onefile[] = 140222347Spst{ 140322347Spst "", 140422347Spst 0 140522347Spst}; 140622347Spst 140722347SpstVOIDRET send_file_list FUNCTION((whichfiles), char *whichfiles) 140822347Spst{ 140922347Spst struct stat st; 141022347Spst DIR *dirp = NULL; 141122347Spst struct dirent *dir; 141222347Spst FILE *dout = NULL; 141322347Spst register char **dirlist, *dirname; 141422347Spst int simple = 0; 141522347Spst 141622347Spst if (strpbrk(whichfiles, "~{[*?") != NULL) { 141722347Spst extern char **ftpglob(), *globerr; 141822347Spst 141922347Spst globerr = NULL; 142022347Spst dirlist = ftpglob(whichfiles); 142122347Spst if (globerr != NULL) { 142222347Spst reply(550, globerr); 142322347Spst return; 142422347Spst } else 142522347Spst if (dirlist == NULL) { 142622347Spst errno = ENOENT; 142722347Spst perror_reply(550, whichfiles); 142822347Spst return; 142922347Spst } 143022347Spst } else { 143122347Spst onefile[0] = whichfiles; 143222347Spst dirlist = onefile; 143322347Spst simple = 1; 143422347Spst } 143522347Spst 143622347Spst if (setjmp(urgcatch)) { 143722347Spst transflag = 0; 143822347Spst return; 143922347Spst } 144022347Spst while (dirname = *dirlist++) { 144122347Spst if (stat(dirname, &st) < 0) { 144222347Spst /* If user typed "ls -l", etc, and the client used NLST, do what the 144322347Spst user meant. */ 144422347Spst if (dirname[0] == '-' && *dirlist == NULL && 144522347Spst transflag == 0) { 144622347Spst retrieve("/bin/ls %s", dirname); 144722347Spst return; 144822347Spst } 144922347Spst perror_reply(550, whichfiles); 145022347Spst if (dout != NULL) { 145122347Spst fclose(dout); 145222347Spst transflag = 0; 145322347Spst data = -1; 145422347Spst pdata = -1; 145522347Spst } 145622347Spst return; 145722347Spst } 145822347Spst if ((st.st_mode & S_IFMT) == S_IFREG) { 145922347Spst if (dout == NULL) { 146022347Spst dout = dataconn("file list", (off_t) - 1, "w"); 146122347Spst if (dout == NULL) 146222347Spst return; 146322347Spst transflag++; 146422347Spst } 146522347Spst fprintf(dout, "%s%s\n", dirname, 146622347Spst type == TYPE_A ? "\r" : ""); 146722347Spst byte_count += strlen(dirname) + 1; 146822347Spst continue; 146922347Spst } else 147022347Spst if ((st.st_mode & S_IFMT) != S_IFDIR) 147122347Spst continue; 147222347Spst 147322347Spst if ((dirp = opendir(dirname)) == NULL) 147422347Spst continue; 147522347Spst 147622347Spst while ((dir = readdir(dirp)) != NULL) { 147729964Sache char nbuf[MAXPATHLEN+1]; 147822347Spst 147922347Spst if (dir->d_name[0] == '.' && (strlen(dir->d_name) == 1)) 148022347Spst continue; 148122347Spst if (dir->d_name[0] == '.' && dir->d_name[1] == '.' && 148222347Spst (strlen(dir->d_name) == 2)) 148322347Spst continue; 148422347Spst 148539012Simp snprintf(nbuf, sizeof(nbuf), "%s/%s", dirname, dir->d_name); 148622347Spst 148722347Spst /* We have to do a stat to insure it's not a directory or special file. */ 148822347Spst if (simple || (stat(nbuf, &st) == 0 && 148922347Spst (st.st_mode & S_IFMT) == S_IFREG)) { 149022347Spst if (dout == NULL) { 149122347Spst dout = dataconn("file list", (off_t) - 1, "w"); 149222347Spst if (dout == NULL) 149322347Spst return; 149422347Spst transflag++; 149522347Spst } 149622347Spst if (nbuf[0] == '.' && nbuf[1] == '/') 149722347Spst fprintf(dout, "%s%s\n", &nbuf[2], 149822347Spst type == TYPE_A ? "\r" : ""); 149922347Spst else 150022347Spst fprintf(dout, "%s%s\n", nbuf, 150122347Spst type == TYPE_A ? "\r" : ""); 150222347Spst byte_count += strlen(nbuf) + 1; 150322347Spst } 150422347Spst } 150522347Spst closedir(dirp); 150622347Spst } 150722347Spst 150822347Spst if (dout == NULL) 150922347Spst reply(550, "No files found."); 151022347Spst else 151122347Spst if (ferror(dout) != 0) 151222347Spst perror_reply(550, "Data connection"); 151322347Spst else 151422347Spst reply(226, "Transfer complete."); 151522347Spst 151622347Spst transflag = 0; 151722347Spst if (dout != NULL) 151822347Spst fclose(dout); 151922347Spst data = -1; 152022347Spst pdata = -1; 152122347Spst} 152222347Spst 152322347Spst#if DOTITLE 152422347Spst/* 152522347Spst * clobber argv so ps will show what we're doing. 152622347Spst * (stolen from sendmail) 152722347Spst * warning, since this is usually started from inetd.conf, it 152822347Spst * often doesn't have much of an environment or arglist to overwrite. 152922347Spst */ 153022347SpstVOIDRET setproctitle FUNCTION((fmt, a, b, c), char *fmt AND int a AND int b AND int c) 153122347Spst{ 153222347Spst register char *p, *bp, ch; 153322347Spst register int i; 153422347Spst char buf[BUFSIZ]; 153522347Spst 153639012Simp snprintf(buf, sizeof(buf), fmt, a, b, c); 153722347Spst 153822347Spst /* make ps print our process name */ 153922347Spst p = Argv[0]; 154022347Spst *p++ = '-'; 154122347Spst 154222347Spst i = strlen(buf); 154322347Spst if (i > LastArgv - p - 2) { 154422347Spst i = LastArgv - p - 2; 154522347Spst buf[i] = '\0'; 154622347Spst } 154722347Spst bp = buf; 154822347Spst while (ch = *bp++) 154922347Spst if (ch != '\n' && ch != '\r') 155022347Spst *p++ = ch; 155122347Spst while (p < LastArgv) 155222347Spst *p++ = ' '; 155322347Spst} 155422347Spst#endif /* DOTITLE */ 155522347Spst 155629964SacheVOIDRET catchexit FUNCTION_NOARGS 155722347Spst{ 155822347Spst closelog(); 155922347Spst} 156022347Spst 156122347Spstint main FUNCTION((argc, argv, envp), int argc AND char *argv[] AND char *envp[]) 156222347Spst{ 156322347Spst int addrlen, on = 1; 156422347Spst char *cp; 156522347Spst#ifdef IP_TOS 156622347Spst int tos; 156722347Spst#endif /* IP_TOS */ 156822347Spst 156922347Spst { 157022347Spst int i; 157122347Spst 157222347Spst for (i = sysconf(_SC_OPEN_MAX); i > 2; i--) 157322347Spst close(i); 157422347Spst } 157522347Spst 157622347Spst /* LOG_NDELAY sets up the logging connection immediately, necessary for 157722347Spst anonymous ftp's that chroot and can't do it later. */ 157822347Spst openlog("ftpd", LOG_PID | LOG_NDELAY, LOG_DAEMON); 157922347Spst atexit(catchexit); 158022347Spst addrlen = sizeof(his_addr); 158122347Spst if (getpeername(0, (struct sockaddr *) & his_addr, &addrlen) < 0) { 158222347Spst syslog(LOG_ERR, "getpeername (%s): %m", argv[0]); 158322347Spst exit(1); 158422347Spst } 158522347Spst addrlen = sizeof(ctrl_addr); 158622347Spst if (getsockname(0, (struct sockaddr *) & ctrl_addr, &addrlen) < 0) { 158722347Spst syslog(LOG_ERR, "getsockname (%s): %m", argv[0]); 158822347Spst exit(1); 158922347Spst } 159022347Spst#ifdef IP_TOS 159122347Spst tos = IPTOS_LOWDELAY; 159222347Spst if (setsockopt(0, IPPROTO_IP, IP_TOS, (char *) &tos, sizeof(int)) < 0) 159322347Spst syslog(LOG_WARNING, "setsockopt (IP_TOS): %m"); 159422347Spst#endif 159522347Spst data_source.sin_port = htons(ntohs(ctrl_addr.sin_port) - 1); 159622347Spst debug = 0; 159722347Spst#if DOTITLE 159822347Spst /* Save start and extent of argv for setproctitle. */ 159922347Spst Argv = argv; 160022347Spst while (*envp) 160122347Spst envp++; 160222347Spst LastArgv = envp[-1] + strlen(envp[-1]); 160322347Spst#endif /* DOTITLE */ 160422347Spst 160522347Spst argc--, argv++; 160622347Spst while (argc > 0 && *argv[0] == '-') { 160722347Spst for (cp = &argv[0][1]; *cp; cp++) 160822347Spst switch (*cp) { 160922347Spst 161022347Spst case 'v': 161122347Spst debug = 1; 161222347Spst break; 161322347Spst 161422347Spst case 'd': 161522347Spst debug = 1; 161622347Spst break; 161722347Spst 161822347Spst case 'l': 161922347Spst break; 162022347Spst 162122347Spst case 't': 162222347Spst timeout = atoi(++cp); 162322347Spst if (maxtimeout < timeout) 162422347Spst maxtimeout = timeout; 162522347Spst goto nextopt; 162622347Spst 162722347Spst case 'T': 162822347Spst maxtimeout = atoi(++cp); 162922347Spst if (timeout > maxtimeout) 163022347Spst timeout = maxtimeout; 163122347Spst goto nextopt; 163222347Spst 163322347Spst case 'u': 163422347Spst { 163522347Spst int val = 0; 163622347Spst 163722347Spst while (*++cp && *cp >= '0' && *cp <= '9') 163822347Spst val = val * 8 + *cp - '0'; 163922347Spst if (*cp) 164022347Spst fprintf(stderr, "ftpd: Bad value for -u\n"); 164122347Spst else 164222347Spst defumask = val; 164322347Spst goto nextopt; 164422347Spst } 164522347Spst 164622347Spst default: 164722347Spst fprintf(stderr, "ftpd: Unknown flag -%c ignored.\n", 164822347Spst *cp); 164922347Spst break; 165022347Spst } 165122347Spstnextopt: 165222347Spst argc--, argv++; 165322347Spst } 165422347Spst freopen(_PATH_DEVNULL, "w", stderr); 165522347Spst signal(SIGCHLD, SIG_IGN); 165629964Sache enable_signalling(); 165722347Spst 165822347Spst /* Try to handle urgent data inline */ 165922347Spst#ifdef SO_OOBINLINE 166022347Spst if (setsockopt(0, SOL_SOCKET, SO_OOBINLINE, (char *) &on, sizeof(on)) < 0) 166122347Spst syslog(LOG_ERR, "setsockopt: %m"); 166222347Spst#endif 166322347Spst 166422347Spst#ifdef F_SETOWN 166522347Spst if (fcntl(fileno(stdin), F_SETOWN, getpid()) == -1) 166622347Spst syslog(LOG_ERR, "fcntl F_SETOWN: %m"); 166722347Spst#endif 166822347Spst dolog(&his_addr); 166922347Spst /* Set up default state */ 167022347Spst data = -1; 167122347Spst type = TYPE_A; 167222347Spst form = FORM_N; 167322347Spst stru = STRU_F; 167422347Spst mode = MODE_S; 167522347Spst tmpline[0] = '\0'; 167622347Spst af_pwok = opieaccessfile(remotehost); 167722347Spst 167822347Spst { 167929964Sache FILE *fd; 168029964Sache char line[128]; 168122347Spst 168229964Sache /* If logins are disabled, print out the message. */ 168329964Sache if ((fd = fopen(_PATH_NOLOGIN,"r")) != NULL) { 168429964Sache while (fgets(line, sizeof(line), fd) != NULL) { 168529964Sache if ((cp = strchr(line, '\n')) != NULL) 168629964Sache *cp = '\0'; 168729964Sache lreply(530, "%s", line); 168829964Sache } 168929964Sache (void) fflush(stdout); 169029964Sache (void) fclose(fd); 169129964Sache reply(530, "System not available."); 169229964Sache exit(0); 169322347Spst } 169429964Sache if ((fd = fopen(_PATH_FTPWELCOME, "r")) != NULL) { 169529964Sache while (fgets(line, sizeof(line), fd) != NULL) { 169629964Sache if ((cp = strchr(line, '\n')) != NULL) 169729964Sache *cp = '\0'; 169829964Sache lreply(220, "%s", line); 169929964Sache } 170029964Sache (void) fflush(stdout); 170129964Sache (void) fclose(fd); 170229964Sache /* reply(220,) must follow */ 170329964Sache } 170429964Sache }; 170522347Spst 170622347Spst reply(220, "FTP server ready."); 170722347Spst 170822347Spst setjmp(errcatch); 170922347Spst for (;;) 171022347Spst yyparse(); 171122347Spst /* NOTREACHED */ 171222347Spst return 0; 171322347Spst} 1714