/*

Create a suitable configuration for kdm taking previous xdm/kdm
installations into account

Copyright (C) 2001-2005 Oswald Buddenhagen <ossi@kde.org>


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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

*/

#include <greet.h>

#include <X11/Xlib.h>
#include <X11/Xresource.h>

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <stdarg.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <utime.h>
#include <dirent.h>
#include <errno.h>
#include <pwd.h>
#include <time.h>
#include <limits.h>
#include <sys/stat.h>
#include <sys/param.h>
#ifdef BSD
# include <utmp.h>
#endif

#define WANT_CONF_GEN
#include <config.ci>

#define RCVERSTR stringify(RCVERMAJOR) "." stringify(RCVERMINOR)

static int old_scripts, no_old_scripts, old_confs, no_old,
	no_backup, no_in_notice, use_destdir, mixed_scripts;
static const char *newdir = KDMCONF, *facesrc = KDMDATA "/pics/users",
	*oldxdm, *oldkde, *oldkdepfx;

static int oldver;


typedef struct StrList {
	struct StrList *next;
	const char *str;
} StrList;

typedef struct StrMap {
	struct StrMap *next;
	const char *key, *value;
} StrMap;


static void *
mmalloc( size_t sz )
{
	void *ptr;

	if (!(ptr = malloc( sz ))) {
		fprintf( stderr, "Out of memory\n" );
		exit( 1 );
	}
	return ptr;
}

static void *
mcalloc( size_t sz )
{
	void *ptr;

	if (!(ptr = calloc( 1, sz ))) {
		fprintf( stderr, "Out of memory\n" );
		exit( 1 );
	}
	return ptr;
}

static void *
mrealloc( void *optr, size_t sz )
{
	void *ptr;

	if (!(ptr = realloc( optr, sz ))) {
		fprintf( stderr, "Out of memory\n" );
		exit( 1 );
	}
	return ptr;
}

static char *
mstrdup( const char *optr )
{
	char *ptr;

	if (!optr)
		return 0;
	if (!(ptr = strdup( optr ))) {
		fprintf( stderr, "Out of memory\n" );
		exit( 1 );
	}
	return ptr;
}


#define NO_LOGGER
#define STATIC static
#include <printf.c>

typedef struct {
	char *buf;
	int clen, blen, tlen;
} OCABuf;

static void
outCh_OCA( void *bp, char c )
{
	OCABuf *ocabp = (OCABuf *)bp;

	ocabp->tlen++;
	if (ocabp->clen >= ocabp->blen) {
		ocabp->blen = ocabp->blen * 3 / 2 + 100;
		ocabp->buf = mrealloc( ocabp->buf, ocabp->blen );
	}
	ocabp->buf[ocabp->clen++] = c;
}

static int
VASPrintf( char **strp, const char *fmt, va_list args )
{
	OCABuf ocab = { 0, 0, 0, -1 };

	doPrint( outCh_OCA, &ocab, fmt, args );
	outCh_OCA( &ocab, 0 );
	*strp = realloc( ocab.buf, ocab.clen );
	if (!*strp)
		*strp = ocab.buf;
	return ocab.tlen;
}

static int
ASPrintf( char **strp, const char *fmt, ... )
{
	va_list args;
	int len;

	va_start( args, fmt );
	len = VASPrintf( strp, fmt, args );
	va_end( args );
	return len;
}

static void
strCat( char **strp, const char *fmt, ... )
{
	char *str, *tstr;
	va_list args;
	int el;

	va_start( args, fmt );
	el = VASPrintf( &str, fmt, args );
	va_end( args );
	if (*strp) {
		int ol = strlen( *strp );
		tstr = mmalloc( el + ol + 1 );
		memcpy( tstr, *strp, ol );
		memcpy( tstr + ol, str, el + 1 );
		free( *strp );
		free( str );
		*strp = tstr;
	} else
		*strp = str;
}


#define WANT_CLOSE 1

typedef struct File {
	char *buf, *eof, *cur;
#if defined(HAVE_MMAP) && defined(WANT_CLOSE)
	int ismapped;
#endif
} File;

static int
readFile( File *file, const char *fn )
{
	off_t flen;
	int fd;

	if ((fd = open( fn, O_RDONLY )) < 0)
		return False;

	flen = lseek( fd, 0, SEEK_END );
#ifdef HAVE_MMAP
# ifdef WANT_CLOSE
	file->ismapped = False;
# endif
	file->buf = mmap( 0, flen + 1, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0 );
# ifdef WANT_CLOSE
	if (file->buf)
		file->ismapped = True;
	else
# else
	if (!file->buf)
# endif
#endif
	{
		file->buf = mmalloc( flen + 1 );
		lseek( fd, 0, SEEK_SET );
		if (read( fd, file->buf, flen ) != flen) {
			free( file->buf );
			close( fd );
			fprintf( stderr, "Cannot read file\n" );
			return False; /* maybe better abort? */
		}
	}
	file->eof = file->buf + flen;
	close( fd );
	return True;
}

#ifdef WANT_CLOSE
static void
freeBuf( File *file )
{
# ifdef HAVE_MMAP
	if (file->ismapped)
		munmap( file->buf, file->eof - file->buf );
	else
# endif
		free( file->buf );
}
#endif

static int
isTrue( const char *val )
{
	return !strcmp( val, "true" ) ||
	       !strcmp( val, "yes" ) ||
	       !strcmp( val, "on" ) ||
	       atoi( val );
}


static int
mkpdirs( const char *name, const char *what )
{
	char *mfname = mstrdup( name );
	int i;
	struct stat st;

	for (i = 1; mfname[i]; i++)
		if (mfname[i] == '/') {
			mfname[i] = 0;
			if (stat( mfname, &st )) {
				if (mkdir( mfname, 0755 )) {
					fprintf( stderr, "Cannot create parent %s of %s directory %s: %s\n",
					         mfname, what, name, strerror( errno ) );
					free( mfname );
					return False;
				}
				chmod( mfname, 0755 );
			}
			mfname[i] = '/';
		}
	free( mfname );
	return True;
}

static int
mkdirp( const char *name, int mode, const char *what, int existok )
{
	struct stat st;

	if (stat( name, &st )) {
		mkpdirs( name, what );
		if (mkdir( name, mode )) {
			fprintf( stderr, "Cannot create %s directory %s: %s\n",
			         what, name, strerror( errno ) );
			return False;
		}
		chmod( name, mode );
		return True;
	}
	return existok;
}


static void
displace( const char *fn )
{
	if (!no_backup) {
		char bn[PATH_MAX + 4];
		sprintf( bn, "%s.bak", fn ); /* won't overflow if only existing paths are passed */
		rename( fn, bn );
	} else
		unlink( fn );
}


static char *
locate( const char *exe )
{
	int len;
	char *path, *pathe, *name, *thenam, nambuf[PATH_MAX+1];

	if (!(path = getenv( "PATH" )))
		return 0;
	len = strlen( exe );
	name = nambuf + PATH_MAX - len;
	memcpy( name, exe, len + 1 );
	*--name = '/';
	do {
		if (!(pathe = strchr( path, ':' )))
			pathe = path + strlen( path );
		len = pathe - path;
		if (len && !(len == 1 && *path == '.')) {
			thenam = name - len;
			if (thenam >= nambuf) {
				memcpy( thenam, path, len );
				if (!access( thenam, X_OK ))
					return mstrdup( thenam );
			}
		}
		path = pathe;
	} while (*path++ != '\0');
	return 0;
}


/*
 * target data to be written to kdmrc
 */

typedef struct Entry {
	struct Entry *next;
	struct Ent *spec;
	const char *value;
	int active:1;
	int written:1;
} Entry;

typedef struct Section {
	struct Section *next;
	struct Sect *spec;
	const char *name;
	const char *comment;
	Entry *ents;
} Section;

static Section *config; /* the kdmrc data to be written */

/*
 * Specification of the (currently possible) kdmrc entries
 */

typedef struct Ent {
	const char *key;
	int prio;
	void (*func)( Entry *ce, Section *cs );
	const char *comment;
} Ent;

typedef struct Sect {
	const char *name;
	Ent *ents;
	int nents;
} Sect;

static Sect *findSect( const char *name );
static Ent *findEnt( Sect *sect, const char *key );

/*
 * Functions to manipulate the current kdmrc data
 */

static const char *
getFqVal( const char *sect, const char *key, const char *defval )
{
	Section *cs;
	Entry *ce;

	for (cs = config; cs; cs = cs->next)
		if (!strcmp( cs->name, sect )) {
			for (ce = cs->ents; ce; ce = ce->next)
				if (!strcmp( ce->spec->key, key )) {
					if (ce->active && ce->written)
						return ce->value;
					break;
				}
			break;
		}
	return defval;
}

static void
putFqVal( const char *sect, const char *key, const char *value )
{
	Section *cs, **csp;
	Entry *ce, **cep;

	if (!value)
		return;

	for (csp = &config; (cs = *csp); csp = &(cs->next))
		if (!strcmp( sect, cs->name ))
			goto havesec;
	cs = mcalloc( sizeof(*cs) );
	ASPrintf( (char **)&cs->name, "%s", sect );
	cs->spec = findSect( sect );
	*csp = cs;
  havesec:

	for (cep = &(cs->ents); (ce = *cep); cep = &(ce->next))
		if (!strcmp( key, ce->spec->key ))
			goto haveent;
	ce = mcalloc( sizeof(*ce) );
	ce->spec = findEnt( cs->spec, key );
	*cep = ce;
  haveent:
	ASPrintf( (char **)&ce->value, "%s", value );
	ce->written = ce->active = True;
}

static const char *csect;

#define setSect(se) csect = se

static void
putVal( const char *key, const char *value )
{
	putFqVal( csect, key, value );
}


static void
writeKdmrc( FILE *f )
{
	Section *cs;
	Entry *ce;
	StrList *sl = 0, *sp;
	const char *cmt;

	putFqVal( "General", "ConfigVersion", RCVERSTR );
	for (cs = config; cs; cs = cs->next) {
		fprintf( f, "%s[%s]\n",
		         cs->comment ? cs->comment : "\n", cs->name );
		for (ce = cs->ents; ce; ce = ce->next) {
			if (ce->spec->comment) {
				cmt = ce->spec->comment;
				for (sp = sl; sp; sp = sp->next)
					if (sp->str == cmt) {
						cmt = "# See above\n";
						goto havit;
					}
				if (!(sp = malloc( sizeof(*sp) )))
					fprintf( stderr, "Warning: Out of memory\n" );
				else {
					sp->str = cmt;
					sp->next = sl; sl = sp;
				}
			} else
				cmt = "";
		  havit:
			fprintf( f, "%s%s%s=%s\n",
			         cmt, ce->active ? "" : "#", ce->spec->key, ce->value );
		}
	}
}


/*
 * defaults
 */
