/*

    File: ftpproxy/ftp.c

    Copyright (C) 1999, 2000  Wolfgang Zekoll  <wzk@quietsche-entchen.de>
    Copyright (C) 2000, 2003  Andreas Schoenberg  <asg@ftpproxy.org>
  
    This software is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.
  
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
  
    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

 */


#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdarg.h>

#include <time.h>
#include <signal.h>
#include <sys/wait.h>
#include <ctype.h>
#include <errno.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <syslog.h>
#include <sys/time.h>

#include "ftp.h"
#include "ip-lib.h"
#include "lib.h"


typedef struct _ftpcmd {
    char	name[20];
    int		par, ispath, useccp;
    int		resp;
    int		log;
    } ftpcmd_t;

ftpcmd_t cmdtab[] = {

	/*
	 * Einfache FTP Kommandos.
	 */

    { "ABOR", 0, 0, 0,	225, 1 },		/* oder 226 */
    { "ACCT", 1, 0, 0,	230, 0 },
    { "CDUP", 1, 1, 1,	200, 1 },
    { "CWD",  1, 1, 1,	250, 1 },
    { "DELE", 1, 1, 1,	250, 1 },
    { "NOOP", 0, 0, 0,	200, 0 },
    { "MDTM", 1, 1, 1,	257, 1 },
    { "MKD",  1, 1, 1,	257, 1 },
    { "MODE", 1, 0, 0,	200, 0 },
    { "PWD",  0, 0, 0,	257, 0 },
    { "QUIT", 0, 0, 0,	221, 0 },
    { "REIN", 0, 0, 0,	0, /* 220, */ 0 },	/* wird nicht unterstuetzt */
    { "REST", 1, 0, 0,	350, 0 },
    { "RNFR", 1, 1, 1,	350, 1 },
    { "RNTO", 1, 1, 1,	250, 1 },
    { "RMD",  1, 1, 1,	250, 1 },
    { "SITE", 1, 0, 1,	200, 0 },
    { "SIZE", 1, 1, 1,	213, 1 },
    { "SMNT", 1, 0, 0,	250, 0 },
    { "STAT", 1, 1, 1,	211, 0 },			/* oder 212, 213 */
    { "STRU", 1, 0, 0,	0, /* 200, */ 0 },	/* wird nicht unterstuetzt */
    { "SYST", 0, 0, 0,	215, 0 },
    { "TYPE", 1, 0, 0,	200, 0 },
    { "XCUP", 1, 1, 1,	200, 1 },
    { "XCWD", 1, 1, 1,	250, 1 },
    { "XMKD", 1, 1, 1,	257, 1 },
    { "XPWD", 0, 0, 0,	257, 0 },
    { "XRMD", 1, 1, 1,	250, 1 },

	/*
	 * Nur der Vollstaendigkeit halber: FTP Kommandos die gesondert
	 * behandelt werden.
	 */

    { "LIST", 1, 1, 1,	0, 0 },
    { "NLST", 1, 1, 1,	0, 0 },
    { "PORT", 1, 0, 0,	0, /* 200, */ 0 },
    { "PASV", 0, 0, 0,	0, /* 200, */ 0 },
    { "ALLO", 1, 0, 0,	0, /* 200, */ 0 },
    { "RETR", 1, 1, 1,	0, 0 },
    { "STOR", 1, 1, 1,	0, 0 },
    { "STOU", 0, 0, 1,	0, 0 },
    { "APPE", 1, 1, 1,	0, 0 },
    { "HELP", 0, 0, 0,	0, 0 },
    { "FEAT", 0, 0, 0,	0, 0 },
    { "",     0, 0, 0,	0, 0 }
    };


unsigned get_interface_info(int pfd, char *ip, int max)
{
	int	size;
	unsigned int port;
	struct sockaddr_in saddr;

	size = sizeof(saddr);
	if (getsockname(pfd, (struct sockaddr *) &saddr, &size) < 0) {
		syslog(LOG_NOTICE, "-ERR: can't get interface info: %s", strerror(errno));
		exit (-1);
		}

	copy_string(ip, (char *) inet_ntoa(saddr.sin_addr), max);
	port = ntohs(saddr.sin_port);

	return (port);
}

int get_client_info(ftp_t *x, int pfd)
{
	int	size;
	struct sockaddr_in saddr;
	struct in_addr *addr;
	struct hostent *hostp = NULL;

	*x->client = 0;
	size = sizeof(saddr);
	if (getpeername(pfd, (struct sockaddr *) &saddr, &size) < 0 )
		return (-1);
		
	copy_string(x->client_ip, (char *) inet_ntoa(saddr.sin_addr), sizeof(x->client_ip));

	if (x->config->numeric_only == 1)
		copy_string(x->client, x->client_ip, sizeof(x->client));
	else {
		addr = &saddr.sin_addr,
		hostp = gethostbyaddr((char *) addr,
				sizeof (saddr.sin_addr.s_addr), AF_INET);

		copy_string(x->client, hostp == NULL? x->client_ip: hostp->h_name, sizeof(x->client));
		}

	strlwr(x->client);

	return (0);
}


	/*
	 * Basic I/O functions
	 */

int close_ch(ftp_t *x, dtc_t *ch)
{
	if (ch->isock >= 0)
		close(ch->isock);

	if (ch->osock >= 0)
		close (ch->osock);

	ch->isock     = -1;
	ch->osock     = -1;
	ch->state     = 0;
	ch->operation = 0;
	ch->seen150   = 0;

	return (0);
}

