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