#ifdef XDMCP
static const char def_xaccess[] =
"# Xaccess - Access control file for XDMCP connections\n"
"#\n"
"# To control Direct and Broadcast access:\n"
"#\n"
"#	pattern\n"
"#\n"
"# To control Indirect queries:\n"
"#\n"
"#	pattern		list of hostnames and/or macros ...\n"
"#\n"
"# To use the chooser:\n"
"#\n"
"#	pattern		CHOOSER BROADCAST\n"
"#\n"
"# or\n"
"#\n"
"#	pattern		CHOOSER list of hostnames and/or macros ...\n"
"#\n"
"# To define macros:\n"
"#\n"
"#	%name		list of hosts ...\n"
"#\n"
"# The first form tells xdm which displays to respond to itself.\n"
"# The second form tells xdm to forward indirect queries from hosts matching\n"
"# the specified pattern to the indicated list of hosts.\n"
"# The third form tells xdm to handle indirect queries using the chooser;\n"
"# the chooser is directed to send its own queries out via the broadcast\n"
"# address and display the results on the terminal.\n"
"# The fourth form is similar to the third, except instead of using the\n"
"# broadcast address, it sends DirectQuerys to each of the hosts in the list\n"
"#\n"
"# In all cases, xdm uses the first entry which matches the terminal;\n"
"# for IndirectQuery messages only entries with right hand sides can\n"
"# match, for Direct and Broadcast Query messages, only entries without\n"
"# right hand sides can match.\n"
"#\n"
"\n"
"*					#any host can get a login window\n"
"\n"
"#\n"
"# To hardwire a specific terminal to a specific host, you can\n"
"# leave the terminal sending indirect queries to this host, and\n"
"# use an entry of the form:\n"
"#\n"
"\n"
"#terminal-a	host-a\n"
"\n"
"\n"
"#\n"
"# The nicest way to run the chooser is to just ask it to broadcast\n"
"# requests to the network - that way new hosts show up automatically.\n"
"# Sometimes, however, the chooser cannot figure out how to broadcast,\n"
"# so this may not work in all environments.\n"
"#\n"
"\n"
"*		CHOOSER BROADCAST	#any indirect host can get a chooser\n"
"\n"
"#\n"
"# If you would prefer to configure the set of hosts each terminal sees,\n"
"# then just uncomment these lines (and comment the CHOOSER line above)\n"
"# and edit the %hostlist line as appropriate\n"
"#\n"
"\n"
"#%hostlist	host-a host-b\n"
"\n"
"#*		CHOOSER %hostlist	#\n";
#endif

#ifdef XDMCP
static const char def_willing[] =
"#! /bin/sh\n"
"# The output of this script is displayed in the chooser window\n"
"# (instead of \"Willing to manage\").\n"
"\n"
"load=`uptime|sed -e 's/^.*load[^0-9]*//'`\n"
"nrusers=`who|cut -c 1-8|sort -u|wc -l|sed 's/^[ \t]*//'`\n"
"s=\"\"; [ \"$nrusers\" != 1 ] && s=s\n"
"\n"
"echo \"${nrusers} user${s}, load: ${load}\"\n";
#endif

static const char def_setup[] =
"#! /bin/sh\n"
"# Xsetup - run as root before the login dialog appears\n"
"\n"
"#xconsole -geometry 480x130-0-0 -notify -verbose -fn fixed -exitOnFail -file /dev/xconsole &\n";

static const char def_startup[] =
"#! /bin/sh\n"
"# Xstartup - run as root before session starts\n"
"\n"
"# By convention, both xconsole and xterm -C check that the\n"
"# console is owned by the invoking user and is readable before attaching\n"
"# the console output.  This way a random user can invoke xterm -C without\n"
"# causing serious grief; still, it can cause havoc, so xconsole is started\n"
"# by Xsetup usually.\n"
"# This is not required if you use PAM with the pam_console module.\n"
"#\n"
"#chown $USER /dev/console\n"
"\n"
#ifdef _AIX
"# We create a pseudodevice for finger.  (host:0 becomes xdm/host_0)\n"
"# Without it, finger errors out with \"Cannot stat /dev/host:0\".\n"
"#\n"
"#devname=`echo $DISPLAY | cut -c1-8`\n"
"#if [ ! -d /dev/xdm ]; then\n"
"#  mkdir /dev/xdm\n"
"#  chmod 755 /dev/xdm\n"
"#fi\n"
"#touch /dev/xdm/$devname\n"
"#chmod 644 /dev/xdm/$devname\n"
"#exec sessreg -a -l xdm/$devname -h \"`echo $DISPLAY | cut -d: -f1`\""
#else
"#exec sessreg -a -l $DISPLAY -h \"`echo $DISPLAY | cut -d: -f1`\""
# ifdef BSD
" -x " KDMCONF "/Xservers"
# endif
#endif /* _AIX */
" $USER\n"
"\n# NOTE: The session is aborted if the last command returns non-zero.\n";

static const char def_reset[] =
"#! /bin/sh\n"
"# Xreset - run as root after session exits\n"
"\n"
"# Reassign ownership of the console to root, this should disallow\n"
"# assignment of console output to any random users's xterm. See Xstartup.\n"
"#\n"
"#chown root /dev/console\n"
"#chmod 622 /dev/console\n"
"\n"
#ifdef _AIX
"#devname=`echo $DISPLAY | cut -c1-8`\n"
"#exec sessreg -d -l xdm/$devname -h \"`echo $DISPLAY | cut -d: -f1`\""
#else
"#exec sessreg -d -l $DISPLAY -h \"`echo $DISPLAY | cut -d: -f1`\""
# ifdef BSD
" -x " KDMCONF "/Xservers"
# endif
#endif /* _AIX */
" $USER\n";

static const char def_session1[] =
"#! /bin/sh\n"
"# Xsession - run as user\n"
"\n"
"session=$1\n"
"\n"
"# Note that the respective logout scripts are not sourced.\n"
"case $SHELL in\n"
"  */bash)\n"
"    [ -z \"$BASH\" ] && exec $SHELL $0 \"$@\"\n"
"    set +o posix\n"
"    [ -f /etc/profile ] && . /etc/profile\n"
"    if [ -f $HOME/.bash_profile ]; then\n"
"      . $HOME/.bash_profile\n"
"    elif [ -f $HOME/.bash_login ]; then\n"
"      . $HOME/.bash_login\n"
"    elif [ -f $HOME/.profile ]; then\n"
"      . $HOME/.profile\n"
"    fi\n"
"    ;;\n"
"  */zsh)\n"
"    [ -z \"$ZSH_NAME\" ] && exec $SHELL $0 \"$@\"\n"
"    emulate -R zsh\n"
"    [ -d /etc/zsh ] && zdir=/etc/zsh || zdir=/etc\n"
"    zhome=${ZDOTDIR:-$HOME}\n"
"    # zshenv is always sourced automatically.\n"
"    [ -f $zdir/zprofile ] && . $zdir/zprofile\n"
"    [ -f $zhome/.zprofile ] && . $zhome/.zprofile\n"
"    [ -f $zdir/zlogin ] && . $zdir/zlogin\n"
"    [ -f $zhome/.zlogin ] && . $zhome/.zlogin\n"
"    ;;\n"
"  */csh|*/tcsh)\n"
"    # [t]cshrc is always sourced automatically.\n"
"    # Note that sourcing csh.login after .cshrc is non-standard.\n"
"    xsess_tmp=";
static const char def_session2[] =
"\n"
"    $SHELL -c \"if (-f /etc/csh.login) source /etc/csh.login; if (-f ~/.login) source ~/.login; /bin/sh -c export -p >! $xsess_tmp\"\n"
"    . $xsess_tmp\n"
"    rm -f $xsess_tmp\n"
"    ;;\n"
"  *) # Plain sh, ksh, and anything we do not know.\n"
"    [ -f /etc/profile ] && . /etc/profile\n"
"    [ -f $HOME/.profile ] && . $HOME/.profile\n"
"    ;;\n"
"esac\n"
"\n"
"[ -f /etc/xprofile ] && . /etc/xprofile\n"
"[ -f $HOME/.xprofile ] && . $HOME/.xprofile\n"
"\n"
"case $session in\n"
"  \"\")\n"
"    exec xmessage -center -buttons OK:0 -default OK \"Sorry, $DESKTOP_SESSION is no valid session.\"\n"
"    ;;\n"
"  failsafe)\n"
"    exec xterm -geometry 80x24-0-0\n"
"    ;;\n"
"  custom)\n"
"    exec $HOME/.xsession\n"
"    ;;\n"
"  default)\n"
"    exec " KDE_BINDIR "/startkde\n"
"    ;;\n"
"  *)\n"
"    eval exec \"$session\"\n"
"    ;;\n"
"esac\n"
"exec xmessage -center -buttons OK:0 -default OK \"Sorry, cannot execute $session. Check $DESKTOP_SESSION.desktop.\"\n";

static const char def_background[] =
"[Desktop0]\n"
"BackgroundMode=Flat\n"
"BlendBalance=100\n"
"BlendMode=NoBlending\n"
"ChangeInterval=60\n"
"Color1=0,0,200\n"
"Color2=192,192,192\n"
"CurrentWallpaper=0\n"
"LastChange=0\n"
"MinOptimizationDepth=1\n"
"MultiWallpaperMode=NoMulti\n"
"Pattern=fish\n"
"Program=\n"
"ReverseBlending=false\n"
"UseSHM=false\n"
"Wallpaper=default_blue.jpg\n"
"WallpaperList=\n"
"WallpaperMode=Scaled\n";

/* Create a new file in KDMCONF */
static FILE *
createFile( const char *fn, int mode )
{
	char *nname;
	FILE *f;

	ASPrintf( &nname, "%s/%s", newdir, fn );
	displace( nname );
	if (!(f = fopen( nname, "w" ))) {
		fprintf( stderr, "Cannot create %s\n", nname );
		exit( 1 );
	}
	chmod( nname, mode );
	free( nname );
	return f;
}

/* Create a copy of a file under KDMCONF and fill it */
static void
writeCopy( const char *fn, int mode, time_t stamp, const char *buf, size_t len )
{
	char *nname;
	int fd;
	struct utimbuf utim;

	ASPrintf( &nname, "%s/%s", newdir, fn );
	displace( nname );
	mkpdirs( nname, "target" );
	if ((fd = creat( nname, mode )) < 0) {
		fprintf( stderr, "Cannot create %s\n", nname );
		exit( 1 );
	}
	write( fd, buf, len );
	close( fd );
	if (stamp) {
		utim.actime = utim.modtime = stamp;
		utime( nname, &utim );
	}
	free( nname );
}


/* returns static array! */
static const char *
reSect( const char *sec, const char *name )
{
	static char sname[64];
	char *p;

	if ((p = strrchr( sec, '-' ))) {
		sprintf( sname, "%.*s-%s", (int)(p - sec), sec, name );
		return sname;
	} else
		return name;
}

static int
inNewDir( const char *name )
{
	return !memcmp( name, KDMCONF "/", sizeof(KDMCONF) );
}

static const char *
getMapping( StrMap *sm, const char *k )
{
	for (; sm; sm = sm->next)
		if (!strcmp( sm->key, k ))
			return sm->value;
	return 0;
}

static void
addMapping( StrMap **sm, const char *k, const char *v )
{
	for (; *sm; sm = &(*sm)->next)
		if (!strcmp( (*sm)->key, k ))
			return;
	*sm = mcalloc( sizeof(**sm) );
	ASPrintf( (char **)&(*sm)->key, "%s", k );
	ASPrintf( (char **)&(*sm)->value, "%s", v );
}

static int
inList( StrList *sp, const char *s )
{
	for (; sp; sp = sp->next)
		if (!strcmp( sp->str, s ))
			return True;
	return False;
}

static void
addStr( StrList **sp, const char *s )
{
	for (; *sp; sp = &(*sp)->next)
		if (!strcmp( (*sp)->str, s ))
			return;
	*sp = mcalloc( sizeof(**sp) );
	ASPrintf( (char **)&(*sp)->str, "%s", s );
}

