opieftpd.c revision 59121
1/* opieftpd.c: Main program for an FTP daemon.
2
3%%% portions-copyright-cmetz-96
4Portions of this software are Copyright 1996-1998 by Craig Metz, All Rights
5Reserved. The Inner Net License Version 2 applies to these portions of
6the software.
7You should have received a copy of the license with this software. If
8you didn't get a copy, you may request one from <license@inner.net>.
9
10Portions of this software are Copyright 1995 by Randall Atkinson and Dan
11McDonald, All Rights Reserved. All Rights under this copyright are assigned
12to the U.S. Naval Research Laboratory (NRL). The NRL Copyright Notice and
13License Agreement applies to this software.
14
15	History:
16
17	Modified by cmetz for OPIE 2.32. Remove include of dirent.h here; it's
18		done already (and conditionally) in opie_cfg.h.
19	Modified by cmetz for OPIE 2.31. Merged in some 4.4BSD-Lite changes.
20		Merged in a security fix to BSD-derived ftpds.
21	Modified by cmetz for OPIE 2.3. Fixed the filename at the top.
22		Moved LS_COMMAND here.
23	Modified by cmetz for OPIE 2.2. Use FUNCTION definition et al.
24                Removed useless strings (I don't think that removing the
25                ucb copyright one is a problem -- please let me know if
26                I'm wrong). Changed default CMASK to 077. Removed random
27                comments. Use ANSI stdargs for reply/lreply if we can,
28                added stdargs version of reply/lreply. Don't declare the
29                tos variable unless IP_TOS defined. Include stdargs headers
30                early. More headers ifdefed. Made everything static.
31                Got rid of gethostname() call and use of hostname. Pared
32                down status response for places where header files frequently
33                cause trouble. Made logging of user logins (ala -l)
34                non-optional. Moved reply()/lrepy(). Fixed some prototypes.
35	Modified at NRL for OPIE 2.1. Added declaration of envp. Discard
36	        result of opiechallenge (allows access control to work).
37		Added patches for AIX. Symbol changes for autoconf.
38        Modified at NRL for OPIE 2.01. Changed password lookup handling
39                to avoid problems with drain-bamaged shadow password packages.
40                Properly handle internal state for anonymous FTP. Unlock
41                user accounts properly if login fails because of /etc/shells.
42                Make sure to close syslog by function to avoid problems with
43                drain bamaged syslog implementations.
44	Modified at NRL for OPIE 2.0.
45	Originally from BSD Net/2.
46
47	        There is some really, really ugly code in here.
48
49$FreeBSD: head/contrib/opie/opieftpd.c 59121 2000-04-10 11:18:54Z kris $
50*/
51/*
52 * Copyright (c) 1985, 1988, 1990 Regents of the University of California.
53 * All rights reserved.
54 *
55 * Redistribution and use in source and binary forms, with or without
56 * modification, are permitted provided that the following conditions
57 * are met:
58 * 1. Redistributions of source code must retain the above copyright
59 *    notice, this list of conditions and the following disclaimer.
60 * 2. Redistributions in binary form must reproduce the above copyright
61 *    notice, this list of conditions and the following disclaimer in the
62 *    documentation and/or other materials provided with the distribution.
63 * 3. All advertising materials mentioning features or use of this software
64 *    must display the following acknowledgement:
65 *      This product includes software developed by the University of
66 *      California, Berkeley and its contributors.
67 * 4. Neither the name of the University nor the names of its contributors
68 *    may be used to endorse or promote products derived from this software
69 *    without specific prior written permission.
70 *
71 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
72 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
73 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
74 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
75 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
76 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
77 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
78 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
79 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
80 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
81 * SUCH DAMAGE.
82 */
83
84#include "opie_cfg.h"
85
86#if HAVE_ANSISTDARG
87#include <stdarg.h>
88#endif /* HAVE_ANSISTDARG */
89
90/*
91 * FTP server.
92 */
93
94#if HAVE_SYS_PARAM_H
95#include <sys/param.h>
96#endif /* HAVE_SYS_PARAM_H */
97#include <sys/stat.h>
98/* #include <sys/ioctl.h> */
99#include <sys/socket.h>
100#include <sys/wait.h>
101#ifdef SYS_FCNTL_H
102#include <sys/fcntl.h>
103#else
104#include <fcntl.h>
105#endif	/* SYS_FCNTL_H */
106#include <sys/types.h>
107
108#include <netinet/in.h>
109#include <netinet/in_systm.h>
110#include <netinet/ip.h>
111
112#define	FTP_NAMES
113#include <arpa/ftp.h>
114#include <arpa/inet.h>
115#include <arpa/telnet.h>
116
117#include <signal.h>
118#include <fcntl.h>
119#if HAVE_TIME_H
120#include <time.h>
121#endif /* HAVE_TIME_H */
122#if HAVE_PWD_H
123#include <pwd.h>
124#endif /* HAVE_PWD_H */
125#include <setjmp.h>
126#include <netdb.h>
127#include <errno.h>
128#include <syslog.h>
129#if HAVE_UNISTD_H
130#include <unistd.h>
131#endif /* HAVE_UNISTD_H */
132#include <stdio.h>
133#include <ctype.h>
134#include <stdlib.h>
135#include <string.h>
136#include <grp.h>
137
138#include "opie.h"
139
140#if HAVE_SHADOW_H
141#include <shadow.h>
142#endif /* HAVE_SHADOW_H */
143
144#if HAVE_CRYPT_H
145#include <crypt.h>
146#endif /* HAVE_CRYPT_H */
147
148#if HAVE_SYS_UTSNAME_H
149#include <sys/utsname.h>
150#endif /* HAVE_SYS_UTSNAME_H */
151
152#ifdef _AIX
153#include <sys/id.h>
154#include <sys/priv.h>
155#endif /* _AIX */
156
157#ifdef IP_TOS
158#ifndef IPTOS_THROUGHPUT
159#undef IP_TOS
160#endif /* !IPTOS_THROUGHPUT */
161#ifndef IPTOS_LOWDELAY
162#undef IP_TOS
163#endif /* !IPTOS_LOWDELAY */
164#endif /* IP_TOS */
165
166extern int errno;
167extern char *home;	/* pointer to home directory for glob */
168extern FILE *ftpd_popen __P((char *, char *));
169extern int ftpd_pclose __P((FILE *));
170extern char cbuf[];
171extern off_t restart_point;
172
173static struct sockaddr_in ctrl_addr;
174static struct sockaddr_in data_source;
175struct sockaddr_in data_dest;
176struct sockaddr_in his_addr;
177static struct sockaddr_in pasv_addr;
178
179static int data;
180jmp_buf errcatch;
181static jmp_buf urgcatch;
182int logged_in;
183struct passwd *pw;
184int debug;
185int timeout = 900;	/* timeout after 15 minutes of inactivity */
186int maxtimeout = 7200;	/* don't allow idle time to be set beyond 2 hours */
187
188#if DOANONYMOUS
189static int guest;
190#endif	/* DOANONYMOUS */
191int type;
192int form;
193static int stru;	/* avoid C keyword */
194static int mode;
195int usedefault = 1;	/* for data transfers */
196int pdata = -1;	/* for passive mode */
197static int transflag;
198static off_t file_size;
199static off_t byte_count;
200
201#if (!defined(CMASK) || CMASK == 0)
202#undef CMASK
203#define CMASK 077
204#endif
205
206static int defumask = CMASK;	/* default umask value */
207char tmpline[7];
208char remotehost[MAXHOSTNAMELEN];
209
210/*
211 * Timeout intervals for retrying connections
212 * to hosts that don't accept PORT cmds.  This
213 * is a kludge, but given the problems with TCP...
214 */
215#define	SWAITMAX	90	/* wait at most 90 seconds */
216#define	SWAITINT	5	/* interval between retries */
217
218static int swaitmax = SWAITMAX;
219static int swaitint = SWAITINT;
220
221#if DOTITLE
222static char **Argv = NULL;	/* pointer to argument vector */
223static char *LastArgv = NULL;	/* end of argv */
224static char proctitle[BUFSIZ];	/* initial part of title */
225#endif	/* DOTITLE */
226
227static int af_pwok = 0, pwok = 0;
228static struct opie opiestate;
229
230VOIDRET perror_reply __P((int, char *));
231VOIDRET dologout __P((int));
232char *getline __P((char *, int, FILE *));
233VOIDRET upper __P((char *));
234
235static VOIDRET lostconn __P((int));
236static VOIDRET myoob __P((int));
237static FILE *getdatasock __P((char *));
238static FILE *dataconn __P((char *, off_t, char *));
239static int checkuser __P((char *));
240static VOIDRET end_login __P((void));
241static VOIDRET send_data __P((FILE *, FILE *, off_t));
242static int receive_data __P((FILE *, FILE *));
243static char *gunique __P((char *));
244static char *sgetsave __P((char *));
245
246int opielogwtmp __P((char *, char *, char *));
247
248int fclose __P((FILE *));
249
250#ifdef HAVE_ANSISTDARG
251VOIDRET reply FUNCTION((stdarg is ANSI only), int n AND char *fmt AND ...)
252{
253  va_list ap;
254  char buffer[1024];
255
256  va_start(ap, fmt);
257  vsprintf(buffer, fmt, ap);
258  va_end(ap);
259
260  printf("%d %s\r\n", n, buffer);
261  fflush(stdout);
262  if (debug)
263    syslog(LOG_DEBUG, "<--- %d %s", n, buffer);
264}
265#else /* HAVE_ANSISTDARG */
266VOIDRET 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)
267{
268  printf("%d ", n);
269  printf(fmt, p0, p1, p2, p3, p4, p5);
270  printf("\r\n");
271  fflush(stdout);
272  if (debug) {
273    syslog(LOG_DEBUG, "<--- %d ", n);
274    syslog(LOG_DEBUG, fmt, p0, p1, p2, p3, p4, p5);
275  }
276}
277#endif /* HAVE_ANSISTDARG */
278
279#ifdef HAVE_ANSISTDARG
280VOIDRET lreply FUNCTION((stdarg is ANSI only), int n AND char *fmt AND ...)
281{
282  va_list ap;
283  char buffer[1024];
284
285  va_start(ap, fmt);
286  vsprintf(buffer, fmt, ap);
287  va_end(ap);
288
289  printf("%d- %s\r\n", n, buffer);
290  fflush(stdout);
291  if (debug)
292    syslog(LOG_DEBUG, "<--- %d- %s", n, buffer);
293}
294#else /* HAVE_ANSISTDARG */
295VOIDRET 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)
296{
297  printf("%d- ", n);
298  printf(fmt, p0, p1, p2, p3, p4, p5);
299  printf("\r\n");
300  fflush(stdout);
301  if (debug) {
302    syslog(LOG_DEBUG, "<--- %d- ", n);
303    syslog(LOG_DEBUG, fmt, p0, p1, p2, p3, p4, p5);
304  }
305}
306#endif /* HAVE_ANSISTDARG */
307
308VOIDRET enable_signalling FUNCTION_NOARGS
309{
310	signal(SIGPIPE, lostconn);
311	if ((int)signal(SIGURG, myoob) < 0)
312		syslog(LOG_ERR, "signal: %m");
313}
314
315VOIDRET disable_signalling FUNCTION_NOARGS
316{
317	signal(SIGPIPE, SIG_IGN);
318	if ((int)signal(SIGURG, SIG_IGN) < 0)
319		syslog(LOG_ERR, "signal: %m");
320}
321
322static VOIDRET lostconn FUNCTION((input), int input)
323{
324  if (debug)
325    syslog(LOG_DEBUG, "lost connection");
326  dologout(-1);
327}
328
329static char ttyline[20];
330
331/*
332 * Helper function for sgetpwnam().
333 */
334static char *sgetsave FUNCTION((s), char *s)
335{
336  char *new = malloc((unsigned) strlen(s) + 1);
337
338  if (new == NULL) {
339    perror_reply(421, "Local resource failure: malloc");
340    dologout(1);
341    /* NOTREACHED */
342  }
343  strcpy(new, s);
344  return (new);
345}
346
347/*
348 * Save the result of a getpwnam.  Used for USER command, since
349 * the data returned must not be clobbered by any other command
350 * (e.g., globbing).
351 */
352static struct passwd *sgetpwnam FUNCTION((name), char *name)
353{
354  static struct passwd save;
355  register struct passwd *p;
356
357#if HAVE_SHADOW
358  struct spwd *spwd;
359#endif /* HAVE_SHADOW */
360
361  if ((p = getpwnam(name)) == NULL)
362    return (p);
363
364#if HAVE_SHADOW
365  if ((spwd = getspnam(name)) == NULL)
366    return NULL;
367
368  endspent();
369
370  p->pw_passwd = spwd->sp_pwdp;
371#endif /* HAVE_SHADOW */
372
373  endpwent();
374
375  if (save.pw_name) {
376    free(save.pw_name);
377    free(save.pw_passwd);
378    free(save.pw_gecos);
379    free(save.pw_dir);
380    free(save.pw_shell);
381  }
382  save = *p;
383  save.pw_name = sgetsave(p->pw_name);
384  save.pw_passwd = sgetsave(p->pw_passwd);
385  save.pw_gecos = sgetsave(p->pw_gecos);
386  save.pw_dir = sgetsave(p->pw_dir);
387  save.pw_shell = sgetsave(p->pw_shell);
388  return (&save);
389}
390
391int login_attempts;	/* number of failed login attempts */
392int askpasswd;	/* had user command, ask for passwd */
393
394/*
395 * USER command.
396 * Sets global passwd pointer pw if named account exists and is acceptable;
397 * sets askpasswd if a PASS command is expected.  If logged in previously,
398 * need to reset state.  If name is "ftp" or "anonymous", the name is not in
399 * _PATH_FTPUSERS, and ftp account exists, set guest and pw, then just return.
400 * If account doesn't exist, ask for passwd anyway.  Otherwise, check user
401 * requesting login privileges.  Disallow anyone who does not have a standard
402 * shell as returned by getusershell().  Disallow anyone mentioned in the file
403 * _PATH_FTPUSERS to allow people such as root and uucp to be avoided.
404 */
405int user FUNCTION((name), char *name)
406{
407  register char *cp;
408  char *shell;
409
410  if (logged_in) {
411#if DOANONYMOUS
412    if (guest) {
413      reply(530, "Can't change user from guest login.");
414      return -1;
415    }
416#endif	/* DOANONMOUS */
417    end_login();
418  }
419  askpasswd = 1;
420#if DOANONYMOUS
421  guest = 0;
422  if (!strcmp(name, "ftp") || !strcmp(name, "anonymous"))
423    if (!checkuser("ftp") && !checkuser("anonymous"))
424      if ((pw = sgetpwnam("ftp")) != NULL) {
425	guest = 1;
426	askpasswd = 1;
427	reply(331, "Guest login ok, send your e-mail address as your password.");
428	syslog(LOG_INFO, "Anonymous FTP connection made from host %s.", remotehost);
429        return 0;
430      }
431#endif	/* DOANONYMOUS */
432  if (pw = sgetpwnam(name)) {
433    if ((shell = pw->pw_shell) == NULL || *shell == 0)
434      shell = _PATH_BSHELL;
435    while ((cp = getusershell()) != NULL)
436      if (!strcmp(cp, shell))
437	break;
438    endusershell();
439    if (cp == NULL || checkuser(name) || ((pw->pw_passwd[0] == '*') || (pw->pw_passwd[0] == '#'))) {
440#if DEBUG
441      if (!cp)
442        syslog(LOG_DEBUG, "Couldn't find %s in the list of valid shells.", pw->pw_shell);
443      if (checkuser(name))
444        syslog(LOG_DEBUG, "checkuser failed - user in /etc/ftpusers?");
445      if (((pw->pw_passwd[0] == '*') || (pw->pw_passwd[0] == '#')))
446        syslog(LOG_DEBUG, "Login disabled: pw_passwd == %s", pw->pw_passwd);
447#endif /* DEBUG */
448      pw = (struct passwd *) NULL;
449      askpasswd = -1;
450    }
451  }
452  {
453    char prompt[OPIE_CHALLENGE_MAX + 1];
454
455    opiechallenge(&opiestate, name, prompt);
456
457    if (askpasswd == -1) {
458      syslog(LOG_WARNING, "Invalid FTP user name %s attempted from %s.", name, remotehost);
459      pwok = 0;
460    } else
461      pwok = af_pwok && opiealways(pw->pw_dir);
462
463#if NEW_PROMPTS
464    reply(331, "Response to %s %s for %s.", prompt,
465#else /* NEW_PROMPTS */
466    reply(331, "OTP response %s %s for %s.", prompt,
467#endif /* NEW_PROMPTS */
468	  pwok ? "requested" : "required", name);
469  }
470  /* Delay before reading passwd after first failed attempt to slow down
471     passwd-guessing programs. */
472  if (login_attempts)
473    sleep((unsigned) login_attempts);
474
475  return 0;
476}
477
478/*
479 * Check if a user is in the file _PATH_FTPUSERS
480 */
481static int checkuser FUNCTION((name), char *name)
482{
483  register FILE *fd;
484  register char *p;
485  char line[BUFSIZ];
486
487  if ((fd = fopen(_PATH_FTPUSERS, "r")) != NULL) {
488    while (fgets(line, sizeof(line), fd) != NULL)
489      if ((p = strchr(line, '\n')) != NULL) {
490	*p = '\0';
491	if (line[0] == '#')
492	  continue;
493	if (!strcmp(line, name)) {
494          fclose(fd);
495	  return (1);
496        }
497      }
498    fclose(fd);
499  }
500  return (0);
501}
502
503/*
504 * Terminate login as previous user, if any, resetting state;
505 * used when USER command is given or login fails.
506 */
507static VOIDRET end_login FUNCTION_NOARGS
508{
509  disable_signalling();
510  if (seteuid((uid_t) 0))
511    syslog(LOG_ERR, "Can't set euid");
512  if (logged_in)
513    opielogwtmp(ttyline, "", "");
514  pw = NULL;
515  logged_in = 0;
516#if DOANONYMOUS
517  guest = 0;
518#endif	/* DOANONYMOUS */
519  enable_signalling();
520}
521
522VOIDRET pass FUNCTION((passwd), char *passwd)
523{
524  int legit = askpasswd + 1, i;
525
526  if (logged_in || askpasswd == 0) {
527    reply(503, "Login with USER first.");
528    return;
529  }
530  askpasswd = 0;
531
532#if DOANONYMOUS
533  if (!guest) { /* "ftp" is only account allowed no password */
534#endif	/* DOANONYMOUS */
535    i = opieverify(&opiestate, passwd);
536    if (legit && i && pwok)
537      i = strcmp(crypt(passwd, pw->pw_passwd), pw->pw_passwd);
538    if (!legit || i) {
539      reply(530, "Login incorrect.");
540      pw = NULL;
541      if (login_attempts++ >= 5) {
542	syslog(LOG_WARNING,
543	       "Repeated login failures for user %s from %s",
544	       pw->pw_name, remotehost);
545	exit(0);
546      }
547      return;
548    }
549#if DOANONYMOUS
550  } else
551    if ((passwd[0] <= ' ') ||  checkuser(passwd)) {
552      reply(530, "No identity, no service.");
553      syslog(LOG_DEBUG, "Bogus address: %s", passwd);
554      exit(0);
555    }
556#endif	/* DOANONYMOUS */
557  login_attempts = 0;	/* this time successful */
558  if (setegid((gid_t) pw->pw_gid) < 0) {
559    reply(550, "Can't set gid.");
560    syslog(LOG_DEBUG, "gid = %d, errno = %s(%d)", pw->pw_gid, strerror(errno), errno);
561    return;
562  }
563  initgroups(pw->pw_name, pw->pw_gid);
564
565  /* open wtmp before chroot */
566  sprintf(ttyline, "ftp%d", getpid());
567  opielogwtmp(ttyline, pw->pw_name, remotehost);
568  logged_in = 1;
569
570#if DOANONYMOUS
571  if (guest) {
572    /* We MUST do a chdir() after the chroot. Otherwise the old current
573       directory will be accessible as "." outside the new root! */
574    if (chroot(pw->pw_dir) < 0 || chdir("/") < 0) {
575      reply(550, "Can't set guest privileges.");
576      goto bad;
577    }
578  } else
579#endif	/* DOANONYMOUS */
580    if (chdir(pw->pw_dir) < 0) {
581      if (chdir("/") < 0) {
582	reply(530, "User %s: can't change directory to %s.",
583	      pw->pw_name, pw->pw_dir);
584	goto bad;
585      } else
586	lreply(230, "No directory! Logging in with home=/");
587    }
588/* This patch was contributed by an OPIE user. We don't know what it
589   does, exactly. It may or may not work. */
590#ifdef _AIX
591   {
592       priv_t priv;
593       priv.pv_priv[0] = 0;
594       priv.pv_priv[1] = 0;
595       setgroups(NULL, NULL);
596       if (setpriv(PRIV_SET|PRIV_INHERITED|PRIV_EFFECTIVE|PRIV_BEQUEATH,
597                   &priv, sizeof(priv_t)) < 0 ||
598	   setgidx(ID_REAL|ID_EFFECTIVE, (gid_t)pw->pw_gid) < 0 ||
599           setuidx(ID_REAL|ID_EFFECTIVE, (uid_t)pw->pw_uid) < 0 ||
600           seteuid((uid_t)pw->pw_uid) < 0) {
601               reply(550, "Can't set uid (_AIX3).");
602               goto bad;
603       }
604    }
605#else /* _AIX */
606  if (seteuid((uid_t) pw->pw_uid) < 0) {
607    reply(550, "Can't set uid.");
608    goto bad;
609  }
610#endif /* _AIX */
611 /*
612  * Display a login message, if it exists.
613  * N.B. reply(230,) must follow the message.
614  */
615  {
616  FILE *fd;
617
618  if ((fd = fopen(_PATH_FTPLOGINMESG, "r")) != NULL) {
619    char *cp, line[128];
620
621    while (fgets(line, sizeof(line), fd) != NULL) {
622      if ((cp = strchr(line, '\n')) != NULL)
623        *cp = '\0';
624      lreply(230, "%s", line);
625    }
626    (void) fflush(stdout);
627    (void) fclose(fd);
628  }
629  }
630#if DOANONYMOUS
631  if (guest) {
632    reply(230, "Guest login ok, access restrictions apply.");
633#if DOTITLE
634    snprintf(proctitle, sizeof(proctitle), "%s: anonymous/%s", remotehost,
635	passwd);
636    setproctitle(proctitle);
637#endif	/* DOTITLE */
638    syslog(LOG_NOTICE, "ANONYMOUS FTP login from %s with ID %s",
639            remotehost, passwd);
640  } else
641#endif	/* DOANONYMOUS */
642  {
643    reply(230, "User %s logged in.", pw->pw_name);
644
645#if DOTITLE
646    snprintf(proctitle, sizeof(proctitle), "%s: %s", remotehost, pw->pw_name);
647    setproctitle(proctitle);
648#endif	/* DOTITLE */
649    syslog(LOG_INFO, "FTP login from %s with user name %s", remotehost, pw->pw_name);
650  }
651  home = pw->pw_dir;	/* home dir for globbing */
652  umask(defumask);
653  return;
654
655bad:
656  /* Forget all about it... */
657  end_login();
658}
659
660VOIDRET retrieve FUNCTION((cmd, name), char *cmd AND char *name)
661{
662  FILE *fin, *dout;
663  struct stat st;
664  int (*closefunc) ();
665
666  if (cmd == 0) {
667    fin = fopen(name, "r"), closefunc = fclose;
668    st.st_size = 0;
669  } else {
670    char line[BUFSIZ];
671
672    snprintf(line, sizeof(line), cmd, name);
673    name = line;
674    fin = ftpd_popen(line, "r"), closefunc = ftpd_pclose;
675    st.st_size = -1;
676#if HAVE_ST_BLKSIZE
677    st.st_blksize = BUFSIZ;
678#endif /* HAVE_ST_BLKSIZE */
679  }
680  if (fin == NULL) {
681    if (errno != 0)
682      perror_reply(550, name);
683    return;
684  }
685  if (cmd == 0 &&
686      (fstat(fileno(fin), &st) < 0 || (st.st_mode & S_IFMT) != S_IFREG)) {
687    reply(550, "%s: not a plain file.", name);
688    goto done;
689  }
690  if (restart_point) {
691    if (type == TYPE_A) {
692      register int i, n, c;
693
694      n = restart_point;
695      i = 0;
696      while (i++ < n) {
697	if ((c = getc(fin)) == EOF) {
698	  perror_reply(550, name);
699	  goto done;
700	}
701	if (c == '\n')
702	  i++;
703      }
704    } else
705      if (lseek(fileno(fin), restart_point, SEEK_SET /* L_SET */ ) < 0) {
706	perror_reply(550, name);
707	goto done;
708      }
709  }
710  dout = dataconn(name, st.st_size, "w");
711  if (dout == NULL)
712    goto done;
713#if HAVE_ST_BLKSIZE
714  send_data(fin, dout, st.st_blksize);
715#else /* HAVE_ST_BLKSIZE */
716  send_data(fin, dout, BUFSIZ);
717#endif /* HAVE_ST_BLKSIZE */
718  fclose(dout);
719  data = -1;
720  pdata = -1;
721done:
722  (*closefunc) (fin);
723}
724
725VOIDRET store FUNCTION((name, mode, unique), char *name AND char *mode AND int unique)
726{
727  FILE *fout, *din;
728  struct stat st;
729  int (*closefunc) ();
730
731  if (unique && stat(name, &st) == 0 &&
732      (name = gunique(name)) == NULL)
733    return;
734
735  if (restart_point)
736    mode = "r+w";
737  fout = fopen(name, mode);
738  closefunc = fclose;
739  if (fout == NULL) {
740    perror_reply(553, name);
741    return;
742  }
743  if (restart_point) {
744    if (type == TYPE_A) {
745      register int i, n, c;
746
747      n = restart_point;
748      i = 0;
749      while (i++ < n) {
750	if ((c = getc(fout)) == EOF) {
751	  perror_reply(550, name);
752	  goto done;
753	}
754	if (c == '\n')
755	  i++;
756      }
757      /* We must do this seek to "current" position because we are changing
758         from reading to writing. */
759      if (fseek(fout, 0L, SEEK_CUR /* L_INCR */ ) < 0) {
760	perror_reply(550, name);
761	goto done;
762      }
763    } else
764      if (lseek(fileno(fout), restart_point, SEEK_SET /* L_SET */ ) < 0) {
765	perror_reply(550, name);
766	goto done;
767      }
768  }
769  din = dataconn(name, (off_t) - 1, "r");
770  if (din == NULL)
771    goto done;
772  if (receive_data(din, fout) == 0) {
773    if (unique)
774      reply(226, "Transfer complete (unique file name:%s).",
775	    name);
776    else
777      reply(226, "Transfer complete.");
778  }
779  fclose(din);
780  data = -1;
781  pdata = -1;
782done:
783  (*closefunc) (fout);
784}
785
786static FILE *getdatasock FUNCTION((mode), char *mode)
787{
788  int s, on = 1, tries;
789
790  if (data >= 0)
791    return (fdopen(data, mode));
792  disable_signalling();
793  if (seteuid((uid_t) 0))
794    syslog(LOG_ERR, "Can't set euid");
795  s = socket(AF_INET, SOCK_STREAM, 0);
796  if (s < 0)
797    goto bad;
798  if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
799		 (char *) &on, sizeof(on)) < 0)
800    goto bad;
801  /* anchor socket to avoid multi-homing problems */
802  data_source.sin_family = AF_INET;
803  data_source.sin_addr = ctrl_addr.sin_addr;
804  for (tries = 1;; tries++) {
805    if (bind(s, (struct sockaddr *) & data_source,
806	     sizeof(data_source)) >= 0)
807      break;
808    if (errno != EADDRINUSE || tries > 10)
809      goto bad;
810    sleep(tries);
811  }
812  if (seteuid((uid_t) pw->pw_uid))
813    syslog(LOG_ERR, "Can't set euid");
814  enable_signalling();
815#ifdef IP_TOS
816  on = IPTOS_THROUGHPUT;
817  if (setsockopt(s, IPPROTO_IP, IP_TOS, (char *) &on, sizeof(int)) < 0)
818    syslog(LOG_WARNING, "setsockopt (IP_TOS): %m");
819#endif
820  return (fdopen(s, mode));
821bad:
822  {
823  int t = errno;
824
825  if (seteuid((uid_t) pw->pw_uid))
826    syslog(LOG_ERR, "Can't set euid");
827  enable_signalling();
828  close(s);
829
830  errno = t;
831  }
832  return (NULL);
833}
834
835static FILE *dataconn FUNCTION((name, size, mode), char *name AND off_t size AND char *mode)
836{
837  char sizebuf[32];
838  FILE *file;
839  int retry = 0;
840#ifdef IP_TOS
841  int tos;
842#endif /* IP_TOS */
843
844  file_size = size;
845  byte_count = 0;
846  if (size != (off_t) - 1)
847    snprintf(sizebuf, sizeof(sizebuf), " (%ld bytes)", size);
848  else
849    strcpy(sizebuf, "");
850  if (pdata >= 0) {
851    struct sockaddr_in from;
852    int s, fromlen = sizeof(from);
853
854    s = accept(pdata, (struct sockaddr *) & from, &fromlen);
855    if (s < 0) {
856      reply(425, "Can't open data connection.");
857      close(pdata);
858      pdata = -1;
859      return (NULL);
860    }
861    close(pdata);
862    pdata = s;
863#ifdef IP_TOS
864    tos = IPTOS_LOWDELAY;
865    setsockopt(s, IPPROTO_IP, IP_TOS, (char *) &tos,
866		      sizeof(int));
867
868#endif
869    reply(150, "Opening %s mode data connection for %s%s.",
870	  type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
871    return (fdopen(pdata, mode));
872  }
873  if (data >= 0) {
874    reply(125, "Using existing data connection for %s%s.",
875	  name, sizebuf);
876    usedefault = 1;
877    return (fdopen(data, mode));
878  }
879  if (usedefault)
880    data_dest = his_addr;
881  usedefault = 1;
882  file = getdatasock(mode);
883  if (file == NULL) {
884    reply(425, "Can't create data socket (%s,%d): %s.",
885	  inet_ntoa(data_source.sin_addr),
886	  ntohs(data_source.sin_port), strerror(errno));
887    return (NULL);
888  }
889  data = fileno(file);
890  while (connect(data, (struct sockaddr *) & data_dest,
891		 sizeof(data_dest)) < 0) {
892    if (errno == EADDRINUSE && retry < swaitmax) {
893      sleep((unsigned) swaitint);
894      retry += swaitint;
895      continue;
896    }
897    perror_reply(425, "Can't build data connection");
898    fclose(file);
899    data = -1;
900    return (NULL);
901  }
902  reply(150, "Opening %s mode data connection for %s%s.",
903	type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
904  return (file);
905}
906
907/*
908 * Tranfer the contents of "instr" to
909 * "outstr" peer using the appropriate
910 * encapsulation of the data subject
911 * to Mode, Structure, and Type.
912 *
913 * NB: Form isn't handled.
914 */
915static VOIDRET send_data FUNCTION((instr, outstr, blksize), FILE *instr AND FILE *outstr AND off_t blksize)
916{
917  register int c, cnt;
918  register char *buf;
919  int netfd, filefd;
920
921  transflag++;
922  if (setjmp(urgcatch)) {
923    transflag = 0;
924    return;
925  }
926  switch (type) {
927
928  case TYPE_A:
929    while ((c = getc(instr)) != EOF) {
930      byte_count++;
931      if (c == '\n') {
932	if (ferror(outstr))
933	  goto data_err;
934	putc('\r', outstr);
935      }
936      putc(c, outstr);
937    }
938    fflush(outstr);
939    transflag = 0;
940    if (ferror(instr))
941      goto file_err;
942    if (ferror(outstr))
943      goto data_err;
944    reply(226, "Transfer complete.");
945    return;
946
947  case TYPE_I:
948  case TYPE_L:
949    if ((buf = malloc((u_int) blksize)) == NULL) {
950      transflag = 0;
951      perror_reply(451, "Local resource failure: malloc");
952      return;
953    }
954    netfd = fileno(outstr);
955    filefd = fileno(instr);
956    while ((cnt = read(filefd, buf, (u_int) blksize)) > 0 &&
957	   write(netfd, buf, cnt) == cnt)
958      byte_count += cnt;
959    transflag = 0;
960    free(buf);
961    if (cnt != 0) {
962      if (cnt < 0)
963	goto file_err;
964      goto data_err;
965    }
966    reply(226, "Transfer complete.");
967    return;
968  default:
969    transflag = 0;
970    reply(550, "Unimplemented TYPE %d in send_data", type);
971    return;
972  }
973
974data_err:
975  transflag = 0;
976  perror_reply(426, "Data connection");
977  return;
978
979file_err:
980  transflag = 0;
981  perror_reply(551, "Error on input file");
982}
983
984/*
985 * Transfer data from peer to
986 * "outstr" using the appropriate
987 * encapulation of the data subject
988 * to Mode, Structure, and Type.
989 *
990 * N.B.: Form isn't handled.
991 */
992static int receive_data FUNCTION((instr, outstr), FILE *instr AND FILE *outstr)
993{
994  register int c;
995  int cnt, bare_lfs = 0;
996  char buf[BUFSIZ];
997
998  transflag++;
999  if (setjmp(urgcatch)) {
1000    transflag = 0;
1001    return (-1);
1002  }
1003  switch (type) {
1004
1005  case TYPE_I:
1006  case TYPE_L:
1007    while ((cnt = read(fileno(instr), buf, sizeof buf)) > 0) {
1008      if (write(fileno(outstr), buf, cnt) != cnt)
1009	goto file_err;
1010      byte_count += cnt;
1011    }
1012    if (cnt < 0)
1013      goto data_err;
1014    transflag = 0;
1015    return (0);
1016
1017  case TYPE_E:
1018    reply(553, "TYPE E not implemented.");
1019    transflag = 0;
1020    return (-1);
1021
1022  case TYPE_A:
1023    while ((c = getc(instr)) != EOF) {
1024      byte_count++;
1025      if (c == '\n')
1026	bare_lfs++;
1027      while (c == '\r') {
1028	if (ferror(outstr))
1029	  goto data_err;
1030	if ((c = getc(instr)) != '\n') {
1031	  putc('\r', outstr);
1032	  if (c == '\0' || c == EOF)
1033	    goto contin2;
1034	}
1035      }
1036      putc(c, outstr);
1037  contin2:;
1038    }
1039    fflush(outstr);
1040    if (ferror(instr))
1041      goto data_err;
1042    if (ferror(outstr))
1043      goto file_err;
1044    transflag = 0;
1045    if (bare_lfs) {
1046      lreply(230, "WARNING! %d bare linefeeds received in ASCII mode", bare_lfs);
1047      printf("   File may not have transferred correctly.\r\n");
1048    }
1049    return (0);
1050  default:
1051    reply(550, "Unimplemented TYPE %d in receive_data", type);
1052    transflag = 0;
1053    return (-1);
1054  }
1055
1056data_err:
1057  transflag = 0;
1058  perror_reply(426, "Data Connection");
1059  return (-1);
1060
1061file_err:
1062  transflag = 0;
1063  perror_reply(452, "Error writing file");
1064  return (-1);
1065}
1066
1067VOIDRET statfilecmd FUNCTION((filename), char *filename)
1068{
1069  char line[BUFSIZ];
1070  FILE *fin;
1071  int c;
1072
1073#if HAVE_LS_G_FLAG
1074  snprintf(line, sizeof(line), "%s %s", "/bin/ls -lgA", filename);
1075#else /* HAVE_LS_G_FLAG */
1076  snprintf(line, sizeof(line), "%s %s", "/bin/ls -lA", filename);
1077#endif /* HAVE_LS_G_FLAG */
1078  fin = ftpd_popen(line, "r");
1079  lreply(211, "status of %s:", filename);
1080  while ((c = getc(fin)) != EOF) {
1081    if (c == '\n') {
1082      if (ferror(stdout)) {
1083	perror_reply(421, "control connection");
1084	ftpd_pclose(fin);
1085	dologout(1);
1086	/* NOTREACHED */
1087      }
1088      if (ferror(fin)) {
1089	perror_reply(551, filename);
1090	ftpd_pclose(fin);
1091	return;
1092      }
1093      putc('\r', stdout);
1094    }
1095    putc(c, stdout);
1096  }
1097  ftpd_pclose(fin);
1098  reply(211, "End of Status");
1099}
1100
1101VOIDRET statcmd FUNCTION_NOARGS
1102{
1103/* COMMENTED OUT STUFF BECAUSE THINGS BROKE ON SUNOS. */
1104  struct sockaddr_in *sin;
1105  u_char *a, *p;
1106
1107  lreply(211, "FTP server status:");
1108  printf("     \r\n");
1109  printf("     Connected to %s", remotehost);
1110  if (!isdigit(remotehost[0]))
1111    printf(" (%s)", inet_ntoa(his_addr.sin_addr));
1112  printf("\r\n");
1113  if (logged_in) {
1114#if DOANONYMOUS
1115    if (guest)
1116      printf("     Logged in anonymously\r\n");
1117    else
1118#endif	/* DOANONYMOUS */
1119      printf("     Logged in as %s\r\n", pw->pw_name);
1120  } else
1121    if (askpasswd)
1122      printf("     Waiting for password\r\n");
1123    else
1124      printf("     Waiting for user name\r\n");
1125  if (data != -1)
1126    printf("     Data connection open\r\n");
1127  else
1128    if (pdata != -1) {
1129      printf("     in Passive mode");
1130      sin = &pasv_addr;
1131      goto printaddr;
1132    } else
1133      if (usedefault == 0) {
1134	printf("     PORT");
1135	sin = &data_dest;
1136    printaddr:
1137	a = (u_char *) & sin->sin_addr;
1138	p = (u_char *) & sin->sin_port;
1139#define UC(b) (((int) b) & 0xff)
1140	printf(" (%d,%d,%d,%d,%d,%d)\r\n", UC(a[0]),
1141	       UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1]));
1142#undef UC
1143      } else
1144	printf("     No data connection\r\n");
1145  reply(211, "End of status");
1146}
1147
1148VOIDRET opiefatal FUNCTION((s), char *s)
1149{
1150  reply(451, "Error in server: %s\n", s);
1151  reply(221, "Closing connection due to server error.");
1152  dologout(0);
1153  /* NOTREACHED */
1154}
1155
1156static VOIDRET ack FUNCTION((s), char *s)
1157{
1158  reply(250, "%s command successful.", s);
1159}
1160
1161VOIDRET nack FUNCTION((s), char *s)
1162{
1163  reply(502, "%s command not implemented.", s);
1164}
1165
1166VOIDRET yyerror FUNCTION((s), char *s)
1167{
1168  char *cp;
1169
1170  if (cp = strchr(cbuf, '\n'))
1171    *cp = '\0';
1172  reply(500, "'%s': command not understood.", cbuf);
1173}
1174
1175VOIDRET delete FUNCTION((name), char *name)
1176{
1177  struct stat st;
1178
1179  if (stat(name, &st) < 0) {
1180    perror_reply(550, name);
1181    return;
1182  }
1183  if ((st.st_mode & S_IFMT) == S_IFDIR) {
1184    if (rmdir(name) < 0) {
1185      perror_reply(550, name);
1186      return;
1187    }
1188    goto done;
1189  }
1190  if (unlink(name) < 0) {
1191    perror_reply(550, name);
1192    return;
1193  }
1194done:
1195  ack("DELE");
1196}
1197
1198VOIDRET cwd FUNCTION((path), char *path)
1199{
1200  if (chdir(path) < 0)
1201    perror_reply(550, path);
1202  else
1203    ack("CWD");
1204}
1205
1206VOIDRET makedir FUNCTION((name), char *name)
1207{
1208  if (mkdir(name, 0777) < 0)
1209    perror_reply(550, name);
1210  else
1211    reply(257, "MKD command successful.");
1212}
1213
1214VOIDRET removedir FUNCTION((name), char *name)
1215{
1216  if (rmdir(name) < 0)
1217    perror_reply(550, name);
1218  else
1219    ack("RMD");
1220}
1221
1222VOIDRET pwd FUNCTION_NOARGS
1223{
1224  char path[MAXPATHLEN + 1];
1225
1226  if (getcwd(path, MAXPATHLEN) == (char *) NULL)
1227    reply(550, "%s.", path);
1228  else
1229    reply(257, "\"%s\" is current directory.", path);
1230}
1231
1232char *renamefrom FUNCTION((name), char *name)
1233{
1234  struct stat st;
1235
1236  if (stat(name, &st) < 0) {
1237    perror_reply(550, name);
1238    return ((char *) 0);
1239  }
1240  reply(350, "File exists, ready for destination name");
1241  return (name);
1242}
1243
1244VOIDRET renamecmd FUNCTION((from, to), char *from AND char *to)
1245{
1246  if (rename(from, to) < 0)
1247    perror_reply(550, "rename");
1248  else
1249    ack("RNTO");
1250}
1251
1252static VOIDRET dolog FUNCTION((sin), struct sockaddr_in *sin)
1253{
1254  struct hostent *hp = gethostbyaddr((char *) &sin->sin_addr,
1255				     sizeof(struct in_addr), AF_INET);
1256  time_t t, time();
1257
1258  if (hp)
1259    strncpy(remotehost, hp->h_name, sizeof(remotehost));
1260  else
1261    strncpy(remotehost, inet_ntoa(sin->sin_addr), sizeof(remotehost));
1262  remotehost[sizeof(remotehost) - 1] = '\0';
1263#if DOTITLE
1264  snprintf(proctitle, sizeof(proctitle), "%s: connected", remotehost);
1265  setproctitle(proctitle);
1266#endif	/* DOTITLE */
1267
1268  t = time((time_t *) 0);
1269  syslog(LOG_INFO, "connection from %s at %s",
1270    remotehost, ctime(&t));
1271}
1272
1273/*
1274 * Record logout in wtmp file
1275 * and exit with supplied status.
1276 */
1277VOIDRET dologout FUNCTION((status), int status)
1278{
1279  disable_signalling();
1280  if (logged_in) {
1281    if (seteuid((uid_t) 0))
1282      syslog(LOG_ERR, "Can't set euid");
1283    opielogwtmp(ttyline, "", "");
1284  }
1285  /* beware of flushing buffers after a SIGPIPE */
1286  _exit(status);
1287}
1288
1289static VOIDRET myoob FUNCTION((input), int input)
1290{
1291  char *cp;
1292
1293  /* only process if transfer occurring */
1294  if (!transflag)
1295    return;
1296  cp = tmpline;
1297  if (getline(cp, 7, stdin) == NULL) {
1298    reply(221, "You could at least say goodbye.");
1299    dologout(0);
1300  }
1301  upper(cp);
1302  if (strcmp(cp, "ABOR\r\n") == 0) {
1303    tmpline[0] = '\0';
1304    reply(426, "Transfer aborted. Data connection closed.");
1305    reply(226, "Abort successful");
1306    longjmp(urgcatch, 1);
1307  }
1308  if (strcmp(cp, "STAT\r\n") == 0) {
1309    if (file_size != (off_t) - 1)
1310      reply(213, "Status: %lu of %lu bytes transferred",
1311	    byte_count, file_size);
1312    else
1313      reply(213, "Status: %lu bytes transferred", byte_count);
1314  }
1315}
1316
1317/*
1318 * Note: a response of 425 is not mentioned as a possible response to
1319 *      the PASV command in RFC959. However, it has been blessed as
1320 *      a legitimate response by Jon Postel in a telephone conversation
1321 *      with Rick Adams on 25 Jan 89.
1322 */
1323VOIDRET passive FUNCTION_NOARGS
1324{
1325  int len;
1326  register char *p, *a;
1327
1328  pdata = socket(AF_INET, SOCK_STREAM, 0);
1329  if (pdata < 0) {
1330    perror_reply(425, "Can't open passive connection");
1331    return;
1332  }
1333  pasv_addr = ctrl_addr;
1334  pasv_addr.sin_port = 0;
1335  if (seteuid((uid_t) 0))
1336    syslog(LOG_ERR, "Can't set euid");
1337  if (bind(pdata, (struct sockaddr *) & pasv_addr, sizeof(pasv_addr)) < 0) {
1338    seteuid((uid_t) pw->pw_uid);
1339    goto pasv_error;
1340  }
1341  if (seteuid((uid_t) pw->pw_uid))
1342    syslog(LOG_ERR, "Can't set euid");
1343  len = sizeof(pasv_addr);
1344  if (getsockname(pdata, (struct sockaddr *) & pasv_addr, &len) < 0)
1345    goto pasv_error;
1346  if (listen(pdata, 1) < 0)
1347    goto pasv_error;
1348  a = (char *) &pasv_addr.sin_addr;
1349  p = (char *) &pasv_addr.sin_port;
1350
1351#define UC(b) (((int) b) & 0xff)
1352
1353  reply(227, "Entering Passive Mode (%d,%d,%d,%d,%d,%d)", UC(a[0]),
1354	UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1]));
1355  return;
1356
1357pasv_error:
1358  close(pdata);
1359  pdata = -1;
1360  perror_reply(425, "Can't open passive connection");
1361  return;
1362}
1363
1364/*
1365 * Generate unique name for file with basename "local".
1366 * The file named "local" is already known to exist.
1367 * Generates failure reply on error.
1368 */
1369static char *gunique FUNCTION((local), char *local)
1370{
1371  static char new[MAXPATHLEN+1];
1372  struct stat st;
1373  char *cp = strrchr(local, '/');
1374  int count = 0;
1375
1376  if (cp)
1377    *cp = '\0';
1378  if (stat(cp ? local : ".", &st) < 0) {
1379    perror_reply(553, cp ? local : ".");
1380    return ((char *) 0);
1381  }
1382  if (cp)
1383    *cp = '/';
1384  strcpy(new, local);
1385  cp = new + strlen(new);
1386  *cp++ = '.';
1387  for (count = 1; count < 100; count++) {
1388    snprintf(cp, sizeof(new) - (cp - new), "%d", count);
1389    if (stat(new, &st) < 0)
1390      return (new);
1391  }
1392  reply(452, "Unique file name cannot be created.");
1393  return ((char *) 0);
1394}
1395
1396/*
1397 * Format and send reply containing system error number.
1398 */
1399VOIDRET perror_reply FUNCTION((code, string), int code AND char *string)
1400{
1401  reply(code, "%s: %s.", string, strerror(errno));
1402}
1403
1404static char *onefile[] =
1405{
1406  "",
1407  0
1408};
1409
1410VOIDRET send_file_list FUNCTION((whichfiles), char *whichfiles)
1411{
1412  struct stat st;
1413  DIR *dirp = NULL;
1414  struct dirent *dir;
1415  FILE *dout = NULL;
1416  register char **dirlist, *dirname;
1417  int simple = 0;
1418
1419  if (strpbrk(whichfiles, "~{[*?") != NULL) {
1420    extern char **ftpglob(), *globerr;
1421
1422    globerr = NULL;
1423    dirlist = ftpglob(whichfiles);
1424    if (globerr != NULL) {
1425      reply(550, globerr);
1426      return;
1427    } else
1428      if (dirlist == NULL) {
1429	errno = ENOENT;
1430	perror_reply(550, whichfiles);
1431	return;
1432      }
1433  } else {
1434    onefile[0] = whichfiles;
1435    dirlist = onefile;
1436    simple = 1;
1437  }
1438
1439  if (setjmp(urgcatch)) {
1440    transflag = 0;
1441    return;
1442  }
1443  while (dirname = *dirlist++) {
1444    if (stat(dirname, &st) < 0) {
1445      /* If user typed "ls -l", etc, and the client used NLST, do what the
1446         user meant. */
1447      if (dirname[0] == '-' && *dirlist == NULL &&
1448	  transflag == 0) {
1449	retrieve("/bin/ls %s", dirname);
1450	return;
1451      }
1452      perror_reply(550, whichfiles);
1453      if (dout != NULL) {
1454	fclose(dout);
1455	transflag = 0;
1456	data = -1;
1457	pdata = -1;
1458      }
1459      return;
1460    }
1461    if ((st.st_mode & S_IFMT) == S_IFREG) {
1462      if (dout == NULL) {
1463	dout = dataconn("file list", (off_t) - 1, "w");
1464	if (dout == NULL)
1465	  return;
1466	transflag++;
1467      }
1468      fprintf(dout, "%s%s\n", dirname,
1469	      type == TYPE_A ? "\r" : "");
1470      byte_count += strlen(dirname) + 1;
1471      continue;
1472    } else
1473      if ((st.st_mode & S_IFMT) != S_IFDIR)
1474	continue;
1475
1476    if ((dirp = opendir(dirname)) == NULL)
1477      continue;
1478
1479    while ((dir = readdir(dirp)) != NULL) {
1480      char nbuf[MAXPATHLEN+1];
1481
1482      if (dir->d_name[0] == '.' && (strlen(dir->d_name) == 1))
1483	continue;
1484      if (dir->d_name[0] == '.' && dir->d_name[1] == '.' &&
1485	  (strlen(dir->d_name) == 2))
1486	continue;
1487
1488      snprintf(nbuf, sizeof(nbuf), "%s/%s", dirname, dir->d_name);
1489
1490      /* We have to do a stat to insure it's not a directory or special file. */
1491      if (simple || (stat(nbuf, &st) == 0 &&
1492		     (st.st_mode & S_IFMT) == S_IFREG)) {
1493	if (dout == NULL) {
1494	  dout = dataconn("file list", (off_t) - 1, "w");
1495	  if (dout == NULL)
1496	    return;
1497	  transflag++;
1498	}
1499	if (nbuf[0] == '.' && nbuf[1] == '/')
1500	  fprintf(dout, "%s%s\n", &nbuf[2],
1501		  type == TYPE_A ? "\r" : "");
1502	else
1503	  fprintf(dout, "%s%s\n", nbuf,
1504		  type == TYPE_A ? "\r" : "");
1505	byte_count += strlen(nbuf) + 1;
1506      }
1507    }
1508    closedir(dirp);
1509  }
1510
1511  if (dout == NULL)
1512    reply(550, "No files found.");
1513  else
1514    if (ferror(dout) != 0)
1515      perror_reply(550, "Data connection");
1516    else
1517      reply(226, "Transfer complete.");
1518
1519  transflag = 0;
1520  if (dout != NULL)
1521    fclose(dout);
1522  data = -1;
1523  pdata = -1;
1524}
1525
1526#if DOTITLE
1527/*
1528 * clobber argv so ps will show what we're doing.
1529 * (stolen from sendmail)
1530 * warning, since this is usually started from inetd.conf, it
1531 * often doesn't have much of an environment or arglist to overwrite.
1532 */
1533VOIDRET setproctitle FUNCTION((fmt, a, b, c), char *fmt AND int a AND int b AND int c)
1534{
1535  register char *p, *bp, ch;
1536  register int i;
1537  char buf[BUFSIZ];
1538
1539  snprintf(buf, sizeof(buf), fmt, a, b, c);
1540
1541  /* make ps print our process name */
1542  p = Argv[0];
1543  *p++ = '-';
1544
1545  i = strlen(buf);
1546  if (i > LastArgv - p - 2) {
1547    i = LastArgv - p - 2;
1548    buf[i] = '\0';
1549  }
1550  bp = buf;
1551  while (ch = *bp++)
1552    if (ch != '\n' && ch != '\r')
1553      *p++ = ch;
1554  while (p < LastArgv)
1555    *p++ = ' ';
1556}
1557#endif	/* DOTITLE */
1558
1559VOIDRET catchexit FUNCTION_NOARGS
1560{
1561  closelog();
1562}
1563
1564int main FUNCTION((argc, argv, envp), int argc AND char *argv[] AND char *envp[])
1565{
1566  int addrlen, on = 1;
1567  char *cp;
1568#ifdef IP_TOS
1569  int tos;
1570#endif /* IP_TOS */
1571
1572  {
1573  int i;
1574
1575  for (i = sysconf(_SC_OPEN_MAX); i > 2; i--)
1576    close(i);
1577  }
1578
1579  /* LOG_NDELAY sets up the logging connection immediately, necessary for
1580     anonymous ftp's that chroot and can't do it later. */
1581  openlog("ftpd", LOG_PID | LOG_NDELAY, LOG_DAEMON);
1582  atexit(catchexit);
1583  addrlen = sizeof(his_addr);
1584  if (getpeername(0, (struct sockaddr *) & his_addr, &addrlen) < 0) {
1585    syslog(LOG_ERR, "getpeername (%s): %m", argv[0]);
1586    exit(1);
1587  }
1588  addrlen = sizeof(ctrl_addr);
1589  if (getsockname(0, (struct sockaddr *) & ctrl_addr, &addrlen) < 0) {
1590    syslog(LOG_ERR, "getsockname (%s): %m", argv[0]);
1591    exit(1);
1592  }
1593#ifdef IP_TOS
1594  tos = IPTOS_LOWDELAY;
1595  if (setsockopt(0, IPPROTO_IP, IP_TOS, (char *) &tos, sizeof(int)) < 0)
1596    syslog(LOG_WARNING, "setsockopt (IP_TOS): %m");
1597#endif
1598  data_source.sin_port = htons(ntohs(ctrl_addr.sin_port) - 1);
1599  debug = 0;
1600#if DOTITLE
1601  /* Save start and extent of argv for setproctitle. */
1602  Argv = argv;
1603  while (*envp)
1604    envp++;
1605  LastArgv = envp[-1] + strlen(envp[-1]);
1606#endif	/* DOTITLE */
1607
1608  argc--, argv++;
1609  while (argc > 0 && *argv[0] == '-') {
1610    for (cp = &argv[0][1]; *cp; cp++)
1611      switch (*cp) {
1612
1613      case 'v':
1614	debug = 1;
1615	break;
1616
1617      case 'd':
1618	debug = 1;
1619	break;
1620
1621      case 'l':
1622	break;
1623
1624      case 't':
1625	timeout = atoi(++cp);
1626	if (maxtimeout < timeout)
1627	  maxtimeout = timeout;
1628	goto nextopt;
1629
1630      case 'T':
1631	maxtimeout = atoi(++cp);
1632	if (timeout > maxtimeout)
1633	  timeout = maxtimeout;
1634	goto nextopt;
1635
1636      case 'u':
1637	{
1638	  int val = 0;
1639
1640	  while (*++cp && *cp >= '0' && *cp <= '9')
1641	    val = val * 8 + *cp - '0';
1642	  if (*cp)
1643	    fprintf(stderr, "ftpd: Bad value for -u\n");
1644	  else
1645	    defumask = val;
1646	  goto nextopt;
1647	}
1648
1649      default:
1650	fprintf(stderr, "ftpd: Unknown flag -%c ignored.\n",
1651		*cp);
1652	break;
1653      }
1654nextopt:
1655    argc--, argv++;
1656  }
1657  freopen(_PATH_DEVNULL, "w", stderr);
1658  signal(SIGCHLD, SIG_IGN);
1659  enable_signalling();
1660
1661  /* Try to handle urgent data inline */
1662#ifdef SO_OOBINLINE
1663  if (setsockopt(0, SOL_SOCKET, SO_OOBINLINE, (char *) &on, sizeof(on)) < 0)
1664    syslog(LOG_ERR, "setsockopt: %m");
1665#endif
1666
1667#ifdef	F_SETOWN
1668  if (fcntl(fileno(stdin), F_SETOWN, getpid()) == -1)
1669    syslog(LOG_ERR, "fcntl F_SETOWN: %m");
1670#endif
1671  dolog(&his_addr);
1672  /* Set up default state */
1673  data = -1;
1674  type = TYPE_A;
1675  form = FORM_N;
1676  stru = STRU_F;
1677  mode = MODE_S;
1678  tmpline[0] = '\0';
1679  af_pwok = opieaccessfile(remotehost);
1680
1681  {
1682  FILE *fd;
1683  char line[128];
1684
1685  /* If logins are disabled, print out the message. */
1686  if ((fd = fopen(_PATH_NOLOGIN,"r")) != NULL) {
1687    while (fgets(line, sizeof(line), fd) != NULL) {
1688      if ((cp = strchr(line, '\n')) != NULL)
1689        *cp = '\0';
1690      lreply(530, "%s", line);
1691    }
1692    (void) fflush(stdout);
1693    (void) fclose(fd);
1694    reply(530, "System not available.");
1695    exit(0);
1696  }
1697  if ((fd = fopen(_PATH_FTPWELCOME, "r")) != NULL) {
1698    while (fgets(line, sizeof(line), fd) != NULL) {
1699      if ((cp = strchr(line, '\n')) != NULL)
1700        *cp = '\0';
1701      lreply(220, "%s", line);
1702    }
1703    (void) fflush(stdout);
1704    (void) fclose(fd);
1705    /* reply(220,) must follow */
1706  }
1707  };
1708
1709  reply(220, "FTP server ready.");
1710
1711  setjmp(errcatch);
1712  for (;;)
1713    yyparse();
1714  /* NOTREACHED */
1715  return 0;
1716}
1717