/* cfloader.c Configuration loader

   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, 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 "ftpd.h"
#include "reply.h"

VSERVER serverdefaults = 
{
	NULL,
	MAXUSERS,
	LOGSTRENGTH,
	TIMEOUT,
	LOGFILE,
	EMAIL,
	LOGINTRIES,
	BADAUTHWAIT,
	0,
	0,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL
};	

char *gethostname2(void)
{
	char *hostname;
	int size = 32;
	struct hostent *h;
	
	hostname = mallocwrapper(size);
	while(gethostname(hostname, size-1) == -1)
	{
		size *= 2;
		reallocwrapper(size, (void **)&hostname);
	}
	
	h = gethostbyname(hostname);
	freewrapper(hostname);
	if (!h)
		return(NULL);
	hostname = strdupwrapper(h->h_name);

	return(hostname);
}

/* generates a busy string from config file data */

char *makebusystring(char *input, int alt)
{
	char *r, *n;
	
	r = mallocwrapper(strlen(input) + ((strchrcount(input, '/') + 1) * 6) 
			  + strlen(REPLY_SERVERBUSY) + 2);
	
	strcpy(r, input);
	
	/* do / translation */
	converttorealstr(r);
	
	/* insert inital 421 */
	memmove(r + 4, r, strlen(r) + 1);
	memcpy(r, "421-", 4);

	/* insert further 421 for each line */
	n = r;
	while((n = strchr(n, '\n')) != NULL)
	{
		int len = strlen(n);
		memmove(n + 5, n, len + 1);
		if(alt)
			memcpy(n, "\r\n421-", 6);
		else
			memcpy(n, "\r\n    ", 6);
		n = n + 2;
	}
	
	/* insert server message */
	strcpy(r + strlen(r), "\r\n");
	strcpy(r + strlen(r), REPLY_SERVERBUSY);

	return(r);
}

void configerror(char *str)
{
	switch(logerrors)
	{
		case SYSLOG:
			syslog(LOG_ERR, PROGNAME":%s", str);
			break;
		case TERMINAL:
			fprintf(stderr, "CONFIG: %s\n", str);
			break;
		case MUDLOG:
			log_addentry(MYLOG_INFO, NULL, str);
	}
	freewrapper(str);
}

void vserver_kill(VSERVER *c)
{
	if (c->ipaccess)
		ipacllist_destroy(c->ipaccess);
	freeifnotnull(c->prelogindumpdata);
	freeifnotnull(c->grouplist);
	freeifnotnull(c->toobusy);
	freewrapper(c);
}