int getc_fd(ftp_t *x, int fd)
{
	int	c;
	bio_t	*bio;

	if (fd == 0)
		bio = &x->cbuf;
	else if (fd == x->fd.server)
		bio = &x->sbuf;
	else {
		syslog(LOG_NOTICE, "-ERR: internal bio/fd error");
		exit (1);
		}

	if (bio->here >= bio->len) {
		int	rc, max, bytes, earlyreported;
		struct timeval tov;
		fd_set	available, fdset;

		bio->len = bio->here = 0;
		earlyreported = 0;

		FD_ZERO(&fdset);
		FD_SET(fd, &fdset);
/*		x->fd.max = fd; */
		max = fd;

		if (x->ch.operation == 0)
			/* nichts */ ;
		else if (x->ch.state == PORT_LISTEN) {
			if (x->ch.mode == MODE_PORT) {
				FD_SET(x->ch.osock, &fdset);
				if (x->ch.osock > max)
					max = x->ch.osock;

				x->ch.active = x->ch.osock;
				}
			else if (x->ch.mode == MODE_PASSIVE) {
				FD_SET(x->ch.isock, &fdset);
				if (x->ch.isock > max)
					max = x->ch.isock;

				x->ch.active = x->ch.isock;
				}
			else {
				syslog(LOG_NOTICE, "-ERR: internal mode error");
				exit (-1);
				}
			}
		else if (x->ch.state == PORT_CONNECTED  &&  x->ch.seen150 == 1) {
			FD_SET(x->ch.active, &fdset);
			if (x->ch.active > max)
				max = x->ch.active;
			}
			
		bytes = 0;
		while (1) {
/*			memmove(&available, &fdset, sizeof(fd_set)); */
			available = fdset;
			tov.tv_sec  = x->config->timeout;
			tov.tv_usec = 0;

			if (debug >= 2)
				fprintf (stderr, "select max= %d\n", max);

			rc = select(max + 1, &available, (fd_set *) NULL, (fd_set *) NULL, &tov);
			if (rc < 0) {
				syslog(LOG_NOTICE, "select() error: %s\n", strerror(errno));
				break;
				}
			else if (rc == 0) {
				syslog(LOG_NOTICE, "connection timed out: client= %s, server= %s:%u",
					x->client, x->server.name, x->server.port);
				return (-1);
				}

			if (FD_ISSET(fd, &available)) {
				if ((bytes = read(fd, bio->buffer, sizeof(bio->buffer) - 2)) <= 0) {
					if (debug != 0) {
						if (bytes == 0)
							fprintf (stderr, "received zero bytes on fd %d\n", fd);
						else
							fprintf (stderr, "received %d bytes on fd %d, errno= %d, error= %s\n", bytes, fd, errno, strerror(errno));
						}

					return (-1);
					}

				break;
				}
			else if (FD_ISSET(x->ch.active, &available)) {
				if (x->ch.state == PORT_LISTEN) {
					int	sock, adrlen;
					struct sockaddr_in adr;

					earlyreported = 0;
					adrlen = sizeof(struct sockaddr);
					sock = accept(x->ch.active, (struct sockaddr *) &adr, &adrlen);
					if (debug != 0)
						fprintf (stderr, "accept() on socket\n");

					if (sock < 0) {
						syslog(LOG_NOTICE, "-ERR: accept error: %s", strerror(errno));
						exit (1);
						}
					else {
						char	remote[80];

						copy_string(remote, inet_ntoa(adr.sin_addr), sizeof(remote));
						if (debug)
							fprintf (stderr, "connection from %s\n", remote);

						/*
						 * Gegenstelle ueberpruefen.
						 */

						if (x->ch.mode == MODE_PORT) {
							if (strcmp(x->server.ipnum, remote) != 0) {
								if (x->config->allow_anyremote != 0)
									/* configuration tells us not to care -- 31JAN02asg */ ;
								else {
									syslog(LOG_NOTICE, "-ERR: unexpected connect: %s, expected= %s", remote, x->server.ipnum);
									exit (1);
									}
								}
							}
						else {
							if (strcmp(x->client_ip, remote) != 0) {
								if (x->config->allow_anyremote != 0)
									/* ok -- 31JAN02asg */ ;
								else {
									syslog(LOG_NOTICE, "-ERR: unexpected connect: %s, expected= %s", remote, x->client_ip);
									exit (1);
									}
								}
							}
						}

					/*
					 * Datenkanal zur anderen Seite aufbauen.
					 */

					if (x->ch.mode == MODE_PORT) {
						dup2(sock, x->ch.osock);
						close (sock);
						x->ch.state = PORT_CONNECTED;
						if (debug)
							fprintf (stderr, "osock= %d\n", x->ch.osock);

						if ((x->ch.isock = openip(x->ch.client.ipnum, x->ch.client.port, x->interface, x->config->dataport)) < 0) {
							syslog(LOG_NOTICE, "-ERR: can't connect to client: %s", strerror(errno));
							exit (1);
							}

						if (debug)
							fprintf (stderr, "isock= %d\n", x->ch.isock);
						}
					else if (x->ch.mode == MODE_PASSIVE) {
						dup2(sock, x->ch.isock);
						close (sock);
						x->ch.state = PORT_CONNECTED;
						if (debug)
							fprintf (stderr, "isock= %d\n", x->ch.isock);

						if ((x->ch.osock = openip(x->ch.server.ipnum, x->ch.server.port, x->config->sourceip, 0)) < 0) {
							syslog(LOG_NOTICE, "-ERR: can't connect to server: %s", strerror(errno));
							exit (1);
							}

						if (debug)
							fprintf (stderr, "osock= %d\n", x->ch.osock);
						}


					/*
					 * Setzen der Datenquelle (Server oder Client).
					 */

					if (x->ch.operation == OP_GET) {
						x->ch.active = x->ch.osock;
						x->ch.other  = x->ch.isock;
						}
					else if (x->ch.operation == OP_PUT) {
						x->ch.active = x->ch.isock;
						x->ch.other  = x->ch.osock;
						}
					else {
						syslog(LOG_NOTICE, "-ERR: transfer operation error");
						exit (1);
						}

					if (x->ch.seen150 == 0) {

						/*
						 * And finally ... another attempt to solve the short
						 * data transmission timing problem: If we didn't receive
						 * the 150 response yet from the server we deactivate the
						 * data channel until we have the 150 -- 030406asg
						 */

						if (debug >= 2)
							fprintf (stderr, "150 not seen, deactivating data channel\n");

						FD_ZERO(&fdset);
						FD_SET(fd, &fdset);
						max = fd;
						}
					else {
						if (debug >= 2)
							fprintf (stderr, "150 already seen, activating data channel\n");

						FD_ZERO(&fdset);
						FD_SET(fd, &fdset);
						FD_SET(x->ch.active, &fdset);
						max = (fd > x->ch.active)? fd: x->ch.active;
						}

					if (debug)
						fprintf (stderr, "active= %d, other= %d\n", x->ch.active, x->ch.other);

					x->ch.bytes = 0;
					x->ch.started = time(NULL);
					}
				else if (x->ch.state == PORT_CONNECTED) {
					int	wrote;
					char	buffer[FTPMAXBSIZE + 10];

					if (x->ch.operation == 0) {
						if (earlyreported == 0) {
							earlyreported = 1;
							syslog(LOG_NOTICE, "early write/read event, sleeping 2 seconds");
							sleep(2);
							continue;
							}
						}

					bytes = read(x->ch.active, buffer, x->config->bsize /* sizeof(buffer) */ );

					/*
					 * Handling servers that close the data connection -- 24APR02asg
					 */

					wrote = 0;
					if ((bytes > 0)  &&  ((wrote = write(x->ch.other, buffer, bytes)) == bytes))
						x->ch.bytes += bytes;
					else {
						if (wrote < 0)
							syslog(LOG_NOTICE, "error writing data channel, error= %s", strerror(errno));

						if (debug)
							fprintf (stderr, "closing data connection\n");

						close_ch(x, &x->ch);
						FD_ZERO(&fdset);
						FD_SET(fd, &fdset);
						max = fd;

						return (1);
						}
					}
				}
			}

		bio->len  = bytes;
		bio->here = 0;
		}

	if (bio->here >= bio->len)
		return (-1);

	c = (unsigned char) bio->buffer[bio->here++];
	return (c);
}

char *readline_fd(ftp_t *x, int fd, char *line, int size)
{
	int	c, k;

	*line = 0;
	size = size - 2;

	c = getc_fd(x, fd);
	if (c < 0)
		return (NULL);
	else if (c == 1) {
		strcpy(line, "\001");
		return (line);
		}

	k = 0;
	while (c > 0  &&  c != '\n'  &&  c != 0) {
		if (k < size)
			line[k++] = c;

		c = getc_fd(x, fd);
		}

	line[k] = 0;
	noctrl(line);

	k = 0;
	while ((c = (unsigned char ) line[k]) != 0  &&  c > 126)
		k++;

	if (k > 0)
		copy_string(line, &line[k], size);

	return (line);
}


char *cfgets(ftp_t *x, char *line, int size)
{
	char	*p;

	*line = 0;
	if ((p = readline_fd(x, 0, line, size)) == NULL)
		return (NULL);
	else if (debug != 0)
		fprintf (stderr, "CLI >>>: %s\n", p);

	return (line);
}