static StrList *
splitList( const char *str )
{
	StrList *sp, **spp = &sp;
	const char *e;
	if (!*str)
		return 0;
	for (;;) {
		*spp = mcalloc( sizeof(**spp) );
		if (!(e = strchr( str, ',' )))
			break;
		ASPrintf( (char **)&(*spp)->str, "%.*s", (int)(e - str), str );
		str = e + 1;
		spp = &(*spp)->next;
	}
	(*spp)->str = mstrdup( str );
	(*spp)->next = 0;
	return sp;
}

static char *
joinList( StrList *sp )
{
	char *s = 0;
	if (!sp)
		return mstrdup( "" );
	s = mstrdup( sp->str );
	for (;;) {
		sp = sp->next;
		if (!sp)
			return s;
		strCat( &s, ",%s", sp->str );
	}
}

StrMap *cfmap;
StrList *aflist, *uflist, *eflist, *cflist, *lflist;

/* file is part of new config */
static void
addedFile( const char *fn )
{
	addStr( &aflist, fn );
}

/* file from old config was parsed */
static void
usedFile( const char *fn )
{
	addStr( &uflist, fn );
}

/* file from old config was copied with slight modifications */
static void
editedFile( const char *fn )
{
	addStr( &eflist, fn );
}

/* file from old config was copied verbatim */
static void
copiedFile( const char *fn )
{
	addStr( &cflist, fn );
}

/* file from old config is still being used */
static void
linkedFile( const char *fn )
{
	addStr( &lflist, fn );
}

/*
 * NOTE: This code will not correctly deal with default files colliding
 * with pre-existing files. This should be OK, as for each class of files
 * (scripts, configs) only one origin is used, and conflicts between classes
 * are rather unlikely.
 */

/* Make a possibly modified copy of a file under KDMCONF */
static int
copyFile( Entry *ce, int mode, int (*proc)( File * ) )
{
	const char *tptr;
	char *nname;
	File file;
	int rt;

	if (!*ce->value)
		return True;

	if ((nname = (char *)getMapping( cfmap, ce->value ))) {
		rt = inList( aflist, nname );
		goto doret;
	}
	if (oldkde) {
		int olen = strlen( oldkde );
		if (!memcmp( ce->value, oldkde, olen )) {
			if (!memcmp( ce->value + olen, "/kdm/", 5 )) {
				tptr = ce->value + olen + 4;
				goto gotn;
			}
			if (ce->value[olen] == '/') {
				tptr = ce->value + olen;
				goto gotn;
			}
		}
	}
	if (oldxdm) {
		int olen = strlen( oldxdm );
		if (!memcmp( ce->value, oldxdm, olen ) && ce->value[olen] == '/') {
			tptr = ce->value + olen;
			goto gotn;
		}
	}
	if (!(tptr = strrchr( ce->value, '/' ))) {
		fprintf( stderr, "Warning: cannot cope with relative path %s\n", ce->value );
		return False;
	}
  gotn:
	ASPrintf( &nname, KDMCONF "%s", tptr );
	if (inList( aflist, nname )) {
		int cnt = 1;
		do {
			free( nname );
			ASPrintf( &nname, KDMCONF "%s-%d", tptr , ++cnt );
		} while (inList( aflist, nname ));
	}
	addMapping( &cfmap, ce->value, nname );
	if (!readFile( &file, ce->value )) {
		fprintf( stderr, "Warning: cannot copy file %s\n", ce->value );
		rt = False;
	} else {
		if (!proc || !proc( &file )) {
			if (!use_destdir && !strcmp( ce->value, nname ))
				linkedFile( nname );
			else {
				struct stat st;
				stat( ce->value, &st );
				writeCopy( nname + sizeof(KDMCONF), mode, st.st_mtime,
				           file.buf, file.eof - file.buf );
				copiedFile( ce->value );
			}
		} else {
			writeCopy( nname + sizeof(KDMCONF), mode, 0,
			           file.buf, file.eof - file.buf );
			editedFile( ce->value );
		}
		if (strcmp( ce->value, nname ) && inNewDir( ce->value ) && !use_destdir)
			displace( ce->value );
		addedFile( nname );
		rt = True;
	}
  doret:
	ce->value = nname;
	return rt;
}

static void
doLinkFile( const char *name )
{
	File file;

	if (inList( aflist, name ))
		return;
	if (!readFile( &file, name )) {
		fprintf( stderr, "Warning: cannot read file %s\n", name );
		return;
	}
	if (inNewDir( name ) && use_destdir) {
		struct stat st;
		stat( name, &st );
		writeCopy( name + sizeof(KDMCONF), st.st_mode, st.st_mtime,
		           file.buf, file.eof - file.buf );
		copiedFile( name );
	} else
		linkedFile( name );
	addedFile( name );
}

/* Incorporate an existing file */
static void
linkFile( Entry *ce )
{
	if (ce->written && *ce->value)
		doLinkFile( ce->value );
}

/* Create a new file in KDMCONF and fill it */
static void
writeFile( const char *tname, int mode, const char *cont )
{
	FILE *f = createFile( tname + sizeof(KDMCONF), mode );
	fputs( cont, f );
	fclose( f );
	addedFile( tname );
}


static void
handleBgCfg( Entry *ce, Section *cs ATTR_UNUSED )
{
	if (!ce->active) /* can be only the X-*-Greeter one */
		writeFile( def_BackgroundCfg, 0644, def_background );
#if 0 /* risk of kcontrol clobbering the original file */
	else if (old_confs)
		linkFile( ce );
#endif
	else {
		if (!copyFile( ce, 0644, 0 )) {
			if (!strcmp( cs->name, "X-*-Greeter" ))
				writeFile( def_BackgroundCfg, 0644, def_background );
			ce->active = False;
		}
	}
}


#ifdef HAVE_VTS
static char *
memMem( char *mem, int lmem, const char *smem, int lsmem )
{
	for (; lmem >= lsmem; mem++, lmem--)
		if (!memcmp( mem, smem, lsmem ))
			return mem + lsmem;
	return 0;
}

static int maxTTY, TTYmask;

static void
getInitTab( void )
{
	File it;
	char *p, *eol, *ep;
	int tty;

	if (maxTTY)
		return;
	if (readFile( &it, "/etc/inittab" )) {
		usedFile( "/etc/inittab" );
		for (p = it.buf; p < it.eof; p = eol + 1) {
			for (eol = p; eol < it.eof && *eol != '\n'; eol++);
			if (*p != '#') {
				if ((ep = memMem( p, eol - p, " tty", 4 )) &&
				    ep < eol && isdigit( *ep ))
				{
					if (ep + 1 == eol || isspace( *(ep + 1) ))
						tty = *ep - '0';
					else if (isdigit( *(ep + 1) ) &&
					         (ep + 2 == eol || isspace( *(ep + 2) )))
						tty = (*ep - '0') * 10 + (*(ep + 1) - '0');
					else
						continue;
					TTYmask |= 1 << (tty - 1);
					if (tty > maxTTY)
						maxTTY = tty;
				}
			}
		}
		freeBuf( &it );
	}
	if (!maxTTY) {
		maxTTY = 6;
		TTYmask = 0x3f;
	}
}
#endif


/* TODO: handle solaris' local_uid specs */

static char *
readWord( File *file, int EOFatEOL )
{
	char *wordp, *wordBuffer;
	int quoted;
	char c;

  rest:
	wordp = wordBuffer = file->cur;
  mloop:
	quoted = False;
  qloop:
	if (file->cur == file->eof) {
	  doeow:
		if (wordp == wordBuffer)
			return 0;
	  retw:
		*wordp = '\0';
		return wordBuffer;
	}
	c = *file->cur++;
	switch (c) {
	case '#':
		if (quoted)
			break;
		do {
			if (file->cur == file->eof)
				goto doeow;
			c = *file->cur++;
		} while (c != '\n');
	case '\0':
	case '\n':
		if (EOFatEOL && !quoted) {
			file->cur--;
			goto doeow;
		}
		if (wordp != wordBuffer) {
			file->cur--;
			goto retw;
		}
		goto rest;
	case ' ':
	case '\t':
		if (wordp != wordBuffer)
			goto retw;
		goto rest;
	case '\\':
		if (!quoted) {
			quoted = True;
			goto qloop;
		}
		break;
	}
	*wordp++ = c;
	goto mloop;
}

/* backslashes are double-escaped - first parseArgs, then KConfig */

static StrList *
splitArgs( const char *string )
{
	const char *word;
	char *str;
	int wlen;
	StrList *args, **argp = &args;

	while (*string) {
		if (isspace( *string )) {
			string++;
			continue;
		}
		word = string;
		wlen = 0;
		do {
			if (*string == '\\') {
				if (*++string != '\\')
					string--;
				if (*++string != '\\')
					string--;
				if (!*++string)
					string--;
				wlen++;
			} else if (*string == '\'') {
				while (*++string != '\'' && *string) {
					if (*string == '\\' && *++string != '\\')
						string--;
					wlen++;
				}
			} else if (*string == '"') {
				while (*++string != '"' && *string) {
					if (*string == '\\') {
						if (*++string != '\\')
							string--;
						if (*++string != '\\')
							string--;
						if (!*++string)
							string--;
					}
					wlen++;
				}
			} else
				wlen++;
		} while (*++string && !isspace( *string ));
		*argp = mmalloc( sizeof(**argp) );
		(*argp)->str = str = mmalloc( wlen + 1 );
		do {
			if (*word == '\\') {
				if (*++word != '\\')
					word--;
				if (*++word != '\\')
					word--;
				if (!*++word)
					word--;
				*str++ = *word;
			} else if (*word == '\'') {
				while (*++word != '\'' && *word) {
					if (*word == '\\' && *++word != '\\')
						word--;
					*str++ = *word;
				}
			} else if (*word == '"') {
				while (*++word != '"' && *word) {
					if (*word == '\\') {
						if (*++word != '\\')
							word--;
						if (*++word != '\\')
							word--;
						if (!*++word)
							word--;
					}
					*str++ = *word;
				}
			} else
				*str++ = *word;
		} while (*++word && !isspace( *word ));
		*str = 0;
		argp = &(*argp)->next;
	}
	*argp = 0;
	return args;
}

static const char *
joinArgs( StrList *argv )
{
	StrList *av;
	const char *s, *rs;
	char *str;
	int slen;

	if (!argv)
		return "";
	for (slen = 0, av = argv; slen++, av; av = av->next) {
		int nq = 0;
		for (s = av->str; *s; s++, slen++)
			if (isspace( *s ) || *s == '\'')
				nq = 2;
			else if (*s == '"')
				slen += 2;
			else if (*s == '\\')
				slen += 3;
		slen += nq;
	}
	rs = str = mmalloc( slen );
	for (av = argv; av; av = av->next) {
		int nq = 0;
		for (s = av->str; *s; s++)
			if (isspace( *s ) || *s == '\'')
				nq = 2;
		if (av != argv)
			*str++ = ' ';
		if (nq)
			*str++ = '"';
		for (s = av->str; *s; s++) {
			if (*s == '\\')
				*str++ = '\\';
			if (*s == '"' || *s == '\\') {
				*str++ = '\\';
				*str++ = '\\';
			}
			*str++ = *s;
		}
		if (nq)
			*str++ = '"';
	}
	*str = 0;
	return rs;
}

static struct displayMatch {
	const char *name;
	int len, local;
} displayTypes[] = {
	{ "local", 5, True },
	{ "foreign", 7, False },
};

static int
parseDisplayType( const char *string, const char **atPos )
{
	struct displayMatch *d;

	*atPos = 0;
	for (d = displayTypes; d < displayTypes + as(displayTypes); d++) {
		if (!memcmp( d->name, string, d->len ) &&
		    (!string[d->len] || string[d->len] == '@'))
		{
			if (string[d->len] == '@' && string[d->len + 1])
				*atPos = string + d->len + 1;
			return d->local;
		}
	}
	return -1;
}

