* Copyright (c) 1985 Regents of the University of California.
* All rights reserved. The Berkeley software License Agreement
* specifies the terms and conditions for redistribution.
"@(#) Copyright (c) 1985 Regents of the University of California.\n\
static char sccsid
[] = "@(#)ftpd.c 5.8 (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 *popen(), *fopen(), *freopen();
extern int pclose(), fclose();
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
; /* for passive mode */
* 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();
addrlen
= sizeof (his_addr
);
if (getpeername(0, &his_addr
, &addrlen
) < 0) {
syslog(LOG_ERR
, "getpeername (%s): %m",argv
[0]);
addrlen
= sizeof (ctrl_addr
);
if (getsockname(0, (char *) &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
);
while (argc
> 0 && *argv
[0] == '-') {
for (cp
= &argv
[0][1]; *cp
; cp
++) switch (*cp
) {
fprintf(stderr
, "ftpd: Unknown flag -%c ignored.\n",
(void) signal(SIGPIPE
, lostconn
);
(void) signal(SIGCHLD
, SIG_IGN
);
if (signal(SIGURG
, myoob
) < 0) {
syslog(LOG_ERR
, "signal: %m");
/* handle urgent data inline */
if (setsockopt(0, SOL_SOCKET
, SO_OOBINLINE
, (char *)&on
, sizeof(on
)) < 0) {
syslog(LOG_ERR
, "setsockopt: %m");
if (ioctl(fileno(stdin
), SIOCSPGRP
, (char *) &pgid
) < 0) {
syslog(LOG_ERR
, "ioctl: %m");
/* do telnet option negotiation here */
(void) gethostname(hostname
, sizeof (hostname
));
reply(220, "%s FTP server (%s) ready.",
syslog(LOG_DEBUG
, "lost connection");
char *xpasswd
, *savestr();
static struct passwd save
;
if (logged_in
|| pw
== NULL
) {
reply(503, "Login with USER first.");
if (!guest
) { /* "ftp" is only account allowed no password */
xpasswd
= crypt(passwd
, pw
->pw_passwd
);
/* The strcmp does not catch null passwords! */
if (*pw
->pw_passwd
== '\0' || strcmp(xpasswd
, pw
->pw_passwd
)) {
reply(530, "Login incorrect.");
initgroups(pw
->pw_name
, pw
->pw_gid
);
reply(530, "User %s: can't change directory to %s.",
pw
->pw_name
, pw
->pw_dir
);
/* grab wtmp before chroot */
wtmp
= open("/usr/adm/wtmp", O_WRONLY
|O_APPEND
);
if (guest
&& chroot(pw
->pw_dir
) < 0) {
reply(550, "Can't set guest privileges.");
reply(230, "User %s logged in.", pw
->pw_name
);
reply(230, "Guest login ok, access restrictions apply.");
* Save everything so globbing doesn't
save
.pw_name
= savestr(pw
->pw_name
);
save
.pw_passwd
= savestr(pw
->pw_passwd
);
save
.pw_comment
= savestr(pw
->pw_comment
);
save
.pw_gecos
= savestr(pw
->pw_gecos
);
save
.pw_dir
= savestr(pw
->pw_dir
);
save
.pw_shell
= savestr(pw
->pw_shell
);
home
= pw
->pw_dir
; /* home dir for globbing */
char *new = malloc((unsigned) strlen(s
) + 1);
/* no remote command execution -- it's a security hole */
fin
= popen(name
+ 1, "r"), closefunc
= pclose
;
fin
= fopen(name
, "r"), closefunc
= fclose
;
(void) sprintf(line
, cmd
, name
), name
= line
;
fin
= popen(line
, "r"), closefunc
= pclose
;
reply(550, "%s: %s.", name
, sys_errlist
[errno
]);
(stat(name
, &st
) < 0 || (st
.st_mode
&S_IFMT
) != S_IFREG
)) {
reply(550, "%s: not a plain file.", name
);
dout
= dataconn(name
, st
.st_size
, "w");
if ((tmp
= send_data(fin
, dout
)) > 0 || ferror(dout
) > 0) {
reply(550, "%s: %s.", name
, sys_errlist
[errno
]);
reply(226, "Transfer complete.");
int (*closefunc
)(), dochown
= 0, tmp
;
/* no remote command execution -- it's a security hole */
fout
= popen(&name
[1], "w"), closefunc
= pclose
;
if (stat(name
, &st
) < 0) {
if ((local
= gunique(name
)) == NULL
) {
fout
= fopen(local
, mode
), closefunc
= fclose
;
reply(553, "%s: %s.", local
, sys_errlist
[errno
]);
din
= dataconn(local
, (off_t
)-1, "r");
if ((tmp
= receive_data(din
, fout
)) > 0 || ferror(fout
) > 0) {
reply(552, "%s: %s.", local
, sys_errlist
[errno
]);
else if (tmp
== 0 && !unique
) {
reply(226, "Transfer complete.");
else if (tmp
== 0 && unique
) {
reply(226, "Transfer complete (unique file name:%s).", local
);
(void) chown(local
, pw
->pw_uid
, -1);
return (fdopen(data
, mode
));
s
= socket(AF_INET
, SOCK_STREAM
, 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
, &data_source
, sizeof (data_source
)) < 0)
return (fdopen(s
, mode
));
dataconn(name
, size
, mode
)
(void) sprintf (sizebuf
, " (%ld bytes)", size
);
(void) strcpy(sizebuf
, "");
int s
, fromlen
= sizeof(from
);
s
= accept(pdata
, &from
, &fromlen
);
reply(425, "Can't open data connection.");
reply(150, "Openning data connection for %s (%s,%d)%s.",
name
, inet_ntoa(from
.sin_addr
),
ntohs(from
.sin_port
), 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
),
while (connect(data
, &data_dest
, sizeof (data_dest
)) < 0) {
if (errno
== EADDRINUSE
&& retry
< swaitmax
) {
sleep((unsigned) swaitint
);
reply(425, "Can't build data connection: %s.",
reply(150, "Opening data connection for %s (%s,%d)%s.",
name
, inet_ntoa(data_dest
.sin_addr
),
ntohs(data_dest
.sin_port
), 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.
while ((c
= getc(instr
)) != EOF
) {
(void) putc('\r', outstr
);
/* putc ('\0', outstr); */
if (ferror (instr
) || ferror (outstr
)) {
while ((cnt
= read(filefd
, buf
, sizeof (buf
))) > 0) {
if (write(netfd
, buf
, cnt
) < 0) {
reply(550, "Unimplemented TYPE %d in send_data", type
);
* 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
) < 0) {
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
))
fatal("Unknown type in receive_data.");
reply(451, "Error in server: %s\n", s
);
reply(221, "Closing connection due to server error.");
_doprnt(s
, &args
, stdout
);
syslog(LOG_DEBUG
, "<--- %d ", n
);
syslog(LOG_DEBUG
, s
, &args
);
_doprnt(s
, &args
, stdout
);
syslog(LOG_DEBUG
, "<--- %d- ", n
);
syslog(LOG_DEBUG
, s
, &args
);
reply(250, "%s command successful.", s
);
reply(502, "%s command not implemented.", s
);
reply(500, "'%s': command not understood.",cbuf
);
if (stat(name
, &st
) < 0) {
reply(550, "%s: %s.", name
, sys_errlist
[errno
]);
if ((st
.st_mode
&S_IFMT
) == S_IFDIR
) {
reply(550, "%s: %s.", name
, sys_errlist
[errno
]);
reply(550, "%s: %s.", name
, sys_errlist
[errno
]);
reply(550, "%s: %s.", path
, sys_errlist
[errno
]);
int dochown
= stat(name
, &st
) < 0;
if (mkdir(name
, 0777) < 0) {
reply(550, "%s: %s.", name
, sys_errlist
[errno
]);
(void) chown(name
, pw
->pw_uid
, -1);
reply(257, "MKD command successful.");
reply(550, "%s: %s.", name
, sys_errlist
[errno
]);
char path
[MAXPATHLEN
+ 1];
if (getwd(path
) == NULL
) {
reply(257, "\"%s\" is current directory.", path
);
if (stat(name
, &st
) < 0) {
reply(550, "%s: %s.", name
, sys_errlist
[errno
]);
reply(350, "File exists, ready for destination name");
if (rename(from
, to
) < 0) {
reply(550, "rename: %s.", sys_errlist
[errno
]);
struct hostent
*hp
= gethostbyaddr(&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
,"FTPD: connection from %s at %s", remotehost
, ctime(&t
));
#define SCPYN(a, b) (void) strncpy(a, b, sizeof (a))
* Record login in wtmp file.
/* hack, but must be unique and no tty line */
(void) sprintf(line
, "ftp%d", getpid());
SCPYN(utmp
.ut_line
, line
);
SCPYN(utmp
.ut_name
, pw
->pw_name
);
SCPYN(utmp
.ut_host
, remotehost
);
utmp
.ut_time
= (long) time((time_t *) 0);
(void) write(wtmp
, (char *)&utmp
, sizeof (utmp
));
if (!guest
) { /* anon must hang on */
* Record logout in wtmp file
* and exit with supplied status.
wtmp
= open("/usr/adm/wtmp", O_WRONLY
|O_APPEND
);
utmp
.ut_time
= (long) time((time_t *) 0);
(void) write(wtmp
, (char *)&utmp
, sizeof (utmp
));
/* beware of flushing buffers after a SIGPIPE */
* Special version of popen which avoids
* call to shell. This insures noone may
* create a pipe to a hidden program as a side
* effect of a list or dir command.
#define tst(a,b) (*mode == 'r'? (b) : (a))
while (*cp
&& *cp
!= ' ' && *cp
!= '\t')
if (*cp
== ' ' || *cp
== '\t') {
while (*cp
== ' ' || *cp
== '\t')
register myside
, hisside
, pid
;
/* break up string into pieces */
} while (cp
&& *cp
&& ac
< 20);
for (gac
= ac
= 1; av
[ac
] != NULL
; ac
++) {
extern char **glob(), **copyblk();
if (pop
== (char **)NULL
) { /* globbing failed */
av
[ac
] = (char *)pop
; /* save to free later */
while (*pop
&& gac
< 512)
myside
= tst(p
[WTR
], p
[RDR
]);
hisside
= tst(p
[RDR
], p
[WTR
]);
if ((pid
= fork()) == 0) {
/* myside and hisside reverse roles in child */
(void) dup2(hisside
, tst(0, 1));
for (ac
= 1; av
[ac
] != NULL
; ac
++)
blkfree((char **)av
[ac
]);
return (fdopen(myside
, mode
));
register f
, r
, (*hstat
)(), (*istat
)(), (*qstat
)();
istat
= signal(SIGINT
, SIG_IGN
);
qstat
= signal(SIGQUIT
, SIG_IGN
);
hstat
= signal(SIGHUP
, SIG_IGN
);
while ((r
= wait(&status
)) != popen_pid
[f
] && r
!= -1)
(void) signal(SIGINT
, istat
);
(void) signal(SIGQUIT
, qstat
);
(void) signal(SIGHUP
, hstat
);
* Check user requesting login priviledges.
* 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 uucp to be avoided.
char line
[BUFSIZ
], *index(), *getusershell();
if (pw
->pw_shell
== NULL
|| pw
->pw_shell
[0] == NULL
)
pw
->pw_shell
= "/bin/sh";
while ((cp
= getusershell()) != NULL
)
if (strcmp(cp
, pw
->pw_shell
) == 0)
fd
= fopen(FTPUSERS
, "r");
while (fgets(line
, sizeof (line
), fd
) != NULL
) {
if (strcmp(line
, name
) == 0) {
/* only process if transfer occurring */
if (getline(cp
, 7, stdin
) == NULL
) {
reply(221, "You could at least say goodby.");
if (strcmp(cp
, "ABOR\r\n"))
reply(426,"Transfer aborted. Data connection closed.");
reply(226,"Abort successful");
* Note: The 530 reply codes could be 4xx codes, except nothing is
* given in the state tables except 421 which implies an exit. (RFC959)
pdata
= socket(AF_INET
, SOCK_STREAM
, 0);
reply(530, "Can't open passive connection");
if (bind(pdata
, (struct sockaddr
*) &tmp
, sizeof(tmp
)) < 0) {
reply(530, "Can't open passive connection");
if (getsockname(pdata
, (char *) &tmp
, &len
) < 0) {
reply(530, "Can't open passive connection");
if (listen(pdata
, 1) < 0) {
reply(530, "Can't open passive connection");
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]));
static char new[MAXPATHLEN
];
char *cp
= rindex(local
, '/');
d
= access(cp
? local
: ".", 2);
syslog(LOG_ERR
, "%s: %m", local
);
(void) strcpy(new, local
);
reply(452, "Unique file name not cannot be created.");
if ((d
= access(new, 0)) < 0) {
else if (*(cp
- 2) == '.') {
*(cp
- 2) = *(cp
- 2) + 1;