/* 
 * Copyright (C) 1999-2004 Joachim Wieland <joe@mcknight.de>
 * 
 * This program 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.,
 * 59 Temple Place - Suite 330, Boston, MA 02111, USA.
 */

#include <ctype.h>
#include <sys/stat.h>
#include "jftpgw.h"

#ifdef HAVE_LIBWRAP
#include <syslog.h>
#include <tcpd.h>
#ifndef LIBWRAP_ALLOW_FACILITY
#	define LIBWRAP_ALLOW_FACILITY LOG_AUTH
#endif
#ifndef LIBWRAP_ALLOW_SEVERITY
#	define LIBWRAP_ALLOW_SEVERITY LOG_INFO
#endif
#ifndef LIBWRAP_DENY_FACILITY
#	define LIBWRAP_DENY_FACILITY LOG_AUTH
#endif
#ifndef LIBWRAP_DENY_SEVERITY
#	define LIBWRAP_DENY_SEVERITY LOG_WARNING
#endif
int allow_severity = LIBWRAP_ALLOW_FACILITY | LIBWRAP_ALLOW_SEVERITY;
int deny_severity  = LIBWRAP_DENY_FACILITY  | LIBWRAP_DENY_SEVERITY;
#endif

extern struct hostent_list* hostcache;
extern struct serverinfo srvinfo;

int chlds_exited;
int should_read_config;

struct descriptor_set {
	fd_set set;
	int maxfd;
};

static int child_setup(int, struct clientinfo*);
static struct descriptor_set listen_on_ifaces(const char*, struct clientinfo*);
static int get_connecting_socket(struct descriptor_set);
static int say_welcome(int);
static char* prependcode(const char* s, int code);

/* bindport binds to the specified PORT on HOSTNAME (which may also be a
 * dot-notation IP and returns the socket descriptor
 *
 * Parameters: hostname & port: Where to bind
 *
 * Return value: The socket descriptor of the bound socket
 *
 * Called by: waitclient
 *
 * */

