1253 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1253 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* file.c Copyright 1992-2000 by Michael Temari All Rights Reserved
 | |
|  *
 | |
|  * This file is part of ftpd.
 | |
|  *
 | |
|  * This file handles:
 | |
|  *
 | |
|  *      ALLO APPE CDUP CWD  DELE LIST MDTM MODE MKD  NLST PWD REST RETR
 | |
|  *      RMD  RNFR RNTO SITE SIZE STAT STOR STOU STRU SYST TYPE
 | |
|  *
 | |
|  * 01/25/96 Initial Release	Michael Temari
 | |
|  * 03/09/00 			Michael Temari, <Michael@TemWare.Com>
 | |
|  */
 | |
| 
 | |
| #include <sys/types.h>
 | |
| #include <sys/stat.h>
 | |
| #include <sys/wait.h>
 | |
| #include <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <fcntl.h>
 | |
| #include <errno.h>
 | |
| #include <unistd.h>
 | |
| #include <string.h>
 | |
| #include <time.h>
 | |
| #include <net/hton.h>
 | |
| #include <net/gen/in.h>
 | |
| #include <net/gen/inet.h>
 | |
| #include <net/gen/tcp.h>
 | |
| 
 | |
| #include "ftpd.h"
 | |
| #include "access.h"
 | |
| #include "file.h"
 | |
| #include "net.h"
 | |
| 
 | |
| _PROTOTYPE(static int fdxcmd, (int cmd, char *arg));
 | |
| _PROTOTYPE(static int endfdxcmd, (int fd));
 | |
| _PROTOTYPE(static int asciisize, (char *filename, unsigned long *filesize));
 | |
| _PROTOTYPE(static int cnvtfile, (char *name, char **name2));
 | |
| _PROTOTYPE(static int procfile, (char *name));
 | |
| _PROTOTYPE(static unsigned long fsize, (char *fname));
 | |
| _PROTOTYPE(static int sendfile, (char *name, int xmode));
 | |
| _PROTOTYPE(static int recvfile, (char *name, int xmode));
 | |
| _PROTOTYPE(static char *uniqname, (void));
 | |
| _PROTOTYPE(static int docrc, (char *buff, int xmode));
 | |
| _PROTOTYPE(static int dofdet, (char *buff));
 | |
| _PROTOTYPE(static char *path, (char *fname));
 | |
| 
 | |
| #define	SEND_FILE	0
 | |
| #define	SEND_NLST	1
 | |
| #define	SEND_LIST	2
 | |
| 
 | |
| #define	RECV_FILE	0
 | |
| #define	RECV_APND	1
 | |
| #define	RECV_UNIQ	2
 | |
| 
 | |
| #define	CNVT_ERROR	0
 | |
| #define	CNVT_NONE	1
 | |
| #define	CNVT_TAR	2
 | |
| #define	CNVT_TAR_Z	3
 | |
| #define	CNVT_COMP	4
 | |
| #define	CNVT_TAR_GZ	5
 | |
| #define	CNVT_GZIP	6
 | |
| #define	CNVT_UNCOMP	7
 | |
| 
 | |
| 
 | |
| #define	PROG_FTPDSH	"ftpdsh"
 | |
| #define	CMD_NLST	1
 | |
| #define	CMD_LIST	2
 | |
| #define	CMD_CRC		3
 | |
| 
 | |
| static char *msg550 = "550 %s %s.\r\n";
 | |
| 
 | |
| static unsigned long file_restart = 0;
 | |
| 
 | |
| static char rnfr[256];
 | |
| static char buffer[8192];
 | |
| static char bufout[8192];
 | |
| 
 | |
| static cmdpid = -1;
 | |
| 
 | |
| /* allocate, we don't need no stink'n allocate */
 | |
| int doALLO(buff)
 | |
| char *buff;
 | |
| {
 | |
|    printf("202 ALLO command not needed at this site.\r\n");
 | |
| 
 | |
|    return(GOOD);
 | |
| }
 | |
| 
 | |
| /* append to a file if it exists */
 | |
| int doAPPE(buff)
 | |
| char *buff;
 | |
| {
 | |
|    return(recvfile(buff, RECV_APND));
 | |
| }
 | |
| 
 | |
| /* change to parent directory */
 | |
| int doCDUP(buff)
 | |
| char *buff;
 | |
| {
 | |
|    if(ChkLoggedIn())
 | |
| 	return(GOOD);
 | |
| 
 | |
|    return(doCWD(".."));
 | |
| }
 | |
| 
 | |
| /* change directory */
 | |
| int doCWD(buff)
 | |
| char *buff;
 | |
| {
 | |
|    if(ChkLoggedIn())
 | |
| 	return(GOOD);
 | |
| 
 | |
|    if(chdir(buff))
 | |
| 	printf(msg550, buff, strerror(errno));
 | |
|    else {
 | |
| 	showmsg("250", ".ftpd_msg");
 | |
| 	printf("250 %s command okay.\r\n", line);
 | |
|    }
 | |
| 
 | |
|    return(GOOD);
 | |
| }
 | |
| 
 | |
| /* remove a file */
 | |
| int doDELE(buff)
 | |
| char *buff;
 | |
| {
 | |
|    if(ChkLoggedIn())
 | |
| 	return(GOOD);
 | |
| 
 | |
|    if(anonymous) {
 | |
| 	printf("550 Command not allowed for anonymous user\r\n");
 | |
| 	return(GOOD);
 | |
|    }
 | |
| 
 | |
|    if(unlink(buff))
 | |
| 	printf(msg550, buff, strerror(errno));
 | |
|    else {
 | |
| 	printf("250 File \"%s\" deleted.\r\n", buff);
 | |
| 	logit("DELE", path(buff));
 | |
|    }
 | |
| 
 | |
|    return(GOOD);
 | |
| }
 | |
| 
 | |
| /* directory listing */
 | |
| int doLIST(buff)
 | |
| char *buff;
 | |
| {
 | |
|    file_restart = 0;
 | |
| 
 | |
|    return(sendfile(buff, SEND_LIST));
 | |
| }
 | |
| 
 | |
| /* file modification time, btw when will this be put into an RFC */
 | |
| int doMDTM(buff)
 | |
