コード例 #1
0
void command_adminlogin(char *params)
{
    char adminpass[31];
    char rootpass[31];
    char buffer[256];
    char *realadminpass = config_getoption("ADMIN_PASS");
    if (sscanf(params, "%30s %30s", adminpass, rootpass) < 2)
        loginfailed();
    if (!realadminpass[0])
        loginfailed();
    if (strcmp(crypt(adminpass, realadminpass), realadminpass))
        loginfailed();
    /* Admin password is right */
    strcpy(user, "root");
    init_userinfo();
    if (checkpass(rootpass))
        loginfailed();
    /* Root password is right as well */
    signal(SIGALRM, SIG_IGN);
    control_printf(SL_SUCCESS, "230 Administrative login successful.");
    bftpd_log("Administrative login SUCCESSFUL\n");
    while (fgets(buffer, sizeof(buffer), stdin)) {
        admin_parsecmd(buffer);
    }
    exit(0);
}
コード例 #2
0
/*
 * record_failed_login: generic "login failed" interface function
 */
void
record_failed_login(const char *user, const char *ttyname)
{
	char *hostname = get_canonical_hostname(options.use_dns);

	if (geteuid() != 0)
		return;

	aix_setauthdb(user);
#  ifdef AIX_LOGINFAILED_4ARG
	loginfailed((char *)user, hostname, (char *)ttyname, AUDIT_FAIL_AUTH);
#  else
	loginfailed((char *)user, hostname, (char *)ttyname);
#  endif
}
コード例 #3
0
ファイル: port-aix.c プロジェクト: 2014-class/freerouter
/*
 * record_failed_login: generic "login failed" interface function
 */
