static char sccsid
[] = "@(#)ftpd.c 4.20 (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();
extern int pclose(), fclose();
struct sockaddr_in ctrl_addr
;
struct sockaddr_in data_source
;
struct sockaddr_in data_dest
;
struct sockaddr_in his_addr
;
int stru
; /* avoid C keyword */
int usedefault
= 1; /* for data transfers */
* 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();
int ctrl
, s
, options
= 0;
sp
= getservbyname("ftp", "tcp");
fprintf(stderr
, "ftpd: ftp/tcp: unknown service\n");
ctrl_addr
.sin_port
= sp
->s_port
;
data_source
.sin_port
= htons(ntohs(sp
->s_port
) - 1);
signal(SIGPIPE
, lostconn
);
while (argc
> 0 && *argv
[0] == '-') {
for (cp
= &argv
[0][1]; *cp
; cp
++) switch (*cp
) {
fprintf(stderr
, "Unknown flag -%c ignored.\n", *cp
);
if (!logging
|| (s
!= 2))
{ int tt
= open("/dev/tty", 2);
while ((s
= socket(AF_INET
, SOCK_STREAM
, 0, 0)) < 0) {
if (setsockopt(s
, SOL_SOCKET
, SO_DEBUG
, 0, 0) < 0)
perror("ftpd: setsockopt (SO_DEBUG)");
if (setsockopt(s
, SOL_SOCKET
, SO_KEEPALIVE
, 0, 0) < 0)
perror("ftpd: setsockopt (SO_KEEPALIVE)");
while (bind(s
, &ctrl_addr
, sizeof (ctrl_addr
), 0) < 0) {
signal(SIGCHLD
, reapchild
);
int hisaddrlen
= sizeof (his_addr
);
ctrl
= accept(s
, &his_addr
, &hisaddrlen
, 0);
signal (SIGCHLD
, SIG_IGN
);
dup2(ctrl
, 0), close(ctrl
), dup2(0, 1);
/* do telnet option negotiation here */
gethostname(hostname
, sizeof (hostname
));
reply(220, "%s FTP server (%s) ready.",
* Anchor data source address to that
* of the control port so hosts with
* multiple address won't have data
* connections bound to an address different
if (getsockname(0, &ctrl_addr
, sizeof (ctrl_addr
)) >= 0)
data_source
.sin_addr
= ctrl_addr
.sin_addr
;
while (wait3(&status
, WNOHANG
, 0) > 0)
fatal("Connection closed.");
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
);
if (strcmp(xpasswd
, pw
->pw_passwd
) != 0) {
reply(530, "Login incorrect.");
initgroups(pw
->pw_name
, pw
->pw_gid
);
reply(550, "User %s: can't change directory to $s.",
pw
->pw_name
, pw
->pw_dir
);
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_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(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
;
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 (send_data(fin
, dout
) || ferror(dout
))
reply(550, "%s: %s.", name
, sys_errlist
[errno
]);
reply(226, "Transfer complete.");
int (*closefunc
)(), dochown
= 0;
/* no remote command execution -- it's a security hole */
fout
= popen(&name
[1], "w"), closefunc
= pclose
;
fout
= fopen(name
, mode
), closefunc
= fclose
;
reply(550, "%s: %s.", name
, sys_errlist
[errno
]);
din
= dataconn(name
, (off_t
)-1, "r");
if (receive_data(din
, fout
) || ferror(fout
))
reply(550, "%s: %s.", name
, sys_errlist
[errno
]);
reply(226, "Transfer complete.");
(void) chown(name
, pw
->pw_uid
, -1);
return (fdopen(data
, mode
));
s
= socket(AF_INET
, SOCK_STREAM
, 0, 0);
if (setsockopt(s
, SOL_SOCKET
, SO_REUSEADDR
, 0, 0) < 0)
if (bind(s
, &data_source
, sizeof (data_source
), 0) < 0)
linger
= 60; /* value ignored by system */
(void) setsockopt(s
, SOL_SOCKET
, SO_LINGER
, &linger
, sizeof (linger
));
return (fdopen(s
, mode
));
dataconn(name
, size
, mode
)
sprintf (sizebuf
, " (%ld bytes)", size
);
(void) strcpy(sizebuf
, "");
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.",
ntoa(data_source
.sin_addr
),
ntohs(data_source
.sin_port
),
reply(150, "Opening data connection for %s (%s,%d)%s.",
name
, ntoa(data_dest
.sin_addr
.s_addr
),
ntohs(data_dest
.sin_port
), sizebuf
);
while (connect(data
, &data_dest
, sizeof (data_dest
), 0) < 0) {
if (errno
== EADDRINUSE
&& retry
< swaitmax
) {
reply(425, "Can't build data connection: %s.",
* 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
) {
if (ferror (instr
) || ferror (outstr
))
while ((cnt
= read(filefd
, buf
, sizeof (buf
))) > 0)
if (write(netfd
, buf
, cnt
) < 0)
reply(504,"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(504, "TYPE E not implemented.");
while ((c
= getc(instr
)) != EOF
) {
if ((c
= getc(instr
)) != '\n')
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
);
fprintf(stderr
, "<--- %d ", n
);
_doprnt(s
, &args
, stderr
);
_doprnt(s
, &args
, stdout
);
fprintf(stderr
, "<--- %d-", n
);
_doprnt(s
, &args
, stderr
);
fprintf(stderr
, "<--- %s\n", s
);
reply(200, "%s command okay.", s
);
reply(502, "%s command not implemented.", s
);
reply(500, "Command not understood.");
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(550, "%s: %s.", name
, sys_errlist
[errno
]);
char path
[MAXPATHLEN
+ 1];
if (getwd(path
) == NULL
) {
reply(251, "\"%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
]);
* Convert network-format internet address
* to base 256 d.d.d.d representation.
#define UC(b) (((int)b)&0xff)
sprintf(b
, "%d.%d.%d.%d", UC(p
[0]), UC(p
[1]), UC(p
[2]), UC(p
[3]));
struct hostent
*hp
= gethostbyaddr(&sin
->sin_addr
,
sizeof (struct in_addr
), AF_INET
);
remotehost
= "UNKNOWNHOST";
fprintf(stderr
,"FTPD: connection from %s at %s", remotehost
, ctime(&t
));
* 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
;
char **pop
, **popargs
= NULL
;
for (ac
= 1, pop
= popargs
; *pop
;)
myside
= tst(p
[WTR
], p
[RDR
]);
hisside
= tst(p
[RDR
], p
[WTR
]);
if ((pid
= fork()) == 0) {
/* myside and hisside reverse roles in child */
dup2(hisside
, tst(0, 1));
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)
* Check user requesting login priviledges.
* Disallow anyone mentioned in the file FTPUSERS
* to allow people such as uucp to be avoided.
char line
[BUFSIZ
], *index();
fd
= fopen(FTPUSERS
, "r");
while (fgets(line
, sizeof (line
), fd
) != NULL
) {
register char *cp
= index(line
, '\n');
if (strcmp(line
, name
) == 0) {