| char *buff;
 | |
| {
 | |
| struct stat st;
 | |
| struct tm *t;
 | |
| 
 | |
|    if(ChkLoggedIn())
 | |
| 	return(GOOD);
 | |
| 
 | |
|    if(stat(buff, &st)) {
 | |
| 	printf(msg550, buff, strerror(errno));
 | |
| 	return(GOOD);
 | |
|    }
 | |
| 
 | |
|    if((st.st_mode & S_IFMT) != S_IFREG) {
 | |
| 	printf("550 Not a regular file.\r\n");
 | |
| 	return(GOOD);
 | |
|    }
 | |
| 
 | |
|    t = gmtime(&st.st_mtime);
 | |
| 
 | |
|    printf("215 %04d%02d%02d%02d%02d%02d\r\n",
 | |
| 	t->tm_year+1900, t->tm_mon+1, t->tm_mday,
 | |
| 	t->tm_hour, t->tm_min, t->tm_sec);
 | |
| 
 | |
|    return(GOOD);
 | |
| }
 | |
| 
 | |
| /* mode */
 | |
| int doMODE(buff)
 | |
| char *buff;
 | |
| {
 | |
|    switch(*buff) {
 | |
| 	case 'b':
 | |
| 	case 'B':
 | |
| 		printf("200 Mode set to %c.\r\n", *buff);
 | |
| 		mode = MODE_B;
 | |
| 		break;
 | |
| 	case 's':
 | |
| 	case 'S':
 | |
| 		printf("200 Mode set to %c.\r\n", *buff);
 | |
| 		mode = MODE_S;
 | |
| 		break;
 | |
| 	default:
 | |
| 		printf("501 Unknown mode %c.\r\n", *buff);
 | |
|    }
 | |
| 
 | |
|    return(GOOD);
 | |
| }
 | |
| 
 | |
| /* make a directory */
 | |
| int doMKD(buff)
 | |
| char *buff;
 | |
| {
 | |
|    if(ChkLoggedIn())
 | |
| 	return(GOOD);
 | |
| 
 | |
|    if(anonymous) {
 | |
| 	printf("550 Command not allowed for anonymous user\r\n");
 | |
| 	return(GOOD);
 | |
|    }
 | |
| 
 | |
|    if(mkdir(buff, 0777))
 | |
| 	printf(msg550, buff, strerror(errno));
 | |
|    else {
 | |
| 	printf("257 \"%s\" directory created.\r\n", buff);
 | |
| 	logit("MKD ", path(buff));
 | |
|    }
 | |
| 
 | |
|    return(GOOD);
 | |
| }
 | |
| 
 | |
| /* name listing */
 | |
| int doNLST(buff)
 | |
| char *buff;
 | |
| {
 | |
|    file_restart = 0;
 | |
| 
 | |
|    return(sendfile(buff, SEND_NLST));
 | |
| }
 | |
| 
 | |
| /* where are we */
 | |
| int doPWD(buff)
 | |
| char *buff;
 | |
| {
 | |
| char dir[128];
 | |
| 
 | |
|    if(ChkLoggedIn())
 | |
| 	return(GOOD);
 | |
| 
 | |
|    if(getcwd(dir, sizeof(dir)) == (char *)NULL)
 | |
| 	printf(msg550, buff, strerror(errno));
 | |
|    else
 | |
| 	printf("257 \"%s\" is current directory.\r\n", dir);
 | |
| 
 | |
|    return(GOOD);
 | |
| }
 | |
| 
 | |
| /* restart command */
 | |
| int doREST(buff)
 | |
| char *buff;
 | |
| {
 | |
|    if(ChkLoggedIn())
 | |
| 	return(GOOD);
 | |
| 
 | |
|    file_restart = atol(buff);
 | |
| 
 | |
|    printf("350 Next file transfer will restart at %lu.\r\n", file_restart);
 | |
| 
 | |
|    return(GOOD);
 | |
| }
 | |
| 
 | |
| /* they want a file */
 | |
| int doRETR(buff)
 | |
| char *buff;
 | |
| {
 | |
|    return(sendfile(buff, SEND_FILE));
 | |
| }
 | |
| 
 | |
| /* remove a directory */
 | |
| int doRMD(buff)
 | |
| char *buff;
 | |
| {
 | |
|    if(ChkLoggedIn())
 | |
| 	return(GOOD);
 | |
| 
 | |
|    if(anonymous) {
 | |
| 	printf("550 Command not allowed for anonymous user\r\n");
 | |
| 	return(GOOD);
 | |
|    }
 | |
| 
 | |
|    if(rmdir(buff))
 | |
| 	printf(msg550, buff, strerror(errno));
 | |
|    else {
 | |
| 	printf("250 Directory \"%s\" deleted.\r\n", buff);
 | |
| 	logit("RMD ", path(buff));
 | |
|    }
 | |
| 
 | |
|    return(GOOD);
 | |
| }
 | |
| 
 | |
| /* rename from */
 | |
| int doRNFR(buff)
 | |
| char *buff;
 | |
| {
 | |
|    if(ChkLoggedIn())
 | |
| 	return(GOOD);
 | |
| 
 | |
|    if(anonymous) {
 | |
| 	printf("550 Command not allowed for anonymous user\r\n");
 | |
| 	return(GOOD);
 | |
|    }
 | |
| 
 | |
|    strncpy(rnfr, buff, sizeof(rnfr));
 | |
|    rnfr[sizeof(rnfr)-1] = '\0';
 | |
| 
 | |
|    printf("350 Got RNFR waiting for RNTO.\r\n");
 | |
| 
 | |
|    return(GOOD);
 | |
| }
 | |
| 
 | |
| /* rename to */
 | |
| int doRNTO(buff)
 | |
| char *buff;
 | |