VSERVER *vserver_load(CONFIGDATA *cf, char *sectionname, VSERVER *def, VSERVERCONN ***vp)
{
	int section, occur;
	char *setting;
	VSERVER *newvs = mallocwrapper(sizeof(VSERVER));
	VSERVER *dvs = def;
	
	if (!dvs)
		dvs = &serverdefaults;
	newvs->grouplist = NULL;
	newvs->ipaccess = NULL;
	newvs->sectionname = sectionname;

	section = getsectionid(cf->configfile, sectionname);
	if (section == -1)
	{
		configerror(safe_snprintf("Cannot get %s section in config file!", sectionname));
		vserver_kill(newvs);
		return(NULL);
	}
	
	loadintfromconfig(cf->configfile, section, "maxusers", 
			  &(newvs->maxusers), dvs->maxusers);
	loadintfromconfig(cf->configfile, section, "logstrength",
			  &(newvs->loglevel), dvs->loglevel);
	loadintfromconfig(cf->configfile, section, "logintries",
			  &(newvs->logincount), dvs->logincount);
	loadintfromconfig(cf->configfile, section, "timeout",
			  &(newvs->timeout), dvs->timeout);
	loadintfromconfig(cf->configfile, section, "umask",
			  &(newvs->umask), dvs->umask);
	loadintfromconfig(cf->configfile, section, "maxconnectperip",
			  &(newvs->maxperip), dvs->maxperip);

	if (newvs->logincount == 0)
 		newvs->logincount = -1;
	loadintfromconfig(cf->configfile, section, "badauthwait",
			  &(newvs->authwait), dvs->authwait / 1000);
	newvs->authwait *= 1000;
	
	loadstrfromconfig(cf->configfile, section, "logfile",
			  &(newvs->logfile), dvs->logfile);
	loadstrfromconfig(cf->configfile, section, "busydumpdata",
			  &setting, NULL);
	if (setting)
		newvs->toobusy = makebusystring(setting, cf->altlongreplies);
	else if (dvs->toobusy)
		newvs->toobusy = strdupwrapper(dvs->toobusy);
	else
		newvs->toobusy = NULL;
	
	loadstrfromconfig(cf->configfile, section, "email",
			  &(newvs->email), dvs->email);
	loadstrfromconfig(cf->configfile, section, "greeting",
			  &(newvs->greetline), dvs->greetline);
	loadstrfromconfig(cf->configfile, section, "hostname",
			  &(newvs->vhostname), dvs->vhostname);
	if (newvs->vhostname == NULL)
		newvs->vhostname = cf->hostname;
	loadstrfromconfig(cf->configfile, section, "logindump",
			  &(newvs->prelogindump), dvs->prelogindump);
	
	loadstrfromconfig(cf->configfile, section, "logindumpdata",
			  &(newvs->prelogindumpdata), dvs->prelogindumpdata);
	
	/* if prelogindumpdata is there, allocate it by itself and then
	   convert it to a displayable string */
	if (newvs->prelogindumpdata)
	{
		newvs->prelogindumpdata = strdupwrapper(newvs->prelogindumpdata);
		converttorealstr(newvs->prelogindumpdata);
	}	
	newvs->grouplist = makeconfiglist(cf->configfile, sectionname, "group");
	newvs->ipaccess = ipacllist_new(cf->configfile, section, "ipacl");

	if (vp == NULL)
		return(newvs);
		
	occur = 1;
	while((setting = getconfigdata(cf->configfile, section, "ftpport", occur)) != NULL)
	{
		char *bindip = strchr(setting, '/');
		**vp = mallocwrapper(sizeof(VSERVERCONN));

		if (bindip == NULL)
			getnetworkint("0.0.0.0", &((**vp)->ip));
		else
		{
			*bindip = 0;
			getnetworkint(++bindip, &((**vp)->ip));
		}

		sscanf(setting, "%d", &((**vp)->port));
 		(**vp)->fd = 0;
		(**vp)->vptr = newvs;
		*vp = &((**vp)->next);
		occur++;
 	}

	return(newvs);
}

void ftpd_killconfig(CONFIGDATA *dc)
{
	if (dc->configfile)
		freeconfigcache(dc->configfile);
	freeifnotnull(dc->vserverlist);
	freeifnotnull(dc->hostname);
	if (dc->vservers)
	{
		VSERVER *c = dc->vservers;
		while(c != NULL)
		{
			VSERVER *d = c->next;
			vserver_kill(c);
			
			c = d;
		}
	}
	if (dc->inports)
	{
		VSERVERCONN *c = dc->inports;
		while (c != NULL)
		{
			VSERVERCONN *d = c->next;
			freewrapper(c);
			c = d;
		}
	}
	if (dc->defaults)
		vserver_kill(dc->defaults);
	freewrapper(dc);
}

