xref: /csrg-svn/libexec/ftpd/ftpd.c (revision 27106)
122499Sdist /*
226044Sminshall  * Copyright (c) 1985 Regents of the University of California.
322499Sdist  * All rights reserved.  The Berkeley software License Agreement
422499Sdist  * specifies the terms and conditions for redistribution.
522499Sdist  */
622499Sdist 
710275Ssam #ifndef lint
822499Sdist char copyright[] =
926044Sminshall "@(#) Copyright (c) 1985 Regents of the University of California.\n\
1022499Sdist  All rights reserved.\n";
1122499Sdist #endif not lint
1210275Ssam 
1322499Sdist #ifndef lint
14*27106Smckusick static char sccsid[] = "@(#)ftpd.c	5.5 (Berkeley) 04/16/86";
1522499Sdist #endif not lint
1622499Sdist 
1710275Ssam /*
1810275Ssam  * FTP server.
1910275Ssam  */
2010303Ssam #include <sys/param.h>
2110275Ssam #include <sys/stat.h>
2210275Ssam #include <sys/ioctl.h>
2310275Ssam #include <sys/socket.h>
2413247Ssam #include <sys/file.h>
2513595Ssam #include <sys/wait.h>
2610275Ssam 
2710275Ssam #include <netinet/in.h>
2810275Ssam 
2913034Ssam #include <arpa/ftp.h>
3013211Sroot #include <arpa/inet.h>
3126044Sminshall #include <arpa/telnet.h>
3213034Ssam 
3310275Ssam #include <stdio.h>
3410275Ssam #include <signal.h>
3510275Ssam #include <pwd.h>
3610275Ssam #include <setjmp.h>
3710275Ssam #include <netdb.h>
3810423Ssam #include <errno.h>
3926044Sminshall #include <strings.h>
4026493Sminshall #include <syslog.h>
4110275Ssam 
4210695Ssam /*
4310695Ssam  * File containing login names
4410695Ssam  * NOT to be used on this machine.
4510695Ssam  * Commonly used to disallow uucp.
4610695Ssam  */
4710695Ssam #define	FTPUSERS	"/etc/ftpusers"
4810695Ssam 
4910275Ssam extern	int errno;
5010275Ssam extern	char *sys_errlist[];
5110275Ssam extern	char *crypt();
5210275Ssam extern	char version[];
5310275Ssam extern	char *home;		/* pointer to home directory for glob */
5426044Sminshall extern	FILE *popen(), *fopen(), *freopen();
5526493Sminshall extern	int  pclose(), fclose();
5626044Sminshall extern	char *getline();
5726044Sminshall extern	char cbuf[];
5810275Ssam 
5910275Ssam struct	sockaddr_in ctrl_addr;
6010275Ssam struct	sockaddr_in data_source;
6110275Ssam struct	sockaddr_in data_dest;
6210275Ssam struct	sockaddr_in his_addr;
6310275Ssam 
6410275Ssam int	data;
6526044Sminshall jmp_buf	errcatch, urgcatch;
6610275Ssam int	logged_in;
6710275Ssam struct	passwd *pw;
6810275Ssam int	debug;
6926493Sminshall int	timeout = 900;    /* timeout after 15 minutes of inactivity */
7011757Ssam int	logging;
7110275Ssam int	guest;
7216033Sralph int	wtmp;
7310275Ssam int	type;
7410275Ssam int	form;
7510275Ssam int	stru;			/* avoid C keyword */
7610275Ssam int	mode;
7710321Ssam int	usedefault = 1;		/* for data transfers */
7826044Sminshall int	pdata;			/* for passive mode */
7926044Sminshall int	unique;
8026044Sminshall int	transflag;
8126044Sminshall char	tmpline[7];
8210275Ssam char	hostname[32];
8313247Ssam char	remotehost[32];
8410275Ssam 
8511653Ssam /*
8611653Ssam  * Timeout intervals for retrying connections
8711653Ssam  * to hosts that don't accept PORT cmds.  This
8811653Ssam  * is a kludge, but given the problems with TCP...
8911653Ssam  */
9011653Ssam #define	SWAITMAX	90	/* wait at most 90 seconds */
9111653Ssam #define	SWAITINT	5	/* interval between retries */
9211653Ssam 
9311653Ssam int	swaitmax = SWAITMAX;
9411653Ssam int	swaitint = SWAITINT;
9511653Ssam 
9610275Ssam int	lostconn();
9726044Sminshall int	myoob();
9810275Ssam FILE	*getdatasock(), *dataconn();
9910275Ssam 
10010275Ssam main(argc, argv)
10110275Ssam 	int argc;
10210275Ssam 	char *argv[];
10310275Ssam {
10426493Sminshall 	int addrlen;
10526044Sminshall 	long pgid;
10610275Ssam 	char *cp;
10710275Ssam 
10816339Skarels 	addrlen = sizeof (his_addr);
10916339Skarels 	if (getpeername(0, &his_addr, &addrlen) < 0) {
11026493Sminshall 		syslog(LOG_ERR, "getpeername (%s): %m",argv[0]);
11110275Ssam 		exit(1);
11210275Ssam 	}
11316339Skarels 	addrlen = sizeof (ctrl_addr);
11426493Sminshall 	if (getsockname(0, (char *) &ctrl_addr, &addrlen) < 0) {
11526493Sminshall 		syslog(LOG_ERR, "getsockname (%s): %m",argv[0]);
11616339Skarels 		exit(1);
11716339Skarels 	}
11816339Skarels 	data_source.sin_port = htons(ntohs(ctrl_addr.sin_port) - 1);
11910275Ssam 	debug = 0;
12026493Sminshall 	openlog("ftpd", LOG_PID, LOG_DAEMON);
12110275Ssam 	argc--, argv++;
12210275Ssam 	while (argc > 0 && *argv[0] == '-') {
12310275Ssam 		for (cp = &argv[0][1]; *cp; cp++) switch (*cp) {
12410275Ssam 
12511653Ssam 		case 'v':
12611653Ssam 			debug = 1;
12711653Ssam 			break;
12811653Ssam 
12910275Ssam 		case 'd':
13010275Ssam 			debug = 1;
13110275Ssam 			break;
13210275Ssam 
13311757Ssam 		case 'l':
13411757Ssam 			logging = 1;
13511757Ssam 			break;
13611757Ssam 
13711653Ssam 		case 't':
13811653Ssam 			timeout = atoi(++cp);
13911653Ssam 			goto nextopt;
14011653Ssam 			break;
14111653Ssam 
14210275Ssam 		default:
14316339Skarels 			fprintf(stderr, "ftpd: Unknown flag -%c ignored.\n",
14416339Skarels 			     *cp);
14510275Ssam 			break;
14610275Ssam 		}
14711653Ssam nextopt:
14810275Ssam 		argc--, argv++;
14910275Ssam 	}
15026493Sminshall 	(void) signal(SIGPIPE, lostconn);
15126493Sminshall 	(void) signal(SIGCHLD, SIG_IGN);
15226044Sminshall 	if (signal(SIGURG, myoob) < 0) {
15326493Sminshall 		syslog(LOG_ERR, "signal: %m");
15426044Sminshall 	}
15526044Sminshall 	pgid = getpid();
15626493Sminshall 	if (ioctl(fileno(stdin), (int) SIOCSPGRP, (char *) &pgid) < 0) {
15726493Sminshall 		syslog(LOG_ERR, "ioctl: %m");
15826044Sminshall 	}
15916760Slepreau 	dolog(&his_addr);
16016339Skarels 	/* do telnet option negotiation here */
16116339Skarels 	/*
16216339Skarels 	 * Set up default state
16316339Skarels 	 */
16416339Skarels 	logged_in = 0;
16516339Skarels 	data = -1;
16616339Skarels 	type = TYPE_A;
16716339Skarels 	form = FORM_N;
16816339Skarels 	stru = STRU_F;
16916339Skarels 	mode = MODE_S;
17026044Sminshall 	tmpline[0] = '\0';
17126493Sminshall 	(void) gethostname(hostname, sizeof (hostname));
17216339Skarels 	reply(220, "%s FTP server (%s) ready.",
17316339Skarels 		hostname, version);
17410275Ssam 	for (;;) {
17526493Sminshall 		(void) setjmp(errcatch);
17626493Sminshall 		(void) yyparse();
17710275Ssam 	}
17810275Ssam }
17910419Ssam 
18010275Ssam lostconn()
18110275Ssam {
18210275Ssam 
18314089Ssam 	if (debug)
18426493Sminshall 		syslog(LOG_DEBUG, "lost connection");
18514089Ssam 	dologout(-1);
18610275Ssam }
18710275Ssam 
18810275Ssam pass(passwd)
18910275Ssam 	char *passwd;
19010275Ssam {
19110303Ssam 	char *xpasswd, *savestr();
19210303Ssam 	static struct passwd save;
19310275Ssam 
19410275Ssam 	if (logged_in || pw == NULL) {
19510275Ssam 		reply(503, "Login with USER first.");
19610275Ssam 		return;
19710275Ssam 	}
19810275Ssam 	if (!guest) {		/* "ftp" is only account allowed no password */
19910275Ssam 		xpasswd = crypt(passwd, pw->pw_passwd);
20016760Slepreau 		/* The strcmp does not catch null passwords! */
20116760Slepreau 		if (*pw->pw_passwd == '\0' || strcmp(xpasswd, pw->pw_passwd)) {
20210275Ssam 			reply(530, "Login incorrect.");
20310275Ssam 			pw = NULL;
20410275Ssam 			return;
20510275Ssam 		}
20610275Ssam 	}
20710303Ssam 	setegid(pw->pw_gid);
20810275Ssam 	initgroups(pw->pw_name, pw->pw_gid);
20910275Ssam 	if (chdir(pw->pw_dir)) {
210*27106Smckusick 		reply(530, "User %s: can't change directory to %s.",
21110275Ssam 			pw->pw_name, pw->pw_dir);
21210303Ssam 		goto bad;
21310275Ssam 	}
21416033Sralph 
21516760Slepreau 	/* grab wtmp before chroot */
21616760Slepreau 	wtmp = open("/usr/adm/wtmp", O_WRONLY|O_APPEND);
21710303Ssam 	if (guest && chroot(pw->pw_dir) < 0) {
21810275Ssam 		reply(550, "Can't set guest privileges.");
21916760Slepreau 		if (wtmp >= 0) {
22016760Slepreau 			(void) close(wtmp);
22116760Slepreau 			wtmp = -1;
22216760Slepreau 		}
22310303Ssam 		goto bad;
22410275Ssam 	}
22510275Ssam 	if (!guest)
22610275Ssam 		reply(230, "User %s logged in.", pw->pw_name);
22710275Ssam 	else
22810275Ssam 		reply(230, "Guest login ok, access restrictions apply.");
22910275Ssam 	logged_in = 1;
23013247Ssam 	dologin(pw);
23110303Ssam 	seteuid(pw->pw_uid);
23210303Ssam 	/*
23310303Ssam 	 * Save everything so globbing doesn't
23410303Ssam 	 * clobber the fields.
23510303Ssam 	 */
23610303Ssam 	save = *pw;
23710303Ssam 	save.pw_name = savestr(pw->pw_name);
23810303Ssam 	save.pw_passwd = savestr(pw->pw_passwd);
23910303Ssam 	save.pw_comment = savestr(pw->pw_comment);
24026493Sminshall 	save.pw_gecos = savestr(pw->pw_gecos);
24110303Ssam 	save.pw_dir = savestr(pw->pw_dir);
24210303Ssam 	save.pw_shell = savestr(pw->pw_shell);
24310303Ssam 	pw = &save;
24410303Ssam 	home = pw->pw_dir;		/* home dir for globbing */
24510303Ssam 	return;
24610303Ssam bad:
24710303Ssam 	seteuid(0);
24810303Ssam 	pw = NULL;
24910275Ssam }
25010275Ssam 
25110303Ssam char *
25210303Ssam savestr(s)
25310303Ssam 	char *s;
25410303Ssam {
25510303Ssam 	char *malloc();
25626493Sminshall 	char *new = malloc((unsigned) strlen(s) + 1);
25710303Ssam 
25810303Ssam 	if (new != NULL)
25926493Sminshall 		(void) strcpy(new, s);
26011347Ssam 	return (new);
26110303Ssam }
26210303Ssam 
26310275Ssam retrieve(cmd, name)
26410275Ssam 	char *cmd, *name;
26510275Ssam {
26610275Ssam 	FILE *fin, *dout;
26710275Ssam 	struct stat st;
26826044Sminshall 	int (*closefunc)(), tmp;
26910275Ssam 
27010275Ssam 	if (cmd == 0) {
27110317Ssam #ifdef notdef
27210317Ssam 		/* no remote command execution -- it's a security hole */
27311653Ssam 		if (*name == '|')
27410275Ssam 			fin = popen(name + 1, "r"), closefunc = pclose;
27510275Ssam 		else
27610317Ssam #endif
27710275Ssam 			fin = fopen(name, "r"), closefunc = fclose;
27810275Ssam 	} else {
27910275Ssam 		char line[BUFSIZ];
28010275Ssam 
28126493Sminshall 		(void) sprintf(line, cmd, name), name = line;
28210275Ssam 		fin = popen(line, "r"), closefunc = pclose;
28310275Ssam 	}
28410275Ssam 	if (fin == NULL) {
28513152Ssam 		if (errno != 0)
28613152Ssam 			reply(550, "%s: %s.", name, sys_errlist[errno]);
28710275Ssam 		return;
28810275Ssam 	}
28910275Ssam 	st.st_size = 0;
29010275Ssam 	if (cmd == 0 &&
29110275Ssam 	    (stat(name, &st) < 0 || (st.st_mode&S_IFMT) != S_IFREG)) {
29210275Ssam 		reply(550, "%s: not a plain file.", name);
29310275Ssam 		goto done;
29410275Ssam 	}
29510275Ssam 	dout = dataconn(name, st.st_size, "w");
29610275Ssam 	if (dout == NULL)
29710275Ssam 		goto done;
29826044Sminshall 	if ((tmp = send_data(fin, dout)) > 0 || ferror(dout) > 0) {
29910275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
30026044Sminshall 	}
30126044Sminshall 	else if (tmp == 0) {
30210275Ssam 		reply(226, "Transfer complete.");
30326044Sminshall 	}
30426493Sminshall 	(void) fclose(dout);
30526044Sminshall 	data = -1;
30626044Sminshall 	pdata = -1;
30710275Ssam done:
30810275Ssam 	(*closefunc)(fin);
30910275Ssam }
31010275Ssam 
31110275Ssam store(name, mode)
31210275Ssam 	char *name, *mode;
31310275Ssam {
31410275Ssam 	FILE *fout, *din;
31526044Sminshall 	int (*closefunc)(), dochown = 0, tmp;
31626044Sminshall 	char *gunique(), *local;
31710275Ssam 
31810317Ssam #ifdef notdef
31910317Ssam 	/* no remote command execution -- it's a security hole */
32011653Ssam 	if (name[0] == '|')
32110275Ssam 		fout = popen(&name[1], "w"), closefunc = pclose;
32210317Ssam 	else
32310317Ssam #endif
32410317Ssam 	{
32510303Ssam 		struct stat st;
32610303Ssam 
32726044Sminshall 		local = name;
32826044Sminshall 		if (stat(name, &st) < 0) {
32910303Ssam 			dochown++;
33026044Sminshall 		}
33126044Sminshall 		else if (unique) {
33226044Sminshall 			if ((local = gunique(name)) == NULL) {
33326044Sminshall 				return;
33426044Sminshall 			}
33526044Sminshall 			dochown++;
33626044Sminshall 		}
33726044Sminshall 		fout = fopen(local, mode), closefunc = fclose;
33810303Ssam 	}
33910275Ssam 	if (fout == NULL) {
340*27106Smckusick 		reply(553, "%s: %s.", local, sys_errlist[errno]);
34110275Ssam 		return;
34210275Ssam 	}
34326044Sminshall 	din = dataconn(local, (off_t)-1, "r");
34410275Ssam 	if (din == NULL)
34510275Ssam 		goto done;
34626044Sminshall 	if ((tmp = receive_data(din, fout)) > 0 || ferror(fout) > 0) {
347*27106Smckusick 		reply(552, "%s: %s.", local, sys_errlist[errno]);
34826044Sminshall 	}
34926044Sminshall 	else if (tmp == 0 && !unique) {
35010275Ssam 		reply(226, "Transfer complete.");
35126044Sminshall 	}
35226044Sminshall 	else if (tmp == 0 && unique) {
35326044Sminshall 		reply(226, "Transfer complete (unique file name:%s).", local);
35426044Sminshall 	}
35526493Sminshall 	(void) fclose(din);
35626044Sminshall 	data = -1;
35726044Sminshall 	pdata = -1;
35810275Ssam done:
35910303Ssam 	if (dochown)
36026044Sminshall 		(void) chown(local, pw->pw_uid, -1);
36110275Ssam 	(*closefunc)(fout);
36210275Ssam }
36310275Ssam 
36410275Ssam FILE *
36510275Ssam getdatasock(mode)
36610275Ssam 	char *mode;
36710275Ssam {
36817157Ssam 	int s, on = 1;
36910275Ssam 
37010275Ssam 	if (data >= 0)
37110275Ssam 		return (fdopen(data, mode));
37213247Ssam 	s = socket(AF_INET, SOCK_STREAM, 0);
37310602Ssam 	if (s < 0)
37410275Ssam 		return (NULL);
37510275Ssam 	seteuid(0);
37626493Sminshall 	if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof (on)) < 0)
37710602Ssam 		goto bad;
37813152Ssam 	/* anchor socket to avoid multi-homing problems */
37913152Ssam 	data_source.sin_family = AF_INET;
38013152Ssam 	data_source.sin_addr = ctrl_addr.sin_addr;
38126493Sminshall 	if (bind(s, &data_source, sizeof (data_source)) < 0)
38210602Ssam 		goto bad;
38310311Ssam 	seteuid(pw->pw_uid);
38410275Ssam 	return (fdopen(s, mode));
38510602Ssam bad:
38610602Ssam 	seteuid(pw->pw_uid);
38726493Sminshall 	(void) close(s);
38810602Ssam 	return (NULL);
38910275Ssam }
39010275Ssam 
39110275Ssam FILE *
39210275Ssam dataconn(name, size, mode)
39310275Ssam 	char *name;
39411653Ssam 	off_t size;
39510275Ssam 	char *mode;
39610275Ssam {
39710275Ssam 	char sizebuf[32];
39810275Ssam 	FILE *file;
39911653Ssam 	int retry = 0;
40010275Ssam 
40110275Ssam 	if (size >= 0)
40226493Sminshall 		(void) sprintf (sizebuf, " (%ld bytes)", size);
40310275Ssam 	else
40410275Ssam 		(void) strcpy(sizebuf, "");
40526044Sminshall 	if (pdata > 0) {
40626044Sminshall 		struct sockaddr_in from;
40726044Sminshall 		int s, fromlen = sizeof(from);
40826044Sminshall 
40926493Sminshall 		s = accept(pdata, &from, &fromlen);
41026044Sminshall 		if (s < 0) {
41126044Sminshall 			reply(425, "Can't open data connection.");
41226044Sminshall 			(void) close(pdata);
41326044Sminshall 			pdata = -1;
41426044Sminshall 			return(NULL);
41526044Sminshall 		}
41626044Sminshall 		(void) close(pdata);
41726044Sminshall 		pdata = s;
41826044Sminshall 		reply(150, "Openning data connection for %s (%s,%d)%s.",
41926493Sminshall 		     name, inet_ntoa(from.sin_addr),
42026044Sminshall 		     ntohs(from.sin_port), sizebuf);
42126044Sminshall 		return(fdopen(pdata, mode));
42226044Sminshall 	}
42310275Ssam 	if (data >= 0) {
42410275Ssam 		reply(125, "Using existing data connection for %s%s.",
42510275Ssam 		    name, sizebuf);
42610321Ssam 		usedefault = 1;
42710275Ssam 		return (fdopen(data, mode));
42810275Ssam 	}
42910566Ssam 	if (usedefault)
43010422Ssam 		data_dest = his_addr;
43110422Ssam 	usedefault = 1;
43210275Ssam 	file = getdatasock(mode);
43310275Ssam 	if (file == NULL) {
43410275Ssam 		reply(425, "Can't create data socket (%s,%d): %s.",
43513247Ssam 		    inet_ntoa(data_source.sin_addr),
43610275Ssam 		    ntohs(data_source.sin_port),
43710275Ssam 		    sys_errlist[errno]);
43810275Ssam 		return (NULL);
43910275Ssam 	}
44010602Ssam 	reply(150, "Opening data connection for %s (%s,%d)%s.",
44126493Sminshall 	    name, inet_ntoa(data_dest.sin_addr),
44210602Ssam 	    ntohs(data_dest.sin_port), sizebuf);
44310275Ssam 	data = fileno(file);
44426044Sminshall 	while (connect(data, &data_dest, sizeof (data_dest)) < 0) {
44511653Ssam 		if (errno == EADDRINUSE && retry < swaitmax) {
44626493Sminshall 			sleep((unsigned) swaitint);
44711653Ssam 			retry += swaitint;
44811653Ssam 			continue;
44911653Ssam 		}
45010275Ssam 		reply(425, "Can't build data connection: %s.",
45110275Ssam 		    sys_errlist[errno]);
45210275Ssam 		(void) fclose(file);
45310275Ssam 		data = -1;
45410275Ssam 		return (NULL);
45510275Ssam 	}
45610275Ssam 	return (file);
45710275Ssam }
45810275Ssam 
45910275Ssam /*
46010275Ssam  * Tranfer the contents of "instr" to
46110275Ssam  * "outstr" peer using the appropriate
46210275Ssam  * encapulation of the date subject
46310275Ssam  * to Mode, Structure, and Type.
46410275Ssam  *
46510275Ssam  * NB: Form isn't handled.
46610275Ssam  */
46710275Ssam send_data(instr, outstr)
46810275Ssam 	FILE *instr, *outstr;
46910275Ssam {
47010275Ssam 	register int c;
47110275Ssam 	int netfd, filefd, cnt;
47210275Ssam 	char buf[BUFSIZ];
47310275Ssam 
47426044Sminshall 	transflag++;
47526044Sminshall 	if (setjmp(urgcatch)) {
47626044Sminshall 		transflag = 0;
47726044Sminshall 		return(-1);
47826044Sminshall 	}
47910275Ssam 	switch (type) {
48010275Ssam 
48110275Ssam 	case TYPE_A:
48210275Ssam 		while ((c = getc(instr)) != EOF) {
48311220Ssam 			if (c == '\n') {
48426044Sminshall 				if (ferror (outstr)) {
48526044Sminshall 					transflag = 0;
48611220Ssam 					return (1);
48726044Sminshall 				}
48810275Ssam 				putc('\r', outstr);
48911220Ssam 			}
49011220Ssam 			putc(c, outstr);
49126044Sminshall 		/*	if (c == '\r')			*/
49226044Sminshall 		/*		putc ('\0', outstr);	*/
49310275Ssam 		}
49426044Sminshall 		transflag = 0;
49526044Sminshall 		if (ferror (instr) || ferror (outstr)) {
49611220Ssam 			return (1);
49726044Sminshall 		}
49810275Ssam 		return (0);
49910275Ssam 
50010275Ssam 	case TYPE_I:
50110275Ssam 	case TYPE_L:
50210275Ssam 		netfd = fileno(outstr);
50310275Ssam 		filefd = fileno(instr);
50410275Ssam 
50526044Sminshall 		while ((cnt = read(filefd, buf, sizeof (buf))) > 0) {
50626044Sminshall 			if (write(netfd, buf, cnt) < 0) {
50726044Sminshall 				transflag = 0;
50810275Ssam 				return (1);
50926044Sminshall 			}
51026044Sminshall 		}
51126044Sminshall 		transflag = 0;
51210275Ssam 		return (cnt < 0);
51310275Ssam 	}
514*27106Smckusick 	reply(550, "Unimplemented TYPE %d in send_data", type);
51526044Sminshall 	transflag = 0;
516*27106Smckusick 	return (-1);
51710275Ssam }
51810275Ssam 
51910275Ssam /*
52010275Ssam  * Transfer data from peer to
52110275Ssam  * "outstr" using the appropriate
52210275Ssam  * encapulation of the data subject
52310275Ssam  * to Mode, Structure, and Type.
52410275Ssam  *
52510275Ssam  * N.B.: Form isn't handled.
52610275Ssam  */
52710275Ssam receive_data(instr, outstr)
52810275Ssam 	FILE *instr, *outstr;
52910275Ssam {
53010275Ssam 	register int c;
53111220Ssam 	int cnt;
53210275Ssam 	char buf[BUFSIZ];
53310275Ssam 
53410275Ssam 
53526044Sminshall 	transflag++;
53626044Sminshall 	if (setjmp(urgcatch)) {
53726044Sminshall 		transflag = 0;
53826044Sminshall 		return(-1);
53926044Sminshall 	}
54010275Ssam 	switch (type) {
54110275Ssam 
54210275Ssam 	case TYPE_I:
54310275Ssam 	case TYPE_L:
54426044Sminshall 		while ((cnt = read(fileno(instr), buf, sizeof buf)) > 0) {
54526044Sminshall 			if (write(fileno(outstr), buf, cnt) < 0) {
54626044Sminshall 				transflag = 0;
54710275Ssam 				return (1);
54826044Sminshall 			}
54926044Sminshall 		}
55026044Sminshall 		transflag = 0;
55110275Ssam 		return (cnt < 0);
55210275Ssam 
55310275Ssam 	case TYPE_E:
554*27106Smckusick 		reply(553, "TYPE E not implemented.");
55526044Sminshall 		transflag = 0;
556*27106Smckusick 		return (-1);
55710275Ssam 
55810275Ssam 	case TYPE_A:
55910275Ssam 		while ((c = getc(instr)) != EOF) {
56010275Ssam 			if (c == '\r') {
56126044Sminshall 				if (ferror (outstr)) {
56226044Sminshall 					transflag = 0;
56311220Ssam 					return (1);
56426044Sminshall 				}
56511220Ssam 				if ((c = getc(instr)) != '\n')
56611220Ssam 					putc ('\r', outstr);
56726044Sminshall 			/*	if (c == '\0')			*/
56826044Sminshall 			/*		continue;		*/
56910275Ssam 			}
57011220Ssam 			putc (c, outstr);
57110275Ssam 		}
57226044Sminshall 		transflag = 0;
57311220Ssam 		if (ferror (instr) || ferror (outstr))
57411220Ssam 			return (1);
57510275Ssam 		return (0);
57610275Ssam 	}
57726044Sminshall 	transflag = 0;
57810275Ssam 	fatal("Unknown type in receive_data.");
57910275Ssam 	/*NOTREACHED*/
58010275Ssam }
58110275Ssam 
58210275Ssam fatal(s)
58310275Ssam 	char *s;
58410275Ssam {
58510275Ssam 	reply(451, "Error in server: %s\n", s);
58610275Ssam 	reply(221, "Closing connection due to server error.");
58713247Ssam 	dologout(0);
58810275Ssam }
58910275Ssam 
59026493Sminshall /*VARARGS2*/
59110275Ssam reply(n, s, args)
59210275Ssam 	int n;
59310275Ssam 	char *s;
59410275Ssam {
59510275Ssam 
59610275Ssam 	printf("%d ", n);
59710275Ssam 	_doprnt(s, &args, stdout);
59810275Ssam 	printf("\r\n");
59926493Sminshall 	(void) fflush(stdout);
60010275Ssam 	if (debug) {
60126493Sminshall 		syslog(LOG_DEBUG, "<--- %d ", n);
60226493Sminshall 		syslog(LOG_DEBUG, s, &args);
60310275Ssam 	}
60410275Ssam }
60510275Ssam 
60626493Sminshall /*VARARGS2*/
60710275Ssam lreply(n, s, args)
60810275Ssam 	int n;
60910275Ssam 	char *s;
61010275Ssam {
61110275Ssam 	printf("%d-", n);
61210275Ssam 	_doprnt(s, &args, stdout);
61310275Ssam 	printf("\r\n");
61426493Sminshall 	(void) fflush(stdout);
61510275Ssam 	if (debug) {
61626493Sminshall 		syslog(LOG_DEBUG, "<--- %d- ", n);
61726493Sminshall 		syslog(LOG_DEBUG, s, &args);
61810275Ssam 	}
61910275Ssam }
62010275Ssam 
62110275Ssam ack(s)
62210275Ssam 	char *s;
62310275Ssam {
624*27106Smckusick 	reply(250, "%s command successful.", s);
62510275Ssam }
62610275Ssam 
62710275Ssam nack(s)
62810275Ssam 	char *s;
62910275Ssam {
63010275Ssam 	reply(502, "%s command not implemented.", s);
63110275Ssam }
63210275Ssam 
63326493Sminshall yyerror(s)
63426493Sminshall 	char *s;
63510275Ssam {
63626044Sminshall 	char *cp;
63726044Sminshall 
63826044Sminshall 	cp = index(cbuf,'\n');
63926044Sminshall 	*cp = '\0';
64026044Sminshall 	reply(500, "'%s': command not understood.",cbuf);
64110275Ssam }
64210275Ssam 
64310275Ssam delete(name)
64410275Ssam 	char *name;
64510275Ssam {
64610275Ssam 	struct stat st;
64710275Ssam 
64810275Ssam 	if (stat(name, &st) < 0) {
64910275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
65010275Ssam 		return;
65110275Ssam 	}
65210275Ssam 	if ((st.st_mode&S_IFMT) == S_IFDIR) {
65310275Ssam 		if (rmdir(name) < 0) {
65410275Ssam 			reply(550, "%s: %s.", name, sys_errlist[errno]);
65510275Ssam 			return;
65610275Ssam 		}
65710275Ssam 		goto done;
65810275Ssam 	}
65910275Ssam 	if (unlink(name) < 0) {
66010275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
66110275Ssam 		return;
66210275Ssam 	}
66310275Ssam done:
66410275Ssam 	ack("DELE");
66510275Ssam }
66610275Ssam 
66710275Ssam cwd(path)
66810275Ssam 	char *path;
66910275Ssam {
67010275Ssam 
67110275Ssam 	if (chdir(path) < 0) {
67210275Ssam 		reply(550, "%s: %s.", path, sys_errlist[errno]);
67310275Ssam 		return;
67410275Ssam 	}
67510275Ssam 	ack("CWD");
67610275Ssam }
67710275Ssam 
67810303Ssam makedir(name)
67910275Ssam 	char *name;
68010275Ssam {
68110303Ssam 	struct stat st;
68210303Ssam 	int dochown = stat(name, &st) < 0;
68310275Ssam 
68410275Ssam 	if (mkdir(name, 0777) < 0) {
68510275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
68610275Ssam 		return;
68710275Ssam 	}
68810303Ssam 	if (dochown)
68910303Ssam 		(void) chown(name, pw->pw_uid, -1);
690*27106Smckusick 	reply(257, "MKD command successful.");
69110275Ssam }
69210275Ssam 
69310303Ssam removedir(name)
69410275Ssam 	char *name;
69510275Ssam {
69610275Ssam 
69710275Ssam 	if (rmdir(name) < 0) {
69810275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
69910275Ssam 		return;
70010275Ssam 	}
701*27106Smckusick 	ack("RMD");
70210275Ssam }
70310275Ssam 
70410303Ssam pwd()
70510275Ssam {
70610303Ssam 	char path[MAXPATHLEN + 1];
70710275Ssam 
70810275Ssam 	if (getwd(path) == NULL) {
709*27106Smckusick 		reply(550, "%s.", path);
71010275Ssam 		return;
71110275Ssam 	}
712*27106Smckusick 	reply(257, "\"%s\" is current directory.", path);
71310275Ssam }
71410275Ssam 
71510275Ssam char *
71610275Ssam renamefrom(name)
71710275Ssam 	char *name;
71810275Ssam {
71910275Ssam 	struct stat st;
72010275Ssam 
72110275Ssam 	if (stat(name, &st) < 0) {
72210275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
72310275Ssam 		return ((char *)0);
72410275Ssam 	}
72510303Ssam 	reply(350, "File exists, ready for destination name");
72610275Ssam 	return (name);
72710275Ssam }
72810275Ssam 
72910275Ssam renamecmd(from, to)
73010275Ssam 	char *from, *to;
73110275Ssam {
73210275Ssam 
73310275Ssam 	if (rename(from, to) < 0) {
73410275Ssam 		reply(550, "rename: %s.", sys_errlist[errno]);
73510275Ssam 		return;
73610275Ssam 	}
73710275Ssam 	ack("RNTO");
73810275Ssam }
73910275Ssam 
74010275Ssam dolog(sin)
74110275Ssam 	struct sockaddr_in *sin;
74210275Ssam {
74310275Ssam 	struct hostent *hp = gethostbyaddr(&sin->sin_addr,
74410275Ssam 		sizeof (struct in_addr), AF_INET);
74510275Ssam 	time_t t;
74626493Sminshall 	extern char *ctime();
74710275Ssam 
74813247Ssam 	if (hp) {
74926493Sminshall 		(void) strncpy(remotehost, hp->h_name, sizeof (remotehost));
75013247Ssam 		endhostent();
75113247Ssam 	} else
75226493Sminshall 		(void) strncpy(remotehost, inet_ntoa(sin->sin_addr),
75313247Ssam 		    sizeof (remotehost));
75413247Ssam 	if (!logging)
75513247Ssam 		return;
75626493Sminshall 	t = time((time_t *) 0);
75726493Sminshall 	syslog(LOG_INFO,"FTPD: connection from %s at %s", remotehost, ctime(&t));
75810275Ssam }
75910695Ssam 
76013247Ssam #include <utmp.h>
76113247Ssam 
76226493Sminshall #define	SCPYN(a, b)	(void) strncpy(a, b, sizeof (a))
76313247Ssam struct	utmp utmp;
76413247Ssam 
76510695Ssam /*
76613247Ssam  * Record login in wtmp file.
76713247Ssam  */
76813247Ssam dologin(pw)
76913247Ssam 	struct passwd *pw;
77013247Ssam {
77113247Ssam 	char line[32];
77213247Ssam 
77313247Ssam 	if (wtmp >= 0) {
77413247Ssam 		/* hack, but must be unique and no tty line */
77526493Sminshall 		(void) sprintf(line, "ftp%d", getpid());
77613247Ssam 		SCPYN(utmp.ut_line, line);
77713247Ssam 		SCPYN(utmp.ut_name, pw->pw_name);
77813247Ssam 		SCPYN(utmp.ut_host, remotehost);
77926493Sminshall 		utmp.ut_time = (long) time((time_t *) 0);
78013247Ssam 		(void) write(wtmp, (char *)&utmp, sizeof (utmp));
78116760Slepreau 		if (!guest) {		/* anon must hang on */
78216760Slepreau 			(void) close(wtmp);
78316760Slepreau 			wtmp = -1;
78416760Slepreau 		}
78513247Ssam 	}
78613247Ssam }
78713247Ssam 
78813247Ssam /*
78913247Ssam  * Record logout in wtmp file
79013247Ssam  * and exit with supplied status.
79113247Ssam  */
79213247Ssam dologout(status)
79313247Ssam 	int status;
79413247Ssam {
79516339Skarels 
79617580Ssam 	if (logged_in) {
79717580Ssam 		(void) seteuid(0);
79817580Ssam 		if (wtmp < 0)
79917580Ssam 			wtmp = open("/usr/adm/wtmp", O_WRONLY|O_APPEND);
80017580Ssam 		if (wtmp >= 0) {
80117580Ssam 			SCPYN(utmp.ut_name, "");
80217580Ssam 			SCPYN(utmp.ut_host, "");
80326493Sminshall 			utmp.ut_time = (long) time((time_t *) 0);
80417580Ssam 			(void) write(wtmp, (char *)&utmp, sizeof (utmp));
80517580Ssam 			(void) close(wtmp);
80617580Ssam 		}
80713247Ssam 	}
80814436Ssam 	/* beware of flushing buffers after a SIGPIPE */
80914436Ssam 	_exit(status);
81013247Ssam }
81113247Ssam 
81213247Ssam /*
81310695Ssam  * Special version of popen which avoids
81410695Ssam  * call to shell.  This insures noone may
81510695Ssam  * create a pipe to a hidden program as a side
81610695Ssam  * effect of a list or dir command.
81710695Ssam  */
81810695Ssam #define	tst(a,b)	(*mode == 'r'? (b) : (a))
81910695Ssam #define	RDR	0
82010695Ssam #define	WTR	1
82110695Ssam static	int popen_pid[5];
82210695Ssam 
82310695Ssam static char *
82410695Ssam nextarg(cpp)
82510695Ssam 	char *cpp;
82610695Ssam {
82710695Ssam 	register char *cp = cpp;
82810695Ssam 
82910695Ssam 	if (cp == 0)
83010695Ssam 		return (cp);
83110695Ssam 	while (*cp && *cp != ' ' && *cp != '\t')
83210695Ssam 		cp++;
83310695Ssam 	if (*cp == ' ' || *cp == '\t') {
83410695Ssam 		*cp++ = '\0';
83510695Ssam 		while (*cp == ' ' || *cp == '\t')
83610695Ssam 			cp++;
83710695Ssam 	}
83810695Ssam 	if (cp == cpp)
83910695Ssam 		return ((char *)0);
84010695Ssam 	return (cp);
84110695Ssam }
84210695Ssam 
84310695Ssam FILE *
84410695Ssam popen(cmd, mode)
84510695Ssam 	char *cmd, *mode;
84610695Ssam {
84713211Sroot 	int p[2], ac, gac;
84810695Ssam 	register myside, hisside, pid;
84913211Sroot 	char *av[20], *gav[512];
85010695Ssam 	register char *cp;
85110695Ssam 
85210695Ssam 	if (pipe(p) < 0)
85310695Ssam 		return (NULL);
85410695Ssam 	cp = cmd, ac = 0;
85513211Sroot 	/* break up string into pieces */
85610695Ssam 	do {
85710695Ssam 		av[ac++] = cp;
85810695Ssam 		cp = nextarg(cp);
85913211Sroot 	} while (cp && *cp && ac < 20);
86010695Ssam 	av[ac] = (char *)0;
86113211Sroot 	gav[0] = av[0];
86213211Sroot 	/* glob each piece */
86313211Sroot 	for (gac = ac = 1; av[ac] != NULL; ac++) {
86413211Sroot 		char **pop;
86522024Ssam 		extern char **glob(), **copyblk();
86613211Sroot 
86713211Sroot 		pop = glob(av[ac]);
86822024Ssam 		if (pop == (char **)NULL) {	/* globbing failed */
86922024Ssam 			char *vv[2];
87022024Ssam 
87122024Ssam 			vv[0] = av[ac];
87222024Ssam 			vv[1] = 0;
87322024Ssam 			pop = copyblk(vv);
87413211Sroot 		}
87522024Ssam 		av[ac] = (char *)pop;		/* save to free later */
87622024Ssam 		while (*pop && gac < 512)
87722024Ssam 			gav[gac++] = *pop++;
87811757Ssam 	}
87913211Sroot 	gav[gac] = (char *)0;
88010695Ssam 	myside = tst(p[WTR], p[RDR]);
88110695Ssam 	hisside = tst(p[RDR], p[WTR]);
88210695Ssam 	if ((pid = fork()) == 0) {
88310695Ssam 		/* myside and hisside reverse roles in child */
88426493Sminshall 		(void) close(myside);
88526493Sminshall 		(void) dup2(hisside, tst(0, 1));
88626493Sminshall 		(void) close(hisside);
88713211Sroot 		execv(gav[0], gav);
88810695Ssam 		_exit(1);
88910695Ssam 	}
89013211Sroot 	for (ac = 1; av[ac] != NULL; ac++)
89113211Sroot 		blkfree((char **)av[ac]);
89210695Ssam 	if (pid == -1)
89310695Ssam 		return (NULL);
89410695Ssam 	popen_pid[myside] = pid;
89526493Sminshall 	(void) close(hisside);
89610695Ssam 	return (fdopen(myside, mode));
89710695Ssam }
89810695Ssam 
89910695Ssam pclose(ptr)
90010695Ssam 	FILE *ptr;
90110695Ssam {
90210695Ssam 	register f, r, (*hstat)(), (*istat)(), (*qstat)();
90310695Ssam 	int status;
90410695Ssam 
90510695Ssam 	f = fileno(ptr);
90626493Sminshall 	(void) fclose(ptr);
90710695Ssam 	istat = signal(SIGINT, SIG_IGN);
90810695Ssam 	qstat = signal(SIGQUIT, SIG_IGN);
90910695Ssam 	hstat = signal(SIGHUP, SIG_IGN);
91010695Ssam 	while ((r = wait(&status)) != popen_pid[f] && r != -1)
91110695Ssam 		;
91210695Ssam 	if (r == -1)
91310695Ssam 		status = -1;
91426493Sminshall 	(void) signal(SIGINT, istat);
91526493Sminshall 	(void) signal(SIGQUIT, qstat);
91626493Sminshall 	(void) signal(SIGHUP, hstat);
91710695Ssam 	return (status);
91810695Ssam }
91910695Ssam 
92010695Ssam /*
92110695Ssam  * Check user requesting login priviledges.
92210695Ssam  * Disallow anyone mentioned in the file FTPUSERS
92310695Ssam  * to allow people such as uucp to be avoided.
92410695Ssam  */
92510695Ssam checkuser(name)
92610695Ssam 	register char *name;
92710695Ssam {
92810695Ssam 	char line[BUFSIZ], *index();
92910695Ssam 	FILE *fd;
93010695Ssam 	int found = 0;
93110695Ssam 
93210695Ssam 	fd = fopen(FTPUSERS, "r");
93310695Ssam 	if (fd == NULL)
93410695Ssam 		return (1);
93510695Ssam 	while (fgets(line, sizeof (line), fd) != NULL) {
93610695Ssam 		register char *cp = index(line, '\n');
93710695Ssam 
93810695Ssam 		if (cp)
93910695Ssam 			*cp = '\0';
94010695Ssam 		if (strcmp(line, name) == 0) {
94110695Ssam 			found++;
94210695Ssam 			break;
94310695Ssam 		}
94410695Ssam 	}
94526493Sminshall 	(void) fclose(fd);
94610695Ssam 	return (!found);
94710695Ssam }
94826044Sminshall 
94926044Sminshall myoob()
95026044Sminshall {
95126493Sminshall 	int aflag = 0, atmark;
95226227Ssam 	char c, *cp;
95326044Sminshall 
95426044Sminshall 	if (!transflag) {
95526044Sminshall 		for (;;) {
95626493Sminshall 			if (ioctl(fileno(stdin), (int) SIOCATMARK, (char *) &atmark) < 0) {
95726493Sminshall 				syslog(LOG_ERR, "ioctl: %m");
95826044Sminshall 				break;
95926044Sminshall 			}
96026227Ssam 			if (atmark)
96126044Sminshall 				break;
96226493Sminshall 			(void) read(fileno(stdin), &c, 1);
96326044Sminshall 		}
96426493Sminshall 		(void) recv(fileno(stdin), &c, 1, MSG_OOB);
96526493Sminshall 		(void) read(fileno(stdin), &c, 1);
96626044Sminshall 		return;
96726044Sminshall 	}
96826044Sminshall 	for (;;) {
96926493Sminshall 		if (ioctl(fileno(stdin), (int) SIOCATMARK, (char *) &atmark) < 0) {
97026493Sminshall 			syslog(LOG_ERR, "ioctl: %m");
97126044Sminshall 			break;
97226044Sminshall 		}
97326227Ssam 		if (atmark)
97426044Sminshall 			break;
97526493Sminshall 		(void) read(fileno(stdin), &c, 1);
97626227Ssam 		if (c == IAC || c == IP)
97726044Sminshall 			aflag++;
97826044Sminshall 	}
97926493Sminshall 	(void) recv(fileno(stdin), &c, 1, MSG_OOB);
98026227Ssam 	if (c == IAC)
98126044Sminshall 		aflag++;
98226493Sminshall 	(void) read(fileno(stdin), &c, 1);
98326227Ssam 	if (c == DM)
98426044Sminshall 		aflag++;
98526227Ssam 	if (aflag != 4)
98626044Sminshall 		return;
98726044Sminshall 	cp = tmpline;
98826044Sminshall 	(void) getline(cp, 7, stdin);
98926044Sminshall 	upper(cp);
99026227Ssam 	if (strcmp(cp, "ABOR\r\n"))
99126044Sminshall 		return;
99226044Sminshall 	tmpline[0] = '\0';
99326044Sminshall 	reply(426,"Transfer aborted. Data connection closed.");
99426044Sminshall 	reply(226,"Abort successful");
99526044Sminshall 	longjmp(urgcatch, 1);
99626044Sminshall }
99726044Sminshall 
998*27106Smckusick /*
999*27106Smckusick  * Note: The 530 reply codes could be 4xx codes, except nothing is
1000*27106Smckusick  * given in the state tables except 421 which implies an exit.  (RFC959)
1001*27106Smckusick  */
100226044Sminshall passive()
100326044Sminshall {
100426044Sminshall 	int len;
100526044Sminshall 	struct sockaddr_in tmp;
100626044Sminshall 	register char *p, *a;
100726044Sminshall 
100826044Sminshall 	pdata = socket(AF_INET, SOCK_STREAM, 0);
100926044Sminshall 	if (pdata < 0) {
1010*27106Smckusick 		reply(530, "Can't open passive connection");
101126044Sminshall 		return;
101226044Sminshall 	}
101326044Sminshall 	tmp = ctrl_addr;
101426044Sminshall 	tmp.sin_port = 0;
101526044Sminshall 	seteuid(0);
101626493Sminshall 	if (bind(pdata, (struct sockaddr *) &tmp, sizeof(tmp)) < 0) {
101726044Sminshall 		seteuid(pw->pw_uid);
101826044Sminshall 		(void) close(pdata);
101926044Sminshall 		pdata = -1;
1020*27106Smckusick 		reply(530, "Can't open passive connection");
102126044Sminshall 		return;
102226044Sminshall 	}
102326044Sminshall 	seteuid(pw->pw_uid);
102426044Sminshall 	len = sizeof(tmp);
102526044Sminshall 	if (getsockname(pdata, (char *) &tmp, &len) < 0) {
102626044Sminshall 		(void) close(pdata);
102726044Sminshall 		pdata = -1;
1028*27106Smckusick 		reply(530, "Can't open passive connection");
102926044Sminshall 		return;
103026044Sminshall 	}
103126044Sminshall 	if (listen(pdata, 1) < 0) {
103226044Sminshall 		(void) close(pdata);
103326044Sminshall 		pdata = -1;
1034*27106Smckusick 		reply(530, "Can't open passive connection");
103526044Sminshall 		return;
103626044Sminshall 	}
103726044Sminshall 	a = (char *) &tmp.sin_addr;
103826044Sminshall 	p = (char *) &tmp.sin_port;
103926044Sminshall 
104026044Sminshall #define UC(b) (((int) b) & 0xff)
104126044Sminshall 
104226044Sminshall 	reply(227, "Entering Passive Mode (%d,%d,%d,%d,%d,%d)", UC(a[0]),
104326044Sminshall 		UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1]));
104426044Sminshall }
104526044Sminshall 
104626044Sminshall char *
104726044Sminshall gunique(local)
104826044Sminshall 	char *local;
104926044Sminshall {
105026044Sminshall 	static char new[MAXPATHLEN];
105126044Sminshall 	char *cp = rindex(local, '/');
105226044Sminshall 	int d, count=0;
105326044Sminshall 	char ext = '1';
105426044Sminshall 
105526044Sminshall 	if (cp) {
105626044Sminshall 		*cp = '\0';
105726044Sminshall 	}
105826044Sminshall 	d = access(cp ? local : ".", 2);
105926044Sminshall 	if (cp) {
106026044Sminshall 		*cp = '/';
106126044Sminshall 	}
106226044Sminshall 	if (d < 0) {
106326493Sminshall 		syslog(LOG_ERR, "%s: %m", local);
106426044Sminshall 		return((char *) 0);
106526044Sminshall 	}
106626044Sminshall 	(void) strcpy(new, local);
106726044Sminshall 	cp = new + strlen(new);
106826044Sminshall 	*cp++ = '.';
106926044Sminshall 	while (!d) {
107026044Sminshall 		if (++count == 100) {
1071*27106Smckusick 			reply(452, "Unique file name not cannot be created.");
107226044Sminshall 			return((char *) 0);
107326044Sminshall 		}
107426044Sminshall 		*cp++ = ext;
107526044Sminshall 		*cp = '\0';
107626044Sminshall 		if (ext == '9') {
107726044Sminshall 			ext = '0';
107826044Sminshall 		}
107926044Sminshall 		else {
108026044Sminshall 			ext++;
108126044Sminshall 		}
108226044Sminshall 		if ((d = access(new, 0)) < 0) {
108326044Sminshall 			break;
108426044Sminshall 		}
108526044Sminshall 		if (ext != '0') {
108626044Sminshall 			cp--;
108726044Sminshall 		}
108826044Sminshall 		else if (*(cp - 2) == '.') {
108926044Sminshall 			*(cp - 1) = '1';
109026044Sminshall 		}
109126044Sminshall 		else {
109226044Sminshall 			*(cp - 2) = *(cp - 2) + 1;
109326044Sminshall 			cp--;
109426044Sminshall 		}
109526044Sminshall 	}
109626044Sminshall 	return(new);
109726044Sminshall }
1098