/*
 * This file is part of DesktopBSD, see the README file.
 *
 * Authors: Reinhard Pointner
 *			 Peter Hofer <ph@desktopbsd.net>
 *			 (C) 2004, 2005
 *
 * Copyright: BSD, see the COPYING file included in this distribution
 *
 */

#include "user.h"

/* System */
#include <sys/types.h>
#include <grp.h>
#include <pwd.h>
#include <login_cap.h>

#include <qstringlist.h>

#include <executor.h>

#define	PW_CMD	"/usr/sbin/pw"


User::User(const struct passwd  & pwd) {
	uid = pwd.pw_uid;
	gid = pwd.pw_gid;
	name = pwd.pw_name;
	comment = pwd.pw_gecos;
	homeDir = pwd.pw_dir;
	shell = pwd.pw_shell;
	locked = (strncmp("*LOCKED*", pwd.pw_passwd, 8) == 0);
}

User &	User::operator = (const User & user) {
	if(this == &user)
		return  *this;

	uid = user.uid;
	gid = user.gid;
	name = user.name;
	comment = user.comment;
	homeDir = user.homeDir;
	shell = user.shell;
	locked = user.locked;
	return  *this;
}

/** Returns FALSE if the given group doesn't exist
  * or the user is not in the group, otherwise TRUE.
  */
bool User::isMemberOf(const QString & group) {
	struct group	*grp = getgrnam(group);
	if(grp == NULL)
		return  false;

	for(uint i = 0;  grp->gr_mem[i] != NULL;  ++i) {
		if(name == grp->gr_mem[i])
			return true;

		if(grp->gr_gid == gid)
			return true;
	}

	return false;
}

/** Changes the authentication method for this user
  * to 'none', which means that the users need not
  * enter a password to log in.
  */
bool User::unsetPassword() {
	const QString	args = " usermod '%1' -w none";
	return Executor::exec(PW_CMD + args.arg(name)) == Executor::NoError;
}

/**
  * Encrypts a given password using crypt(3) and a
  * random salt created by arc4random(3).
  *
  */
QCString User::encrypt(const QCString & upasswd) {
	static char const chars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.";

	int  i;
	char salt[12];
	for(i = 0; i < 8; ++i)
		salt[i] = chars[arc4random() % 63];

	salt[i] = '\0';

	return QCString(crypt(upasswd, salt));
}

/** Changes the user's password. If the password is empty,
  * no password will be required for further logins.
  *
  * @param  newpw  New password.
  *
  * @return TRUE if changing the password succeeded,
  *			otherwise FALSE.
  */
bool User::setPassword(const QString &  newpw) {
	const QString args = " usermod '%1' -H '%2'";

	
	/* Set crypt(3) format first */
	struct passwd *pwd = getpwuid(uid);
	if(pwd == NULL)
		return false;
	login_cap_t *lct = login_getpwclass(pwd);
	if(lct == NULL || login_setcryptfmt(lct, "md5", NULL) == NULL)
		qWarning("Setting crypt(3) format failed");
	login_close(lct);


	QCString encrypted = encrypt(newpw.latin1());
	if(encrypted.isEmpty())
		return false;

	int h[2];
	pipe(h);
	write(h[1], encrypted, encrypted.length() + 1);

	bool success = (Executor::exec(PW_CMD + args.arg(name)
															  .arg(h[0])) == Executor::NoError);

	close(h[0]); close(h[1]);
	return (success && (!locked || lock()));
}

/** Changes the user's comment field that contains
  * the user's full name in most cases.
  */
bool User::setComment(const QString &  comment) {
	const QString args = " usermod '%1' -c '%2'";
	return Executor::exec(PW_CMD + args.arg(name)
												  .arg(comment)) == Executor::NoError;
}


/** Locks the user so he is no longer able to log in.
  *
  * Hint: pw does accomplish this by prepending "*LOCKED*"
  *		 to the encrypted password.
  */
bool User::lock() {
	const QString args = " lock '%1'";
	return (locked = Executor::exec(PW_CMD + args.arg(name)) == Executor::NoError);
}


/** Unlocks a locked user.
  * @see lock()
  */
bool User::unlock() {
	const QString args = " unlock '%1'";
	return (locked = !(Executor::exec(PW_CMD + args.arg(name)) == Executor::NoError));
}


/** Returns TRUE if an user is locked, otherwise FALSE. */
bool User::isLocked() const {
	return locked;
}

/** Sets all groups this user should belong to. The first group
  * in the list will be the user's primary group.
  *
  * Returns TRUE on success, otherwise FALSE.
  *
  * @attention
  *  The underlying pw segfaults when the groups list is empty.
  */
bool User::setGroupMemberships(const QStringList & groups) {
	const QString args = " usermod '%1' -g '%2' -G '%3'";

	QStringList grps = groups;
	QString pgrp = grps.first();

	/* Qt bug workaround: QStringList::join() seems to crash
	 * when pop_front() was used in an empty list.
	 */
	if(!grps.isEmpty())
		grps.pop_front();

	return (Executor::exec(PW_CMD + args.arg(getUsername())
													.arg(pgrp)
													.arg(grps.join(","))) == Executor::NoError);
}

/** Returns all groups this user belongs to. The first
  * group in the list is the primary group.
  */
QStringList User::getGroupMemberships() {
	const char *ascName = name.latin1();
	QStringList  lst;

	QString pGrp;

	setgrent();
	struct group *gr;
	while((gr = getgrent()) != NULL) {
		if(gr->gr_gid == gid)
			pGrp = gr->gr_name;
		else
			for(char **g = gr->gr_mem;  *g != NULL;  ++g)
				if(strcmp(*g, ascName) == 0) {
					lst.prepend(gr->gr_name);
					break;
				}
	}
	endgrent();

	lst.prepend(pGrp);
	return  lst;
}

bool User::addGroupMembership(const QString & grp) {
	QStringList m = getGroupMemberships();
	m.append(grp);
	return setGroupMemberships(m);
}

bool User::removeGroupMembership(const QString & grp) {
	QStringList m = getGroupMemberships();
	m.remove(grp);
	return setGroupMemberships(m);
}