int cfputs(ftp_t *x, char *line)
{
	char	buffer[310];

	if (debug)
		fprintf (stderr, ">>> CLI: %s\n", line);

	snprintf (buffer, sizeof(buffer) - 2, "%s\r\n", line);
	write(1, buffer, strlen(buffer));

	return (0);
}


char *sfgets(ftp_t *x, char *line, int size)
{
	char *p;

	*line = 0;
	if ((p = readline_fd(x, x->fd.server, line, size)) == NULL)
		return (NULL);
	else if (debug != 0)
		fprintf (stderr, "SVR >>>: %s\n", p);

	return (line);
}

int sfputs(ftp_t *x, char *format, ...)
{
	int	len;
	char	buffer[310];
	va_list	ap;

	va_start(ap, format);
	vsnprintf (buffer, sizeof(buffer) - 10, format, ap);
	va_end(ap);

	if (debug)
		fprintf (stderr, ">>> SVR: %s\n", buffer);

	/*
	 * There are firewalls that don't like command to be split in
	 * two packets.  Notice: the `- 10' above is really important
	 * to protect the proxy against buffer overflows.
	 */

	strcat(buffer, "\r\n");
	len = strlen(buffer);

	/*
	 * SIGPIPE is catched but then ignored, we have to handle it
	 * one our own now -- 24APR02asg
	 */

	if (write(x->fd.server, buffer, len) != len) {
		syslog(LOG_NOTICE, "-ERR: error writing control connect, error= %s", strerror(errno));
		exit (1);
		}

/*
 *	write(x->fd.server, buffer, strlen(buffer));
 *	write(x->fd.server, "\r\n", 2);
 */
	return (0);
}

int sfputc(ftp_t *x, char *command, char *parameter, char *line, int size, char **here)
{
	int	rc;
	char	*p, buffer[300];

	if (command != NULL  &&  *command != 0) {
		if (parameter != NULL  &&  *parameter != 0)
			snprintf (buffer, sizeof(buffer) - 2, "%s %s", command, skip_ws(parameter));
		else
			copy_string(buffer, command, sizeof(buffer));

		sfputs(x, "%s", buffer);
		}
	
	if (sfgets(x, line, size) == NULL) {
		if (debug != 0)
			fprintf (stderr, "server disappered in sfputc(), pos #1\n");

		return (-1);
		}
	else if (strlen(line) < 3) {
		if (debug != 0)
			fprintf (stderr, "short server reply in sfputc()\n");

		return (-1);
		}

	rc = atoi(line);
	if (line[3] != ' '  &&  line[3] != 0) {
        	while (1) {
                	if (sfgets(x, line, size) == NULL) {
				syslog(LOG_NOTICE, "-ERR: lost server while reading client greeting: %s", x->server.name);
				exit (1);
				}

			if (strlen(line) < 3)
				/* line too short to be response's last line */ ;
			else if (line[3] != ' '  &&  line[3] != 0)
				/* neither white space nor EOL at position #4 */ ;
			else if (line[0] >= '0'  &&  line[0] <= '9'  &&  atoi(line) == rc)
                       		break;		/* status code followed by EOL or blank detected */
			}
        	}

	if (here != NULL) {
		p = skip_ws(&line[3]);
		*here = p;
		}

	return (rc);
}



int doquit(ftp_t *x)
{
	int	rc;
	char	resp[200];

	if ((rc = sfputc(x, "QUIT", "", resp, sizeof(resp), NULL)) != 221)
		syslog(LOG_NOTICE, "unexpected resonse to QUIT: %s", resp);

	cfputs(x, "221 goodbye");
	syslog(LOG_NOTICE, "%d QUIT", rc);
	
	return (0);
}


char *_getipnum(char *line, char **here, char *ip, int size)
{
	int	c, i, k;

	copy_string(ip, line, size);
	k = 0;
	for (i=0; (c = ip[i]) != 0; i++) {
		if (c == ',') {
			if (k < 3) {
				ip[i] = '.';
				k++;
				}
			else {
				ip[i++] = 0;
				break;
				}
			}
		}

	if (here != NULL)
		*here = &line[i];

	return (ip);
}

unsigned long _getport(char *line, char **here)
{
	unsigned long port;
	char	*p;

	p = line;
	port = strtoul(p, &p, 10);
	if (*p != ',')
		return (0);

	p++;
	port = (port << 8) + strtoul(p, &p, 10);
	if (here != NULL)
		*here = p;

	return (port);
}

int doport(ftp_t *x, char *command, char *par)
{
	int	c, rc;
	char	*p, line[200];
	dtc_t	*ch;

	ch = &x->ch;
	_getipnum(par, &p, ch->client.ipnum, sizeof(ch->client.ipnum));
	ch->client.port = _getport(p, &p);
	if (debug)
		fprintf (stderr, "client listens on %s:%u\n", ch->client.ipnum, ch->client.port);

	get_interface_info(x->fd.server, ch->outside.ipnum, sizeof(ch->outside.ipnum));
	ch->osock = bind_to_port(ch->outside.ipnum, 0);
	ch->outside.port = get_interface_info(ch->osock, line, sizeof(line));
	if (debug)
		fprintf (stderr, "listening on %s:%u\n", ch->outside.ipnum, ch->outside.port);

	copy_string(line, ch->outside.ipnum, sizeof(line));
	for (p=line; (c = *p) != 0; p++) {
		if (c == '.')
			*p = ',';
		}

	*p++ = ',';
	snprintf (p, 20, "%u,%u", ch->outside.port >> 8, ch->outside.port & 0xFF);


	/* Open port first */		
	ch->isock     = -1;
	ch->mode      = MODE_PORT;
	ch->state     = PORT_LISTEN;

	/* then send PORT cmd */
	rc = sfputc(x, "PORT", line, line, sizeof(line), &p);

	/* check return code */
	if (rc != 200){
		cfputs(x, "500 not accepted");
		close_ch(x, &x->ch);
		}
	else 
		cfputs(x, "200 ok, port allocated");



/*	if (rc != 200)
		cfputs(x, "500 not accepted");
	else {
		cfputs(x, "200 ok, port allocated");

		ch->isock  = -1;
		ch->mode   = MODE_PORT;
		ch->state  = PORT_LISTEN;
		}
*/

	*ch->command = 0;
	return (rc);
}

int dopasv(ftp_t *x, char *command, char *par)
{
	int	c, k, rc;
	char	*p, line[200];
	dtc_t	*ch;

	ch = &x->ch;
	rc = sfputc(x, "PASV", "", line, sizeof(line), &p);
	if (rc != 227) {
		cfputs(x, "500 not accepted");
		return (0);
		}


	/*
	 * Ende der Port-Koordinaten im Server-Response suchen.
	 */

	k = strlen(line);
	while (k > 0  &&  isdigit(line[k-1]) == 0)
		k--;

	if (isdigit(line[k-1])) {
		line[k--] = 0;
		while (k > 0  &&  (isdigit(line[k-1])  ||  line[k-1] == ','))
			k--;
		}

	/*
	 * line[k] sollte jetzt auf die erste Ziffer des PASV Response
	 * zeigen.
	 */

	if (isdigit(line[k]) == 0) {
		syslog(LOG_NOTICE, "can't locate passive response: %s", line);
		cfputs(x, "500 not accepted");
		return (0);
		}

	/*
	 * Auslesen der PASV IP-Nummer und des Ports.
	 */

	p = &line[k];
	_getipnum(p, &p, ch->server.ipnum, sizeof(ch->server.ipnum));
	ch->server.port = _getport(p, &p);
	if (debug)
		fprintf (stderr, "server listens on %s:%u\n", ch->server.ipnum, ch->server.port);

	get_interface_info(0, ch->inside.ipnum, sizeof(ch->inside.ipnum));
	ch->isock = bind_to_port(ch->inside.ipnum, 0);
	ch->inside.port = get_interface_info(ch->isock, line, sizeof(line));
	if (debug)
		fprintf (stderr, "listening on %s:%u\n", ch->inside.ipnum, ch->inside.port);

	snprintf (line, sizeof(line) - 2, "227 Entering Passive Mode (%s,%u,%u)",
			ch->inside.ipnum,
			ch->inside.port >> 8, ch->inside.port & 0xFF);
	for (p=line; (c = *p) != 0; p++) {
		if (c == '.')
			*p = ',';
		}

	cfputs(x, line);
	ch->osock = -1;
	ch->mode  = MODE_PASSIVE;
	ch->state = PORT_LISTEN;

	*ch->command = 0;
	ch->operation = 0;

	return (rc);
}