void
record_failed_login(const char *user, const char *hostname, const char *ttyname)
{
	if (geteuid() != 0)
		return;

	aix_setauthdb(user);
#   ifdef AIX_LOGINFAILED_4ARG
	loginfailed((char *)user, (char *)hostname, (char *)ttyname,
	    AUDIT_FAIL_AUTH);
#   else
	loginfailed((char *)user, (char *)hostname, (char *)ttyname);
#   endif
	aix_restoreauthdb();
}
コード例 #4
0
void
auth_log(Authctxt *authctxt, int authenticated, char *method, char *info)
{
	void (*authlog) (const char *fmt,...) = verbose;
	char *authmsg;

	/* Raise logging level */
	if (authenticated == 1 ||
	    !authctxt->valid ||
	    authctxt->failures >= AUTH_FAIL_LOG ||
	    strcmp(method, "password") == 0)
		authlog = log;

	if (authctxt->postponed)
		authmsg = "Postponed";
	else
		authmsg = authenticated ? "Accepted" : "Failed";

	authlog("%s %s for %s%.100s from %.200s port %d%s",
	    authmsg,
	    method,
	    authctxt->valid ? "" : "illegal user ",
	    authctxt->user,
	    get_remote_ipaddr(),
	    get_remote_port(),
	    info);

#ifdef WITH_AIXAUTHENTICATE
	if (authenticated == 0 && strcmp(method, "password") == 0)
	    loginfailed(authctxt->user,
		get_canonical_hostname(options.verify_reverse_mapping),
		"ssh");
#endif /* WITH_AIXAUTHENTICATE */

}
コード例 #5
0
ファイル: client.c プロジェクト: jschwartzenberg/kicker
static int
#if defined(USE_PAM) || defined(_AIX)
isNoPassAllowed( const char *un )
{
	struct passwd *pw = 0;
# ifdef HAVE_GETSPNAM /* (sic!) - not USESHADOW */
	struct spwd *spw;
# endif
#else
isNoPassAllowed( const char *un, struct passwd *pw )
{
#endif
	struct group *gr;
	char **fp;
	int hg;

	if (!*un)
		return 0;

	if (cursource != PWSRC_MANUAL)
		return 1;

	for (hg = 0, fp = td->noPassUsers; *fp; fp++)
		if (**fp == '@')
			hg = 1;
		else if (!strcmp( un, *fp ))
			return 1;
		else if (!strcmp( "*", *fp )) {
#if defined(USE_PAM) || defined(_AIX)
			if (!(pw = getpwnam( un )))
				return 0;
			if (pw->pw_passwd[0] == '!' || pw->pw_passwd[0] == '*')
				continue;
# ifdef HAVE_GETSPNAM /* (sic!) - not USESHADOW */
			if ((spw = getspnam( un )) &&
			    (spw->sp_pwdp[0] == '!' || spw->sp_pwdp[0] == '*'))
					continue;
# endif
#endif
			if (pw->pw_uid)
				return 1;
		}

#if defined(USE_PAM) || defined(_AIX)
	if (hg && (pw || (pw = getpwnam( un )))) {
#else
	if (hg) {
#endif
		for (setgrent(); (gr = getgrent()); )
			for (fp = td->noPassUsers; *fp; fp++)
				if (**fp == '@' && !strcmp( gr->gr_name, *fp + 1 )) {
					if (pw->pw_gid == gr->gr_gid) {
						endgrent();
						return 1;
					}
					for (; *gr->gr_mem; gr->gr_mem++)
						if (!strcmp( un, *gr->gr_mem )) {
							endgrent();
							return 1;
						}
				}
		endgrent();
	}

	return 0;
}

#if !defined(USE_PAM) && !defined(_AIX) && defined(HAVE_SETUSERCONTEXT)
# define LC_RET0 do { login_close(lc); return 0; } while(0)
#else
# define LC_RET0 return 0
#endif

int
verify( GConvFunc gconv, int rootok )
{
#ifdef USE_PAM
	const char *psrv;
	struct pam_data pdata;
	int pretc, pnopass;
	char psrvb[64];
#elif defined(_AIX)
	char *msg, *curret;
	int i, reenter;
#else
	struct stat st;
	const char *nolg;
	char *buf;
	int fd;
# ifdef HAVE_GETUSERSHELL
	char *s;
# endif
# if defined(HAVE_STRUCT_PASSWD_PW_EXPIRE) || defined(USESHADOW)
	int tim, expir, warntime, quietlog;
# endif
#endif

	debug( "verify ...\n" );

#ifdef USE_PAM

	pnopass = FALSE;
	if (!strcmp( curtype, "classic" )) {
		if (!gconv( GCONV_USER, 0 ))
			return 0;
		if (isNoPassAllowed( curuser )) {
			gconv( GCONV_PASS_ND, 0 );
			if (!*curpass) {
				pnopass = TRUE;
				sprintf( psrvb, "%.31s-np", PAMService );
				psrv = psrvb;
			} else
				psrv = PAMService;
		} else
			psrv = PAMService;
		pdata.usecur = TRUE;
	} else {
		sprintf( psrvb, "%.31s-%.31s", PAMService, curtype );
		psrv = psrvb;
		pdata.usecur = FALSE;
	}
	pdata.gconv = gconv;
	if (!doPAMAuth( psrv, &pdata ))
		return 0;

#elif defined(_AIX)

	if ((td->displayType & d_location) == dForeign) {
		char *tmpch;
		strncpy( hostname, td->name, sizeof(hostname) - 1 );
		hostname[sizeof(hostname)-1] = '\0';
		if ((tmpch = strchr( hostname, ':' )))
			*tmpch = '\0';
	} else
		hostname[0] = '\0';

	/* tty names should only be 15 characters long */
# if 0
	for (i = 0; i < 15 && td->name[i]; i++) {
		if (td->name[i] == ':' || td->name[i] == '.')
			tty[i] = '_';
		else
			tty[i] = td->name[i];
	}
	tty[i] = '\0';
# else
	memcpy( tty, "/dev/xdm/", 9 );
	for (i = 0; i < 6 && td->name[i]; i++) {
		if (td->name[i] == ':' || td->name[i] == '.')
			tty[9 + i] = '_';
		else
			tty[9 + i] = td->name[i];
	}
	tty[9 + i] = '\0';
# endif

	if (!strcmp( curtype, "classic" )) {
		if (!gconv( GCONV_USER, 0 ))
			return 0;
		if (isNoPassAllowed( curuser )) {
			gconv( GCONV_PASS_ND, 0 );
			if (!*curpass) {
				debug( "accepting despite empty password\n" );
				goto done;
			}
		} else
			if (!gconv( GCONV_PASS, 0 ))
				return 0;
		enduserdb();
		msg = NULL;
		if ((i = authenticate( curuser, curpass, &reenter, &msg ))) {
			debug( "authenticate() failed: %s\n", msg );
			if (msg)
				free( msg );
			loginfailed( curuser, hostname, tty );
			if (i == ENOENT || i == ESAD)
				V_RET_AUTH;
			else
				V_RET_FAIL( 0 );
		}
		if (reenter) {
			logError( "authenticate() requests more data: %s\n", msg );
			free( msg );
			V_RET_FAIL( 0 );
		}
	} else if (!strcmp( curtype, "generic" )) {
		if (!gconv( GCONV_USER, 0 ))
			return 0;
		for (curret = 0;;) {
			msg = NULL;
			if ((i = authenticate( curuser, curret, &reenter, &msg ))) {
				debug( "authenticate() failed: %s\n", msg );
				if (msg)
					free( msg );
				loginfailed( curuser, hostname, tty );
				if (i == ENOENT || i == ESAD)
					V_RET_AUTH;
				else
					V_RET_FAIL( 0 );
			}
			if (curret)
				free( curret );
			if (!reenter)
				break;
			if (!(curret = gconv( GCONV_HIDDEN, msg )))
				return 0;
			free( msg );
		}
	} else {
		logError( "Unsupported authentication type %\"s requested\n", curtype );
		V_RET_FAIL( 0 );
	}
	if (msg) {
		displayStr( V_MSG_INFO, msg );
		free( msg );
	}

  done:

#else

	if (strcmp( curtype, "classic" )) {
		logError( "Unsupported authentication type %\"s requested\n", curtype );
		V_RET_FAIL( 0 );
	}

	if (!gconv( GCONV_USER, 0 ))
		return 0;

	if (!(p = getpwnam( curuser ))) {
		debug( "getpwnam() failed.\n" );
		gconv( GCONV_PASS, 0 );
		V_RET_AUTH;
	}
	if (p->pw_passwd[0] == '!' || p->pw_passwd[0] == '*') {
		debug( "account is locked\n" );
		gconv( GCONV_PASS, 0 );
		V_RET_AUTH;
	}

# ifdef USESHADOW
	if ((sp = getspnam( curuser ))) {
		p->pw_passwd = sp->sp_pwdp;
		if (p->pw_passwd[0] == '!' || p->pw_passwd[0] == '*') {
			debug( "account is locked\n" );
			gconv( GCONV_PASS, 0 );
			V_RET_AUTH;
		}
	} else
		debug( "getspnam() failed: %m. Are you root?\n" );
# endif

	if (!*p->pw_passwd) {
		if (!td->allowNullPasswd) {
			debug( "denying user with empty password\n" );
			gconv( GCONV_PASS, 0 );
			V_RET_AUTH;
		}
		goto nplogin;
	}

	if (isNoPassAllowed( curuser, p )) {
	  nplogin:
		gconv( GCONV_PASS_ND, 0 );
		if (!*curpass) {
			debug( "accepting password-less login\n" );
			goto done;
		}
	} else
		if (!gconv( GCONV_PASS, 0 ))
			return 0;

# ifdef KERBEROS
	if (p->pw_uid) {
		int ret;
		char realm[REALM_SZ];

		if (krb_get_lrealm( realm, 1 )) {
			logError( "Cannot get KerberosIV realm.\n" );
			V_RET_FAIL( 0 );
		}

		sprintf( krbtkfile, "%s.%.*s", TKT_ROOT, MAXPATHLEN - strlen( TKT_ROOT ) - 2, td->name );
		krb_set_tkt_string( krbtkfile );
		unlink( krbtkfile );

		ret = krb_verify_user( curuser, "", realm, curpass, 1, "rcmd" );
		if (ret == KSUCCESS) {
			chown( krbtkfile, p->pw_uid, p->pw_gid );
			debug( "KerberosIV verify succeeded\n" );
			goto done;
		} else if (ret != KDC_PR_UNKNOWN && ret != SKDC_CANT) {
			logError( "KerberosIV verification failure %\"s for %s\n",
			          krb_get_err_text( ret ), curuser );
			krbtkfile[0] = '\0';
			V_RET_FAIL( 0 );
		}
		debug( "KerberosIV verify failed: %s\n", krb_get_err_text( ret ) );
	}
	krbtkfile[0] = '\0';
# endif	 /* KERBEROS */

# if defined(ultrix) || defined(__ultrix__)
	if (authenticate_user( p, curpass, NULL ) < 0)
# elif defined(HAVE_PW_ENCRYPT)
	if (strcmp( pw_encrypt( curpass, p->pw_passwd ), p->pw_passwd ))
# elif defined(HAVE_CRYPT)
	if (strcmp( crypt( curpass, p->pw_passwd ), p->pw_passwd ))
# else
	if (strcmp( curpass, p->pw_passwd ))
# endif
	{
		debug( "password verify failed\n" );
		V_RET_AUTH;
	}

  done:

#endif /* !defined(USE_PAM) && !defined(_AIX) */

	debug( "restrict %s ...\n", curuser );

#if defined(USE_PAM) || defined(_AIX)
	if (!(p = getpwnam( curuser ))) {
		logError( "getpwnam(%s) failed.\n", curuser );
		V_RET_FAIL( 0 );
	}
#endif
	if (!p->pw_uid) {
		if (!rootok && !td->allowRootLogin)
			V_RET_FAIL( "Root logins are not allowed" );
		return 1; /* don't deny root to log in */
	}

#ifdef USE_PAM

	debug( " pam_acct_mgmt() ...\n" );
	pretc = pam_acct_mgmt( pamh, 0 );
	reInitErrorLog();
	debug( " pam_acct_mgmt() returned: %s\n", pam_strerror( pamh, pretc ) );
	if (pretc == PAM_NEW_AUTHTOK_REQD) {
		pdata.usecur = FALSE;
		pdata.gconv = conv_interact;
		/* pam will have output a message already, so no prepareErrorGreet() */
		if (gconv != conv_interact || pnopass) {
			pam_end( pamh, PAM_SUCCESS );
			pamh = 0;
			gSendInt( V_CHTOK_AUTH );
			/* this cannot auth the wrong user, as only classic auths get here */
			while (!doPAMAuth( PAMService, &pdata ))
				if (pdata.abort)
					return 0;
			gSendInt( V_PRE_OK );
		} else
			gSendInt( V_CHTOK );
		for (;;) {
			debug( " pam_chauthtok() ...\n" );
			pretc = pam_chauthtok( pamh, PAM_CHANGE_EXPIRED_AUTHTOK );
			reInitErrorLog();
			debug( " pam_chauthtok() returned: %s\n", pam_strerror( pamh, pretc ) );
			if (pdata.abort) {
				pam_end( pamh, PAM_SUCCESS );
				pamh = 0;
				return 0;
			}
			if (pretc == PAM_SUCCESS)
				break;
			/* effectively there is only PAM_AUTHTOK_ERR */
			gSendInt( V_FAIL );
		}
		if (curpass)
			free( curpass );
		curpass = newpass;
		newpass = 0;
	} else if (pretc != PAM_SUCCESS) {
		pam_end( pamh, pretc );
		pamh = 0;
		V_RET_AUTH;
	}

#elif defined(_AIX) /* USE_PAM */

	msg = NULL;
	if (loginrestrictions( curuser,
	                       ((td->displayType & d_location) == dForeign) ? S_RLOGIN : S_LOGIN,
	                       tty, &msg ) == -1)
	{
		debug( "loginrestrictions() - %s\n", msg ? msg : "error" );
		loginfailed( curuser, hostname, tty );
		prepareErrorGreet();
		if (msg) {
			displayStr( V_MSG_ERR, msg );
			free( msg );
		}
		gSendInt( V_AUTH );
		return 0;
	}
	if (msg)
		free( (void *)msg );

#endif /* USE_PAM || _AIX */

#ifndef _AIX

# ifdef HAVE_SETUSERCONTEXT
#  ifdef HAVE_LOGIN_GETCLASS
	lc = login_getclass( p->pw_class );
#  else
	lc = login_getpwclass( p );
#  endif
	if (!lc)
		V_RET_FAIL( 0 );

	p->pw_shell = login_getcapstr( lc, "shell", p->pw_shell, p->pw_shell );
# endif

# ifndef USE_PAM

/* restrict_expired */
#  if defined(HAVE_STRUCT_PASSWD_PW_EXPIRE) || defined(USESHADOW)

#   if !defined(HAVE_STRUCT_PASSWD_PW_EXPIRE) || (!defined(HAVE_SETUSERCONTEXT) && defined(USESHADOW))
	if (sp)
#   endif
	{

#   define DEFAULT_WARN	(2L * 7L)  /* Two weeks */

		tim = time( NULL ) / 86400L;

#   ifdef HAVE_SETUSERCONTEXT
		quietlog = login_getcapbool( lc, "hushlogin", 0 );
		warntime = login_getcaptime( lc, "warnexpire",
		                             DEFAULT_WARN * 86400L,
		                             DEFAULT_WARN * 86400L ) / 86400L;
#   else
		quietlog = 0;
#    ifdef USESHADOW
		warntime = sp->sp_warn != -1 ? sp->sp_warn : DEFAULT_WARN;
#    else
		warntime = DEFAULT_WARN;
#    endif
#   endif

#   ifdef HAVE_STRUCT_PASSWD_PW_EXPIRE
		if (p->pw_expire) {
			expir = p->pw_expire / 86400L;
#   else
		if (sp->sp_expire != -1) {
			expir = sp->sp_expire;
#   endif
			if (tim > expir) {
				displayStr( V_MSG_ERR,
				            "Your account has expired;"
				            " please contact your system administrator" );
				gSendInt( V_FAIL );
				LC_RET0;
			} else if (tim > (expir - warntime) && !quietlog) {
				displayMsg( V_MSG_INFO,
				            "Warning: your account will expire in %d day(s)",
				            expir - tim );
			}
		}

#   ifdef HAVE_STRUCT_PASSWD_PW_EXPIRE
		if (p->pw_change) {
			expir = p->pw_change / 86400L;
#   else
		if (!sp->sp_lstchg) {
			displayStr( V_MSG_ERR,
			            "You are required to change your password immediately"
			            " (root enforced)" );
			/* XXX todo password change */
			gSendInt( V_FAIL );
			LC_RET0;
		} else if (sp->sp_max != -1) {
			expir = sp->sp_lstchg + sp->sp_max;
			if (sp->sp_inact != -1 && tim > expir + sp->sp_inact) {
				displayStr( V_MSG_ERR,
				            "Your account has expired;"
				            " please contact your system administrator" );
				gSendInt( V_FAIL );
				LC_RET0;
			}
#   endif
			if (tim > expir) {
				displayStr( V_MSG_ERR,
				            "You are required to change your password immediately"
				            " (password aged)" );
				/* XXX todo password change */
				gSendInt( V_FAIL );
				LC_RET0;
			} else if (tim > (expir - warntime) && !quietlog) {
				displayMsg( V_MSG_INFO,
				            "Warning: your password will expire in %d day(s)",
				            expir - tim );
			}
		}

	}

#  endif /* HAVE_STRUCT_PASSWD_PW_EXPIRE || USESHADOW */

/* restrict_nologin */
#  ifndef _PATH_NOLOGIN
#   define _PATH_NOLOGIN "/etc/nologin"
#  endif

	if ((
#  ifdef HAVE_SETUSERCONTEXT
	     /* Do we ignore a nologin file? */
	     !login_getcapbool( lc, "ignorenologin", 0 )) &&
	    (!stat( (nolg = login_getcapstr( lc, "nologin", "", NULL )), &st ) ||
#  endif
		 !stat( (nolg = _PATH_NOLOGIN), &st )))
	{
		if (st.st_size && (fd = open( nolg, O_RDONLY )) >= 0) {
			if ((buf = Malloc( st.st_size + 1 ))) {
				if (read( fd, buf, st.st_size ) == st.st_size) {
					close( fd );
					buf[st.st_size] = 0;
					displayStr( V_MSG_ERR, buf );
					free( buf );
					gSendInt( V_FAIL );
					LC_RET0;
				}
				free( buf );
			}
			close( fd );
		}
		displayStr( V_MSG_ERR,
		            "Logins are not allowed at the moment.\nTry again later" );
		gSendInt( V_FAIL );
		LC_RET0;
	}

/* restrict_time */
#  if defined(HAVE_SETUSERCONTEXT) && defined(HAVE_AUTH_TIMEOK)
	if (!auth_timeok( lc, time( NULL ) )) {
		displayStr( V_MSG_ERR,
		            "You are not allowed to login at the moment" );
		gSendInt( V_FAIL );
		LC_RET0;
	}
#  endif

#  ifdef HAVE_GETUSERSHELL
	for (;;) {
		if (!(s = getusershell())) {
			debug( "shell not in /etc/shells\n" );
			endusershell();
			V_RET_FAIL( "Your login shell is not listed in /etc/shells" );
		}
		if (!strcmp( s, p->pw_shell )) {
			endusershell();
			break;
		}
	}
#  endif

# endif /* !USE_PAM */

/* restrict_nohome */
# ifdef HAVE_SETUSERCONTEXT
	if (login_getcapbool( lc, "requirehome", 0 )) {
		struct stat st;
		if (!*p->pw_dir || stat( p->pw_dir, &st ) || st.st_uid != p->pw_uid) {
			displayStr( V_MSG_ERR, "Home folder not available" );
			gSendInt( V_FAIL );
			LC_RET0;
		}
	}
# endif

#endif /* !_AIX */

	return 1;

}


static const char *envvars[] = {
	"TZ", /* SYSV and SVR4, but never hurts */
#ifdef _AIX
	"AUTHSTATE", /* for kerberos */
#endif
	NULL
};


#if defined(USE_PAM) && defined(HAVE_INITGROUPS)
static int num_saved_gids;
static gid_t *saved_gids;

static int
saveGids( void )
{
	num_saved_gids = getgroups( 0, 0 );
	if (!(saved_gids = Malloc( sizeof(gid_t) * num_saved_gids )))
		return 0;
	if (getgroups( num_saved_gids, saved_gids ) < 0) {
		logError( "saving groups failed: %m\n" );
		return 0;
	}
	return 1;
}

static int
restoreGids( void )
{
	if (setgroups( num_saved_gids, saved_gids ) < 0) {
		logError( "restoring groups failed: %m\n" );
		return 0;
	}
	if (setgid( p->pw_gid ) < 0) {
		logError( "restoring gid failed: %m\n" );
		return 0;
	}
	return 1;
}
#endif /* USE_PAM && HAVE_INITGROUPS */

static int
resetGids( void )
{
#ifdef HAVE_INITGROUPS
	if (setgroups( 0, &p->pw_gid /* anything */ ) < 0) {
		logError( "restoring groups failed: %m\n" );
		return 0;
	}
#endif
	if (setgid( 0 ) < 0) {
		logError( "restoring gid failed: %m\n" );
		return 0;
	}
	return 1;
}

static int
setGid( const char *name, int gid )
{
	if (setgid( gid ) < 0) {
		logError( "setgid(%d) (user %s) failed: %m\n", gid, name );
		return 0;
	}
#ifdef HAVE_INITGROUPS
	if (initgroups( name, gid ) < 0) {
		logError( "initgroups for %s failed: %m\n", name );
		setgid( 0 );
		return 0;
	}
#endif	 /* QNX4 doesn't support multi-groups, no initgroups() */
	return 1;
}

static int
setUid( const char *name, int uid )
{
	if (setuid( uid ) < 0) {
		logError( "setuid(%d) (user %s) failed: %m\n", uid, name );
		return 0;
	}
	return 1;
}

static int
setUser( const char *name, int uid, int gid )
{
	if (setGid( name, gid )) {
		if (setUid( name, uid ))
			return 1;
		resetGids();
	}
	return 0;
}

#if defined(SECURE_RPC) || defined(K5AUTH)
static void
nukeAuth( int len, const char *name )
{
	int i;

	for (i = 0; i < td->authNum; i++)
		if (td->authorizations[i]->name_length == len &&
		    !memcmp( td->authorizations[i]->name, name, len ))
		{
			memcpy( &td->authorizations[i], &td->authorizations[i+1],
			        sizeof(td->authorizations[i]) * (--td->authNum - i) );
			break;
		}
}
#endif

static void
mergeSessionArgs( int cansave )
{
	char *mfname;
	const char *fname;
	int i, needsave;

	mfname = 0;
	fname = ".dmrc";
	if ((!curdmrc || newdmrc) && *dmrcDir)
		if (strApp( &mfname, dmrcDir, "/", curuser, fname, (char *)0 ))
			fname = mfname;
	needsave = 0;
	if (!curdmrc) {
		curdmrc = iniLoad( fname );
		if (!curdmrc) {
			strDup( &curdmrc, "[Desktop]\nSession=default\n" );
			needsave = 1;
		}
	}
	if (newdmrc) {
		curdmrc = iniMerge( curdmrc, newdmrc );
		needsave = 1;
	}
	if (needsave && cansave)
		if (!iniSave( curdmrc, fname ) && errno == ENOENT && mfname) {
			for (i = 0; mfname[i]; i++)
				if (mfname[i] == '/') {
					mfname[i] = 0;
					mkdir( mfname, 0755 );
					mfname[i] = '/';
				}
			iniSave( curdmrc, mfname );
		}
	if (mfname)
		free( mfname );
}

static int
createClientLog( const char *log )
{
	char randstr[32], *randstrp = 0, *lname;
	int lfd;

	for (;;) {
		struct expando macros[] = {
			{ 'd', 0, td->name },
			{ 'u', 0, curuser },
			{ 'r', 0, randstrp },
			{ 0, 0, 0 }
		};
		if (!(lname = expandMacros( log, macros )))
			exit( 1 );
		unlink( lname );
		if ((lfd = open( lname, O_WRONLY|O_CREAT|O_EXCL, 0600 )) >= 0) {
			dup2( lfd, 1 );
			dup2( lfd, 2 );
			close( lfd );
			free( lname );
			return TRUE;
		}
		if (errno != EEXIST || !macros[2].uses) {
			free( lname );
			return FALSE;
		}
		logInfo( "Session log file %s not usable, trying another one.\n",
		         lname );
		free( lname );
		sprintf( randstr, "%d", secureRandom() );
		randstrp = randstr;
	}
}