| {
 | |
|    if(ChkLoggedIn())
 | |
| 	return(GOOD);
 | |
| 
 | |
|    if(anonymous) {
 | |
| 	printf("550 Command not allowed for anonymous user\r\n");
 | |
| 	return(GOOD);
 | |
|    }
 | |
| 
 | |
|    if(rnfr[0] == '\0') {
 | |
| 	printf("550 Rename failed.\r\n");
 | |
| 	return(GOOD);
 | |
|    }
 | |
| 
 | |
|    if(rename(rnfr, buff) < 0)
 | |
| 	printf("550 Rename failed. Error %s\r\n", strerror(errno));
 | |
|    else {
 | |
| 	printf("250 Renamed %s to %s.\r\n", rnfr, buff);
 | |
| 	logit("RNFR", path(rnfr));
 | |
| 	logit("RNTO", path(buff));
 | |
|    }
 | |
| 
 | |
|    rnfr[0] = '\0';
 | |
| 
 | |
|    return(GOOD);
 | |
| }
 | |
| 
 | |
| /* xmode = 0 for multiline crc, xmode <> 0 for single file single line crc */
 | |
| static int docrc(buff, xmode)
 | |
| char *buff;
 | |
| int xmode;
 | |
| {
 | |
| unsigned short cs;
 | |
| long fs;
 | |
| int fd;
 | |
| int s;
 | |
| char *p;
 | |
| 
 | |
|    if((fd = fdxcmd(CMD_CRC, buff)) < 0) {
 | |
| 	printf("501 Could not obtain CRC.\r\n");
 | |
| 	return(GOOD);
 | |
|    }
 | |
| 
 | |
|    if(xmode == 0)
 | |
| 	printf("202-SITE CRC \"%s\"\r\n", buff);
 | |
| 
 | |
|    while(1) {
 | |
| 	p = buffer;
 | |
| 	while(1) {
 | |
| 		if((s = read(fd, p, 1)) != 1) {
 | |
| 			if(xmode == 0)
 | |
| 				printf("202 SITE CRC DONE.\r\n");
 | |
| 			else
 | |
| 				printf("501 Could not obtain CRC.\r\n");
 | |
| 			endfdxcmd(fd);
 | |
| 			return(GOOD);
 | |
| 		}
 | |
| 		if(*p == '\n') {
 | |
| 			*p++ = '\r';
 | |
| 			*p++ = '\n';
 | |
| 			*p = '\0';
 | |
| 			break;
 | |
| 		}
 | |
| 		p++;
 | |
| 	}
 | |
| 	if(xmode != 0)
 | |
| 		break;
 | |
| 	printf("    %s", buffer);
 | |
|    }
 | |
| 
 | |
|    cs = atoi(buffer);
 | |
| 
 | |
|    fs = atol(buffer+6);
 | |
| 
 | |
|    printf("202 CRC %05u %ld.\r\n", cs, fs);
 | |
| 
 | |
|    endfdxcmd(fd);
 | |
| 
 | |
|    return(GOOD);
 | |
| }
 | |
| 
 | |
| /* site specific */
 | |
| int doSITE(buff)
 | |
| char *buff;
 | |
| {
 | |
| char *args;
 | |
| 
 | |
|    if(ChkLoggedIn())
 | |
| 	return(GOOD);
 | |
| 
 | |
| 
 | |
|    strncpy(line, buff, sizeof(line));
 | |
|    line[sizeof(line)-1] = '\0';
 | |
| 
 | |
|    cvtline(&args);
 | |
| 
 | |
|    if(!strcmp(line, "CRC") || !strcmp(line, "CCRC"))
 | |
| 	return(docrc(args, strcmp(line, "CRC")));
 | |
| 
 | |
|    if(!strcmp(line, "FDET"))
 | |
| 	return(dofdet(args));
 | |
| 
 | |
|    printf("501 Unknown SITE command %s.\r\n", line);
 | |
| 
 | |
|    return(GOOD);
 | |
| }
 | |
| 
 | |
| static unsigned long fsize(fname)
 | |
| char *fname;
 | |
| {
 | |
| struct stat st;
 | |
| unsigned long fs = 0L;
 | |
| 
 | |
|    if(stat(fname, &st))
 | |
| 	return(fs);
 | |
| 
 | |
|    if((st.st_mode & S_IFMT) != S_IFREG)
 | |
| 	return(fs);
 | |
| 
 | |
|    if(type == TYPE_A)
 | |
| 	return(fs);
 | |
| 
 | |
|    fs = st.st_size;
 | |
| 
 | |
|    return(fs);
 | |
| }
 | |
| 
 | |
| /* file size, btw when will this be put into an RFC */
 | |
| int doSIZE(buff)
 | |
| char *buff;
 | |
| {
 | |
| struct stat st;
 | |
| unsigned long filesize;
 | |
| 
 | |
|    if(ChkLoggedIn())
 | |
| 	return(GOOD);
 | |
| 
 | |
|    if(stat(buff, &st)) {
 | |
| 	printf(msg550, buff, strerror(errno));
 | |
| 	return(GOOD);
 | |
|    }
 | |
| 
 | |
|    if((st.st_mode & S_IFMT) != S_IFREG) {
 | |
| 	printf("550 Not a regular file.\r\n");
 | |
| 	return(GOOD);
 | |
|    }
 | |
| 
 | |
|    filesize = st.st_size;
 | |
| 
 | |
|    if(type == TYPE_A)
 | |
| 	if(asciisize(buff, &filesize))
 | |
| 		return(GOOD);
 | |
| 
 | |
|    printf("215 %lu\r\n", filesize);
 | |
| 
 | |
|    return(GOOD);
 | |
| }
 | |
| 
 | |
| /* server status, or file status */
 | |
| int doSTAT(buff)
 | |
| char *buff;
 | |
