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