CONFIGDATA *ftpd_loadconfig(char *inidata, int as_inetd, int use_umask)
{
	CONFIGDATA *newconfig = mallocwrapper(sizeof(CONFIGDATA));
	int section, occur, line, error;
	struct passwd *userinfo;
	char *setting;
	VSERVERCONN **vscpos;
	VSERVER **vspos, *vpos;
	
	newconfig->configfile = NULL;
	newconfig->vserverlist = NULL;
	newconfig->vservers = NULL;
	newconfig->inports = NULL;
	
	newconfig->inetd = as_inetd;
	newconfig->parentpid = getpid(); 
	newconfig->defaults = NULL;
	newconfig->defaulthost = 0;

	userinfo = getpwnam("nobody");
	if (userinfo == NULL)
	{
		ftpd_killconfig(newconfig);
		configerror(strdupwrapper("Cannot find uid/gid for user nobody!"));
		return(NULL);
	}

	newconfig->hostname = gethostname2();
	if (!(newconfig->hostname))
	{
		ftpd_killconfig(newconfig);
		configerror(strdupwrapper("Could not resolve hostname for local machine."));
		return(NULL);
	}

	newconfig->gidt_nobodygid = userinfo->pw_gid;
	newconfig->uidt_nobodyuid = userinfo->pw_uid;
	
	newconfig->configfile = loadconfigcache(inidata, &line, &error);
	
	if (!(newconfig->configfile))
	{
		configerror(safe_snprintf("Error on line %d, %s", line, config_errorstr(error)));
		configerror(safe_snprintf("Cannot open config file %s", inidata));
		ftpd_killconfig(newconfig);
		return(NULL);
	}

	section = getsectionid(newconfig->configfile, "main");
	if (section == -1)
	{
		ftpd_killconfig(newconfig);
		configerror(strdupwrapper("Cannot get main section in config file!"));
		return(NULL);
	}
		
	loadintfromconfig(newconfig->configfile, section, "altlongreplies", 
			  &(newconfig->altlongreplies), ALTLONGREPLIES);
	loadintfromconfig(newconfig->configfile, section, "smartbind",
			  &(newconfig->smartbind), SMARTBIND);
	loadintfromconfig(newconfig->configfile, section, "zerobind",
			  &(newconfig->zerobind), ZEROBIND);
	loadintfromconfig(newconfig->configfile, section, "vserverhost",
			  &(newconfig->hostvservers), 0);

	loadintfromconfig(newconfig->configfile, section, "rdnstimeout", 
			  &(newconfig->dnstimeout), RDNSTIMEOUT);
	if (newconfig->dnstimeout == 0)
 		newconfig->dnstimeout = -1;

	if (!(setting = getconfigdata(newconfig->configfile, section, "runasuser", 1)))			
	{
		newconfig->gidt_asgid = getgid();
		newconfig->uidt_asuid = getuid();
		newconfig->username = NULL;
	}
	else
	{
		userinfo = getpwnam(setting);
		if (userinfo == NULL)
		{
			configerror(safe_snprintf("runasuser: username '%s' doesn't exist!", setting));
			ftpd_killconfig(newconfig);
			return(NULL);
		}
		newconfig->gidt_asgid = userinfo->pw_gid;
		newconfig->uidt_asuid = userinfo->pw_uid;
		newconfig->username = setting;
	}

	vscpos = &(newconfig->inports);
	newconfig->vserverlist = makeconfiglist(newconfig->configfile, "main", "vserver");
	if (newconfig->vserverlist[0] == NULL)
	{
		/* we don't have vservers, so we create one vserver
		   using the main section. This has the major advantage
		   of not needing lots of code for non-vserver setups */
		newconfig->defaults = vserver_load(newconfig, "main", NULL, &(vscpos));
		newconfig->defaults->umask = use_umask;
		newconfig->vservers = NULL;
	}
	else
	{
		if (newconfig->hostvservers)
			newconfig->defaults = vserver_load(newconfig, "main", NULL, &(vscpos));
		else
			newconfig->defaults = vserver_load(newconfig, "main", NULL, NULL);
		newconfig->defaults->umask = use_umask;
		occur = 0;
		vspos = &(newconfig->vservers);
		while(newconfig->vserverlist[occur] != NULL)
		{
			if (strlen(newconfig->vserverlist[occur]) >= MAXSECTIONLEN)
			{
				configerror(safe_snprintf("vserver '%s', name too long. Must be less than %d characters long.", newconfig->vserverlist[occur], MAXSECTIONLEN));
				ftpd_killconfig(newconfig);
				return(NULL);
			}
			*vspos = vserver_load(newconfig, newconfig->vserverlist[occur], newconfig->defaults, &(vscpos));
			if (*vspos == NULL)
			{
				ftpd_killconfig(newconfig);
				return(NULL);
			}
			vspos = &((*vspos)->next);
			occur++;
		}
		*vspos = NULL;
	}
	
	*vscpos = NULL;
	newconfig->rootmode = ((int)newconfig->uidt_asuid == 0);
	
	loadstrfromconfig(newconfig->configfile, section, "vserverdefault",
			  &setting, NULL);
	if ((setting) && (newconfig->vservers) && (newconfig->hostvservers))
	{	
		vpos = newconfig->vservers;
		while((vpos != NULL) && (strcmp(vpos->sectionname, setting) != 0))
			vpos = vpos->next;
		
		if (vpos == NULL)
		{
			configerror(safe_snprintf("vserverdefault section '%s' is not defined."));
			ftpd_killconfig(newconfig);
			return(NULL);
		}
		newconfig->defaulthost = vpos;  
	}

	return(newconfig);
}