| {
 | |
| time_t now;
 | |
| struct tm *tm;
 | |
| int fd;
 | |
| int s;
 | |
| 
 | |
|    if(!*buff) {
 | |
| 	(void) time(&now);
 | |
| 	tm = localtime(&now);
 | |
| 	printf("211-%s(%s:%u) FTP server status:\r\n",
 | |
| 		myhostname, inet_ntoa(myipaddr), ntohs(myport));
 | |
| 	printf("    Version %s  ", FtpdVersion);
 | |
| 	printf("%s, %02d %s %d %02d:%02d:%02d %s\r\n", days[tm->tm_wday],
 | |
| 		tm->tm_mday, months[tm->tm_mon], 1900+tm->tm_year,
 | |
| 		tm->tm_hour, tm->tm_min, tm->tm_sec, tzname[tm->tm_isdst]);
 | |
| 	printf("    Connected to %s:%u\r\n", inet_ntoa(rmtipaddr), ntohs(rmtport));
 | |
| 	if(!loggedin)
 | |
| 		printf("    Not logged in\r\n");
 | |
| 	else
 | |
| 		printf("    Logged in %s\r\n", username);
 | |
| 	printf("    MODE: %s\r\n",(mode == MODE_B) ? "Block" : "Stream");
 | |
| 	printf("    TYPE: %s\r\n",(type == TYPE_A) ? "Ascii" : "Binary");
 | |
| 	printf("211 End of status\r\n");
 | |
| 	return(GOOD);
 | |
|    }
 | |
| 
 | |
|    if(ChkLoggedIn())
 | |
| 	return(GOOD);
 | |
| 
 | |
|    printf("211-Status of %s:\r\n", buff);
 | |
| 
 | |
|    if((fd = fdxcmd(CMD_LIST, buff)) < 0)
 | |
| 	printf("   Could not retrieve status");
 | |
|    else {
 | |
| 	while((s = read(fd, buffer, 1)) == 1) {
 | |
| 		if(*buffer == '\n')
 | |
| 			printf("\r\n");
 | |
| 		else
 | |
| 			printf("%c", *buffer);
 | |
| 	}
 | |
| 	endfdxcmd(fd);
 | |
|    }
 | |
| 
 | |
|    printf("211 End of status\r\n");
 | |
| 
 | |
|    return(GOOD);
 | |
| }
 | |
| 
 | |
| /* hey look, we're getting a file */
 | |
| int doSTOR(buff)
 | |
| char *buff;
 | |
| {
 | |
|    return(recvfile(buff, RECV_FILE));
 | |
| }
 | |
| 
 | |
| /* hey, get a file unique */
 | |
| int doSTOU(buff)
 | |
| char *buff;
 | |
| {
 | |
|    return(recvfile(buff, RECV_UNIQ));
 | |
| }
 | |
| 
 | |
| /* structure */
 | |
| int doSTRU(buff)
 | |
| char *buff;
 | |
| {
 | |
|    switch(*buff) {
 | |
| 	case 'f':
 | |
| 	case 'F':
 | |
| 		printf("200 Structure set to %c.\r\n", *buff);
 | |
| 		break;
 | |
| 	default:
 | |
| 		printf("501 Unknown structure %c.\r\n", *buff);
 | |
|    }
 | |
| 
 | |
|    return(GOOD);
 | |
| }
 | |
| 
 | |
| /* we're UNIX and proud of it! */
 | |
| int doSYST(buff)
 | |
| char *buff;
 | |
| {
 | |
|    printf("215 UNIX Type: L8\r\n");
 | |
| 
 | |
|    return(GOOD);
 | |
| }
 | |
| 
 | |
| /* change transfer type */
 | |
| int doTYPE(buff)
 | |
| char *buff;
 | |
| {
 | |
|    if(*(buff+1) != '\0') {
 | |
| 	printf("501 Syntax error in parameters.\r\n");
 | |
| 	return(GOOD);
 | |
|    }
 | |
| 
 | |
|    switch(*buff) {
 | |
| 	case 'A':
 | |
| 	case 'a':
 | |
| 		type = TYPE_A;
 | |
| 		printf("200 Type set to A.\r\n");
 | |
| 		break;
 | |
| 	case 'I':
 | |
| 	case 'i':
 | |
| 		type = TYPE_I;
 | |
| 		printf("200 Type set to I.\r\n");
 | |
| 		break;
 | |
| 	default:
 | |
| 		printf("501 Invalid type %c.\r\n", *buff);
 | |
|    }
 | |
| 
 | |
|    return(GOOD);
 | |
| }
 | |
| 
 | |
| static int fdxcmd(cmd, arg)
 | |
| int   cmd;
 | |
| char *arg;
 | |
| {
 | |
| char xcmd[3];
 | |
| char *argv[5];
 | |
| int fds[2];
 | |
| char *smallenv[] = { "PATH=/bin:/usr/bin:/usr/local/bin", NULL, NULL };
 | |
| 
 | |
|    if((smallenv[1] = getenv("TZ")) != NULL) smallenv[1] -= 3;	/* ouch... */
 | |
| 
 | |
|    sprintf(xcmd, "%d", cmd);
 | |
| 
 | |
|    argv[0] = PROG_FTPDSH;
 | |
|    argv[1] = xcmd;
 | |
|    argv[2] = arg;
 | |
|    argv[3] = (char *)NULL;
 | |
| 
 | |
|    if(pipe(fds) < 0)
 | |
| 	return(-1);
 | |
| 
 | |
|    if((cmdpid = fork()) < 0) {
 | |
| 	close(fds[0]);
 | |
| 	close(fds[1]);
 | |
| 	return(-1);
 | |
|    }
 | |
| 
 | |
|    if(cmdpid == 0) { /* Child */
 | |
| 	close(fds[0]);
 | |
| 	close(0);
 | |
| 	open("/dev/null", O_RDONLY);
 | |
| 	dup2(fds[1], 1);
 | |
| 	dup2(fds[1], 2);
 | |
| 	close(fds[1]);
 | |
| 	sprintf(argv[0], "/bin/%s", PROG_FTPDSH);
 | |
| 	execve(argv[0], argv, smallenv);
 | |
| 	sprintf(argv[0], "/usr/bin/%s", PROG_FTPDSH);
 | |
| 	execve(argv[0], argv, smallenv);
 | |
| 	sprintf(argv[0], "/usr/local/bin/%s", PROG_FTPDSH);
 | |
| 	execve(argv[0], argv, smallenv);
 | |
| 	exit(0);
 | |
|    }
 | |
| 
 | |
|    close(fds[1]);
 | |
| 
 | |
|    return(fds[0]);
 | |
| }
 | |
| 
 | |
| /* Same as close if not cmd child started */
 | |
| static int endfdxcmd(fd)
 | |
| int fd;
 | |
