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