int dofeat(ftp_t *x)
{
	/*
	 * Not so easy because we have to align with the server response. 
	 */

	int	rc;
	char	*p, word[80], serverfeature[80], line[300];
	static char *proxyfeatlist = "SIZE:MDTM";

	sfputs(x, "%s", "FEAT");
	if (sfgets(x, line, sizeof(line)) == NULL) {
		syslog(LOG_NOTICE, "monitor: server not responding");
		exit (1);
		}

	rc = atoi(line);
	if (rc != 211) {
		/* kein FEAT Support */ ;
		cfputs(x, "502 command not implemented");
		return (1);
		}


	cfputs(x, "211-feature list follows");
	while (1) {
		if (sfgets(x, line, sizeof(line)) == NULL) {
			syslog(LOG_NOTICE, "lost server in FEAT response");
			exit (1);
			}
		else if (*line != ' ') { 

			/*
			 * RFC2389 specifies exactly one space in this
			 * multi-line response.  Nothing else.
			 */

			break;
			}


		/* Get feature from server response ...
		 */

		copy_string(serverfeature, line, sizeof(serverfeature));
		strupr(serverfeature);


		/* ... and compare it against our feature list
		 */


		p = proxyfeatlist;
		while (*get_quoted(&p, ':', word, sizeof(word)) != 0) {
			if (strcmp(word, serverfeature) == 0) {
				snprintf (line, sizeof(line) - 4, " %s", word);
				cfputs(x, line);
				break;
				}
			}
		}

	cfputs(x, "211 end");
	return (0);
}

int setvar(ftp_t *x, char *var, char *value)
{
	char	varname[200];

	#if defined SOLARIS
	snprintf (varname, sizeof(varname) - 2, "%s%s=%s", x->config->varname, var, value != NULL? value: "");
	putenv(varname);
	#else
	snprintf (varname, sizeof(varname) - 2, "%s%s", x->config->varname, var);
	setenv(varname, value != NULL? value: "", 1);
	#endif

	return (0);
}

int set_variables(ftp_t *x)
{
	char	val[200];

	setvar(x, "INTERFACE", x->interface);
	snprintf (val, sizeof(val) - 2, "%u", x->port);
	setvar(x, "PORT", val);

	setvar(x, "CLIENT", x->client_ip);
	setvar(x, "CLIENTNAME", x->client);

	setvar(x, "SERVER", x->server.ipnum);
	snprintf (val, sizeof(val) - 2, "%u", x->server.port);
	setvar(x, "SERVERPORT", val);

	setvar(x, "SERVERNAME", x->server.name);
	setvar(x, "SERVERLOGIN", x->username);
	setvar(x, "USERNAME", x->local.username);
	setvar(x, "PASSWD", x->local.password);

	return (0);
}

int run_acp(ftp_t *x)
{
	int	rc, pid, pfd[2];
	char	line[300];
	
	if (*x->config->acp == 0)
		return (0);

	rc = 0;
	if (pipe(pfd) != 0) {
		syslog(LOG_NOTICE, "-ERR: can't pipe: %s", strerror(errno));
		exit (1);
		}
	else if ((pid = fork()) < 0) {
		syslog(LOG_NOTICE, "-ERR: can't fork acp: %s", strerror(errno));
		exit (1);
		}
	else if (pid == 0) {
		int	argc;
		char	*argv[32];

		close(0);		/* Das acp kann nicht vom client lesen. */
		dup2(pfd[1], 2);	/* stderr wird vom parent gelesen. */
		close(pfd[0]);
		set_variables(x);
		
		copy_string(line, x->config->acp, sizeof(line));
		argc = split(line, argv, ' ', 30);
		argv[argc] = NULL;
		execvp(argv[0], argv);

		syslog(LOG_NOTICE, "-ERR: can't exec acp %s: %s", argv[0], strerror(errno));
		exit (1);
		}
	else {
		int	len;
		char	message[300];

		close(pfd[1]);
		*message = 0;
		if ((len = read(pfd[0], message, sizeof(message) - 2)) < 0)
			len = 0;

		message[len] = 0;
		noctrl(message);
		close(pfd[0]);

		if (waitpid(pid, &rc, 0) < 0) {
			syslog(LOG_NOTICE, "-ERR: error while waiting for acp: %s", strerror(errno));
			exit (1);
			}

		rc = WIFEXITED(rc) != 0? WEXITSTATUS(rc): 1;
		if (*message == 0)
			copy_string(message, rc == 0? "access granted": "access denied", sizeof(message));

		if (*message != 0)
			syslog(LOG_NOTICE, "%s (rc= %d)", message, rc);
		}
		
	return (rc);
}

static char *getvarname(char **here, char *var, int size)
{
	int	c, k;

	size = size - 2;
	k = 0;
	while ((c = **here) != 0) {
		*here += 1;
		if (c == ' '  ||  c == '\t'  ||  c == '=')
			break;

		if (k < size)
			var[k++] = c;
		}

	var[k] = 0;
	strupr(var);
	*here = skip_ws(*here);

	return (var);
}