typedef struct serverEntry {
	struct serverEntry *next;
	const char *name, *class2, *console, *argvs, *arglvs;
	StrList *argv, *arglv;
	int local, reserve, vt;
} ServerEntry;

static void
absorbXservers( const char *sect ATTR_UNUSED, char **value )
{
	ServerEntry *se, *se1, *serverList, **serverPtr;
	const char *word, *word2;
	char *sdpys, *rdpys;
	StrList **argp, **arglp, *ap, *ap2;
	File file;
	int nldpys = 0, nrdpys = 0, dpymask = 0;
	int cpcmd, cpcmdl;
#ifdef HAVE_VTS
	int dn, cpvt, mtty;
#endif

	if (**value == '/') {
		if (!readFile( &file, *value ))
			return;
		usedFile( *value );
	} else {
		file.buf = *value;
		file.eof = *value + strlen( *value );
	}
	file.cur = file.buf;

	serverPtr = &serverList;
#ifdef HAVE_VTS
  bustd:
#endif
	while ((word = readWord( &file, 0 ))) {
		se = mcalloc( sizeof(*se) );
		se->name = word;
		if (!(word = readWord( &file, 1 )))
			continue;
		se->local = parseDisplayType( word, &se->console );
		if (se->local < 0) {
			se->class2 = word;
			if (!(word = readWord( &file, 1 )))
				continue;
			se->local = parseDisplayType( word, &se->console );
			if (se->local < 0) {
				while (readWord( &file, 1 ));
				continue;
			}
		}
		word = readWord( &file, 1 );
		if (word && !strcmp( word, "reserve" )) {
			se->reserve = True;
			word = readWord( &file, 1 );
		}
		if (se->local != (word != 0))
			continue;
		argp = &se->argv;
		arglp = &se->arglv;
		while (word) {
#ifdef HAVE_VTS
			if (word[0] == 'v' && word[1] == 't')
				se->vt = atoi( word + 2 );
			else if (!strcmp( word, "-crt" )) { /* SCO style */
				if (!(word = readWord( &file, 1 )) ||
				    memcmp( word, "/dev/tty", 8 ))
					goto bustd;
				se->vt = atoi( word + 8 );
			} else
#endif
			if (strcmp( word, se->name )) {
				ap = mmalloc( sizeof(*ap) );
				ap->str = word;
				if (!strcmp( word, "-nolisten" )) {
					if (!(word2 = readWord( &file, 1 )))
						break;
					ap2 = mmalloc( sizeof(*ap2) );
					ap2->str = word2;
					ap->next = ap2;
					if (!strcmp( word2, "unix" )) {
						*argp = ap;
						argp = &ap2->next;
					} else {
						*arglp = ap;
						arglp = &ap2->next;
					}
				} else {
					*argp = ap;
					argp = &ap->next;
				}
			}
			word = readWord( &file, 1 );
		}
		*argp = *arglp = 0;
		if (se->local) {
			nldpys++;
			dpymask |= 1 << atoi( se->name + 1 );
			if (se->reserve)
				nrdpys++;
		}
		*serverPtr = se;
		serverPtr = &se->next;
	}
	*serverPtr = 0;

#ifdef HAVE_VTS
	/* don't copy only if all local displays are ordered and have a vt */
	cpvt = False;
	getInitTab();
	for (se = serverList, mtty = maxTTY; se; se = se->next)
		if (se->local) {
			mtty++;
			if (se->vt != mtty) {
				cpvt = True;
				break;
			}
		}
#endif

	for (se = serverList; se; se = se->next) {
		se->argvs = joinArgs( se->argv );
		se->arglvs = joinArgs( se->arglv );
	}

	se1 = 0, cpcmd = cpcmdl = False;
	for (se = serverList; se; se = se->next)
		if (se->local) {
			if (!se1)
				se1 = se;
			else {
				if (strcmp( se1->argvs, se->argvs ))
					cpcmd = True;
				if (strcmp( se1->arglvs, se->arglvs ))
					cpcmdl = True;
			}
		}
	if (se1) {
		putFqVal( "X-:*-Core", "ServerCmd", se1->argvs );
		putFqVal( "X-:*-Core", "ServerArgsLocal", se1->arglvs );
		for (se = serverList; se; se = se->next)
			if (se->local) {
				char sec[32];
				sprintf( sec, "X-%s-Core", se->name );
				if (cpcmd)
					putFqVal( sec, "ServerCmd", se->argvs );
				if (cpcmdl)
					putFqVal( sec, "ServerArgsLocal", se->arglvs );
#ifdef HAVE_VTS
				if (cpvt && se->vt) {
					char vt[8];
					sprintf( vt, "%d", se->vt );
					putFqVal( sec, "ServerVT", vt );
				}
#else
				if (se->console)
					putFqVal( sec, "ServerTTY", se->console );
#endif
			}
	}

	sdpys = rdpys = 0;
	for (se = serverList; se; se = se->next)
		strCat( se->reserve ? &rdpys : &sdpys,
		        se->class2 ? ",%s_%s" : ",%s", se->name, se->class2 );

#ifdef HAVE_VTS
	/* add reserve dpys */
	if (nldpys < 4 && nldpys && !nrdpys)
		for (; nldpys < 4; nldpys++) {
			for (dn = 0; dpymask & (1 << dn); dn++);
			dpymask |= (1 << dn);
			strCat( &rdpys, ",:%d", dn );
		}
#endif

	putFqVal( "General", "StaticServers", sdpys ? sdpys + 1 : "" );
	putFqVal( "General", "ReserveServers", rdpys ? rdpys + 1 : "" );

	if (**value == '/' && inNewDir( *value ) && !use_destdir)
		displace( *value );
}

#ifdef HAVE_VTS
static void
upd_servervts( Entry *ce, Section *cs ATTR_UNUSED )
{
	if (!ce->active) { /* there is only the Global one */
#ifdef __linux__ /* XXX actually, sysvinit */
		getInitTab();
		ASPrintf( (char **)&ce->value, "-%d", maxTTY + 1 );
		ce->active = ce->written = True;
#endif
	}
}

static void
upd_consolettys( Entry *ce, Section *cs ATTR_UNUSED )
{
	if (!ce->active) { /* there is only the Global one */
#ifdef __linux__ /* XXX actually, sysvinit */
		char *buf;
		int i;

		getInitTab();
		for (i = 0, buf = 0; i < 16; i++)
			if (TTYmask & (1 << i))
				strCat( &buf, ",tty%d", i + 1 );
		if (buf) {
			ce->value = buf + 1;
			ce->active = ce->written = True;
		}
#endif
	}
}
#endif

static void
upd_servercmd( Entry *ce, Section *cs ATTR_UNUSED )
{
	StrList *sa;
	FILE *fp;
	char *svr;
	char buf[20000];

	if (!ce->active || oldver >= 0x0204)
		return;
	if (!(sa = splitArgs( ce->value )))
		return;
	ASPrintf( &svr, "%s -help 2>&1", sa->str );
	if (!(fp = popen( svr, "r" )))
		return;
	buf[fread( buf, 1, sizeof(buf) - 1, fp )] = 0;
	pclose( fp );
	if (strstr( buf, "\n-br " ))
		addStr( &sa, "-br" );
	if (strstr( buf, "\n-novtswitch " ))
		addStr( &sa, "-novtswitch" );
	if (strstr( buf, "\n-quiet " ))
		addStr( &sa, "-quiet" );
	ce->value = joinArgs( sa );
	ce->written = True;
}

#ifdef XDMCP
static void
cp_keyfile( Entry *ce, Section *cs ATTR_UNUSED )
{
	if (!ce->active) /* there is only the Global one */
		return;
	if (old_confs)
		linkFile( ce );
	else
		if (!copyFile( ce, 0600, 0 ))
			ce->active = False;
}

static void
mk_xaccess( Entry *ce, Section *cs ATTR_UNUSED )
{
	if (!ce->active) /* there is only the Global one */
		writeFile( def_Xaccess, 0644, def_xaccess );
	else if (old_confs)
		linkFile( ce );
	else
		copyFile( ce, 0644, 0 ); /* don't handle error, it will disable Xdmcp automatically */
}

static void
mk_willing( Entry *ce, Section *cs ATTR_UNUSED )
{
	const char *fname;

	if (!ce->active) /* there is only the Global one */
		goto dflt;
	else {
		if (!(fname = strchr( ce->value, '/' )))
			return; /* obviously in-line (or empty) */
		if (old_scripts || inNewDir( fname ))
			doLinkFile( fname );
		else {
		  dflt:
			ce->value = KDMCONF "/Xwilling";
			ce->active = ce->written = True;
			writeFile( ce->value, 0755, def_willing );
		}
	}
}
#endif

/*
static int
edit_resources( File *file )
{
	// XXX remove any login*, chooser*, ... resources
	return False;
}
*/

static void
cp_resources( Entry *ce, Section *cs ATTR_UNUSED )
{
	if (!ce->active) /* the X-*-Greeter one */
		return;
	if (old_confs)
		linkFile( ce );
	else
		if (!copyFile( ce, 0644, 0/*edit_resources*/ ))
			ce->active = False;
}

static int
delstr( File *fil, const char *pat )
{
	char *p, *pp, *bpp;
	const char *pap, *paap;

	*fil->eof = 0;
	for (p = fil->buf; *p; p++) {
		for (pp = p, pap = pat; ; ) {
			if (!*pap) {
				*p = '\n';
				memcpy( p + 1, pp, fil->eof - pp + 1 );
				fil->eof -= pp - p - 1;
				return True;
			} else if (!memcmp( pap, "*/", 2 )) {
				paap = pap += 2;
				while (!isspace( *pap ))
					pap++;
				if (*pp != '/')
					break;
				for (;;)
					for (bpp = ++pp; *pp != '/'; pp++)
						if (!*pp || isspace( *pp ))
							goto wbrk;
			  wbrk:
				if ((pp - bpp != pap - paap) || memcmp( bpp, paap, pap - paap ))
					break;
			} else if (*pap == '\t') {
				pap++;
				while (*pp == ' ' || *pp == '\t')
					pp++;
			} else if (*pap == '[') {
				pap++;
				for (;;) {
					if (!*pap) {
						fprintf( stderr, "Internal error: unterminated char set\n" );
						exit( 1 );
					}
					if (*pap == *pp) {
						while (*++pap != ']')
							if (!*pap) {
								fprintf( stderr, "Internal error: unterminated char set\n" );
								exit( 1 );
							}
						pap++;
						pp++;
						break;
					}
					if (*++pap == ']')
						goto no;
				}
			} else {
				if (*pap == '\n')
					while (*pp == ' ' || *pp == '\t')
						pp++;
				if (*pap != *pp)
					break;
				pap++;
				pp++;
			}
		}
	  no: ;
	}
	return False;
}

/* XXX
   the UseBackground voodoo will horribly fail, if multiple sections link
   to the same Xsetup file
*/

static int mod_usebg;

static int
edit_setup( File *file )
{
	int chg =
		delstr( file, "\n"
		        "(\n"
		        "  PIDFILE=/var/run/kdmdesktop-$DISPLAY.pid\n"
		        "  */kdmdesktop\t&\n"
		        "  echo $! >$PIDFILE\n"
		        "  wait $!\n"
		        "  rm $PIDFILE\n"
		        ")\t&\n" ) |
		delstr( file, "\n"
		        "*/kdmdesktop\t&\n" ) |
		delstr( file, "\n"
		        "kdmdesktop\t&\n" ) |
		delstr( file, "\n"
		        "kdmdesktop\n" );
	putVal( "UseBackground", chg ? "true" : "false" );
	return chg;
}