int ftpd_checkvserver(CONFIGDATA *cdat, VSERVER *vs)
{
	int result = TRUE;
	int count = 0;
	
	if (vs->maxusers <= 0)
	{
		configerror(safe_snprintf("section '%s': must has maxusers more than zero.", vs->sectionname));
		result = FALSE;
	}
	if ((vs->loglevel > 127) || (vs->loglevel < 0))
	{
		configerror(safe_snprintf("section '%s': Logstrength must be between 0 and 127", vs->sectionname));
		result = FALSE;
	}
	if (vs->logfile)
		if (vs->logfile[0] != '/')
		{
			configerror(safe_snprintf("section '%s': logfile is not an absolute filename", vs->sectionname));
			result = FALSE;
		}
		
	if (vs->prelogindump)
		if (vs->prelogindump[0] != '/')
		{
			configerror(safe_snprintf("section '%s': logindump is not an absolute filename.", vs->sectionname));
			result = FALSE;
		}

	if ((vs->logincount == 0) || (vs->logincount < -1))
	{
		configerror(safe_snprintf("section '%s': logintries must be more than 0 or set to -1.", vs->sectionname));
		result = FALSE;
	}

	if (vs->authwait < 0)
	{
		configerror(safe_snprintf("section '%s': badauthwait must be 0 or more milliseconds.", vs->sectionname));
		result = FALSE;
	}

	while(vs->grouplist[count] != NULL)
	{
		if (strlen(vs->grouplist[count]) >= MAXSECTIONLEN)
		{
			configerror(safe_snprintf("section '%s': group '%s', name too long. Must be less than %d characters long", vs->sectionname, vs->grouplist[count], MAXSECTIONLEN));
			result = FALSE;
		}
		else if (getsectionid(cdat->configfile, vs->grouplist[count]) == -1)
		{
			configerror(safe_snprintf("section '%s': group '%s' does not have a section in the config file.", vs->sectionname, vs->grouplist[count]));
			result = FALSE;
		}
		count++;
	}

	return(result);
}

int ftpd_checkconfig(CONFIGDATA *cdat)
{
	int result = TRUE;
	VSERVERCONN *vsc, *vsc2;
	VSERVER *vs;
	
	if (cdat->dnstimeout < -1)
	{
		result = FALSE;
		configerror(strdupwrapper("dnstimeout must be zero or more seconds"));
	}
	/* search for duplicate binds! */

	vsc = cdat->inports;
	if (vsc == NULL)
	{
		result = FALSE;
		configerror(strdupwrapper("there are no ports to bind to."));
	}
	
	while ((vsc != NULL) && result)
	{
		vsc2 = vsc->next;
		if ((vsc->port <= 0) || (vsc->port > 32768))
		{
			result = FALSE;
			configerror(strdupwrapper("port to bind to not in valid range"));	
		}

		while ((vsc2 != NULL) && result)
		{
			if ((vsc2->port) == (vsc->port))
				if ((vsc2->ip == 0) ||
				   (vsc2->ip == vsc->ip))
				{
					result = FALSE;
					configerror(strdupwrapper("Overlapping port binds found!"));
				}
			vsc2 = vsc2->next;
		}
		vsc = vsc->next;
	}

	/* now check all the vservers */

	vs = cdat->vservers;
	while ((vs != NULL) && result)
	{
		result = ftpd_checkvserver(cdat, vs);
		vs = vs->next;
	}

	if (result)
		result = ftpd_checkvserver(cdat, cdat->defaults);
	
	/* now check the config file, and open attempt to open it */
	
	if (result)	/* if everything is ok still */		
	{
		cdat->logout = log_initcontext(cdat->defaults->logfile);
		if (cdat->logout == -1)
		{
			result = FALSE;
			configerror(safe_snprintf("Couldn't open logfile '%s'", cdat->defaults->logfile));
		}
	}

	return(result);
}