| {
 | |
| int s;
 | |
| int cs;
 | |
| 
 | |
|    close(fd);
 | |
| 
 | |
|    if(cmdpid == -1)
 | |
| 	return(0);
 | |
| 
 | |
|    s = waitpid(cmdpid, &cs, 0);
 | |
| 
 | |
|    cmdpid = -1;
 | |
| 
 | |
|    return(0);
 | |
| }
 | |
| 
 | |
| /* returns -1 = size could not be determined, */
 | |
| /*          0 = size determined and in filesize */
 | |
| static int asciisize(filename, filesize)
 | |
| char *filename;
 | |
| unsigned long *filesize;
 | |
| {
 | |
| unsigned long count;
 | |
| int fd;
 | |
| char *p, *pp;
 | |
| int cnt;
 | |
| 
 | |
|    if((fd = open(filename, O_RDONLY)) < 0) {
 | |
| 	printf(msg550, filename, strerror(errno));
 | |
| 	return(-1);
 | |
|    }
 | |
| 
 | |
|    count = 0;
 | |
| 
 | |
|    while((cnt = read(fd, buffer, sizeof(buffer))) > 0) {
 | |
| 	count += cnt;
 | |
| 	p = buffer;
 | |
| 	while(cnt > 0)
 | |
| 		if((pp = memchr(p, '\n', cnt)) != (char *)NULL) {
 | |
| 			count++;
 | |
| 			cnt = cnt - 1 - (pp - p);
 | |
| 			p = pp + 1;
 | |
| 		} else
 | |
| 			break;
 | |
|    }
 | |
| 
 | |
|    if(cnt == 0) {
 | |
| 	*filesize = count;
 | |
| 	close(fd);
 | |
| 	return(0);
 | |
|    }
 | |
| 
 | |
|    printf(msg550, filename, strerror(errno));
 | |
| 
 | |
|    close(fd);
 | |
| 
 | |
|    return(-1);
 | |
| }
 | |
| 
 | |
| /* see if we need to run a command to convert the file */
 | |
| static int cnvtfile(name, name2)
 | |
| char *name;
 | |
| char **name2;
 | |
| {
 | |
| struct stat st;
 | |
| static char fname[256];
 | |
| char *p;
 | |
| int cmode;
 | |
| 
 | |
|    if(!stat(name, &st))			/* file exists can't be a conversion */
 | |
|    	if((st.st_mode & S_IFMT) != S_IFREG) {	/* must be regular file */
 | |
| 		printf("550 Not a regular file.\r\n");
 | |
| 		return(CNVT_ERROR);
 | |
| 	} else
 | |
| 		return(CNVT_NONE);
 | |
| 
 | |
|    if(errno != ENOENT) {	/* doesn't exist is okay, others are not */
 | |
| 	printf(msg550, name, strerror(errno));
 | |
| 	return(CNVT_ERROR);
 | |
|    }
 | |
| 
 | |
|    /* find out what kind of conversion */
 | |
|    strncpy(fname, name, sizeof(fname));
 | |
|    fname[sizeof(fname)-1] = '\0';
 | |
| 
 | |
|    p = fname + strlen(fname);
 | |
|    cmode = CNVT_ERROR;
 | |
|    while(p > fname && cmode == CNVT_ERROR) {
 | |
| 	if(*p == '.') {
 | |
| 		if(!strcmp(p, ".tar"))
 | |
| 			cmode = CNVT_TAR;
 | |
| 		else
 | |
| 		if(!strcmp(p, ".tar.Z"))
 | |
| 			cmode = CNVT_TAR_Z;
 | |
| 		else
 | |
| 		if(!strcmp(p, ".Z"))
 | |
| 			cmode = CNVT_COMP;
 | |
| 		else
 | |
| 		if(!strcmp(p, ".tar.gz"))
 | |
| 			cmode = CNVT_TAR_GZ;
 | |
| 		else
 | |
| 		if(!strcmp(p, ".gz"))
 | |
| 			cmode = CNVT_GZIP;
 | |
| 
 | |
| 		if (cmode != CNVT_ERROR) {
 | |
| 			/* is there a file to convert? */
 | |
| 			*p = '\0';
 | |
| 			if (!stat(fname, &st)) break;
 | |
| 			*p = '.';
 | |
| 			cmode = CNVT_ERROR;
 | |
| 		}
 | |
| 	}
 | |
| 	p--;
 | |
|    }
 | |
| 
 | |
|    if(cmode == CNVT_ERROR) {
 | |
| 	printf(msg550, fname, strerror(errno));
 | |
| 	return(CNVT_ERROR);
 | |
|    }
 | |
| 
 | |
|    if(cmode == CNVT_COMP || cmode == CNVT_GZIP || cmode == CNVT_UNCOMP)
 | |
| 	if((st.st_mode & S_IFMT) != S_IFREG) {
 | |
| 		printf("550 Not a regular file.\r\n");
 | |
| 		return(CNVT_ERROR);
 | |
| 	}
 | |
| 
 | |
|    *name2 = fname;
 | |
| 
 | |
|    return(cmode);
 | |
| }
 | |
| 
 | |
| static int procfile(name)
 | |
| char *name;
 | |
| {
 | |
| int cmd;
 | |
| int fd;
 | |
| char *name2;
 | |
| 
 | |
|    cmd = cnvtfile(name, &name2);
 | |
| 
 | |
|    switch(cmd) {
 | |
| 	case CNVT_TAR:
 | |
| 		fd = fdxcmd(cmd + 10, name2);
 | |
| 		break;
 | |
| 	case CNVT_TAR_Z:
 | |
| 		fd = fdxcmd(cmd + 10, name2);
 | |
| 		break;
 | |
| 	case CNVT_COMP:
 | |
| 		fd = fdxcmd(cmd + 10, name2);
 | |
| 		break;
 | |
| 	case CNVT_TAR_GZ:
 | |
| 		fd = fdxcmd(cmd + 10, name2);
 | |
| 		break;
 | |
| 	case CNVT_GZIP:
 | |
| 		fd = fdxcmd(cmd + 10, name2);
 | |
| 		break;
 | |
| 	case CNVT_UNCOMP:
 | |
| 		fd = fdxcmd(cmd + 10, name2);
 | |
| 		break;
 | |
| 	case CNVT_NONE:
 | |
| 		fd = open(name, O_RDONLY);
 | |
| 		break;
 | |
| 	case CNVT_ERROR:
 | |
| 	default:
 | |
| 		return(-1);
 | |
|    }
 | |
| 
 | |
|    if(fd < 0)
 | |
| 	printf(msg550, name, strerror(errno));
 | |
| 
 | |
|    return(fd);
 | |
| }
 | |