static void
mk_setup( Entry *ce, Section *cs )
{
	setSect( reSect( cs->name, "Greeter" ) );
	if (old_scripts || mixed_scripts) {
		if (mod_usebg && *ce->value)
			putVal( "UseBackground", "false" );
		linkFile( ce );
	} else {
		if (ce->active && inNewDir( ce->value )) {
			if (mod_usebg)
				copyFile( ce, 0755, edit_setup );
			else
				linkFile( ce );
		} else {
			ce->value = KDMCONF "/Xsetup";
			ce->active = ce->written = True;
			writeFile( ce->value, 0755, def_setup );
		}
	}
}

static int
edit_startup( File *file )
{
	int chg1 = False, chg2 = False;

	if (mod_usebg &&
	    (delstr( file, "\n"
	             "PIDFILE=/var/run/kdmdesktop-$DISPLAY.pid\n"
	             "if [[] -f $PIDFILE ] ; then\n"
	             "	   kill `cat $PIDFILE`\n"
	             "fi\n" ) ||
	     delstr( file, "\n"
	             "PIDFILE=/var/run/kdmdesktop-$DISPLAY.pid\n"
	             "test -f $PIDFILE && kill `cat $PIDFILE`\n" )))
		chg1 = True;
	if (oldver < 0x0203) {
		chg2 =
#ifdef _AIX
			delstr( file, "\n"
"# We create a pseudodevice for finger.  (host:0 becomes [kx]dm/host_0)\n" );
"# Without it, finger errors out with \"Cannot stat /dev/host:0\".\n"
"#\n"
"if [[] -f /usr/lib/X11/xdm/sessreg ]; then\n"
"  devname=`echo $DISPLAY | /usr/bin/sed -e 's/[[]:\\.]/_/g' | /usr/bin/cut -c1-8`\n"
"  hostname=`echo $DISPLAY | /usr/bin/cut -d':' -f1`\n"
"\n"
"  if [[] -z \"$devname\" ]; then\n"
"    devname=\"unknown\"\n"
"  fi\n"
"  if [[] ! -d /dev/[kx]dm ]; then\n"
"    /usr/bin/mkdir /dev/[kx]dm\n"
"    /usr/bin/chmod 755 /dev/[kx]dm\n"
"  fi\n"
"  /usr/bin/touch /dev/[kx]dm/$devname\n"
"  /usr/bin/chmod 644 /dev/[kx]dm/$devname\n"
"\n"
"  if [[] -z \"$hostname\" ]; then\n"
"    exec /usr/lib/X11/xdm/sessreg -a -l [kx]dm/$devname $USER\n"
"  else\n"
"    exec /usr/lib/X11/xdm/sessreg -a -l [kx]dm/$devname -h $hostname $USER\n"
"  fi\n"
"fi\n") |
#else
# ifdef BSD
			delstr( file, "\n"
"exec sessreg -a -l $DISPLAY -x */Xservers -u " _PATH_UTMP " $USER\n" ) |
# endif
#endif /* _AIX */
			delstr( file, "\n"
"exec sessreg -a -l $DISPLAY"
#ifdef BSD
" -x */Xservers"
#endif
" $USER\n" ) |
			delstr( file, "\n"
"exec sessreg -a -l $DISPLAY -u /var/run/utmp -x */Xservers $USER\n" );
		putVal( "UseSessReg", chg2 ? "true" : "false" );
	}
	return chg1 | chg2;
}

static void
mk_startup( Entry *ce, Section *cs )
{
	setSect( cs->name );
	if (old_scripts || mixed_scripts)
		linkFile( ce );
	else {
		if (ce->active && inNewDir( ce->value )) {
			if (mod_usebg || oldver < 0x0203)
				copyFile( ce, 0755, edit_startup );
			else
				linkFile( ce );
		} else {
			ce->value = KDMCONF "/Xstartup";
			ce->active = ce->written = True;
			writeFile( ce->value, 0755, def_startup );
		}
	}
}

static int
edit_reset( File *file )
{
	return
#ifdef _AIX
		delstr( file, "\n"
"if [[] -f /usr/lib/X11/xdm/sessreg ]; then\n"
"  devname=`echo $DISPLAY | /usr/bin/sed -e 's/[[]:\\.]/_/g' | /usr/bin/cut -c1-8`\n"
"  exec /usr/lib/X11/xdm/sessreg -d -l [kx]dm/$devname $USER\n"
"fi\n" ) |
#else
# ifdef BSD
		delstr( file, "\n"
"exec sessreg -d -l $DISPLAY -x */Xservers -u " _PATH_UTMP " $USER\n" ) |
# endif
#endif /* _AIX */
		delstr( file, "\n"
"exec sessreg -d -l $DISPLAY"
# ifdef BSD
" -x */Xservers"
# endif
" $USER\n" ) |
		delstr( file, "\n"
"exec sessreg -d -l $DISPLAY -u /var/run/utmp -x */Xservers $USER\n" );
}

static void
mk_reset( Entry *ce, Section *cs ATTR_UNUSED )
{
	if (old_scripts || mixed_scripts)
		linkFile( ce );
	else {
		if (ce->active && inNewDir( ce->value )) {
			if (oldver < 0x0203)
				copyFile( ce, 0755, edit_reset );
			else
				linkFile( ce );
		} else {
			ce->value = KDMCONF "/Xreset";
			ce->active = ce->written = True;
			writeFile( ce->value, 0755, def_reset );
		}
	}
}

static void
mk_session( Entry *ce, Section *cs ATTR_UNUSED )
{
	char *def_session;
	const char *tmpf;

	if ((old_scripts || (ce->active && inNewDir( ce->value ))) &&
	    oldver >= 0x202)
		linkFile( ce );
	else {
		tmpf = locate( "mktemp" ) ?
		           "`mktemp /tmp/xsess-env-XXXXXX`" :
		           locate( "tempfile" ) ?
		               "`tempfile`" :
		               "$HOME/.xsession-env-$DISPLAY";
		ASPrintf( &def_session, "%s%s%s", def_session1, tmpf, def_session2 );
		ce->value = KDMCONF "/Xsession";
		ce->active = ce->written = True;
		writeFile( ce->value, 0755, def_session );
	}
}

static void
upd_language( Entry *ce, Section *cs ATTR_UNUSED )
{
	if (!strcmp( ce->value, "C" ))
		ce->value = (char *)"en_US";
}

static void
upd_guistyle( Entry *ce, Section *cs ATTR_UNUSED )
{
	if (!strcmp( ce->value, "Motif+" ))
		ce->value = (char *)"MotifPlus";
	else if (!strcmp( ce->value, "KDE" ))
		ce->value = (char *)"Default";
}

static void
upd_showusers( Entry *ce, Section *cs )
{
	if (!strcmp( ce->value, "All" ))
		ce->value = (char *)"NotHidden";
	else if (!strcmp( ce->value, "None" )) {
		if (ce->active)
			putFqVal( cs->name, "UserList", "false" );
		ce->value = (char *)"Selected";
		ce->active = False;
		ce->written = True;
	}
}

static const char *defminuid, *defmaxuid;

static void
upd_minshowuid( Entry *ce, Section *cs ATTR_UNUSED )
{
	if (!ce->active) {
		ce->value = defminuid;
		ce->active = ce->written = True;
	}
}

static void
upd_maxshowuid( Entry *ce, Section *cs ATTR_UNUSED )
{
	if (!ce->active) {
		ce->value = defmaxuid;
		ce->active = ce->written = True;
	}
}

static void
upd_hiddenusers( Entry *ce, Section *cs ATTR_UNUSED )
{
	char *nv;
	const char *msu, *pt, *et;
	struct passwd *pw;
	unsigned minuid, maxuid;
	char nbuf[128];

	if (!ce->active)
		return;

	msu = getFqVal( cs->name, "MinShowUID", "0" );
	sscanf( msu, "%u", &minuid );
	msu = getFqVal( cs->name, "MaxShowUID", "65535" );
	sscanf( msu, "%u", &maxuid );

	nv = 0;
	pt = ce->value;
	for (;;) {
		et = strpbrk( pt, ";," );
		if (et) {
			memcpy( nbuf, pt, et - pt );
			nbuf[et - pt] = 0;
		} else
			strcpy( nbuf, pt );
		if ((pw = getpwnam( nbuf ))) {
			if (!pw->pw_uid ||
			    (pw->pw_uid >= minuid && pw->pw_uid <= maxuid))
			{
				if (nv)
					strCat( &nv, ",%s", nbuf );
				else
					nv = mstrdup( nbuf );
			}
		}
		if (!et)
			break;
		pt = et + 1;
	}
	ce->value = nv ? nv : "";
}

static void
upd_forgingseed( Entry *ce, Section *cs ATTR_UNUSED )
{
	if (!ce->active) {
		ASPrintf( (char **)&ce->value, "%d", time( 0 ) );
		ce->active = ce->written = True;
	}
}

static void
upd_fifodir( Entry *ce, Section *cs ATTR_UNUSED )
{
	const char *dir;
	struct stat st;

	if (use_destdir)
		return;
	dir = ce->active ? ce->value : def_FifoDir;
	stat( dir, &st );
	chmod( dir, st.st_mode | 0755 );
}

static void
upd_datadir( Entry *ce, Section *cs ATTR_UNUSED )
{
	char *oldsts, *newsts;
	const char *dir;

	if (use_destdir)
		return;
	dir = ce->active ? ce->value : def_DataDir;
	if (mkdirp( dir, 0755, "data", 0 ) && oldkde) {
		ASPrintf( &oldsts, "%s/kdm/kdmsts", oldkde );
		ASPrintf( &newsts, "%s/kdmsts", dir );
		rename( oldsts, newsts );
	}
}

static void
upd_userlogfile( Entry *ce, Section *cs ATTR_UNUSED )
{
	char *p;

	if ((p = strstr( ce->value, "%s" )))
		ASPrintf( (char **)&ce->value, "%.*s%%d%s", p - ce->value, ce->value, p + 2 );
}

/*
 * Copy single file.
 * Do not overwrite existing target.
 * Do not complain if source cannot be read.
 */
static void
copyPlainFile( const char *from, const char *to )
{
	File file;
	int fd;

	if (readFile( &file, from )) {
		if ((fd = open( to, O_WRONLY | O_CREAT | O_EXCL, 0644 )) >= 0) {
			write( fd, file.buf, file.eof - file.buf );
			close( fd );
		} else if (errno != EEXIST)
			fprintf( stderr, "Warning: cannot create %s\n", to );
		freeBuf( &file );
	}
}

static int
copyDir( const char *from, const char *to )
{
	DIR *dir;
	struct dirent *ent;
	struct stat st;
	char bn[PATH_MAX], bo[PATH_MAX];

	if (!(dir = opendir( from )))
		return False;
	while ((ent = readdir( dir ))) {
		if (!strcmp( ent->d_name, "." ) || !strcmp( ent->d_name, ".." ))
			continue;
		sprintf( bo, "%s/%s", from, ent->d_name );
		if (stat( bo, &st ) || !S_ISREG( st.st_mode ))
			continue;
		sprintf( bn, "%s/%s", to, ent->d_name );
		copyPlainFile( bo, bn );
	}
	closedir( dir );
	return True;
}

