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