| 
 | |
| /* oh no, they're taking a file */
 | |
| static int sendfile(name, xmode)
 | |
| char *name;
 | |
| int xmode;
 | |
| {
 | |
| char *fname;
 | |
| int fd, s;
 | |
| time_t datastart, dataend;
 | |
| unsigned long datacount;
 | |
| long kbs;
 | |
| char c;
 | |
| char *p;
 | |
| char *op, *ope;
 | |
| off_t sp;
 | |
| int doascii;
 | |
| unsigned long fs;
 | |
| char block[3];
 | |
| 
 | |
|    if(ChkLoggedIn()) 
 | |
| 	return(GOOD);
 | |
| 
 | |
|    switch(xmode) {
 | |
| 	case SEND_NLST:
 | |
| 		fname = "NLST";
 | |
| 		fd = fdxcmd(CMD_NLST, name);
 | |
| 		if(fd < 0)
 | |
| 			printf(msg550, name, strerror(errno));
 | |
| 		break;
 | |
| 	case SEND_LIST:
 | |
| 		fname = "LIST";
 | |
| 		fd = fdxcmd(CMD_LIST, name);
 | |
| 		if(fd < 0)
 | |
| 			printf(msg550, name, strerror(errno));
 | |
| 		break;
 | |
| 	default:
 | |
| 		fname = name;
 | |
| 		fd = procfile(name);
 | |
| 		if(fd < 0)
 | |
| 			logit("FAIL", path(fname));
 | |
| 		else
 | |
| 			logit("SEND", path(fname));
 | |
|    }
 | |
| 
 | |
|    if(fd < 0)
 | |
| 	return(GOOD);
 | |
| 
 | |
|    /* set file position at approriate spot */
 | |
|    if(file_restart) {
 | |
| 	if(type == TYPE_A) {
 | |
| 		sp = 0;
 | |
| 		while(sp < file_restart) {
 | |
| 			sp++;
 | |
| 			s = read(fd, buffer, 1);
 | |
| 			if(s < 0) {
 | |
| 				printf(msg550, fname, strerror(errno));
 | |
| 				endfdxcmd(fd);
 | |
| 				file_restart = 0;
 | |
| 				return(GOOD);
 | |
| 			}
 | |
| 			if(s == 0) break;
 | |
| 			if(*buffer == '\n')
 | |
| 				sp++;
 | |
| 		}
 | |
| 	} else {
 | |
| 		sp = lseek(fd, file_restart, SEEK_SET);
 | |
| 		if(sp == -1) {
 | |
| 			printf(msg550, fname, strerror(errno));
 | |
| 			endfdxcmd(fd);
 | |
| 			file_restart = 0;
 | |
| 			return(GOOD);
 | |
| 		}
 | |
| 	}
 | |
| 	if(sp != file_restart) {
 | |
| 		printf("550 File restart point error. %lu not %lu\r\n", sp, file_restart);
 | |
| 		endfdxcmd(fd);
 | |
| 		file_restart = 0;
 | |
| 		return(GOOD);
 | |
| 	}
 | |
|    }
 | |
|    file_restart = 0;
 | |
| 
 | |
|    fs = fsize(fname);
 | |
|    if(fs == 0L)
 | |
| 	printf("%03d File %s okay.  Opening data connection.\r\n",
 | |
| 		ftpdata_fd >= 0 ? 125 : 150, fname);
 | |
|    else
 | |
| 	printf("%03d Opening %s mode data connection for %s (%ld bytes).\r\n",
 | |
| 		ftpdata_fd >= 0 ? 125 : 150,
 | |
| 		type == TYPE_A ? "ASCII" : "BINARY",
 | |
| 		fname, fs);
 | |
|    fflush(stdout);
 | |
| 
 | |
| #ifdef DEBUG
 | |
|    fprintf(logfile, "After 125/150 b4 DataConnect\n");
 | |
|    fflush(logfile);
 | |
| #endif
 | |
| 
 | |
|    if(DataConnect()) {
 | |
| 	endfdxcmd(fd);
 | |
| 	return(GOOD);
 | |
|    }
 | |
| 
 | |
| #ifdef DEBUG
 | |
|    fprintf(logfile, "After DataConnect\n");
 | |
|    fflush(logfile);
 | |
|    fprintf(logfile, "ftpd: parent %d start sendfile \n", getpid());
 | |
|    fflush(logfile);
 | |
| #endif
 | |
| 
 | |
|    /* start transfer */
 | |
|    doascii = (type == TYPE_A) ||
 | |
|    	((xmode == SEND_LIST) || (xmode == SEND_NLST)); /* per RFC1123 4.1.2.7 */
 | |
|    datacount = 0;
 | |
|    time(&datastart);
 | |
|    op = bufout; ope = bufout + sizeof(bufout) - 3;
 | |
|    while((s = read(fd, buffer, sizeof(buffer))) > 0) {
 | |
| #ifdef DEBUG
 | |
| 	fprintf(logfile, "sendfile read %d\n", s); fflush(logfile);
 | |
| #endif
 | |
| 	datacount += s;
 | |
| 	if(doascii) {
 | |
| 		p = buffer;
 | |
| 		while(s-- > 0) {
 | |
| 			c = *p++;
 | |
| 			if(c == '\n') {
 | |
| 				*op++ = '\r';
 | |
| 				datacount++;
 | |
| 			}
 | |
| 			*op++ = c;
 | |
| 			if(op >= ope) {
 | |
| 				if(mode == MODE_B) {
 | |
| 					block[0] = '\0';
 | |
| 					*(u16_t *)&block[1] = htons(op - bufout);
 | |
| 					write(ftpdata_fd, block, sizeof(block));
 | |
| 				}
 | |
| 				write(ftpdata_fd, bufout, op - bufout);
 | |
| 				op = bufout;
 | |
| 			}
 | |
| 		}
 | |
| 	} else {
 | |
| 		if(mode == MODE_B) {
 | |
| 			block[0] = '\0';
 | |
| 			*(u16_t *)&block[1] = htons(s);
 | |
| 			write(ftpdata_fd, block, sizeof(block));
 | |
| 		}
 | |
| 		s = write(ftpdata_fd, buffer, s);
 | |
| 	}
 | |
|    }
 | |
|    if(op > bufout) {
 | |
| 	if(mode == MODE_B) {
 | |
| 		block[0] = MODE_B_EOF;
 | |
| 		*(u16_t *)&block[1] = htons(op - bufout);
 | |
| 		write(ftpdata_fd, block, sizeof(block));
 | |
| 	}
 | |
| 	write(ftpdata_fd, bufout, op - bufout);
 | |
|    } else
 | |
| 	if(mode == MODE_B) {
 | |
| 		block[0] = MODE_B_EOF;
 | |
| 		*(u16_t *)&block[1] = htons(0);
 | |
| 		write(ftpdata_fd, block, sizeof(block));
 | |
| 	}
 | |
|    time(&dataend);
 | |
| 
 | |
| #ifdef DEBUG
 | |
|    fprintf(logfile, "ftpd: parent %d end sendfile \n", getpid());
 | |
|    fflush(logfile);
 | |
| #endif
 | |
| 
 | |
|    endfdxcmd(fd);
 | |
|    if(mode != MODE_B) {
 | |
| 	close(ftpdata_fd); 
 | |
| 	ftpdata_fd = -1;
 | |
|    }
 | |
| 
 | |
|    if(dataend == datastart) dataend++;
 | |
|    kbs = (datacount * 100 / (dataend - datastart)) / 1024;
 | |
| 
 | |
|    if(s < 0)
 | |
| 	printf("451 Transfer aborted.\r\n");
 | |
|    else
 | |
| 	printf("%03d Transfer finished successfully. %ld.%02d KB/s\r\n",
 | |
| 		mode == MODE_B ? 250 : 226,
 | |
| 		(long)(kbs / 100), (int)(kbs % 100));
 | |
| 
 | |
|    return(GOOD);
 | |
| }
 | |