int run_ctp(ftp_t *x)
{
	int	rc, pid, pfd[2];
	char	line[300];
	FILE	*fp;
	
	if (*x->config->ctp == 0)
		return (0);

	rc = 0;
	if (pipe(pfd) != 0) {
		syslog(LOG_NOTICE, "-ERR: can't pipe: %s", strerror(errno));
		exit (1);
		}
	else if ((pid = fork()) < 0) {
		syslog(LOG_NOTICE, "-ERR: can't fork trp: %s", strerror(errno));
		exit (1);
		}
	else if (pid == 0) {
		int	argc;
		char	*argv[32];

		close(0);		/* Das trp kann nicht vom client lesen. */
		dup2(pfd[1], 1);	/* stdout wird vom parent gelesen. */
		close(pfd[0]);
		set_variables(x);
			
		copy_string(line, x->config->ctp, sizeof(line));
		argc = split(line, argv, ' ', 30);
		argv[argc] = NULL;
		execvp(argv[0], argv);

		syslog(LOG_NOTICE, "-ERR: can't exec trp %s: %s",
			argv[0], strerror(errno));
		exit (1);
		}
	else {
		char	*p, var[80], line[300];

		close(pfd[1]);
		fp = fdopen(pfd[0], "r");
		while (fgets(line, sizeof(line), fp)) {
			p = skip_ws(noctrl(line));
			getvarname(&p, var, sizeof(var));

			if (strcmp(var, "SERVERNAME") == 0  ||  strcmp(var, "SERVER") == 0)
				copy_string(x->server.name, p, sizeof(x->server.name));
			else if (strcmp(var, "SERVERLOGIN") == 0  ||  strcmp(var, "LOGIN") == 0)
				copy_string(x->username, p, sizeof(x->username));
			else if (strcmp(var, "SERVERPASSWD") == 0  ||  strcmp(var, "PASSWD") == 0)
				copy_string(x->password, p, sizeof(x->password));
			else if (strcmp(var, "SERVERPORT") == 0  ||  strcmp(var, "PORT") == 0)
				x->server.port = atoi(p);

			/*
			 * Enable the trp to send error messages.
			 */

			else if (strcmp(var, "-ERR") == 0  ||  strcmp(var, "-ERR:") == 0) {
				syslog(LOG_NOTICE, "-ERR: %s", skip_ws(p));
				exit (1);
				}
			}

		fclose(fp);

		/*
		 * In standalone mode we do not receive the SIGCHLD because
		 * we set it to SIG_IGN -- 030406asg
		 */

		if (x->config->standalone == 0  &&  waitpid(pid, &rc, 0) < 0) {
			syslog(LOG_NOTICE, "-ERR: error while waiting for trp: %s", strerror(errno));
			exit (1);
			}

		rc = WIFEXITED(rc) != 0? WEXITSTATUS(rc): 1;
		if (rc != 0) {
			syslog(LOG_NOTICE, "-ERR: trp signals error condition, rc= %d", rc);
			exit (1);
			}
		}

	return (rc);
}

int get_ftpdir(ftp_t *x)
{
	int	rc, len;
	char	*p, *start, line[300];
	static char *quotes = "'\"'`";

	sfputs(x, "%s", "PWD");
	if (sfgets(x, line, sizeof(line)) == NULL) {
		syslog(LOG_NOTICE, "monitor: server not responding");
		exit (1);
		}

	rc = strtol(line, &p, 10);
	if (rc != 257) {
		syslog(LOG_NOTICE, "monitor: PWD status: %d", rc);
		exit (1);
		}

	p = skip_ws(p);
	if (*p == 0) {
		syslog(LOG_NOTICE, "monitor: directory unset");
		exit (1);
		}


	if ((start = strchr(p, '/')) == NULL) {
		syslog(LOG_NOTICE, "monitor: can't find directory in string: %s", p);
		exit (1);
		}

	get_word(&start, x->cwd, sizeof(x->cwd));
	if ((len = strlen(x->cwd)) > 0  &&  strchr(quotes, x->cwd[len-1]) != NULL)
		x->cwd[len - 1] = 0;

	if (*x->cwd != '/') {
		syslog(LOG_NOTICE, "monitor: invalid path: %s", x->cwd);
		exit (1);
		}
		
	syslog(LOG_NOTICE, "cwd: %s", x->cwd);
	return (0);
}

int get_ftppath(ftp_t *x, char *path)
{
	int	i, k, n, m;
	char	cwp[200], ftpdir[200], pbuf[200];
	char	*part[DIR_MAXDEPTH+5], *dir[DIR_MAXDEPTH+5];

	/*
	 * Zuerst wird das aktuelle Verzeichnis (der ftppath) in seine
	 * Einzelteile zerlegt ...
	 */

	if (*path == '/') {

		/*
		 * ... Ausnahme: die path-Angabe ist absolut ...
		 */

		dir[0] = "";
		n = 1;
		}
	else {
		copy_string(ftpdir, x->cwd, sizeof(ftpdir));
		if (*ftpdir != 0  &&  strcmp(ftpdir, "/") != 0)
			n = split(ftpdir, part, '/', DIR_MAXDEPTH);
		else {
			dir[0] = "";
			n = 1;
			}
		}

	/*
	 * ... danach der path.  Die path Teile werden unter Beachtung
	 * der ueblichen Regeln an die Teile des aktuellen Verzeichnisses
	 * angehangen ...
	 */

	copy_string(pbuf, path, sizeof(pbuf));
	m = split(pbuf, dir, '/', 15);
	for (i=0; i<m; i++) {
		if (*dir[i] == 0)
			/* zwei aufeinander folgende `/' */ ;
		else if (strcmp(dir[i], ".") == 0)
			/* nichts */ ;
		else if (strcmp(dir[i], "..") == 0) {
			if (n > 1)
				n = n - 1;
			}
		else
			part[n++] = dir[i];

		if (n < 1  ||  n >= DIR_MAXDEPTH)
			return (1);		/* ungueltiges Verzeichnis */
		}

	/*
	 * ... und das Ergebnis wieder zusammengesetzt.
	 */

	if (n <= 1) {
		strcpy(cwp, "/");
		}
	else {
		k = 0;
		for (i=1; i<n; i++) {
			if ((k + strlen(part[i]) + 1 + 2) >= sizeof(dir))
				return (1);		/* Name zu lang */
				
			cwp[k++] = '/';
			strcpy(&cwp[k], part[i]);
			k += strlen(&cwp[k]);
			}

		cwp[k] = 0;
		}

	/*
	 * Der normalisierte path auf das Objekt (Datei oder Verzeichnis,
	 * ist hier egal) ist fertig.
	 */

	copy_string(x->filepath, cwp, sizeof(x->filepath));
	return (0);
}

int run_ccp(ftp_t *x, char *cmd, char *par)
{
	int	rc, pid, pfd[2], lfd[2];
	char	message[300], line[300];

	/*
	 * Wenn kein ccp angegeben ist ist alles erlaubt.
	 */

	if (*x->config->ccp == 0)
		return (CCP_OK);


	/*
	 * Der Vorgang fuer ccp's ist fast gleich mit dem fuer acp's.
	 */

	rc = 0;
	if (pipe(pfd) != 0  ||  pipe(lfd)) {
		syslog(LOG_NOTICE, "-ERR: can't pipe: %s", strerror(errno));
		exit (1);
		}
	else if ((pid = fork()) < 0) {
		syslog(LOG_NOTICE, "-ERR: can't fork ccp: %s", strerror(errno));
		exit (1);
		}
	else if (pid == 0) {
		int	argc;
		char	*argv[32];

		dup2(pfd[1], 2);	/* stderr nach FTP Client */
		close(pfd[0]);

		dup2(lfd[1], 1);	/* stdout nach syslog */
		close(lfd[0]);

		close(0);
		set_variables(x);

		setvar(x, "COMMAND", cmd);
		setvar(x, "PARAMETER", par);

		setvar(x, "SESSION", x->session);
		snprintf (line, sizeof(line) - 2, "%d", x->ccpcoll);
		setvar(x, "CCPCOLL", line);

		setvar(x, "FTPHOME", x->home);
		setvar(x, "FTPPATH", x->filepath);

		copy_string(line, x->config->ccp, sizeof(line));
		argc = split(line, argv, ' ', 30);
		argv[argc] = NULL;
		execvp(argv[0], argv);

		syslog(LOG_NOTICE, "-ERR: can't exec ccp %s: %s", argv[0], strerror(errno));
		exit (1);
		}
	else {
		int	len;

		/*
		 * Nicht gebrauchte fd's schliessen.
		 */

		close(pfd[1]);
		close(lfd[1]);


		/*
		 * syslog Meldung lesen und entsprechende pipe schliessen.
		 */

		*message = 0;
		if ((len = read(lfd[0], message, sizeof(message) - 2)) < 0)
			len = 0;

		message[len] = 0;
		noctrl(message);
		close(lfd[0]);

		if (*message != 0)
			syslog(LOG_NOTICE, "%s", message);



		/*
		 * Fehlermeldung lesen, pipe schliessen.
		 */

		*message = 0;
		if ((len = read(pfd[0], message, sizeof(message) - 2)) < 0)
			len = 0;

		message[len] = 0;
		noctrl(message);
		close(pfd[0]);


		/*
		 * return code holen.
		 */

		if (waitpid(pid, &rc, 0) < 0) {
			syslog(LOG_NOTICE, "-ERR: error while waiting for ccp: %s", strerror(errno));
			exit (1);
			}

		rc = WIFEXITED(rc) != 0? WEXITSTATUS(rc): 1;
		if (rc == 0)
			return (CCP_OK);

		if (*message == 0)
			copy_string(message, "permission denied", sizeof(message));

/*
 *		snprintf (command, sizeof(command) - 2, "%s%s%s", cmd, (par != 0? " ": ""), par);
 *		syslog(LOG_NOTICE, "ccp: -ERR: %s@%s: %s: %s: rc= %d",
 *				x->username, x->server.name,
 *				command, message, rc);
 */
		}

	x->ccpcoll++;
	if (isdigit(*message))
		cfputs(x, message);
	else {
		snprintf (line, sizeof(line) - 2, "553 %s", message);
		cfputs(x, line);
		}

/*	cfputs(x, "553 permission denied."); */

	return (CCP_ERROR);
}


	/*
	 * dologin() accepts now blanks with in and at the end of
	 * passwords - 22JAN02asg
	 */

