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