| 
 | |
| static int recvfile(name, xmode)
 | |
| char *name;
 | |
| int xmode;
 | |
| {
 | |
| char *fname;
 | |
| time_t datastart, dataend;
 | |
| unsigned long datacount;
 | |
| long kbs;
 | |
| char c;
 | |
| char *p;
 | |
| char *op, *ope;
 | |
| int fd, oflag;
 | |
| int s;
 | |
| int gotcr;
 | |
| off_t sp;
 | |
| char block[3];
 | |
| unsigned short cnt;
 | |
| 
 | |
|    if(ChkLoggedIn())
 | |
| 	return(GOOD);
 | |
| 
 | |
|    fname = name;
 | |
| 
 | |
|    switch(xmode) {
 | |
| 	case RECV_APND:
 | |
| 		oflag = O_WRONLY | O_APPEND;
 | |
| 		break;
 | |
| 	case RECV_UNIQ:
 | |
| 		fname = uniqname();
 | |
| 		oflag = O_WRONLY | O_CREAT;
 | |
| 		break;
 | |
| 	default:
 | |
| 		oflag = O_WRONLY | O_CREAT | O_TRUNC;
 | |
|    }
 | |
| 
 | |
|    if(file_restart)
 | |
| 	oflag = O_RDWR;
 | |
| 
 | |
|    fd = open(fname, oflag, (anonymous ? 0000:0600));
 | |
| 
 | |
|    if(fd < 0) {
 | |
| 	printf(msg550, fname, strerror(errno));
 | |
| 	return(GOOD);
 | |
|    }
 | |
| 
 | |
|    /* log the received file */
 | |
|    logit("RECV", path(fname));
 | |
| 
 | |
|    /* set file position at approriate spot */
 | |
|    if(file_restart) {
 | |
| 	if(type == TYPE_A) {
 | |
| 		sp = 0;
 | |
| 		while(sp < file_restart) {
 | |
| 			sp++;
 | |
| 			s = read(fd, buffer, 1);
 | |
| 			if(s < 0) {
 | |
| 				printf(msg550, fname, strerror(errno));
 | |
| 				close(fd);
 | |
| 				file_restart = 0;
 | |
| 				return(GOOD);
 | |
| 			}
 | |
| 			if(s == 0) break;
 | |
| 			if(*buffer == '\n')
 | |
| 				sp++;
 | |
| 		}
 | |
| 	} else {
 | |
| 		sp = lseek(fd, file_restart, SEEK_SET);
 | |
| 		if(sp == -1) {
 | |
| 			printf(msg550, fname, strerror(errno));
 | |
| 			close(fd);
 | |
| 			file_restart = 0;
 | |
| 			return(GOOD);
 | |
| 		}
 | |
| 	}
 | |
| 	if(sp != file_restart) {
 | |
| 		printf("550 File restart point error. %lu not %lu\r\n", sp, file_restart);
 | |
| 		close(fd);
 | |
| 		file_restart = 0;
 | |
| 		return(GOOD);
 | |
| 	}
 | |
|    }
 | |
|    file_restart = 0;
 | |
| 
 | |
|    if(xmode == RECV_UNIQ)
 | |
|    	printf("%03d FILE: %s\r\n",
 | |
|    		ftpdata_fd >= 0 ? 125 : 150, fname);	/* per RFC1123 4.1.2.9 */
 | |
|    else
 | |
| 	printf("%03d File %s okay.  Opening data connection.\r\n",
 | |
| 		ftpdata_fd >= 0 ? 125 : 150, fname);
 | |
|    fflush(stdout);
 | |
| 
 | |
|    if(DataConnect()) {
 | |
|    	close(fd);
 | |
| 	return(GOOD);
 | |
|    }
 | |
| 
 | |
| #ifdef DEBUG
 | |
|    fprintf(logfile, "ftpd: parent %d start recvfile \n", getpid());
 | |
|    fflush(logfile);
 | |
| #endif
 | |
| 
 | |
|    /* start receiving file */
 | |
|    datacount = 0;
 | |
|    gotcr = 0;
 | |
|    op = bufout; ope = bufout + sizeof(bufout) - 3;
 | |
|    cnt = 0;
 | |
|    time(&datastart);
 | |
|    while(1) {
 | |
|    	if(mode != MODE_B)
 | |
|    		cnt = sizeof(buffer);
 | |
|    	else
 | |
|    		if(cnt == 0) {
 | |
|    			s = read(ftpdata_fd, block, sizeof(block));
 | |
|    			cnt = ntohs(*(u16_t *)&block[1]);
 | |
|    			s = 0;
 | |
|    			if(cnt == 0 && block[0] & MODE_B_EOF)
 | |
|    				break;
 | |
|    		}
 | |
| 	s = read(ftpdata_fd, buffer, cnt > sizeof(buffer) ? sizeof(buffer) : cnt);
 | |
| 	if(s <= 0) break;
 | |
| 	cnt -= s;
 | |
| 	datacount += (long)s;
 | |
| 	if(type == TYPE_A) {
 | |
| 		p = buffer;
 | |
| 		while(s-- > 0) {
 | |
| 			c = *p++;
 | |
| 			if(gotcr) {
 | |
| 				gotcr = 0;
 | |
| 				if(c != '\n')
 | |
| 					*op++ = '\r';
 | |
| 			}
 | |
| 			if(c == '\r')
 | |
| 				gotcr = 1;
 | |
| 			else
 | |
| 				*op++ = c;
 | |
| 			if(op >= ope) {
 | |
| 				write(fd, bufout, op - bufout);
 | |
| 				op = bufout;
 | |
| 			}
 | |
| 		}
 | |
| 	} else
 | |
| 		write(fd, buffer, s);
 | |
| 	if(cnt == 0 && mode == MODE_B && block[0] & MODE_B_EOF) {
 | |
| 		s = 0;
 | |
| 		break;
 | |
| 	}
 | |
|    }
 | |
|    if(gotcr)
 | |
| 	*op++ = '\r';
 | |
|    if(op > bufout)
 | |
| 	write(fd, bufout, op - bufout);
 | |
|    time(&dataend);
 | |
| 
 | |
| #ifdef DEBUG
 | |
|    fprintf(logfile, "ftpd: parent %d end recvfile \n", getpid());
 | |
|    fflush(logfile);
 | |
| #endif
 | |
| 
 | |
|    close(fd);
 | |
|    if(mode != MODE_B) {
 | |
| 	close(ftpdata_fd); 
 | |
| 	ftpdata_fd = -1;
 | |
|    }
 | |
| 
 | |
|    if(dataend == datastart) dataend++;
 | |
|    kbs = (datacount * 100 / (dataend - datastart)) / 1024;
 | |
| 
 | |
|    if((mode == MODE_B && cnt != 0) || s != 0)
 | |
| 	printf("451 Transfer aborted.\r\n");
 | |
|    else {
 | |
| 	printf("%03d Transfer finished successfully. ",
 | |
| 		mode == MODE_B ? 250 : 226);
 | |
| 	if(xmode == RECV_UNIQ)
 | |
| 		printf("Unique file %s. ", fname);
 | |
| 	printf("%ld.%02d KB/s\r\n", (long)(kbs / 100), (int)(kbs % 100));
 | |
|    }
 | |
| 
 | |
|    return(GOOD);
 | |
| }
 | |