int bindport(const char *hostname, int port) {
	unsigned long inetaddr =1;
	int shandle;
	int one = 1;
	struct sockaddr_in sin;
	unsigned long host_ip;

	memset((void*)&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	inetaddr = inet_addr(hostname);
	if (inetaddr == (unsigned long int) UINT_MAX) {
		/* see if HOSTNAME is an interface */
		if (get_interface_ip(hostname, &sin) == 0) {
			/* found it */
			/* sin.sin_addr is already set */
			sin.sin_family = AF_INET;
		} else {
			/* HOSTNAME was probably a name since inet_addr
			 * returned an error.
			 * Look up the name to get the IP
			 */
			host_ip = hostent_get_ip(&hostcache, hostname);
			if (host_ip == (unsigned long int) UINT_MAX) {
				jlog(1, "Could not resolve %s: %s",
					hostname, strerror(errno));
				perror("Could not resolve the hostname");
				return -1;
			}
			sin.sin_addr.s_addr = host_ip;
		}
	}
	else {
		/* okay, HOSTNAME was a valid dot-notation IP */
		sin.sin_addr.s_addr = inetaddr;
	}
	sin.sin_port = htons(port);

	/* become root again - use our function instead of plain
	 * setuid() for the logging */
	if (changeid(PRIV, UID, "Changing ID to root (socket(), bind())")) {
		return -1;
	}

	shandle = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (shandle < 0) {
		jlog(1, "Error creating socket to bind: %s", strerror(errno));
		perror("Error creating socket to bind to");
		return -1;
	}
	if (setsockopt(shandle, SOL_SOCKET, SO_REUSEADDR,
				(void*) &one, sizeof(one)) < 0) {
		jlog(3, "Error setting socket to SO_REUSEADDR");
	}
	if (bind(shandle, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
		jlog(1, "Error binding: %s", strerror(errno));
		perror("Error binding");
		return -1;
	}
	if (listen(shandle, 5) < 0) {
		jlog(1, "Error listening on the socket: %s", strerror(errno));
		perror("Error listening on a bound socket");
		return -1;
	}

	jlog(6, "Listening on %s, port %d, fd %d", hostname, port, shandle);

	if (changeid(UNPRIV, EUID,
				"Changing id back (socket(), bind())") < 0) {
		return -1;
	}

	return shandle;
}


/* waitclient() waits for a client to connect. It binds to the ports and
 * listens to them. If a connection comes it, jftpgw forks. The parent process
 * keeps on listening whereas the child process handles the connection
 *
 * Parameters: hostnames:      Where to bind to
 *             clntinfo:       connection information
 *
 * Return value: -1 on error (if error message has been sent to the client)
 *               -2 on error (if error message should be created by
 *                            strerror()
 *               socket handle: on success (by the child process
 *
 *               Note: The parent process never returns from this function,
 *                     it is terminated by a signal
 */


int waitclient(const char* hostnames, struct clientinfo* clntinfo) {

	int chldpid =0;
	const char* option = 0;
	int ahandle, i;
	struct sigaction sa;
	struct descriptor_set d_set;
	unsigned int peer_ip;
	struct sockaddr_in c_in;
	time_t now;

	if (srvinfo.multithread) {
		daemonize();
	}

	if (changeid(UNPRIV, EUID,
				"Changing id back (socket(), bind())") < 0) {
		return -1;
	}

	d_set = listen_on_ifaces(hostnames, clntinfo);
	if (d_set.maxfd < 0) {
		jlog(8, "d_set.maxfd was negative: %d", d_set.maxfd);
		return -1;
	}

	/* we have successfully bound */

	/* become root again - use our function instead of plain
	 * setuid() for the logging */
	if (changeid(PRIV, UID, "Changing ID to root (pidfile)") < 0) {
		return -1;
	}

	option = config_get_option("pidfile");
	if (option) {
		FILE* pidf;
		umask(022);
		pidf = fopen(option, "w");
		if (pidf) {
			fprintf(pidf, "%ld\n", (long) getpid());
			fclose(pidf);
			/* if successful register function to remove the
			 * pidfile */
			atexit(removepidfile);
		} else {
			jlog(2, "Error creating pidfile %s", option);
		}
	}

	/* this has to be done for the daemonization. We do it now after
	 * the pidfile has been created */
	umask(0);

	srvinfo.ready_to_serve = SVR_LAUNCH_READY;

	if (stage_action("startsetup") < 0) {
		return -1;
	}

	sa.sa_handler = childterm;
	chlds_exited = 0;
	sigemptyset (&sa.sa_mask);
#ifndef WINDOWS
	sa.sa_flags = SA_RESTART;
#endif
	sigaction (SIGCHLD, &sa, 0);


	/* Close stdin,stdout,stderr */
	for(i = 0; i <= 2 && srvinfo.multithread; i++) {
		close(i);
	}
	srvinfo.main_server_pid = getpid();
	atexit(sayterminating);

	while(1) {
		ahandle = get_connecting_socket(d_set);
		if (ahandle == -1) {
			/* either select() or accept() failed */
			/* I don't try resume here because we are in an
			 * endless loop. The danger of the programm falling
			 * into an infinite loop consuming all cpu time is
			 * too big... */
			jlog(8, "get_connecting_socket() returned error code");
			return -1;
		}

		c_in = socketinfo_get_local_sin(ahandle);
		peer_ip = get_uint_peer_ip(ahandle);
		now = time(NULL);
		config_counter_increase(peer_ip,               /* from ip */
					c_in.sin_addr.s_addr,  /* proxy_ip */
					ntohs(c_in.sin_port),  /* proxy_port */
					now);               /* specific_time */
		if (config_check_limit_violation()) {
			say(ahandle, "500 Too many connections, sorry\r\n");
			close(ahandle);
			config_counter_decrease(peer_ip,       /* from ip */
					c_in.sin_addr.s_addr,  /* proxy_ip */
					ntohs(c_in.sin_port),  /* proxy_port */
					now);               /* specific_time */
			continue;
		}
		if (srvinfo.multithread) {
			if ((chldpid = fork()) < 0) {
				jlog(1, "Error forking: %s", strerror(errno));
				close(ahandle);
				return -1;
			}
			if (chldpid > 0) {
				/* parent process */
				/* register the PID */
				register_pid(chldpid, peer_ip,
					c_in.sin_addr.s_addr,  /* proxy_ip */
					ntohs(c_in.sin_port),  /* proxy_port */
					now);               /* specific_time */
				close(ahandle);
			}
			if (chldpid == 0) {
				/* child process */
				jlog(8, "forked to pid %d", getpid());
			}
		}
		if (!srvinfo.multithread || chldpid == 0) {
			return child_setup(ahandle, clntinfo);
		}
	}
}


static
int get_connecting_socket(struct descriptor_set d_set) {
#ifdef HAVE_SOCKLEN_T
	socklen_t size;
#else
	int size;
#endif
	static int nfd;
	int shandle, ahandle;
	fd_set backupset;
	struct sockaddr_in sin;

	size = sizeof(sin);

	memcpy(&backupset, &d_set.set, sizeof(fd_set));

	/* is there no remaining ready fd from the last select() ? */
	if (nfd == 0) {
		while (1) {
			/* eternal select() */
			/* nfd returns the number of fds that are ready */
			nfd = select(d_set.maxfd + 1, &d_set.set, 0, 0, 0);
			if (nfd > 0) {
				break;
			}
			if (errno == EINTR) {
				memcpy(&d_set.set, &backupset, sizeof(fd_set));
				if (chlds_exited > 0) {
					get_chld_pid();
				}
				if (should_read_config) {
					jlog(9, "Re-reading configuration");
					reread_config();
				}
				continue;
			}
			jlog(1, "select() failed: %s, nfd: %d", strerror(errno), nfd);
			return -1;
		}
	}
	/* a descriptor is ready */
	shandle = 0;
	while (!FD_ISSET(shandle, &d_set.set)) {
		shandle++;
	}
	/* one descriptor less is ready in the set */
	nfd--;

	while(1) {
		ahandle = accept(shandle, (struct sockaddr *) &sin, &size);
		if (ahandle < 0) {
			switch(errno) {
				case EINTR:        /* signal */
				case ECONNRESET:   /* client quit before three-way-handshake */
				case ENETDOWN:     /* the others: see manpage */
#ifdef EPROTO
				case EPROTO:
#endif
				case ENOPROTOOPT:
				case EHOSTDOWN:
#ifdef ENONET
				case ENONET:
#endif
				case EHOSTUNREACH:
				case EOPNOTSUPP:
				case ENETUNREACH:
					continue;
			}
			jlog(1, "accept() failed: %s", strerror(errno));
			return -1;
		}
		break;
	}
	return ahandle;
}


static
int child_setup(int sock_fd, struct clientinfo *clntinfo) {
	struct sockaddr_in t_in;
	struct sockaddr_in c_in;
	unsigned long int peer_ip = get_uint_peer_ip(sock_fd);
	struct sigaction sa;
	int i, ret;
#ifdef HAVE_LIBWRAP
	struct request_info req;
	int libwrap_allow = 0;
#endif

	if (peer_ip == (unsigned long int) UINT_MAX) {
		return -1;
	}

	if (stage_action("connect") < 0) {
		say(sock_fd, "421 Error setting up (see logfile)\r\n");
		return -1;
	}

	/* The clients ignore the SIGHUP signal. Thus
	 * the user can issue a killall -HUP jftpgw
	 * and the master jftpgw process rereads its
	 * configuration file without affecting the
	 * child servers */

	sa.sa_handler = SIG_IGN;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;
	sigaction(SIGHUP, &sa, 0);

	/* And they just reap the status of exited children
	 */
	sa.sa_handler = reap_chld_info;
	sigemptyset(&sa.sa_mask);
#ifndef WINDOWS
	sa.sa_flags = SA_RESTART;
#endif
	sigaction(SIGCHLD, &sa, 0);

	for (i = 0; i < clntinfo->boundsocket_niface; i++) {
		close(clntinfo->boundsocket_list[i]);
	}
	free(clntinfo->boundsocket_list);
	clntinfo->boundsocket_list = (int*) 0;

	clntinfo->forward.passauth = 0;
	clntinfo->serversocket = -1;

	clntinfo->clientsocket = sock_fd;
	jlog(7, "Connection from %s", get_char_peer_ip(sock_fd));
	c_in = socketinfo_get_local_sin(sock_fd);
	jlog(7, "Client tried to connect to %s on port %d",
					inet_ntoa(c_in.sin_addr),
					ntohs(c_in.sin_port));

	clntinfo->addr_to_client = c_in.sin_addr.s_addr;
	clntinfo->proxy_ip = c_in.sin_addr.s_addr;
	clntinfo->proxy_port = ntohs(c_in.sin_port);

	/* see if we are in transparent mode */
	t_in = socketinfo_get_transparent_target_sin(sock_fd);
	jlog(7, "Transparent target seems to be %s on port %d",
					inet_ntoa(t_in.sin_addr),
					ntohs(t_in.sin_port));

	jlog(9, "Checking TAG_GLOBAL | TAG_FROM | TAG_PROXYIP | TAG_PROXYPORT | TAG_TIME | TAG_SERVERTYPE");
	ret = config_shrink_config(peer_ip,	/* source IP */
				-1,		/* dest IP */
				(char*) 0,	/* dest name */
				0,		/* dest port */
				(char*) 0,	/* dest user */
				-1,		/* forwarded IP */
				(char*) 0,	/* forwarded destination */
				0,		/* forwarded destinationport */
				(char*) 0,	/* forwarded username */
				0,		/* set no specific time */
				clntinfo->proxy_ip,   /* ip of proxy if   */
				clntinfo->proxy_port, /* port of proxy if */
				srvinfo.servertype,   /* global variable */
				&hostcache,
				TAG_CONNECTED
				);
	if (ret != 0) {
		jlog(2, "Error shrinking config data");
		return ret;
	}

#ifndef HAVE_LIBWRAP
	srvinfo.tcp_wrapper = 0;
#endif

	if (srvinfo.tcp_wrapper) {
#ifdef HAVE_LIBWRAP
		/* use libwrap(tcp_wrapper) for access control
		 *   patch by <fukachan@fml.org>.
		 * It is useful to check both keywords "jftpgw" and "ftp-gw"
		 * for TIS Gauntlet flabour.  
		 */
		request_init(&req, 
			     RQ_DAEMON, "ftp-gw", 
			     RQ_CLIENT_ADDR, conv_ip_to_char(peer_ip),
			     NULL);
		libwrap_allow = hosts_access(&req);

		request_init(&req, 
			     RQ_DAEMON, "jftpgw", 
			     RQ_CLIENT_ADDR, conv_ip_to_char(peer_ip),
			     NULL);

		if (libwrap_allow || hosts_access(&req)) {
			jlog(5, "%s is allowed by libwrap to connect.", 
					conv_ip_to_char(peer_ip));
		}
		else {
			say(sock_fd, "500 You are not allowed by libwrap to "
					"connect. Goodbye.\r\n");
			jlog(5, "%s was not allowed to connect.",
					conv_ip_to_char(peer_ip));
			close(sock_fd);
			return -2;
		}
#endif
	} else {
		if ( ! config_get_option("access")
			||
			! strcmp(config_get_option("access"), "allow") == 0) {

			say(sock_fd, "500 You are not allowed to "
				"connect. Goodbye.\r\n");
			jlog(5, "%s was not allowed to connect.",
						conv_ip_to_char(peer_ip));
			close(sock_fd);
			return -2;
		} else {
			jlog(6, "%s is allowed to connect.",
					conv_ip_to_char(peer_ip));
		}
	}

	if (config_get_bool("transparent-proxy") && 
			get_uint_peer_ip(sock_fd) == t_in.sin_addr.s_addr) {
		jlog(4, "proxy loop detected - machine connects to itself, disabling transparent proxy support");
	}

	jlog(9, "Proxy loop check: peer_ip: %s, t_in_ip: %s",
		get_char_peer_ip(sock_fd), inet_ntoa(t_in.sin_addr));
#ifdef HAVE_LINUX_NETFILTER_IPV4_H
	jlog(9, "HAVE_LINUX_NETFILTER_IPV4_H true, c_in_ip: %s, t_in_port: %d, "
		"c_in_port: %d", inet_ntoa(c_in.sin_addr),
		ntohs(t_in.sin_port), ntohs(c_in.sin_port));
#endif
	if (config_get_bool("transparent-proxy")
			&&
		/* see if the proxy loops. It loops when the source has the
		 * same IP as the destination. There is no need for such a
		 * configuration "in the wild". Is there? */
		(!(get_uint_peer_ip(sock_fd) == t_in.sin_addr.s_addr)
#ifdef HAVE_LINUX_NETFILTER_IPV4_H
			&& !(t_in.sin_addr.s_addr == c_in.sin_addr.s_addr
				&&
			    t_in.sin_port == c_in.sin_port)
#endif
		)) {

		jlog(9, "Enabling transparent proxy");
		clntinfo->transparent = TRANSPARENT_YES;
		clntinfo->transparent_destination = t_in;
	} else {
		jlog(9, "No transparent proxy support");
		clntinfo->transparent = TRANSPARENT_NO;
		clntinfo->transparent_destination.sin_port = 0;
		clntinfo->transparent_destination.sin_addr.s_addr = 0;
	}

	if (stage_action("connectsetup") < 0) {
		say(sock_fd, "421 Error setting up (see logfile)\r\n");
		return -1;
	}

	/* set the target for the transparent proxy */
	if (clntinfo->transparent == TRANSPARENT_YES) {
		char* colon;
		long int dport;

		if (config_get_option("transparent-forward")
			&& config_compare_option("logintime", "connect")) {

			clntinfo->destination =
			      strdup(config_get_option("transparent-forward"));
			jlog(9, "Enabling transparent forward to %s",
					clntinfo->destination);
		} else {
			clntinfo->destination =
			      socketinfo_get_transparent_target_char(sock_fd);
		}
		colon = strchr(clntinfo->destination, ':');
		if (!colon) {
			/* abort, shouldnt happen */
			jlog(9, "Could not get transparent target properly");
			clntinfo->transparent = TRANSPARENT_NO;
			return say_welcome(sock_fd);
		}
		*colon = '\0';  /* terminate destination */
		colon++;        /* skip to port number   */
		dport = strtol(colon, NULL, 10);
		if (errno == ERANGE 
				&& (dport == LONG_MIN || dport == LONG_MAX)) {
			clntinfo->destinationport =
				config_get_ioption("serverport", DEFAULTSERVERPORT);
			/* reverse for logging */
			colon--; *colon = ':';
			jlog(6, "Could not parse dest/port to connect to: %s",
					clntinfo->destination);
			return -1;
		} else {
			clntinfo->destinationport = dport;
		}
	}
	if ( ! config_compare_option("logintime", "connect") ) {
		ret = say_welcome(sock_fd);
	} else {
		ret = login(clntinfo, LOGIN_ST_CONNECTED);
	}

	/* seed the random number generator */
	srand(time(NULL));

	return ret;
}

int inetd_connected(int sock, struct clientinfo* clntinfo) {
	if (stage_action("startsetup") < 0) {
		return -1;
	}
	return child_setup(sock, clntinfo);
}

static
int say_welcome(int sock_fd) {
	const char* welcome = config_get_option("welcomeline");

	if (config_get_option("welcomeline")) {
		char* welcmsg;

		welcmsg = prependcode(welcome, 220);
		enough_mem(welcmsg);

		jlog(9, "Saying this text as welcomeline: %s", welcmsg);
		say(sock_fd, welcmsg);
		free(welcmsg);
	} else {
		say(sock_fd, "220 Joe FTP Proxy Server/Gateway (v"JFTPGW_VERSION") ready\r\n");
	}
	return 0;
}


static
struct descriptor_set listen_on_ifaces(const char* hostnames,
				struct clientinfo* clntinfo) {
	char* part =0, *portdel =0;
	char* hostnames2;
	size_t hostnames2size;
	int offset, port, shandle, i;
	struct descriptor_set d_set;

	offset = 0;
	clntinfo->boundsocket_niface = 0;
	FD_ZERO(&d_set.set);
	d_set.maxfd = 0;

	/* we have to make the string suitable for quotstrtok by appending a
	 * WHITESPACE character */
	hostnames2size = strlen(hostnames) + 2;
	hostnames2 = (char*) malloc(hostnames2size);
	enough_mem(hostnames2);
	snprintf(hostnames2, hostnames2size, "%s ", hostnames);

	while ((part = quotstrtok(hostnames2, WHITESPACES, &offset))) {
		/* do some counting */
		clntinfo->boundsocket_niface++;
		free(part);
	}
	clntinfo->boundsocket_list =
		(int*) malloc(sizeof(int) * clntinfo->boundsocket_niface);
	enough_mem(clntinfo->boundsocket_list);

	offset = 0; i = 0;
	while ((part = quotstrtok(hostnames2, WHITESPACES, &offset))) {
		portdel = strchr(part, ':');
		if (!portdel) {
			jlog(3, "Invalid IP/Port specification: %s", part);
			free(part);
			continue;
		}
		*portdel = (char) 0;
		errno = 0;
		port = strtol(portdel+1, (char**) 0, 10);
		if (errno || port > 65535 || port <= 0) {
			jlog(4, "Invalid port specification: %s. "
			   "Using default value %d", portdel, DEFAULTBINDPORT);
			port = DEFAULTBINDPORT;
		}
		portdel = (char*) 0;

		jlog(9, "binding to %s, port %d", part, port);

		shandle = bindport(part, port);
		free(part);
		part = (char*) 0;
		if (shandle < 0) {
			jlog(8, "Could not bind: %s", strerror(errno));
			free(hostnames2);
			FD_ZERO(&d_set.set);
			d_set.maxfd = -1;
			return d_set;
		}
		FD_SET(shandle, &d_set.set);
		d_set.maxfd = MAX_VAL(d_set.maxfd, shandle);
		clntinfo->boundsocket_list[ i++ ] = shandle;
	}
	free(hostnames2);
	hostnames2 = (char*) 0;

	if (clntinfo->boundsocket_niface == 0) {
		jlog(2, "No interfaces found to bind to");
		FD_ZERO(&d_set.set);
		d_set.maxfd = -1;
		return d_set;
	}

	return d_set;
}


static
char* prependcode(const char* s, int code) {
	char* sdup = strdup(s);
	char* nextline = sdup;
	char* buffer;
	char* end, *replaceend, *bufferend, *bufferstart;
	char dashp = '-';
	const char* const linefeed = "\r\n";
	const char* const empty = "";
	const char* append = linefeed;
	int count = 0;
	int i, length;
	size_t bufsize;

	enough_mem(sdup);
	replace_not_larger(sdup, "\\r", "\r");
	replace_not_larger(sdup, "\\n", "\n");

	if (code < 0) {
		code = -code;
	}
	if (code > 999) {
		code = code % 1000;
	}
	if (code < 100) {
		code += 100;
	}

	/* count the newlines */
	while ((nextline = strstr(nextline, "\r\n"))) {
		count++;
		nextline++;  /* thus this position does not match again */
	}

	/* memory: (count + 1) * strlen("xxx ") + 4 (to terminate for sure
	 * with "\r\n") + 1 (Terminator)
	 *
	 * memory = (count + 1) * 4  + 1
	 */

	bufsize = strlen(sdup) + (count + 1) * 4  + 4  + 1;
	bufferstart = buffer = (char*) malloc(bufsize);
	enough_mem(buffer);
	bufferend = buffer + bufsize - 1;

	end = nextline = sdup;
	while (strlen(end)) {
		end = strstr(nextline, "\r\n");
		if (!end) {
			end = nextline + strlen(nextline);
			replaceend = end;
			dashp = ' ';
			append = linefeed;
		} else {
			/* don't replace the first '\' */
			replaceend = end - 1; 
			end += strlen("\r\n");
			if (end == nextline + strlen(nextline)) {
				/* at the very end */
				dashp = ' ';
			} else {
				dashp = '-';
			}
			append = empty;
		}
		i = 0;
		while ((nextline + i) < replaceend) {
			if (nextline[i] == '%') {
				/* Remove the '%' */
				nextline[i] = '/';
			}
			/* remove any special characters between nextline
			 * and end */
			if ((unsigned char) nextline[i] < 32) {
				nextline[i] = '_';
			}
			i++;
		}
		length = MIN_VAL( bufferend - bufferstart,
				  strlen("xxx ")
				  + (end - nextline)
				  + strlen(append)
				  + 1
				);
		snprintf(bufferstart, length, "%d%c%s%s",
				code, dashp, nextline, append);
		bufferstart += strlen(bufferstart);
		if (!*nextline) { /* end */
			nextline = 0;
		}
		nextline = strstr(nextline, "\r\n") + strlen("\r\n");
	}
	free(sdup);
	return buffer;
}





syntax highlighted by Code2HTML, v. 0.9.1