static char sccsid
[] = "@(#)ftpd.c 4.30 (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 options
= 0, addrlen
;
addrlen
= sizeof (his_addr
);
if (getpeername(0, &his_addr
, &addrlen
) < 0) {
fprintf(stderr
, "%s: ", argv
[0]);
addrlen
= sizeof (ctrl_addr
);
if (getsockname(0, &ctrl_addr
, &addrlen
) < 0) {
fprintf(stderr
, "%s: ", argv
[0]);
data_source
.sin_port
= htons(ntohs(ctrl_addr
.sin_port
) - 1);
while (argc
> 0 && *argv
[0] == '-') {
for (cp
= &argv
[0][1]; *cp
; cp
++) switch (*cp
) {
fprintf(stderr
, "ftpd: Unknown flag -%c ignored.\n",
signal(SIGPIPE
, lostconn
);
signal(SIGCHLD
, SIG_IGN
);
/* do telnet option negotiation here */
gethostname(hostname
, sizeof (hostname
));
reply(220, "%s FTP server (%s) ready.",
while (wait3(&status
, WNOHANG
, 0) > 0)
fprintf(stderr
, "Lost connection.\n");
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
) /* 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_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);
if (setsockopt(s
, SOL_SOCKET
, SO_REUSEADDR
, 0, 0) < 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) < 0)
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.",
inet_ntoa(data_source
.sin_addr
),
ntohs(data_source
.sin_port
),
reply(150, "Opening data connection for %s (%s,%d)%s.",
name
, inet_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
]);
struct hostent
*hp
= gethostbyaddr(&sin
->sin_addr
,
sizeof (struct in_addr
), AF_INET
);
strncpy(remotehost
, hp
->h_name
, sizeof (remotehost
));
strncpy(remotehost
, inet_ntoa(sin
->sin_addr
),
fprintf(stderr
,"FTPD: connection from %s at %s", remotehost
, ctime(&t
));
#define SCPYN(a, b) strncpy(a, b, sizeof (a))
* Record login in wtmp file.
if (guest
&& (wtmp
>= 0))
wtmp
= open("/usr/adm/wtmp", O_WRONLY
|O_APPEND
);
/* hack, but must be unique and no tty line */
sprintf(line
, "ftp%d", getpid());
SCPYN(utmp
.ut_line
, line
);
SCPYN(utmp
.ut_name
, pw
->pw_name
);
SCPYN(utmp
.ut_host
, remotehost
);
(void) write(wtmp
, (char *)&utmp
, sizeof (utmp
));
* Record logout in wtmp file
* and exit with supplied status.
if (guest
&& (wtmp
>= 0))
wtmp
= open("/usr/adm/wtmp", O_WRONLY
|O_APPEND
);
(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
++) {
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 */
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)
* 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) {