Improve handling SIGURG and OOB commands on the control channel.

The major change is to process STAT sent as an OOB command w/o
breaking the current data transfer.  As a side effect, this gives
better error checking in the code performing data transfers.

A lesser, but in no way cosmetic, change is using the flag `recvurg'
in the only signal-safe way that has been blessed by SUSv3.  The
other flag, `transflag,' becomes private to the SIGURG machinery,
serves debugging purposes only, and may be dropped in the future.

The `byte_count' global variable is now accounting bytes actually
transferred over the network.  This can give status messages looking
strange, like "X of Y bytes transferred," where X > Y, but that has
more sense than trying to compensate for combinations of data formats
on the server and client when transferring ASCII type data.  BTW,
getting the size of a file in advance is unreliable for a number of
reasons in the first place.  See question 18.8 of the Infrequently
Asked Questions in comp.lang.c for details.

PR:		bin/52072
Tested by:	Nick Leuta (earlier versions), a stress-testing tool (final)
MFC after:	1 month
This commit is contained in:
Yaroslav Tykhiy 2005-01-19 10:33:20 +00:00
parent 303793b564
commit 4cd51076c5

View File

@ -147,8 +147,6 @@ int noguestretr = 0; /* RETR command is disabled for anon users. */
int noguestmkd = 0; /* MKD command is disabled for anon users. */
int noguestmod = 1; /* anon users may not modify existing files. */
static volatile sig_atomic_t recvurg;
sig_atomic_t transflag;
off_t file_size;
off_t byte_count;
#if !defined(CMASK) || CMASK == 0
@ -221,13 +219,34 @@ char proctitle[LINE_MAX]; /* initial part of title */
#define LOGCMD2(cmd, file1, file2) logcmd((cmd), (file1), (file2), -1)
#define LOGBYTES(cmd, file, cnt) logcmd((cmd), (file), NULL, (cnt))
static volatile sig_atomic_t recvurg;
static int transflag; /* NB: for debugging only */
#define STARTXFER flagxfer(1)
#define ENDXFER flagxfer(0)
#define START_UNSAFE maskurg(1)
#define END_UNSAFE maskurg(0)
/* It's OK to put an `else' clause after this macro. */
#define CHECKOOB(action) \
if (recvurg) { \
recvurg = 0; \
if (myoob()) { \
ENDXFER; \
action; \
} \
}
#ifdef VIRTUAL_HOSTING
static void inithosts(void);
static void selecthost(union sockunion *);
#endif
static void ack(char *);
static void sigurg(int);
static void myoob(void);
static void maskurg(int);
static void flagxfer(int);
static int myoob(void);
static int checkuser(char *, char *, int, char **);
static FILE *dataconn(char *, off_t, char *);
static void dolog(struct sockaddr *);
@ -1981,6 +2000,30 @@ dataconn(char *name, off_t size, char *mode)
return (file);
}
/*
* A helper macro to avoid code duplication
* in send_data() and receive_data().
*
* XXX We have to block SIGURG during putc() because BSD stdio
* is unable to restart interrupted write operations and hence
* the entire buffer contents will be lost as soon as a write()
* call indicates EINTR to stdio.
*/
#define FTPD_PUTC(ch, file, label) \
do { \
int ret; \
\
do { \
START_UNSAFE; \
ret = putc((ch), (file)); \
END_UNSAFE; \
CHECKOOB(return (-1)) \
else if (ferror(file)) \
goto label; \
clearerr(file); \
} while (ret == EOF); \
} while (0)
/*
* Tranfer the contents of "instr" to "outstr" peer using the appropriate
* encapsulation of the data subject to Mode, Structure, and Type.
@ -1992,33 +2035,50 @@ send_data(FILE *instr, FILE *outstr, size_t blksize, off_t filesize, int isreg)
{
int c, cp, filefd, netfd;
char *buf;
off_t cnt;
transflag++;
STARTXFER;
switch (type) {
case TYPE_A:
cp = '\0';
while ((c = getc(instr)) != EOF) {
if (recvurg)
goto got_oob;
byte_count++;
if (c == '\n' && cp != '\r') {
if (ferror(outstr))
goto data_err;
(void) putc('\r', outstr);
cp = EOF;
for (;;) {
c = getc(instr);
CHECKOOB(return (-1))
else if (c == EOF && ferror(instr))
goto file_err;
if (c == EOF) {
if (ferror(instr)) { /* resume after OOB */
clearerr(instr);
continue;
}
if (feof(instr)) /* EOF */
break;
syslog(LOG_ERR, "Internal: impossible condition"
" on file after getc()");
goto file_err;
}
(void) putc(c, outstr);
if (c == '\n' && cp != '\r') {
FTPD_PUTC('\r', outstr, data_err);
byte_count++;
}
FTPD_PUTC(c, outstr, data_err);
byte_count++;
cp = c;
}
if (recvurg)
goto got_oob;
fflush(outstr);
transflag = 0;
if (ferror(instr))
goto file_err;
if (ferror(outstr))
#ifdef notyet /* BSD stdio isn't ready for that */
while (fflush(outstr) == EOF) {
CHECKOOB(return (-1))
else
goto data_err;
clearerr(outstr);
}
ENDXFER;
#else
ENDXFER;
if (fflush(outstr) == EOF)
goto data_err;
#endif
reply(226, "Transfer complete.");
return (0);
@ -2033,7 +2093,7 @@ send_data(FILE *instr, FILE *outstr, size_t blksize, off_t filesize, int isreg)
if (isreg) {
char *msg = "Transfer complete.";
off_t offset;
off_t cnt, offset;
int err;
cnt = offset = 0;
@ -2046,18 +2106,17 @@ send_data(FILE *instr, FILE *outstr, size_t blksize, off_t filesize, int isreg)
* It can be used in myoob() later.
*/
byte_count += cnt;
if (recvurg)
goto got_oob;
offset += cnt;
filesize -= cnt;
if (err == -1) {
if (cnt == 0 && offset == 0)
CHECKOOB(return (-1))
else if (err == -1) {
if (errno != EINTR &&
cnt == 0 && offset == 0)
goto oldway;
goto data_err;
}
if (err == -1) /* resume after OOB */
continue;
/*
* We hit the EOF prematurely.
* Perhaps the file was externally truncated.
@ -2068,52 +2127,65 @@ send_data(FILE *instr, FILE *outstr, size_t blksize, off_t filesize, int isreg)
break;
}
}
transflag = 0;
ENDXFER;
reply(226, msg);
return (0);
}
oldway:
if ((buf = malloc(blksize)) == NULL) {
transflag = 0;
ENDXFER;
reply(451, "Ran out of memory.");
return (-1);
}
while ((cnt = read(filefd, buf, blksize)) > 0 &&
write(netfd, buf, cnt) == cnt)
byte_count += cnt;
transflag = 0;
free(buf);
if (cnt != 0) {
if (cnt < 0)
for (;;) {
int cnt, len;
char *bp;
cnt = read(filefd, buf, blksize);
CHECKOOB(free(buf); return (-1))
else if (cnt < 0) {
free(buf);
goto file_err;
goto data_err;
}
if (cnt < 0) /* resume after OOB */
continue;
if (cnt == 0) /* EOF */
break;
for (len = cnt, bp = buf; len > 0;) {
cnt = write(netfd, bp, len);
CHECKOOB(free(buf); return (-1))
else if (cnt < 0) {
free(buf);
goto data_err;
}
if (cnt <= 0)
continue;
len -= cnt;
bp += cnt;
byte_count += cnt;
}
}
ENDXFER;
free(buf);
reply(226, "Transfer complete.");
return (0);
default:
transflag = 0;
ENDXFER;
reply(550, "Unimplemented TYPE %d in send_data.", type);
return (-1);
}
data_err:
transflag = 0;
ENDXFER;
perror_reply(426, "Data connection");
return (-1);
file_err:
transflag = 0;
ENDXFER;
perror_reply(551, "Error on input file");
return (-1);
got_oob:
myoob();
recvurg = 0;
transflag = 0;
return (-1);
}
/*
@ -2125,63 +2197,98 @@ send_data(FILE *instr, FILE *outstr, size_t blksize, off_t filesize, int isreg)
static int
receive_data(FILE *instr, FILE *outstr)
{
int c;
int cnt, bare_lfs;
char buf[BUFSIZ];
int c, cp;
int bare_lfs = 0;
transflag++;
bare_lfs = 0;
STARTXFER;
switch (type) {
case TYPE_I:
case TYPE_L:
while ((cnt = read(fileno(instr), buf, sizeof(buf))) > 0) {
if (recvurg)
goto got_oob;
if (write(fileno(outstr), buf, cnt) != cnt)
goto file_err;
byte_count += cnt;
for (;;) {
int cnt, len;
char *bp;
char buf[BUFSIZ];
cnt = read(fileno(instr), buf, sizeof(buf));
CHECKOOB(return (-1))
else if (cnt < 0)
goto data_err;
if (cnt < 0) /* resume after OOB */
continue;
if (cnt == 0) /* EOF */
break;
for (len = cnt, bp = buf; len > 0;) {
cnt = write(fileno(outstr), bp, len);
CHECKOOB(return (-1))
else if (cnt < 0)
goto file_err;
if (cnt <= 0)
continue;
len -= cnt;
bp += cnt;
byte_count += cnt;
}
}
if (recvurg)
goto got_oob;
if (cnt < 0)
goto data_err;
transflag = 0;
ENDXFER;
return (0);
case TYPE_E:
ENDXFER;
reply(553, "TYPE E not implemented.");
transflag = 0;
return (-1);
case TYPE_A:
while ((c = getc(instr)) != EOF) {
if (recvurg)
goto got_oob;
byte_count++;
if (c == '\n')
bare_lfs++;
while (c == '\r') {
if (ferror(outstr))
goto data_err;
if ((c = getc(instr)) != '\n') {
(void) putc ('\r', outstr);
if (c == '\0' || c == EOF)
goto contin2;
}
cp = EOF;
for (;;) {
c = getc(instr);
CHECKOOB(return (-1))
else if (c == EOF && ferror(instr))
goto data_err;
if (c == EOF && ferror(instr)) { /* resume after OOB */
clearerr(instr);
continue;
}
(void) putc(c, outstr);
contin2: ;
if (cp == '\r') {
if (c != '\n')
FTPD_PUTC('\r', outstr, file_err);
} else
if (c == '\n')
bare_lfs++;
if (c == '\r') {
byte_count++;
cp = c;
continue;
}
/* Check for EOF here in order not to lose last \r. */
if (c == EOF) {
if (feof(instr)) /* EOF */
break;
syslog(LOG_ERR, "Internal: impossible condition"
" on data stream after getc()");
goto data_err;
}
byte_count++;
FTPD_PUTC(c, outstr, file_err);
cp = c;
}
if (recvurg)
goto got_oob;
fflush(outstr);
if (ferror(instr))
goto data_err;
if (ferror(outstr))
#ifdef notyet /* BSD stdio isn't ready for that */
while (fflush(outstr) == EOF) {
CHECKOOB(return (-1))
else
goto file_err;
clearerr(outstr);
}
ENDXFER;
#else
ENDXFER;
if (fflush(outstr) == EOF)
goto file_err;
transflag = 0;
#endif
if (bare_lfs) {
lreply(226,
"WARNING! %d bare linefeeds received in ASCII mode.",
@ -2190,26 +2297,20 @@ receive_data(FILE *instr, FILE *outstr)
}
return (0);
default:
ENDXFER;
reply(550, "Unimplemented TYPE %d in receive_data.", type);
transflag = 0;
return (-1);
}
data_err:
transflag = 0;
ENDXFER;
perror_reply(426, "Data connection");
return (-1);
file_err:
transflag = 0;
ENDXFER;
perror_reply(452, "Error writing to file");
return (-1);
got_oob:
myoob();
recvurg = 0;
transflag = 0;
return (-1);
}
void
@ -2625,11 +2726,6 @@ dolog(struct sockaddr *who)
void
dologout(int status)
{
/*
* Prevent reception of SIGURG from resulting in a resumption
* back to the main program loop.
*/
transflag = 0;
if (logged_in && dowtmp) {
(void) seteuid(0);
@ -2647,13 +2743,46 @@ sigurg(int signo)
}
static void
maskurg(int flag)
{
int oerrno;
sigset_t sset;
if (!transflag) {
syslog(LOG_ERR, "Internal: maskurg() while no transfer");
return;
}
oerrno = errno;
sigemptyset(&sset);
sigaddset(&sset, SIGURG);
sigprocmask(flag ? SIG_BLOCK : SIG_UNBLOCK, &sset, NULL);
errno = oerrno;
}
static void
flagxfer(int flag)
{
maskurg(!flag);
if (flag) {
recvurg = 0;
transflag = 1;
} else
transflag = 0;
}
/*
* Returns 0 if OK to resume or -1 if abort requested.
*/
static int
myoob(void)
{
char *cp;
/* only process if transfer occurring */
if (!transflag)
return;
if (!transflag) {
syslog(LOG_ERR, "Internal: myoob() while no transfer");
return (0);
}
cp = tmpline;
if (getline(cp, 7, stdin) == NULL) {
reply(221, "You could at least say goodbye.");
@ -2664,6 +2793,7 @@ myoob(void)
tmpline[0] = '\0';
reply(426, "Transfer aborted. Data connection closed.");
reply(226, "Abort successful.");
return (-1);
}
if (strcmp(cp, "STAT\r\n") == 0) {
tmpline[0] = '\0';
@ -2674,6 +2804,7 @@ myoob(void)
reply(213, "Status: %jd bytes transferred.",
(intmax_t)byte_count);
}
return (0);
}
/*
@ -3016,17 +3147,10 @@ send_file_list(char *whichf)
* used NLST, do what the user meant.
*/
if (dirname[0] == '-' && *dirlist == NULL &&
transflag == 0) {
dout == NULL)
retrieve(_PATH_LS " %s", dirname);
goto out;
}
perror_reply(550, whichf);
if (dout != NULL) {
(void) fclose(dout);
transflag = 0;
data = -1;
pdata = -1;
}
else
perror_reply(550, whichf);
goto out;
}
@ -3035,11 +3159,17 @@ send_file_list(char *whichf)
dout = dataconn("file list", -1, "w");
if (dout == NULL)
goto out;
transflag++;
STARTXFER;
}
START_UNSAFE;
fprintf(dout, "%s%s\n", dirname,
type == TYPE_A ? "\r" : "");
byte_count += strlen(dirname) + 1;
END_UNSAFE;
if (ferror(dout))
goto data_err;
byte_count += strlen(dirname) +
(type == TYPE_A ? 2 : 1);
CHECKOOB(goto abrt);
continue;
} else if (!S_ISDIR(st.st_mode))
continue;
@ -3050,12 +3180,7 @@ send_file_list(char *whichf)
while ((dir = readdir(dirp)) != NULL) {
char nbuf[MAXPATHLEN];
if (recvurg) {
myoob();
recvurg = 0;
transflag = 0;
goto out;
}
CHECKOOB(goto abrt);
if (dir->d_name[0] == '.' && dir->d_namlen == 1)
continue;
@ -3076,33 +3201,43 @@ send_file_list(char *whichf)
dout = dataconn("file list", -1, "w");
if (dout == NULL)
goto out;
transflag++;
STARTXFER;
}
START_UNSAFE;
if (nbuf[0] == '.' && nbuf[1] == '/')
fprintf(dout, "%s%s\n", &nbuf[2],
type == TYPE_A ? "\r" : "");
else
fprintf(dout, "%s%s\n", nbuf,
type == TYPE_A ? "\r" : "");
byte_count += strlen(nbuf) + 1;
END_UNSAFE;
if (ferror(dout))
goto data_err;
byte_count += strlen(nbuf) +
(type == TYPE_A ? 2 : 1);
CHECKOOB(goto abrt);
}
}
(void) closedir(dirp);
dirp = NULL;
}
if (dout == NULL)
reply(550, "No files found.");
else if (ferror(dout) != 0)
perror_reply(550, "Data connection");
else if (ferror(dout))
data_err: perror_reply(550, "Data connection");
else
reply(226, "Transfer complete.");
transflag = 0;
if (dout != NULL)
(void) fclose(dout);
data = -1;
pdata = -1;
out:
if (dout) {
ENDXFER;
abrt:
(void) fclose(dout);
data = -1;
pdata = -1;
}
if (dirp)
(void) closedir(dirp);
if (freeglob) {
freeglob = 0;
globfree(&gl);