| 
 | |
| static char *uniqname()
 | |
| {
 | |
| static char uniq[32];
 | |
| int i;
 | |
| struct stat st;
 | |
| 
 | |
|    for(i = 0; i < 1000; i++) {
 | |
| 	sprintf(uniq, "ftpd%d%d", getpid(), i);
 | |
| 	if(stat(uniq, &st) == -1)
 | |
| 		return(uniq);
 | |
|    }
 | |
|    return(uniq);
 | |
| }
 | |
| 
 | |
| static char *spath[256];
 | |
| static char *path(fname)
 | |
| char *fname;
 | |
| {
 | |
| char dir[128];
 | |
| 
 | |
|    if(getcwd(dir, sizeof(dir)) == (char *)NULL)
 | |
| 	sprintf(dir, "???");
 | |
| 
 | |
|    if(fname[0] == '/')
 | |
| 	sprintf((char *)spath, "%s%s", newroot, fname);
 | |
|    else
 | |
| 	if(dir[1] == '\0')
 | |
| 		sprintf((char *)spath, "%s%s%s", newroot, dir, fname);
 | |
| 	else
 | |
| 		sprintf((char *)spath, "%s%s/%s", newroot, dir, fname);
 | |
| 
 | |
|    return((char *)spath);
 | |
| }
 | |
| 
 | |
| /* do file detail */
 | |
| static int dofdet(buff)
 | |
| char *buff;
 | |
| {
 | |
| struct stat st;
 | |
| char ft;
 | |
| 
 | |
|    if(ChkLoggedIn())
 | |
| 	return(GOOD);
 | |
| 
 | |
|    if(stat(buff, &st)) {
 | |
| 	printf("501 Could not obtain file detail.\r\n");
 | |
| 	return(GOOD);
 | |
|    }
 | |
|    switch(st.st_mode & S_IFMT) {
 | |
|    	case S_IFIFO:	ft = 'p'; break;
 | |
|    	case S_IFCHR:	ft = 'c'; break;
 | |
|    	case S_IFDIR:	ft = 'd'; break;
 | |
|    	case S_IFBLK:	ft = 'b'; break;
 | |
|    	case S_IFREG:	ft = 'f'; break;
 | |
|    	default:	ft = '?'; break;
 | |
|    }
 | |
|    printf("202 %c %u %u %u %u %u %lu %lu\r\n", 
 | |
|    	ft,					/* file type */
 | |
|    	st.st_rdev >> 8,			/* Major */
 | |
|    	st.st_rdev & 0xff,			/* Minor */
 | |
|    	st.st_uid,				/* UID */
 | |
|    	st.st_gid,				/* GID */
 | |
|    	st.st_mode,				/* File Modes */
 | |
|    	st.st_size,				/* SIZE */
 | |
|    	st.st_mtime);				/* Mod Time */
 | |
| 
 | |
|    return(GOOD);
 | |
| }
 | 