static void
upd_facedir( Entry *ce, Section *cs ATTR_UNUSED )
{
	char *oldpic, *newpic, *olddir;
	struct passwd *pw;

	if (use_destdir)
		return;
	if (oldkdepfx) { /* Do we have a previous install? */
		/* This would be the prev install's default location */
		ASPrintf( &olddir, "%s/share/apps/kdm/faces", oldkdepfx );
		if (ce->active && strcmp( olddir, ce->value ))
			/* Not default location, so don't touch the setting. */
			return;
		/* Default location, so absorb it. */
		ce->active = False;
		/* Don't copy if old dir == new new. */
		if (!strcmp( olddir, def_FaceDir ))
			olddir = 0;
	} else
		olddir = 0;
	if (mkdirp( def_FaceDir, 0755, "user face", True )) {
		const char *defpic = def_FaceDir "/.default.face.icon";
		const char *rootpic = def_FaceDir "/root.face.icon";
		if (oldkde && (!olddir || !copyDir( olddir, def_FaceDir )) &&
		    oldver < 0x0201) /* This isn't exact - didn't inc version. */
		{
			setpwent();
			while ((pw = getpwent()))
				if (strcmp( pw->pw_name, "root" )) {
					ASPrintf( &oldpic, "%s/share/apps/kdm/pics/users/%s.png",
					          oldkdepfx, pw->pw_name );
					ASPrintf( &newpic, def_FaceDir "/%s.face.icon", pw->pw_name );
					rename( oldpic, newpic );
					free( newpic );
					free( oldpic );
				}
			endpwent();
			ASPrintf( &oldpic, "%s/share/apps/kdm/pics/users/default.png", oldkdepfx );
			if (!rename( oldpic, defpic ))
				defpic = 0;
			ASPrintf( &oldpic, "%s/share/apps/kdm/pics/users/root.png", oldkdepfx );
			if (!rename( oldpic, rootpic ))
				rootpic = 0;
		}
		if (defpic) {
			ASPrintf( &oldpic, "%s/default1.png", facesrc );
			copyPlainFile( oldpic, defpic );
		}
		if (rootpic) {
			ASPrintf( &oldpic, "%s/root1.png", facesrc );
			copyPlainFile( oldpic, rootpic );
		}
	}
}

static void
upd_sessionsdirs( Entry *ce, Section *cs ATTR_UNUSED )
{
	StrList *sl, *sp;
	int olen;
	char olddir[PATH_MAX];

	if (ce->written) {
		sprintf( olddir, "%s/share/apps/kdm/sessions", oldkdepfx );
		olen = strlen( oldkde );
		sl = splitList( ce->value );
		for (sp = sl; sp; sp = sp->next) {
			if (!strcmp( sp->str, olddir ))
				sp->str = def_SessionsDirs;
			else if (!memcmp( sp->str, oldkde, olen ) &&
			         !memcmp( sp->str + olen, "/kdm/", 5 ))
			{
				char nd[PATH_MAX];
				sprintf( nd, "%s%s", newdir, sp->str + olen + 4 );
				mkdirp( nd, 0755, "sessions", False );
				copyDir( sp->str, nd );
				ASPrintf( (char **)&sp->str, KDMCONF "%s", sp->str + olen + 4 );
			}
		}
		ce->value = joinList( sl );
	} else {
		char nd[PATH_MAX];
		sprintf( nd, "%s/sessions", newdir );
		mkdirp( nd, 0755, "sessions", False );
	}
}

static void
upd_preloader( Entry *ce, Section *cs ATTR_UNUSED )
{
	if (ce->written) { /* implies oldkde != 0 */
		char *oldpl;
		ASPrintf( &oldpl, "%s/bin/preloadkde", oldkdepfx );
		if (!strcmp( ce->value, oldpl ))
			ce->value = (char *)KDE_BINDIR "/preloadkde";
		free( oldpl );
	}
}


CONF_GEN_ENTRIES

static Sect *
findSect( const char *name )
{
	const char *p;
	int i;

	p = strrchr( name, '-' );
	if (!p)
		p = name;
	for (i = 0; i < as(allSects); i++)
		if (!strcmp( allSects[i]->name, p ))
			return allSects[i];
	fprintf( stderr, "Internal error: unknown section %s\n", name );
	exit( 1 );
}

static Ent *
findEnt( Sect *sect, const char *key )
{
	int i;

	for (i = 0; i < sect->nents; i++)
		if (!strcmp( sect->ents[i].key, key ))
			return sect->ents + i;
	fprintf( stderr, "Internal error: unknown key %s in section %s\n",
	         key, sect->name );
	exit( 1 );
}


/*
 * defaults
 */

typedef struct DEnt {
	const char *key;
	const char *value;
	int active;
} DEnt;

typedef struct DSect {
	const char *name;
	DEnt *ents;
	int nents;
	const char *comment;
} DSect;

CONF_GEN_EXAMPLE

static void
makeDefaultConfig( void )
{
	Section *cs, **csp;
	Entry *ce, **cep;
	int sc, ec;

	for (csp = &config, sc = 0; sc < as(dAllSects); csp = &(cs->next), sc++) {
		cs = mcalloc( sizeof(*cs) );
		*csp = cs;
		cs->spec = findSect( dAllSects[sc].name );
		cs->name = dAllSects[sc].name;
		cs->comment = dAllSects[sc].comment;
		for (cep = &(cs->ents), ec = 0; ec < dAllSects[sc].nents;
		     cep = &(ce->next), ec++)
		{
			ce = mcalloc( sizeof(*ce) );
			*cep = ce;
			ce->spec = findEnt( cs->spec, dAllSects[sc].ents[ec].key );
			ce->value = dAllSects[sc].ents[ec].value;
			ce->active = dAllSects[sc].ents[ec].active;
		}
	}
}


/*
 * read rc file structure
 */

typedef struct REntry {
	struct REntry *next;
	const char *key;
	char *value;
} REntry;

typedef struct RSection {
	struct RSection *next;
	const char *name;
	REntry *ents;
} RSection;

static RSection *
readConfig( const char *fname )
{
	char *nstr;
	char *s, *e, *st, *en, *ek, *sl;
	RSection *rootsec = 0, *cursec;
	REntry *curent;
	int nlen;
	int line, sectmoan;
	File file;

	if (!readFile( &file, fname ))
		return 0;
	usedFile( fname );

	for (s = file.buf, line = 0, cursec = 0, sectmoan = 1; s < file.eof; s++) {
		line++;

		while ((s < file.eof) && isspace( *s ) && (*s != '\n'))
			s++;

		if ((s < file.eof) && ((*s == '\n') || (*s == '#'))) {
		  sktoeol:
			while ((s < file.eof) && (*s != '\n'))
				s++;
			continue;
		}
		sl = s;

		if (*s == '[') {
			while ((s < file.eof) && (*s != '\n'))
				s++;
			e = s - 1;
			while ((e > sl) && isspace( *e ))
				e--;
			if (*e != ']') {
				fprintf( stderr, "Invalid section header at %s:%d\n",
				         fname, line );
				continue;
			}
			sectmoan = False;
			nstr = sl + 1;
			nlen = e - nstr;
			for (cursec = rootsec; cursec; cursec = cursec->next)
				if (!memcmp( nstr, cursec->name, nlen ) &&
				    !cursec->name[nlen])
				{
#if 0 /* not our business ... */
					fprintf( stderr, "Warning: Multiple occurrences of section "
					         "[%.*s] in %s. Consider merging them.\n",
					         nlen, nstr, fname );
#endif
					goto secfnd;
				}
			cursec = mmalloc( sizeof(*cursec) );
			ASPrintf( (char **)&cursec->name, "%.*s", nlen, nstr );
			cursec->ents = 0;
			cursec->next = rootsec;
			rootsec = cursec;
		  secfnd:
			continue;
		}

		if (!cursec) {
			if (sectmoan) {
				sectmoan = False;
				fprintf( stderr, "Entry outside any section at %s:%d",
				         fname, line );
			}
			goto sktoeol;
		}

		for (; (s < file.eof) && (*s != '\n'); s++)
			if (*s == '=')
				goto haveeq;
		fprintf( stderr, "Invalid entry (missing '=') at %s:%d\n", fname, line );
		continue;

	  haveeq:
		for (ek = s - 1;; ek--) {
			if (ek < sl) {
				fprintf( stderr, "Invalid entry (empty key) at %s:%d\n",
				         fname, line );
				goto sktoeol;
			}
			if (!isspace( *ek ))
				break;
		}

		s++;
		while ((s < file.eof) && isspace( *s ) && (*s != '\n'))
			s++;
		st = s;
		while ((s < file.eof) && (*s != '\n'))
			s++;
		for (en = s - 1; en >= st && isspace( *en ); en--);

		nstr = sl;
		nlen = ek - sl + 1;
		for (curent = cursec->ents; curent; curent = curent->next)
			if (!memcmp( nstr, curent->key, nlen ) &&
			    !curent->key[nlen]) {
				fprintf( stderr, "Multiple occurrences of key '%s' in section "
				         "[%s] of %s.\n", curent->key, cursec->name, fname );
				goto keyfnd;
			}
		curent = mmalloc( sizeof(*curent) );
		ASPrintf( (char **)&curent->key, "%.*s", nlen, nstr );
		ASPrintf( (char **)&curent->value, "%.*s", en - st + 1, st );
		curent->next = cursec->ents;
		cursec->ents = curent;
	  keyfnd:
		continue;
	}
	return rootsec;
}


static int
mergeKdmRcOld( const char *path )
{
	char *p;
	struct stat st;

	ASPrintf( &p, "%s/kdmrc", path );
	if (stat( p, &st )) {
		free( p );
		return False;
	}
	printf( "Information: ignoring pre-existing kdmrc %s from kde < 2.2\n", p );
	free( p );
	return True;
}

typedef struct {
	const char *sect, *key, *def;
	int (*cond)( void );
} FDefs;

/*
 * The idea is to determine how exactly the pre-existing config would
 * have been interpreted, so no default configs are created where builtin
 * defaults were used so far.
 */
static void
applyDefs( FDefs *chgdef, int ndefs, const char *path )
{
	char *p;
	int i;

	for (i = 0; i < ndefs; i++)
		if (!getFqVal( chgdef[i].sect, chgdef[i].key, 0 ) &&
		    (!chgdef[i].cond || chgdef[i].cond()))
		{
			ASPrintf( &p, chgdef[i].def, path );
			putFqVal( chgdef[i].sect, chgdef[i].key, p );
			free( p );
		}
}

static int
if_usebg (void)
{
	return isTrue( getFqVal( "X-*-Greeter", "UseBackground", "true" ) );
}

static FDefs kdmdefs_all[] = {
#ifdef XDMCP
{ "Xdmcp", "Xaccess", "%s/kdm/Xaccess", 0 },
{ "Xdmcp", "Willing", "", 0 },
#endif
{ "X-*-Core", "Setup", "", 0 },
{ "X-*-Core", "Startup", "", 0 },
{ "X-*-Core", "Reset", "", 0 },
{ "X-*-Core", "Session", XBINDIR "/xterm -ls -T", 0 },
{ "X-*-Greeter", "BackgroundCfg", "%s/kdm/backgroundrc", if_usebg },
};

typedef struct KUpdEnt {
	const char *okey, *nsec, *nkey;
	void (*func)( const char *sect, char **value );
} KUpdEnt;

typedef struct KUpdSec {
	const char *osec;
	KUpdEnt *ents;
	int nents;
} KUpdSec;