int dologin(ftp_t *x)
{
	int	c, i, rc;
	char	*p, word[80], line[300];
	struct hostent *hostp;
	struct sockaddr_in saddr;
			
	while (1) {
		if (readline_fd(x, 0, line, sizeof(line)) == NULL)
			return (1);

		if (x->config->allow_passwdblanks == 0)
			p = noctrl(line);
		else {
			p = line;
			for (i=strlen(line)-1; i>=0; i--) {
				if ((c = line[i]) != '\n'  &&  c != '\r') {
					line[i+1] = 0;
					break;
					}
				}
			}

		get_word(&p, word, sizeof(word));
		strupr(word);
		if (strcmp(word, "USER") == 0) {
			get_word(&p, x->username, sizeof(x->username));
			cfputs(x, "331 password required");
			}
		else if (strcmp(word, "PASS") == 0) {
			if (*x->username == 0) {
				cfputs(x, "503 give USER first");
				continue;
				}

			if (x->config->allow_passwdblanks == 0)
				get_word(&p, x->password, sizeof(x->password)); 
			else
				copy_string(x->password, p, sizeof(x->password));

			break;
			}
		else if (strcmp(word, "QUIT") == 0) {
			cfputs(x, "221 goodbye");
			return (2);
			}
		else {
			cfputs(x, "530 login first");
			}
		}


	if (*x->config->ctp != 0) {

		/*
		 * We are extremly liberate here with server selection
		 * if we have a dynamic control program, we accept
		 * anything here -- 030404asg
		 */

		if ((p = strchr(x->username, '@')) == NULL  &&  (p = strchr(x->username, '%')) == NULL)
			*x->server.name = 0;
		else if (x->config->use_last_at == 0) {
			*p++ = 0;
			copy_string(x->server.name, p, sizeof(x->server.name));
			}
		else {
			if ((p = strrchr(x->username, '@')) == NULL)
				p = strrchr(x->username, '%');

			*p++ = 0;
			copy_string(x->server.name, p, sizeof(x->server.name));
			}
		}
	else if (x->config->selectserver == 0) {
		if ((p = strchr(x->username, '@')) != NULL  &&  (p = strchr(x->username, '%')) != NULL) {
			cfputs(x, "500 service unavailable");
			syslog(LOG_NOTICE, "-ERR: hostname supplied: %s", p);
			exit (1);
			}

		copy_string(x->server.name, x->config->server, sizeof(x->server.name));
		}
	else {

		/*
		 * Normally we search for the first '@' so that the client can 
		 * not use "proxy hopping". The option "-u" can override
		 * this behaviour.
		 */

		if (x->config->use_last_at == 0) {
			if ((p = strchr(x->username, '@')) == NULL  &&  (p = strchr(x->username, '%')) == NULL) {
				cfputs(x, "500 service unavailable");
				syslog(LOG_NOTICE, "-ERR: missing hostname");
				exit (1);
				}
			}
		else {
			if ((p = strrchr(x->username, '@')) == NULL  &&  (p = strrchr(x->username, '%')) == NULL) {
				cfputs(x, "500 service unavailable");
				syslog(LOG_NOTICE, "-ERR: missing hostname");
				exit (1);
				}
			}


		*p++ = 0;
		copy_string(x->server.name, p, sizeof(x->server.name));

		/*
		 * Den Server auf der Serverliste suchen, wenn eine Liste
		 * vorhanden ist.
		 */

/*
 * Checking the server against the given list is done later now,
 * see below.  Code quoted -- 030404asg
 *
 *		if ((p = x->config->serverlist) != NULL  &&  *p != 0) {
 *			int	permitted;
 *			char	pattern[80];
 *
 *			permitted = 0;
 *			while ((p = skip_ws(p)), *get_quoted(&p, ',', pattern, sizeof(pattern)) != 0) {
 *				noctrl(pattern);
 *				if (strpcmp(x->server.name, pattern) == 0) {
 *					permitted = 1;
 *					break;
 *					}
 *				}
 *
 *			if (permitted == 0) {
 *				cfputs(x, "500 service unavailable");
 *				syslog(LOG_NOTICE, "-ERR: hostname not permitted: %s", x->server.name);
 *				exit (1);
 *				}
 *			}
 */
		}
	


	/*
	 * Wenn vorhanden Proxy Login und Passwort auslesen.
	 */

	if ((p = strchr(x->username, ':')) != NULL) {
		*p++ = 0;
		copy_string(x->local.username, x->username, sizeof(x->local.username));
		copy_string(x->username, p, sizeof(x->username));
		}

	if ((p = strchr(x->password, ':')) != NULL) {
		*p++ = 0;
		copy_string(x->local.password, x->password, sizeof(x->local.password));
		copy_string(x->password, p, sizeof(x->password));
		}

        /*
         * Call the dynamic configuration program.
         */

        if (*x->config->ctp != 0) {
		x->server.port = get_port(x->server.name, 21);

                if (run_ctp(x) != 0)
                        exit (0);       /* Never happens, we exit in run_ctp() */

		if (debug != 0) {
	                fprintf (stderr, "trp debug: server= %s:%u, login= %s, passwd= %s",
					x->server.name, x->server.port,
					x->username, x->password);
			}
                }


	/*
	 * Get port an IP number of server.  Moved code here -- 030404asg
	 */

	x->server.port = get_port(x->server.name, 21);
	if ((hostp = gethostbyname(x->server.name)) == NULL) {
		cfputs(x, "500 service unavailable");
		syslog(LOG_NOTICE, "-ERR: can't resolve hostname: %s", x->server.name);
		exit (1);
		}

	memcpy(&saddr.sin_addr, hostp->h_addr, hostp->h_length);
	copy_string(x->server.ipnum, inet_ntoa(saddr.sin_addr), sizeof(x->server.ipnum));


	/*
	 * Call the access control program to check if the proxy
	 * request is allowed.  Moved code here -- 030404asg
	 */

	if (*x->config->acp != 0) {
		if (run_acp(x) != 0)
			exit (0);
		}


	/*
	 * Verification if the destination server is on the given list
	 * is done now here.
	 *
	 * Notice: Prior to this change you could give a fixed desination
	 * server as command line argument and a list of allowed server
	 * too.  Meaningless because the proxy didn't care when the `server
	 * selection' option wasn't turned on.  Now also the fixed server
	 * is checked against the list.
	 *
	 * I don't expect that this breaks an already running configuration
	 * because as said above this configuration was senseless in earlier
	 * proxy versions -- 030404asg
	 */

	if ((p = x->config->serverlist) != NULL  &&  *p != 0) {
		int	permitted;
		char	pattern[80];

		permitted = 0;
		while ((p = skip_ws(p)), *get_quoted(&p, ',', pattern, sizeof(pattern)) != 0) {
			noctrl(pattern);
			if (strpcmp(x->server.name, pattern) == 0) {
				permitted = 1;
				break;
				}
			}

		if (permitted == 0) {
			cfputs(x, "500 service unavailable");
			syslog(LOG_NOTICE, "-ERR: hostname not permitted: %s", x->server.name);
			exit (1);
			}
		}


	/*
	 * Establish connection to the server
	 */

	if ((x->fd.server = openip(x->server.name, x->server.port, x->config->sourceip, 0)) < 0) {
		cfputs(x, "500 service unavailable");
		syslog(LOG_NOTICE, "-ERR: can't connect to server: %s", x->server.name);
		exit (1);
		}

	syslog(LOG_NOTICE, "connected to server: %s", x->server.name);


	if (sfputc(x, NULL, NULL, line, sizeof(line), NULL) != 220) {
		cfputs(x, "500 service unavailable");
		syslog(LOG_NOTICE, "-ERR: unexpected server greeting: %s", line);
		exit (1);
		}

	/*
	 * Login auf FTP-Server.
	 *
	 * Complete rewrite because of servers wanting no password after
	 * login of anonymous user.
	 */

	rc = sfputc(x, "USER", x->username, line, sizeof(line), NULL);

	if (rc == 230) {
		cfputs(x, "230 login accepted");
		syslog(LOG_NOTICE, "login accepted: %s@%s, no password needed.", x->username, x->server.name);
		return (0);
		}
	else if (rc != 331) {
		cfputs(x, "500 service unavailable");
		syslog(LOG_NOTICE, "-ERR: unexpected reply to USER: %s", line);
		exit (1);
		}
	else if (sfputc(x, "PASS", x->password, line, sizeof(line), NULL) != 230) {
		cfputs(x, "530 bad login");
		syslog(LOG_NOTICE, "-ERR: reply to PASS: %s", line);
		exit (1);
		}

	cfputs(x, "230 login accepted");
	syslog(LOG_NOTICE, "login accepted: %s@%s", x->username, x->server.name);

	return (0);

/*
	if (sfputc(x, "USER", x->username, line, sizeof(line), NULL) != 331) {
		cfputs(x, "500 service unavailable");
		syslog(LOG_NOTICE, "-ERR: unexpected reply to USER: %s", line);
		exit (1);
		}
	else if (sfputc(x, "PASS", x->password, line, sizeof(line), NULL) != 230) {
		cfputs(x, "530 bad login");
		syslog(LOG_NOTICE, "-ERR: reply to PASS: %s", line);
		exit (1);
		}

	cfputs(x, "230 login accepted");
	syslog(LOG_NOTICE, "login accepted: %s@%s", x->username, x->server.name);

	return (0);
*/


}



