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