#ifdef XDMCP
static void
P_EnableChooser( const char *sect ATTR_UNUSED, char **value )
{
	*value = (char *)(isTrue( *value ) ? "DefaultLocal" : "LocalOnly");
}
#endif

static void
P_UseLilo( const char *sect ATTR_UNUSED, char **value )
{
	*value = (char *)(isTrue( *value ) ? "Lilo" : "None");
}

static void
P_EchoMode( const char *sect ATTR_UNUSED, char **value )
{
	*value = (char *)(!strcmp( *value, "NoEcho" ) ? "false" : "true");
}

CONF_GEN_KMERGE

static int
mergeKdmRcNewer( const char *path, int obsRet )
{
	char *p;
	const char *cp, *sec, *key;
	RSection *rootsect, *cs;
	REntry *ce;
	int i, j, ma, mi;
	static char sname[64];

	ASPrintf( &p, "%s/kdm/kdmrc", path );
	if (!(rootsect = readConfig( p ))) {
		free( p );
		return False;
	}
	for (cs = rootsect; cs; cs = cs->next)
		if (!strcmp( cs->name, "General" ))
			for (ce = cs->ents; ce; ce = ce->next)
				if (!strcmp( ce->key, "ConfigVersion" ))
					goto gotcfgv;
	printf( "Information: ignoring pre-existing kdmrc %s from kde < 3.1\n", p );
	free( p );
	return obsRet;
  gotcfgv:
	sscanf( ce->value, "%d.%d", &ma, &mi );
	oldver = (ma << 8) | mi;
	printf( "Information: reading pre-existing kdmrc %s (config version %d.%d)\n",
	        p, ma, mi );
	free( p );

	for (cs = rootsect; cs; cs = cs->next) {
			cp = strrchr( cs->name, '-' );
			if (!cp)
				cp = cs->name;
			else if (cs->name[0] != 'X' || cs->name[1] != '-')
				goto dropsec;
			for (i = 0; i < as(kupsects); i++)
				if (!strcmp( cp, kupsects[i].osec )) {
					for (ce = cs->ents; ce; ce = ce->next) {
						for (j = 0; j < kupsects[i].nents; j++)
							if (!strcmp( ce->key, kupsects[i].ents[j].okey )) {
								if (kupsects[i].ents[j].nsec == (char *)-1) {
									kupsects[i].ents[j].func( 0, &ce->value );
									goto gotkey;
								}
								if (!kupsects[i].ents[j].nsec)
									sec = cs->name;
								else {
									sec = sname;
									sprintf( sname, "%.*s-%s",
									         (int)(cp - cs->name), cs->name,
									         kupsects[i].ents[j].nsec );
								}
								if (!kupsects[i].ents[j].nkey)
									key = ce->key;
								else
									key = kupsects[i].ents[j].nkey;
								if (kupsects[i].ents[j].func)
									kupsects[i].ents[j].func( sec, &ce->value );
								putFqVal( sec, key, ce->value );
								goto gotkey;
							}
						printf( "Information: dropping key %s from section [%s]\n",
						        ce->key, cs->name );
					  gotkey: ;
					}
					goto gotsec;
				}
		  dropsec:
			printf( "Information: dropping section [%s]\n", cs->name );
		  gotsec: ;
	}

	applyDefs( kdmdefs_all, as(kdmdefs_all), path );

	return True;
}


typedef struct XResEnt {
	const char *xname;
	const char *ksec, *kname;
	void (*func)( const char *sect, char **value );
} XResEnt;

static void
handleXdmVal( const char *dpy, const char *key, char *value,
              const XResEnt *ents, int nents )
{
	const char *kname;
	int i;
	char knameb[80], sname[80];

	for (i = 0; i < nents; i++)
		if (!strcmp( key, ents[i].xname ) ||
		    (key[0] == toupper( ents[i].xname[0] ) &&
		     !strcmp( key + 1, ents[i].xname + 1 )))
		{
			if (ents[i].ksec == (char *)-1) {
				ents[i].func( 0, &value );
				break;
			}
			sprintf( sname, ents[i].ksec, dpy );
			if (ents[i].kname)
				kname = ents[i].kname;
			else {
				kname = knameb;
				sprintf( knameb, "%c%s",
				         toupper( ents[i].xname[0] ), ents[i].xname + 1 );
			}
			if (ents[i].func)
				ents[i].func( sname, &value );
			putFqVal( sname, kname, value );
			break;
		}
}

static void
P_list( const char *sect ATTR_UNUSED, char **value )
{
	int is, d, s;
	char *st;

	for (st = *value, is = False, d = s = 0; st[s]; s++)
		if (st[s] == ' ' || st[s] == '\t') {
			if (!is)
				st[d++] = ',';
			is = True;
		} else {
			st[d++] = st[s];
			is = False;
		}
	st[d] = 0;
}

static void
P_authDir( const char *sect ATTR_UNUSED, char **value )
{
	int l;

	l = strlen( *value );
	if (l < 4) {
		*value = 0;
		return;
	}
	if ((*value)[l-1] == '/')
		(*value)[--l] = 0;
	if (!strncmp( *value, "/tmp/", 5 ) ||
	    !strncmp( *value, "/var/tmp/", 9 ))
	{
		printf( "Warning: Resetting inappropriate value %s for AuthDir to default\n",
		        *value );
		*value = 0;
		return;
	}
	if ((l >= 4 && !strcmp( *value + l - 4, "/tmp" )) ||
	    (l >= 6 && !strcmp( *value + l - 6, "/xauth" )) ||
	    (l >= 8 && !strcmp( *value + l - 8, "/authdir" )) ||
	    (l >= 10 && !strcmp( *value + l - 10, "/authfiles" )))
		return;
	ASPrintf( value, "%s/authdir", *value );
}

static void
P_openDelay( const char *sect, char **value )
{
	putFqVal( sect, "ServerTimeout", *value );
}

static void
P_noPassUsers( const char *sect, char **value ATTR_UNUSED )
{
	putFqVal( sect, "NoPassEnable", "true" );
}

static void
P_autoUser( const char *sect, char **value ATTR_UNUSED )
{
	putFqVal( sect, "AutoLoginEnable", "true" );
}

#ifdef XDMCP
static void
P_requestPort( const char *sect, char **value )
{
	if (!strcmp( *value, "0" )) {
		*value = 0;
		putFqVal( sect, "Enable", "false" );
	} else
		putFqVal( sect, "Enable", "true" );
}
#endif

static int kdmrcmode = 0644;

static void
P_autoPass( const char *sect ATTR_UNUSED, char **value ATTR_UNUSED )
{
	kdmrcmode = 0600;
}

CONF_GEN_XMERGE

static XrmQuark XrmQString, empty = NULLQUARK;

static Bool
dumpEntry( XrmDatabase *db ATTR_UNUSED,
           XrmBindingList bindings,
           XrmQuarkList quarks,
           XrmRepresentation *type,
           XrmValuePtr value,
           XPointer data ATTR_UNUSED )
{
	const char *dpy, *key;
	int el, hasu;
	char dpybuf[80];

	if (*type != XrmQString)
		return False;
	if (*bindings == XrmBindLoosely ||
	    strcmp( XrmQuarkToString (*quarks), "DisplayManager" ))
		return False;
	bindings++, quarks++;
	if (!*quarks)
		return False;
	if (*bindings != XrmBindLoosely && !quarks[1]) { /* DM.foo */
		key = XrmQuarkToString (*quarks);
		handleXdmVal( 0, key, value->addr, globents, as(globents) );
		return False;
	} else if (*bindings == XrmBindLoosely && !quarks[1]) { /* DM*bar */
		dpy = "*";
		key = XrmQuarkToString (*quarks);
	} else if (*bindings != XrmBindLoosely && quarks[1] &&
	           *bindings != XrmBindLoosely && !quarks[2])
	{ /* DM.foo.bar */
		dpy = dpybuf + 4;
		strcpy( dpybuf + 4, XrmQuarkToString (*quarks) );
		for (hasu = False, el = 4; dpybuf[el]; el++)
			if (dpybuf[el] == '_')
				hasu = True;
		if (!hasu/* && isupper (dpy[0])*/) {
			dpy = dpybuf;
			memcpy( dpybuf, "*:*_", 4 );
		} else {
			for (; --el >= 0; )
				if (dpybuf[el] == '_') {
					dpybuf[el] = ':';
					for (; --el >= 4; )
						if (dpybuf[el] == '_')
							dpybuf[el] = '.';
					break;
				}
		}
		key = XrmQuarkToString (quarks[1]);
	} else
		return False;
	handleXdmVal( dpy, key, value->addr, dpyents, as(dpyents) );
	return False;
}

static FDefs xdmdefs[] = {
#ifdef XDMCP
{ "Xdmcp", "Xaccess", "%s/Xaccess", 0 },
{ "Xdmcp", "Willing", "", 0 },
#endif
{ "X-*-Core", "Setup", "", 0 },
{ "X-*-Core", "Startup", "", 0 },
{ "X-*-Core", "Reset", "", 0 },
{ "X-*-Core", "Session", "", 0 },
};

static int
mergeXdmCfg( const char *path )
{
	char *p;
	XrmDatabase db;

	ASPrintf( &p, "%s/xdm-config", path );
	if ((db = XrmGetFileDatabase( p ))) {
		printf( "Information: reading xdm config file %s\n", p );
		usedFile( p );
		free( p );
		XrmEnumerateDatabase( db, &empty, &empty, XrmEnumAllLevels,
		                      dumpEntry, (XPointer)0 );
		applyDefs( xdmdefs, as(xdmdefs), path );
		mod_usebg = True;
		return True;
	}
	free( p );
	return False;
}

static void
fprintfLineWrap( FILE *f, const char *msg, ... )
{
	char *txt, *ftxt, *line;
	va_list ap;
	int col, lword, fspace;

	va_start( ap, msg );
	VASPrintf( &txt, msg, ap );
	va_end( ap );
	ftxt = 0;
	for (line = txt, col = 0, lword = fspace = -1; line[col]; ) {
		if (line[col] == '\n') {
			strCat( &ftxt, "%.*s", ++col, line );
			line += col;
			col = 0;
			lword = fspace = -1;
			continue;
		} else if (line[col] == ' ') {
			if (lword >= 0) {
				fspace = col;
				lword = -1;
			}
		} else {
			if (lword < 0)
				lword = col;
			if (col >= 78 && fspace >= 0) {
				strCat( &ftxt, "%.*s\n", fspace, line );
				line += lword;
				col -= lword;
				lword = 0;
				fspace = -1;
			}
		}
		col++;
	}
	free( txt );
	if (ftxt) {
		fputs( ftxt, f );
		free( ftxt );
	}
}


static const char *oldkdes[] = {
	KDE_CONFDIR,
	"/opt/kde4/share/config",
	"/usr/local/kde4/share/config",

	"/opt/kde/share/config",
	"/usr/local/kde/share/config",
	"/usr/local/share/config",
	"/usr/share/config",

	"/opt/kde3/share/config",
	"/usr/local/kde3/share/config",
};

static const char *oldxdms[] = {
	"/etc/X11/xdm",
	XLIBDIR "/xdm",
};