void signal_handler(int sig)
{
	/*
	 * Changed the way we handle broken pipes (broken control or
	 * data connection).  We ignore it here but write() returns -1
	 * and errno is set to EPIPE which is checked.
	 */

	if (sig == SIGPIPE) {
		signal(SIGPIPE, signal_handler);
		return;
		}

	syslog(LOG_NOTICE, "-ERR: received signal #%d", sig);
	exit (1);
}

int set_signals(void)
{
	signal(SIGHUP, signal_handler);
	signal(SIGINT, signal_handler);
	signal(SIGQUIT, signal_handler);
	signal(SIGSEGV, signal_handler);
	signal(SIGPIPE, signal_handler);
	signal(SIGALRM, signal_handler);
	signal(SIGTERM, signal_handler);
	signal(SIGUSR1, signal_handler);
	signal(SIGUSR2, signal_handler);

	return (0);
}


ftpcmd_t *getcmd(char *name)
{
	int	i;

	for (i=0; cmdtab[i].name[0] != 0; i++) {
		if (strcmp(cmdtab[i].name, name) == 0)
			return (&cmdtab[i]);
		}

	return (NULL);
}


int proxy_request(config_t *config)
{
	int	rc;
	char	*p, command[200], parameter[200], line[300];
	ftpcmd_t *cmd;
	ftp_t	*x;

	set_signals();

	/*
	 * Set socket options to prevent us from the rare case that
	 * we transfer data to/from the client before the client has
	 * seen our "150 ..." message.
	 * Seems so that is doesn't work on all systems.
	 * So temporary only enable it on linux. 
	 */

#if defined(__linux__)

	rc = 1;
	if (setsockopt(1, SOL_TCP, TCP_NODELAY, &rc, sizeof(rc)) != 0)
		syslog(LOG_NOTICE, "can't set TCP_NODELAY, error= %s", strerror(errno));

#endif

	if (config->bsize <= 0)
		config->bsize = 1024;
	else if (config->bsize > FTPMAXBSIZE)
		config->bsize = FTPMAXBSIZE;

	x = allocate(sizeof(ftp_t));
	x->config = config;
	snprintf (x->session, sizeof(x->session) - 2, "%lu-%u", time(NULL), getpid());


	/*
	 * Fix potential problems after immediate initial unseccsesful
	 * up/downloads.  Wasn't a problem since we all do a LIST
	 * at first.
	 */

	x->ch.isock = -1;
	x->ch.osock = -1;


	if (get_client_info(x, 0) < 0) {
		syslog(LOG_NOTICE, "-ERR: can't get client info: %s", strerror(errno));
		exit (1);
		}

	x->port = get_interface_info(0, x->interface, sizeof(x->interface));
	syslog(LOG_NOTICE, "connected to client: %s, interface= %s:%u", x->client,
				x->interface, x->port);

	if (*x->config->configfile != 0) {
		if (readconfig(x->config, x->config->configfile, x->interface) == 0) {
			cfputs(x, "421 not available");
			syslog(LOG_NOTICE, "-ERR: unconfigured interface: %s", x->interface);
			exit (1);
			}
		}

	syslog(LOG_NOTICE, "info: monitor mode: %s, ccp: %s",
			x->config->monitor == 0? "off": "on",
			*x->config->ccp == 0? "<unset>": x->config->ccp);

	cfputs(x, "220 server ready - login please");
	if ((rc = dologin(x)) < 0)
		return (1);
	else if (rc == 2)
		return (0);


	/*
	 * Open the xferlog if we have one.
	 */

	if (*x->config->xferlog != 0) {
		if (*x->server.name == 0)
			copy_string(x->logusername, x->username, sizeof(x->logusername));
		else if (x->server.port != 21)
			snprintf (x->logusername, sizeof(x->logusername), "%s@%s:%u", x->username, x->server.name, x->server.port);
		else
			snprintf (x->logusername, sizeof(x->logusername), "%s@%s", x->username, x->server.name);

		x->xlfp = fopen(x->config->xferlog, "a");
		if (x->xlfp == NULL) {
			syslog(LOG_NOTICE, "-WARN: can't open xferlog: %s, error= %s",
					x->config->xferlog, strerror(errno));
			}
		}

	if (x->config->monitor) {
		get_ftpdir(x);
		copy_string(x->home, x->cwd, sizeof(x->home));
		}

	while ((p = cfgets(x, line, sizeof(line))) != NULL) {
		if (*p == '\001') {
			if (*x->ch.command != 0) {
				syslog(LOG_NOTICE, "%s %s: %ld bytes", x->ch.command, x->ch.filename, x->ch.bytes);

				if (x->xlfp != NULL) {
					unsigned long now;
					char	date[80];

					/*
					 * Write xferlog entry but notice that (1) session are never
					 * flagged as anonymous and (2) the transfer type is always
					 * binary (type flag was added to data channel but is
					 * actually not used. 10MAY04wzk
					 */

					now = time(NULL);
					copy_string(date, ctime(&now), sizeof(date));
					fprintf (x->xlfp, "%s %lu %s %lu %s %c %c %c %c %s %s %d %s %c\n",
							date,
							now - x->ch.started,
							x->client_ip,
							x->ch.bytes,
							x->ch.filename,
							'b',		/* x->ch.type == TYPE_ASC? 'a': 'b', */
							'-',
							strcmp(x->ch.command, "RETR")? 'i': 'o',
							'u',		/* x->isanonymous == 1? 'a': 'u', */
							x->logusername,
							"ftp", 1, x->logusername, 'c');
					fflush(x->xlfp);
					}
				}

			/*
			 * Handle multiline server responses after the
			 * data transfer.
			 */

			sfputc(x, NULL, NULL, line, sizeof(line), NULL);
			cfputs(x, line);

			continue;
			}

		p = noctrl(line);
		get_word(&p, command, sizeof(command));
		strupr(command);

		if ((cmd = getcmd(command)) == NULL  ||  cmd->resp == -1) {
			cfputs(x, "502 command not implemented");
			syslog(LOG_NOTICE, "command not implemented: %s", command);
			continue;
			}

		*x->filepath = 0;
		if (cmd->par == 0)
			*parameter = 0;
		else {
			if (strcmp(command, "CDUP") == 0)
				strcpy(parameter, "..");
			else if (strcmp(command, "SITE") == 0)
				copy_string(parameter, p, sizeof(parameter));
			else {
				if (x->config->allow_blanks != 0)
					copy_string(parameter, p, sizeof(parameter));
				else
					get_word(&p, parameter, sizeof(parameter));
					
				if (*parameter == 0) {
					if (strcmp(command, "LIST") == 0  ||  strcmp(command, "NLST") == 0)
						/* nichts, ist ok */ ;
					else {
						syslog(LOG_NOTICE, "parameter required: %s", command);
						exit (1);
						}
					}
				}

			if (cmd->ispath != 0) {
				if (x->config->monitor) {
					if ((strcmp(command, "LIST") == 0  ||  strcmp(command, "NLST") == 0)
					    &&  *parameter == 0) {

						/*
						 * Sonderfall: wir simulieren `*' als Parameter.
						 */

						get_ftppath(x, "*");
						}
					else
						get_ftppath(x, parameter);
					}
				}
			}


		if (cmd->useccp != 0) {
			if (run_ccp(x, command, parameter) != CCP_OK)
				continue;
			}


		if (strcmp(command, "QUIT") == 0) {
/*		        run_ccp(x, "QUIT", ""); */
			doquit(x);
			break;
			}
		else if (strcmp(command, "PORT") == 0)
			doport(x, command, parameter);
		else if (strcmp(command, "FEAT") == 0)
			dofeat(x);
		else if (strcmp(command, "PASV") == 0)
			dopasv(x, command, parameter);
		else if (strcmp(command, "LIST") == 0  ||  strcmp(command, "NLST") == 0) {
			x->ch.operation = OP_GET;	/* fuer PASV mode */
			rc = sfputc(x, command, parameter, line, sizeof(line), NULL);
			if (rc == 125  ||  rc == 150) {
				x->ch.operation = OP_GET;
				x->ch.seen150   = 1;
				if (debug >= 2)
					fprintf (stderr, "received 150 response\n");
				}
			else
				close_ch(x, &x->ch);

			cfputs(x, line);
			*x->ch.command = 0;
			}
		else if (strcmp(command, "RETR") == 0) {
			x->ch.operation = OP_GET;	/* fuer PASV mode */
			rc = sfputc(x, "RETR", parameter, line, sizeof(line), NULL);
			if (rc == 125  ||  rc == 150) {
				x->ch.operation = OP_GET;
				x->ch.seen150   = 1;
				if (debug >= 2)
					fprintf (stderr, "received 150 response\n");
				}
			else
				close_ch(x, &x->ch);

			cfputs(x, line);
			copy_string(x->ch.command, "RETR", sizeof(x->ch.command));
			copy_string(x->ch.filename, x->config->monitor != 0? x->filepath: parameter, sizeof(x->ch.filename));

			if (extralog != 0)
				syslog(LOG_NOTICE, "%d RETR %s", rc, (x->config->monitor != 0)? parameter: x->filepath);
			}
		else if (strcmp(command, "STOR") == 0  ||  strcmp(command, "APPE") == 0  ||  strcmp(command, "STOU") == 0) {
			x->ch.operation = OP_PUT;	/* fuer PASV mode */
			rc = sfputc(x, command, parameter, line, sizeof(line), NULL);
			if (rc == 125  ||  rc == 150) {
				x->ch.operation = OP_PUT;
				x->ch.seen150   = 1;
				if (debug >= 2)
					fprintf (stderr, "received 150 response\n");

				copy_string(x->ch.command, command, sizeof(x->ch.command));
				}
			else
				close_ch(x, &x->ch);

			cfputs(x, line);
			copy_string(x->ch.filename, x->config->monitor != 0? x->filepath: parameter, sizeof(x->ch.filename));
			if (extralog != 0) {
				if (strcmp(command, "STOU") == 0)
					syslog(LOG_NOTICE, "%d %s %s", rc, command, "-");
				else
					syslog(LOG_NOTICE, "%d %s %s", rc, command, x->ch.filename);
				}
			}
		else {
			if (strcmp(command, "CDUP") == 0)
				*parameter = 0;

			rc = sfputc(x, command, parameter, line, sizeof(line), NULL);
			cfputs(x, line);
			if (extralog != 0  &&  cmd->log != 0) {
				if (x->config->monitor != 0  &&  cmd->ispath != 0)
					syslog(LOG_NOTICE, "%d %s %s", rc, command, x->filepath);
				else
					syslog(LOG_NOTICE, "%d %s%s%s", rc, command, *parameter != 0? " ": "", parameter);
				}

			if (strcmp(command, "CWD") == 0  ||  strcmp(command, "CDUP") == 0) {
				if (x->config->monitor)
					get_ftpdir(x);
				}
			}
		}

	if (*x->config->ccp != 0)
		run_ccp(x, "+EXIT", x->session);

	syslog(LOG_NOTICE, "+OK: proxy terminating");
	return (0);
}





syntax highlighted by Code2HTML, v. 0.9.1