* Copyright (c) 1985, 1988 Regents of the University of California.
* Redistribution and use in source and binary forms are permitted
* provided that the above copyright notice and this paragraph are
* duplicated in all such forms and that any documentation,
* advertising materials, and other materials related to such
* distribution and use acknowledge that the software was developed
* by the University of California, Berkeley. The name of the
* University may not be used to endorse or promote products derived
* from this software without specific prior written permission.
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
"@(#) Copyright (c) 1985, 1988 Regents of the University of California.\n\
static char sccsid
[] = "@(#)ftpd.c 5.26 (Berkeley) %G%";
* File containing login names
* NOT to be used on this machine.
* Commonly used to disallow uucp.
#define FTPUSERS "/etc/ftpusers"
extern char *sys_errlist
[];
extern char *home
; /* pointer to home directory for glob */
extern FILE *ftpd_popen(), *fopen(), *freopen();
extern int ftpd_pclose(), fclose();
extern off_t restart_point
;
struct sockaddr_in ctrl_addr
;
struct sockaddr_in data_source
;
struct sockaddr_in data_dest
;
struct sockaddr_in his_addr
;
jmp_buf errcatch
, urgcatch
;
int timeout
= 900; /* timeout after 15 minutes of inactivity */
int stru
; /* avoid C keyword */
int usedefault
= 1; /* for data transfers */
int pdata
= -1; /* for passive mode */
char hostname
[MAXHOSTNAMELEN
];
char remotehost
[MAXHOSTNAMELEN
];
* Timeout intervals for retrying connections
* to hosts that don't accept PORT cmds. This
* is a kludge, but given the problems with TCP...
#define SWAITMAX 90 /* wait at most 90 seconds */
#define SWAITINT 5 /* interval between retries */
FILE *getdatasock(), *dataconn();
char **Argv
= NULL
; /* pointer to argument vector */
char *LastArgv
= NULL
; /* end of argv */
#endif /* SETPROCTITLE */
addrlen
= sizeof (his_addr
);
if (getpeername(0, (struct sockaddr
*)&his_addr
, &addrlen
) < 0) {
syslog(LOG_ERR
, "getpeername (%s): %m",argv
[0]);
addrlen
= sizeof (ctrl_addr
);
if (getsockname(0, (struct sockaddr
*)&ctrl_addr
, &addrlen
) < 0) {
syslog(LOG_ERR
, "getsockname (%s): %m",argv
[0]);
data_source
.sin_port
= htons(ntohs(ctrl_addr
.sin_port
) - 1);
openlog("ftpd", LOG_PID
, LOG_DAEMON
);
* Save start and extent of argv for setproctitle.
LastArgv
= envp
[-1] + strlen(envp
[-1]);
#endif /* SETPROCTITLE */
while (argc
> 0 && *argv
[0] == '-') {
for (cp
= &argv
[0][1]; *cp
; cp
++) switch (*cp
) {
fprintf(stderr
, "ftpd: Unknown flag -%c ignored.\n",
(void) freopen("/dev/null", "w", stderr
);
(void) signal(SIGPIPE
, lostconn
);
(void) signal(SIGCHLD
, SIG_IGN
);
if ((int)signal(SIGURG
, myoob
) < 0)
syslog(LOG_ERR
, "signal: %m");
/* handle urgent data inline */
/* Sequent defines this, but it doesn't work */
if (setsockopt(0, SOL_SOCKET
, SO_OOBINLINE
, (char *)&on
, sizeof(on
)) < 0)
syslog(LOG_ERR
, "setsockopt: %m");
if (fcntl(fileno(stdin
), F_SETOWN
, getpid()) == -1)
syslog(LOG_ERR
, "fcntl F_SETOWN: %m");
/* do telnet option negotiation here */
(void) gethostname(hostname
, sizeof (hostname
));
reply(220, "%s FTP server (%s) ready.", hostname
, version
);
syslog(LOG_DEBUG
, "lost connection");
* Helper function for sgetpwnam().
char *new = malloc((unsigned) strlen(s
) + 1);
perror_reply(421, "Local resource failure: malloc");
* Save the result of a getpwnam. Used for USER command, since
* the data returned must not be clobbered by any other command
static struct passwd save
;
register struct passwd
*p
;
if ((p
= getpwnam(name
)) == NULL
)
save
.pw_name
= sgetsave(p
->pw_name
);
save
.pw_passwd
= sgetsave(p
->pw_passwd
);
save
.pw_comment
= sgetsave(p
->pw_comment
);
save
.pw_gecos
= sgetsave(p
->pw_gecos
);
save
.pw_dir
= sgetsave(p
->pw_dir
);
save
.pw_shell
= sgetsave(p
->pw_shell
);
int login_attempts
; /* number of failed login attempts */
int askpasswd
; /* had user command, ask for passwd */
* Sets global passwd pointer pw if named account exists
* and is acceptable; sets askpasswd if a PASS command is
* expected. If logged in previously, need to reset state.
* If name is "ftp" or "anonymous" and ftp account exists,
* set guest and pw, then just return.
* If account doesn't exist, ask for passwd anyway.
* Otherwise, check user requesting login privileges.
* Disallow anyone who does not have a standard
* shell returned by getusershell() (/etc/shells).
* Disallow anyone mentioned in the file FTPUSERS
* to allow people such as root and uucp to be avoided.
char line
[BUFSIZ
], *getusershell();
reply(530, "Can't change user from guest login.");
if (strcmp(name
, "ftp") == 0 || strcmp(name
, "anonymous") == 0) {
if ((pw
= sgetpwnam("ftp")) != NULL
) {
reply(331, "Guest login ok, send ident as password.");
reply(530, "User %s unknown.", name
);
if (pw
= sgetpwnam(name
)) {
if ((shell
= pw
->pw_shell
) == NULL
|| *shell
== 0)
while ((cp
= getusershell()) != NULL
)
if (strcmp(cp
, shell
) == 0)
reply(530, "User %s access denied.", name
);
syslog(LOG_ERR
, "FTP LOGIN REFUSED FROM %s, %s",
pw
= (struct passwd
*) NULL
;
if ((fd
= fopen(FTPUSERS
, "r")) != NULL
) {
while (fgets(line
, sizeof (line
), fd
) != NULL
) {
if ((cp
= index(line
, '\n')) != NULL
)
if (strcmp(line
, name
) == 0) {
reply(530, "User %s access denied.", name
);
syslog(LOG_ERR
, "FTP LOGIN REFUSED FROM %s, %s",
pw
= (struct passwd
*) NULL
;
reply(331, "Password required for %s.", name
);
* Delay before reading passwd after first failed
* attempt to slow down passwd-guessing programs.
sleep((unsigned) login_attempts
);
* Terminate login as previous user, if any, resetting state;
* used when USER command is given or login fails.
(void) seteuid((uid_t
)0);
logwtmp(ttyline
, "", "");
if (logged_in
|| askpasswd
== 0) {
reply(503, "Login with USER first.");
if (!guest
) { /* "ftp" is only account allowed no password */
xpasswd
= crypt(passwd
, salt
);
/* The strcmp does not catch null passwords! */
if (pw
== NULL
|| *pw
->pw_passwd
== '\0' ||
strcmp(xpasswd
, pw
->pw_passwd
)) {
reply(530, "Login incorrect.");
if (login_attempts
++ >= 5) {
"repeated login failures from %s",
login_attempts
= 0; /* this time successful */
(void) setegid((gid_t
)pw
->pw_gid
);
(void) initgroups(pw
->pw_name
, pw
->pw_gid
);
/* open wtmp before chroot */
(void)sprintf(ttyline
, "ftp%d", getpid());
logwtmp(ttyline
, pw
->pw_name
, remotehost
);
* We MUST do a chdir() after the chroot. Otherwise "."
* will be accessible outside the root!
if (chroot(pw
->pw_dir
) < 0 || chdir("/") < 0) {
reply(550, "Can't set guest privileges.");
else if (chdir(pw
->pw_dir
) < 0) {
reply(530, "User %s: can't change directory to %s.",
pw
->pw_name
, pw
->pw_dir
);
lreply(230, "No directory! Logging in with home=/");
if (seteuid((uid_t
)pw
->pw_uid
) < 0) {
reply(550, "Can't set uid.");
reply(230, "Guest login ok, access restrictions apply.");
syslog(LOG_INFO
, "ANONYMOUS FTP LOGIN FROM %s, %s",
reply(230, "User %s logged in.", pw
->pw_name
);
syslog(LOG_INFO
, "FTP LOGIN FROM %s, %s",
remotehost
, pw
->pw_name
);
home
= pw
->pw_dir
; /* home dir for globbing */
/* Forget all about it... */
fin
= fopen(name
, "r"), closefunc
= fclose
;
(void) sprintf(line
, cmd
, name
), name
= line
;
fin
= ftpd_popen(line
, "r"), closefunc
= ftpd_pclose
;
(stat(name
, &st
) < 0 || (st
.st_mode
&S_IFMT
) != S_IFREG
)) {
reply(550, "%s: not a plain file.", name
);
if (fseek(fin
, restart_point
, L_SET
) < 0) {
else if (lseek(fileno(fin
), restart_point
, L_SET
) < 0) {
dout
= dataconn(name
, st
.st_size
, "w");
send_data(fin
, dout
, st
.st_blksize
);
store(name
, mode
, unique
)
if (unique
&& stat(name
, &st
) == 0 &&
(name
= gunique(name
)) == NULL
)
fout
= fopen(name
, mode
);
if (fseek(fout
, restart_point
, L_SET
) < 0) {
else if (lseek(fileno(fout
), restart_point
, L_SET
) < 0) {
din
= dataconn(name
, (off_t
)-1, "r");
if (receive_data(din
, fout
) == 0) {
reply(226, "Transfer complete (unique file name:%s).",
reply(226, "Transfer complete.");
return (fdopen(data
, mode
));
s
= socket(AF_INET
, SOCK_STREAM
, 0);
(void) seteuid((uid_t
)0);
if (setsockopt(s
, SOL_SOCKET
, SO_REUSEADDR
, (char *) &on
, sizeof (on
)) < 0)
/* anchor socket to avoid multi-homing problems */
data_source
.sin_family
= AF_INET
;
data_source
.sin_addr
= ctrl_addr
.sin_addr
;
if (bind(s
, (struct sockaddr
*)&data_source
, sizeof (data_source
)) < 0)
(void) seteuid((uid_t
)pw
->pw_uid
);
return (fdopen(s
, mode
));
(void) seteuid((uid_t
)pw
->pw_uid
);
dataconn(name
, size
, mode
)
(void) sprintf (sizebuf
, " (%ld bytes)", size
);
(void) strcpy(sizebuf
, "");
int s
, fromlen
= sizeof(from
);
s
= accept(pdata
, (struct sockaddr
*)&from
, &fromlen
);
reply(425, "Can't open data connection.");
reply(150, "Opening %s mode data connection for %s%s.",
type
== TYPE_A
? "ASCII" : "BINARY", name
, sizebuf
);
return(fdopen(pdata
, mode
));
reply(125, "Using existing data connection for %s%s.",
return (fdopen(data
, mode
));
file
= getdatasock(mode
);
reply(425, "Can't create data socket (%s,%d): %s.",
inet_ntoa(data_source
.sin_addr
),
ntohs(data_source
.sin_port
),
errno
< sys_nerr
? sys_errlist
[errno
] : "unknown error");
while (connect(data
, (struct sockaddr
*)&data_dest
,
sizeof (data_dest
)) < 0) {
if (errno
== EADDRINUSE
&& retry
< swaitmax
) {
sleep((unsigned) swaitint
);
perror_reply(425, "Can't build data connection");
reply(150, "Opening %s mode data connection for %s%s.",
type
== TYPE_A
? "ASCII" : "BINARY", name
, sizebuf
);
* Tranfer the contents of "instr" to
* "outstr" peer using the appropriate
* encapulation of the date subject
* to Mode, Structure, and Type.
* NB: Form isn't handled.
send_data(instr
, outstr
, blksize
)
while ((c
= getc(instr
)) != EOF
) {
(void) putc('\r', outstr
);
if (ferror (instr
) || ferror (outstr
))
reply(226, "Transfer complete.");
if ((buf
= malloc((u_int
)blksize
)) == NULL
) {
perror_reply(421, "Local resource failure: malloc");
while ((cnt
= read(filefd
, buf
, sizeof(buf
))) > 0 &&
write(netfd
, buf
, cnt
) == cnt
)
reply(226, "Transfer complete.");
reply(550, "Unimplemented TYPE %d in send_data", type
);
perror_reply(421, "Data connection");
* Transfer data from peer to
* "outstr" using the appropriate
* encapulation of the data subject
* to Mode, Structure, and Type.
* N.B.: Form isn't handled.
receive_data(instr
, outstr
)
while ((cnt
= read(fileno(instr
), buf
, sizeof buf
)) > 0) {
if (write(fileno(outstr
), buf
, cnt
) != cnt
)
reply(553, "TYPE E not implemented.");
while ((c
= getc(instr
)) != EOF
) {
if ((c
= getc(instr
)) != '\n')
(void) putc ('\r', outstr
);
if (ferror (instr
) || ferror (outstr
))
reply(550, "Unimplemented TYPE %d in receive_data", type
);
perror_reply(421, "Data Connection");
reply(451, "Error in server: %s\n", s
);
reply(221, "Closing connection due to server error.");
reply(n
, fmt
, p0
, p1
, p2
, p3
, p4
, p5
)
printf(fmt
, p0
, p1
, p2
, p3
, p4
, p5
);
syslog(LOG_DEBUG
, "<--- %d ", n
);
syslog(LOG_DEBUG
, fmt
, p0
, p1
, p2
, p3
, p4
, p5
);
lreply(n
, fmt
, p0
, p1
, p2
, p3
, p4
, p5
)
printf(fmt
, p0
, p1
, p2
, p3
, p4
, p5
);
syslog(LOG_DEBUG
, "<--- %d- ", n
);
syslog(LOG_DEBUG
, fmt
, p0
, p1
, p2
, p3
, p4
, p5
);
reply(250, "%s command successful.", s
);
reply(502, "%s command not implemented.", s
);
if (cp
= index(cbuf
,'\n'))
reply(500, "'%s': command not understood.",cbuf
);
if (stat(name
, &st
) < 0) {
if ((st
.st_mode
&S_IFMT
) == S_IFDIR
) {
if (mkdir(name
, 0777) < 0)
reply(257, "MKD command successful.");
char path
[MAXPATHLEN
+ 1];
if (getwd(path
) == (char *)NULL
)
reply(257, "\"%s\" is current directory.", path
);
if (stat(name
, &st
) < 0) {
reply(350, "File exists, ready for destination name");
if (rename(from
, to
) < 0)
perror_reply(550, "rename");
struct hostent
*hp
= gethostbyaddr((char *)&sin
->sin_addr
,
sizeof (struct in_addr
), AF_INET
);
(void) strncpy(remotehost
, hp
->h_name
, sizeof (remotehost
));
(void) strncpy(remotehost
, inet_ntoa(sin
->sin_addr
),
syslog(LOG_INFO
, "connection from %s at %s",
setproctitle("%s: connected", remotehost
);
#endif /* SETPROCTITLE */
* Record logout in wtmp file
* and exit with supplied status.
(void) seteuid((uid_t
)0);
logwtmp(ttyline
, "", "");
/* beware of flushing buffers after a SIGPIPE */
/* only process if transfer occurring */
if (getline(cp
, 7, stdin
) == NULL
) {
reply(221, "You could at least say goodbye.");
if (strcmp(cp
, "ABOR\r\n"))
reply(426,"Transfer aborted. Data connection closed.");
reply(226,"Abort successful");
* Note: a response of 425 is not mentioned as a possible response to
* the PASV command in RFC959. However, it has been blessed as
* a legitimate response by Jon Postel in a telephone conversation
* with Rick Adams on 25 Jan 89.
pdata
= socket(AF_INET
, SOCK_STREAM
, 0);
perror_reply(425, "Can't open passive connection");
(void) seteuid((uid_t
)0);
if (bind(pdata
, (struct sockaddr
*) &tmp
, sizeof(tmp
)) < 0) {
(void) seteuid((uid_t
)pw
->pw_uid
);
(void) seteuid((uid_t
)pw
->pw_uid
);
if (getsockname(pdata
, (struct sockaddr
*) &tmp
, &len
) < 0)
if (listen(pdata
, 1) < 0)
a
= (char *) &tmp
.sin_addr
;
p
= (char *) &tmp
.sin_port
;
#define UC(b) (((int) b) & 0xff)
reply(227, "Entering Passive Mode (%d,%d,%d,%d,%d,%d)", UC(a
[0]),
UC(a
[1]), UC(a
[2]), UC(a
[3]), UC(p
[0]), UC(p
[1]));
perror_reply(425, "Can't open passive connection");
* Generate unique name for file with basename "local".
* The file named "local" is already known to exist.
* Generates failure reply on error.
static char new[MAXPATHLEN
];
char *cp
= rindex(local
, '/');
if (stat(cp
? local
: ".", &st
) < 0) {
perror_reply(553, local
);
(void) strcpy(new, local
);
for (count
= 1; count
< 100; count
++) {
(void) sprintf(cp
, "%d", count
);
reply(452, "Unique file name cannot be created.");
* Format and send reply containing system error number.
perror_reply(code
, string
)
reply(code
, "%s: %s.", string
, sys_errlist
[errno
]);
reply(code
, "%s: unknown error %d.", string
, errno
);
static char *onefile
[] = {
send_file_list(whichfiles
)
register char **dirlist
, *dirname
;
if (strpbrk(whichfiles
, "~{[*?") != NULL
) {
extern char **glob(), *globerr
;
dirlist
= glob(whichfiles
);
} else if (dirlist
== NULL
) {
perror_reply(550, whichfiles
);
while (dirname
= *dirlist
++) {
if (stat(dirname
, &st
) < 0) {
perror_reply(550, whichfiles
);
if ((st
.st_mode
&S_IFMT
) == S_IFREG
) {
dout
= dataconn(whichfiles
, (off_t
)-1, "w");
fprintf(dout
, "%s\n", dirname
);
} else if ((st
.st_mode
&S_IFMT
) != S_IFDIR
)
if ((dirp
= opendir(dirname
)) == NULL
)
while ((dir
= readdir(dirp
)) != NULL
) {
if (dir
->d_name
[0] == '.' && dir
->d_namlen
== 1)
if (dir
->d_name
[0] == '.' && dir
->d_name
[1] == '.'
sprintf(nbuf
, "%s/%s", dirname
, dir
->d_name
);
* we have to do a stat to insure it's
if (stat(nbuf
, &st
) == 0 &&
(st
.st_mode
&S_IFMT
) == S_IFREG
) {
dout
= dataconn(whichfiles
, (off_t
)-1,
if (nbuf
[0] == '.' && nbuf
[1] == '/')
fprintf(dout
, "%s\n", &nbuf
[2]);
fprintf(dout
, "%s\n", nbuf
);
if (dout
!= NULL
&& ferror(dout
) != 0)
perror_reply(550, whichfiles
);
reply(226, "Transfer complete.");
* clobber argv so ps will show what we're doing.
* warning, since this is usually started from inetd.conf, it
* often doesn't have much of an environment or arglist to overwrite.
setproctitle(fmt
, a
, b
, c
)
register char *p
, *bp
, ch
;
(void) sprintf(buf
, fmt
, a
, b
, c
);
/* make ps print our process name */
if (i
> LastArgv
- p
- 2) {
if (ch
!= '\n' && ch
!= '\r')
#endif /* SETPROCTITLE */