int main( int argc, char **argv )
{
	const char **where;
	FILE *f;
	StrList *fp;
	Section *cs;
	Entry *ce, **cep;
	int i, ap, locals, foreigns;
	int no_old_xdm = 0, no_old_kde = 0;
	struct stat st;

	for (ap = 1; ap < argc; ap++) {
		if (!strcmp( argv[ap], "--help" )) {
			printf(
"genkdmconf - generate configuration files for kdm\n"
"\n"
"If an older xdm/kdm configuration is found, its config files are \"absorbed\";\n"
"if it lives in the new target directory, its scripts are reused (and possibly\n"
"modified) as well, otherwise the scripts are ignored and default scripts are\n"
"installed.\n"
"\n"
"options:\n"
"  --in /path/to/new/kdm-config-dir\n"
"    In which directory to put the new configuration. You can use this\n"
"    to support a $(DESTDIR), but not to change the final location of\n"
"    the installation - the paths inside the files are not affected.\n"
"    Default is " KDMCONF ".\n"
"  --old-xdm /path/to/old/xdm-dir\n"
"    Where to look for the config files of an xdm.\n"
"    Default is to scan /etc/X11/xdm & $XLIBDIR/xdm.\n"
"    Note that you possibly need to use --no-old-kde to make this take effect.\n"
"  --old-kde /path/to/old/kde-config-dir\n"
"    Where to look for the kdmrc of a previously installed kdm.\n"
"    Default is to scan " KDE_CONFDIR " and\n"
"    {/usr,/usr/local,{/opt,/usr/local}/{kde4,kde,kde3}}/share/config.\n"
"  --no-old\n"
"    Do not look at older xdm/kdm configurations, just create default config.\n"
"  --no-old-xdm\n"
"    Do not look at older xdm configurations.\n"
"  --no-old-kde\n"
"    Do not look at older kdm configurations.\n"
"  --old-scripts\n"
"    Directly use all scripts from the older xdm/kdm configuration.\n"
"  --no-old-scripts\n"
"    Do not use scripts from the older xdm/kdm configuration even if it lives\n"
"    in the new target directory.\n"
"  --old-confs\n"
"    Directly use all ancillary config files from the older xdm/kdm\n"
"    configuration. This is usually a bad idea.\n"
"  --no-backup\n"
"    Overwrite/delete old config files instead of backing them up.\n"
"  --no-in-notice\n"
"    Do not put the notice about --in being used into the generated README.\n"
);
			exit( 0 );
		}
		if (!strcmp( argv[ap], "--no-old" )) {
			no_old = True;
			continue;
		}
		if (!strcmp( argv[ap], "--old-scripts" )) {
			old_scripts = True;
			continue;
		}
		if (!strcmp( argv[ap], "--no-old-scripts" )) {
			no_old_scripts = True;
			continue;
		}
		if (!strcmp( argv[ap], "--old-confs" )) {
			old_confs = True;
			continue;
		}
		if (!strcmp( argv[ap], "--no-old-xdm" )) {
			no_old_xdm = True;
			continue;
		}
		if (!strcmp( argv[ap], "--no-old-kde" )) {
			no_old_kde = True;
			continue;
		}
		if (!strcmp( argv[ap], "--no-backup" )) {
			no_backup = True;
			continue;
		}
		if (!strcmp( argv[ap], "--no-in-notice" )) {
			no_in_notice = True;
			continue;
		}
		where = 0;
		if (!strcmp( argv[ap], "--in" ))
			where = &newdir;
		else if (!strcmp( argv[ap], "--old-xdm" ))
			where = &oldxdm;
		else if (!strcmp( argv[ap], "--old-kde" ))
			where = &oldkde;
		else if (!strcmp( argv[ap], "--face-src" ))
			where = &facesrc;
		else {
			fprintf( stderr, "Unknown command line option '%s', try --help\n", argv[ap] );
			exit( 1 );
		}
		if (ap + 1 == argc || argv[ap + 1][0] == '-') {
			fprintf( stderr, "Missing argument to option '%s', try --help\n", argv[ap] );
			exit( 1 );
		}
		*where = argv[++ap];
	}
	if (memcmp( newdir, KDMCONF, sizeof(KDMCONF) ))
		use_destdir = True;

	if (!mkdirp( newdir, 0755, "target", True ))
		exit( 1 );

	makeDefaultConfig();
	if (no_old) {
		DIR *dir;
		StrList *bfl = 0;
		if ((dir = opendir( newdir ))) {
			struct dirent *ent;
			char bn[PATH_MAX];
			while ((ent = readdir( dir ))) {
				int l;
				if (!strcmp( ent->d_name, "." ) || !strcmp( ent->d_name, ".." ))
					continue;
				l = sprintf( bn, "%s/%s", newdir, ent->d_name ); /* cannot overflow (kernel would not allow the creation of a longer path) */
				if (!stat( bn, &st ) && !S_ISREG( st.st_mode ))
					continue;
				if (no_backup || !memcmp( bn + l - 4, ".bak", 5 ))
					unlink( bn );
				else
					addStr( &bfl, bn );
			}
			closedir( dir );
			for (; bfl; bfl = bfl->next)
				displace( bfl->str );
		}
	} else {
		if (oldkde) {
			if (!mergeKdmRcNewer( oldkde, True ) && !mergeKdmRcOld( oldkde )) {
				fprintf( stderr,
				         "Cannot read pre-existing kdmrc at specified location\n" );
				oldkde = 0;
			}
		} else if (!no_old_kde) {
			for (i = 0; i < as(oldkdes); i++) {
				if (i && !strcmp( oldkdes[0], oldkdes[i] ))
					continue;
				if (mergeKdmRcNewer( oldkdes[i], 0 )) {
					oldkde = oldkdes[i];
					break;
				}
				mergeKdmRcOld( oldkdes[i] ); /* only prints a message */
			}
		}
		if (oldkde) {
#define SHR_CONF "/share/config"
			int olen = strlen( oldkde );
			if (olen < (int)sizeof(SHR_CONF) ||
				memcmp( oldkde + olen - sizeof(SHR_CONF) + 1,
						SHR_CONF, sizeof(SHR_CONF) ))
			{
				fprintf( stderr,
				         "Warning: --old-kde does not end with " SHR_CONF ". "
				         "Might wreak havoc.\n" );
				oldkdepfx = oldkde;
			} else
				ASPrintf( (char **)&oldkdepfx,
				           "%.*s", olen - sizeof(SHR_CONF) + 1, oldkde );
			oldxdm = 0;
		} else if (!no_old_xdm) {
			XrmInitialize();
			XrmQString = XrmPermStringToQuark( "String" );
			if (oldxdm) {
				if (!mergeXdmCfg( oldxdm )) {
					fprintf( stderr,
					         "Cannot read xdm-config at specified location\n" );
					oldxdm = 0;
				}
			} else
				for (i = 0; i < as(oldxdms); i++)
					if (mergeXdmCfg( oldxdms[i] )) {
						oldxdm = oldxdms[i];
						break;
					}
		}
	}
	/*
	 * How to proceed with pre-existing scripts (which are named in the config):
	 * - old_scripts set or some scripts in new target already => keep 'em
	 * - no_old_scripts set or all scripts outside new target => pretend that
	 *   the old config did not reference them in the first place
	 */
	if (no_old_scripts)
		goto no_old_s;
	if (!old_scripts) {
		locals = foreigns = False;
		for (cs = config; cs; cs = cs->next)
			if (!strcmp( cs->spec->name, "-Core" )) {
				for (ce = cs->ents; ce; ce = ce->next)
					if (ce->active &&
					    (!strcmp( ce->spec->key, "Setup" ) ||
					     !strcmp( ce->spec->key, "Startup" ) ||
					     !strcmp( ce->spec->key, "Reset" )))
					{
						if (inNewDir( ce->value ))
							locals = True;
						else
							foreigns = True;
					}
			}
		if (foreigns) {
			if (locals) {
				fprintf( stderr,
				         "Warning: both local and foreign scripts referenced. "
				         "Will not touch any.\n" );
				mixed_scripts = True;
			} else {
			  no_old_s:
				for (cs = config; cs; cs = cs->next) {
					if (!strcmp( cs->spec->name, "Xdmcp" )) {
						for (ce = cs->ents; ce; ce = ce->next)
							if (!strcmp( ce->spec->key, "Willing" ))
								ce->active = ce->written = False;
					} else if (!strcmp( cs->spec->name, "-Core" )) {
						for (cep = &cs->ents; (ce = *cep); ) {
							if (ce->active &&
							    (!strcmp( ce->spec->key, "Setup" ) ||
							     !strcmp( ce->spec->key, "Startup" ) ||
							     !strcmp( ce->spec->key, "Reset" ) ||
							     !strcmp( ce->spec->key, "Session" )))
							{
								if (!memcmp( cs->name, "X-*-", 4 ))
									ce->active = ce->written = False;
								else {
									*cep = ce->next;
									free( ce );
									continue;
								}
							}
							cep = &ce->next;
						}
					}
				}
			}
		}
	}
#ifdef __linux__
	if (!stat( "/etc/debian_version", &st )) { /* debian */
		defminuid = "1000";
		defmaxuid = "29999";
	} else if (!stat( "/usr/portage", &st )) { /* gentoo */
		defminuid = "1000";
		defmaxuid = "65000";
	} else if (!stat( "/etc/mandrake-release", &st )) { /* mandrake - check before redhat! */
		defminuid = "500";
		defmaxuid = "65000";
	} else if (!stat( "/etc/redhat-release", &st )) { /* redhat */
		defminuid = "100";
		defmaxuid = "65000";
	} else /* if (!stat( "/etc/SuSE-release", &st )) */ { /* suse */
		defminuid = "500";
		defmaxuid = "65000";
	}
#else
	defminuid = "1000";
	defmaxuid = "65000";
#endif
	for (i = 0; i < CONF_MAX_PRIO; i++)
		for (cs = config; cs; cs = cs->next)
			for (ce = cs->ents; ce; ce = ce->next)
				if (ce->spec->func && i == ce->spec->prio)
					ce->spec->func( ce, cs );
	f = createFile( "kdmrc", kdmrcmode );
	writeKdmrc( f );
	fclose( f );

	f = createFile( "README", 0644 );
	fprintf( f,
"This automatically generated configuration consists of the following files:\n" );
	fprintf( f, "- " KDMCONF "/kdmrc\n" );
	for (fp = aflist; fp; fp = fp->next)
		fprintf( f, "- %s\n", fp->str );
	if (use_destdir && !no_in_notice)
		fprintfLineWrap( f,
"All files destined for " KDMCONF " were actually saved in %s; "
"this config will not be workable until moved in place.\n", newdir );
	if (uflist || eflist || cflist || lflist) {
		fprintf( f,
"\n"
"This config was derived from existing files. As the used algorithms are\n"
"pretty dumb, it may be broken.\n" );
		if (uflist) {
			fprintf( f,
"Information from these files was extracted:\n" );
			for (fp = uflist; fp; fp = fp->next)
				fprintf( f, "- %s\n", fp->str );
		}
		if (lflist) {
			fprintf( f,
"These files were directly incorporated:\n" );
			for (fp = lflist; fp; fp = fp->next)
				fprintf( f, "- %s\n", fp->str );
		}
		if (cflist) {
			fprintf( f,
"These files were copied verbatim:\n" );
			for (fp = cflist; fp; fp = fp->next)
				fprintf( f, "- %s\n", fp->str );
		}
		if (eflist) {
			fprintf( f,
"These files were copied with modifications:\n" );
			for (fp = eflist; fp; fp = fp->next)
				fprintf( f, "- %s\n", fp->str );
		}
		if (!no_backup && !use_destdir)
			fprintf( f,
"Old files that would have been overwritten were renamed to <oldname>.bak.\n" );
	}
	fprintf( f,
"\nTry 'genkdmconf --help' if you want to generate another configuration.\n"
"\nYou may delete this README.\n" );
	fclose( f );

	return 0;
}
