/*****************************************************************************
 *
 * $Id$
 * Purpose ...............: setuid root version of passwd
 * Shadow Suite (c) ......: Julianne Frances Haugh
 *
 *****************************************************************************
 * Copyright (C) 1997-2005
 *   
 * Michiel Broek	FIDO:		2:280/2802
 * Beekmansbos 10
 * 1971 BV IJmuiden
 * the Netherlands
 *
 * This file is part of MBSE BBS.
 *
 * This BBS is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2, or (at your option) any
 * later version.
 *
 * MBSE BBS 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 MBSE BBS; see the file COPYING.  If not, write to the Free
 * Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 *****************************************************************************/

#include "../config.h"
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#ifdef HAVE_SYS_RESOURCE_H
#include <sys/resource.h>
#endif
#include <grp.h>
#include <pwd.h>
#include <sys/stat.h>
#include <syslog.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <stdlib.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#if defined(SHADOW_PASSWORD)
#include <shadow.h>
#endif
#ifdef HAVE_USERSEC_H
#include <userpw.h>
#include <usersec.h>
#include <userconf.h>
#endif

#if defined(__OpenBSD__) || defined(__NetBSD__)
#include <sys/sysctl.h>
#endif

#include "encrypt.h"
#include "rad64.h"
#include "myname.h"
#include "xmalloc.h"
#include "pwio.h"
#include "shadowio.h"
#include "pw_util.h"
#include "getdef.h"
#include "mbpasswd.h"


#ifndef AGING
#if defined(SHADOW_PASSWORD)
#define AGING   1
#endif
#else
#if !defined(SHADOW_PASSWORD)
#undef AGING
#endif
#endif

static int do_update_age = 0;
#ifndef PAM
static char crypt_passwd[128];  /* The "old-style" password, if present */
static int do_update_pwd = 0;
#endif


#ifdef SHADOW_PASSWORD
static int is_shadow_pwd;
static void check_password(const struct passwd *, const struct spwd *);
#else /* !SHADOW_PASSWORD */
static void check_password(const struct passwd *);
#endif /* !SHADOW_PASSWORD */

#ifndef DAY
#define DAY (24L*3600L)
#endif

#define WEEK (7*DAY)
#define SCALE DAY


/*
 * string to use for the pw_passwd field in /etc/passwd when using
 * shadow passwords - most systems use "x" but there are a few
 * exceptions, so it can be changed here if necessary.  --marekm
 */
#ifndef SHADOW_PASSWD_STRING
#define SHADOW_PASSWD_STRING "x"
#endif


/*
 * Global variables
 */

static char *name;	/* The name of user whose password is being changed */
static char *myname;	/* The current user's name */
static int  force;	/* Force update of locked passwords */


#ifdef _VPOPMAIL_PATH

/*
 * Milliseconds timer, returns 0 on success.
 */
int msleep(int msecs)
{
    int             rc;
    struct timespec req, rem;

    rem.tv_sec = 0;
    rem.tv_nsec = 0;
    req.tv_sec = msecs / 1000;
    req.tv_nsec = (msecs % 1000) * 1000000;

    while (1) {

	rc = nanosleep(&req, &rem);
	if (rc == 0)
	    break;
	if ((errno == EINVAL) || (errno == EFAULT)) {
	    break;
	}

	/*
	 * Error was EINTR, run timer again to complete.
	 */
	req.tv_sec = rem.tv_sec;
	req.tv_nsec = rem.tv_nsec;
	rem.tv_sec = 0;
	rem.tv_nsec = 0;
    }

    return rc;
}



int execute(char **args, char *in, char *out, char *err)
{
    char    buf[PATH_MAX];
    int     i, pid, status = 0, rc = 0;

    memset(&buf, 0, sizeof(buf));
    for (i = 0; i < 16; i++) {
        if (args[i])
            snprintf(buf + strlen(buf), PATH_MAX - strlen(buf), " %s", args[i]);
        else
            break;
    }
    syslog(LOG_WARNING, "Execute:%s", buf);
    fflush(stdout);
    fflush(stderr);

    if ((pid = fork()) == 0) {
	msleep(150);

        if (in) {
            close(0);
            if (open(in, O_RDONLY) != 0) {
                syslog(LOG_WARNING, "Reopen of stdin to %s failed", in);
                _exit(-1);
            }
        }
        if (out) {
            close(1);
            if (open(out, O_WRONLY | O_APPEND | O_CREAT,0600) != 1) {
                syslog(LOG_WARNING, "Reopen of stdout to %s failed", out);
                _exit(-1);
            }
        }
        if (err) {
            close(2);
            if (open(err, O_WRONLY | O_APPEND | O_CREAT,0600) != 2) {
                syslog(LOG_WARNING, "Reopen of stderr to %s failed", err);
                _exit(-1);
            }
        }
        rc = execv(args[0],args);
        syslog(LOG_WARNING, "Exec \"%s\" returned %d", args[0], rc);
        _exit(-1);
    }

    do {
        rc = wait(&status);
    } while (((rc > 0) && (rc != pid)) || ((rc == -1) && (errno == EINTR)));

    return 0;
}
#endif



#if !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__)
static void fail_exit(int status)
{
//	gr_unlock();
#ifdef SHADOWGRP
	if (is_shadow_grp)
		sgr_unlock();
#endif
#ifdef SHADOW_PASSWORD
	if (is_shadow_pwd)
		spw_unlock();
#endif
	pw_unlock();
	exit(status);
}



static void oom(void)
{
	fprintf(stderr, "mbpasswd: out of memory\n");
	fail_exit(E_FAILURE);
}



/*
 * insert_crypt_passwd - add an "old-style" password to authentication string
 * result now malloced to avoid overflow, just in case.  --marekm
 */
static char *insert_crypt_passwd(const char *string, char *passwd)
{
#ifdef AUTH_METHODS
        if (string && *string) {
                char *cp, *result;

                result = xmalloc(strlen(string) + strlen(passwd) + 1);
                cp = result;
                while (*string) {
                        if (string[0] == ';') {
                                *cp++ = *string++;
                        } else if (string[0] == '@') {
                                while (*string && *string != ';')
                                        *cp++ = *string++;
                        } else {
                                while (*passwd)
                                        *cp++ = *passwd++;
                                while (*string && *string != ';')
                                        string++;
                        }
                }
                *cp = '\0';
                return result;
        }
#endif
        return xstrdup(passwd);
}


static char *update_crypt_pw(char *cp)
{
	if (do_update_pwd)
		cp = insert_crypt_passwd(cp, crypt_passwd);

	return cp;
}



/*
 * pwd_init - ignore signals, and set resource limits to safe
 * values.  Call this before modifying password files, so that
 * it is less likely to fail in the middle of operation.
 */
void pwd_init(void)
{
#ifdef HAVE_SYS_RESOURCE_H
	struct rlimit rlim;

#ifdef RLIMIT_CORE
	rlim.rlim_cur = rlim.rlim_max = 0;
	setrlimit(RLIMIT_CORE, &rlim);
#endif
	rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY;
#ifdef RLIMIT_AS
	setrlimit(RLIMIT_AS, &rlim);
#endif
#ifdef RLIMIT_CPU
	setrlimit(RLIMIT_CPU, &rlim);
#endif
#ifdef RLIMIT_DATA
	setrlimit(RLIMIT_DATA, &rlim);
#endif
#ifdef RLIMIT_FSIZE
	setrlimit(RLIMIT_FSIZE, &rlim);
#endif
#ifdef RLIMIT_NOFILE
	setrlimit(RLIMIT_NOFILE, &rlim);
#endif
#ifdef RLIMIT_RSS
	setrlimit(RLIMIT_RSS, &rlim);
#endif
#ifdef RLIMIT_STACK
	setrlimit(RLIMIT_STACK, &rlim);
#endif
#endif /* !HAVE_SYS_RESOURCE_H */

	signal(SIGALRM, SIG_IGN);
	signal(SIGHUP,  SIG_IGN);
	signal(SIGINT,  SIG_IGN);
	signal(SIGPIPE, SIG_IGN);
	signal(SIGQUIT, SIG_IGN);
	signal(SIGTERM, SIG_IGN);
#ifdef SIGTSTP
	signal(SIGTSTP, SIG_IGN);
#endif
#ifdef SIGTTOU
	signal(SIGTTOU, SIG_IGN);
#endif
  
	umask(077);
}
#endif /* not FreeBSD && NetBSD */


/*
 * isexpired - determine if account is expired yet
 *
 *      isexpired calculates the expiration date based on the
 *      password expiration criteria.
 */
#ifdef  SHADOW_PASSWORD
int isexpired(const struct passwd *pw, const struct spwd *sp)
{
#else
int isexpired(const struct passwd *pw)
{
#endif
        long    now;
#ifdef  HAVE_USERSEC_H
        int     minage = 0;
        int     maxage = 10000;
        int     curage = 0;
        struct  userpw  *pu;
#endif

        now = time ((time_t *) 0) / SCALE;

#ifdef  SHADOW_PASSWORD

        if (!sp)
                sp = pwd_to_spwd(pw);

        /*
         * Quick and easy - there is an expired account field
         * along with an inactive account field.  Do the expired
         * one first since it is worse.
         */
        if (sp->sp_expire > 0 && now >= sp->sp_expire)
                return 3;

        /*
         * Last changed date 1970-01-01 (not very likely) means that
         * the password must be changed on next login (passwd -e).
         *
         * The check for "x" is a workaround for RedHat NYS libc bug -
         * if /etc/shadow doesn't exist, getspnam() still succeeds and
         * returns sp_lstchg==0 (must change password) instead of -1!
         */
        if (sp->sp_lstchg == 0 && !strcmp(pw->pw_passwd, SHADOW_PASSWD_STRING))
                return 1;
        if (sp->sp_lstchg > 0 && sp->sp_max >= 0 && sp->sp_inact >= 0 &&
                        now >= sp->sp_lstchg + sp->sp_max + sp->sp_inact)
                return 2;
#endif
#ifdef  HAVE_USERSEC_H  /*{*/
        /*
         * The aging information lives someplace else.  Get it from the
         * login.cfg file
         */

        if (getconfattr (SC_SYS_PASSWD, SC_MINAGE, &minage, SEC_INT))
                minage = -1;

        if (getconfattr (SC_SYS_PASSWD, SC_MAXAGE, &maxage, SEC_INT))
                maxage = -1;

        pu = getuserpw (pw->pw_name);
        curage = (time (0) - pu->upw_lastupdate) / (7*86400L);

        if (maxage != -1 && curage > maxage)
                return 1;
#else   /*} !HAVE_USERSEC_H */

        /*
         * The last and max fields must be present for an account
         * to have an expired password.  A maximum of >10000 days
         * is considered to be infinite.
         */

#ifdef  SHADOW_PASSWORD
        if (sp->sp_lstchg == -1 ||
                        sp->sp_max == -1 || sp->sp_max >= (10000L*DAY/SCALE))
                return 0;
#endif
#ifdef  ATT_AGE
        if (pw->pw_age[0] == '\0' || pw->pw_age[0] == '/')
                return 0;
#endif

        /*
         * Calculate today's day and the day on which the password
         * is going to expire.  If that date has already passed,
         * the password has expired.
         */

#ifdef  SHADOW_PASSWORD
        if (now >= sp->sp_lstchg + sp->sp_max)
                return 1;
#endif
#ifdef  ATT_AGE
        if (a64l (pw->pw_age + 2) + c64i (pw->pw_age[1]) < now / 7)
                return 1;
#endif
#endif  /*} HAVE_USERSEC_H */
        return 0;
}



/*
 * check_password - test a password to see if it can be changed
 *
 *      check_password() sees if the invoker has permission to change the
 *      password for the given user.
 */
#ifdef SHADOW_PASSWORD
static void check_password(const struct passwd *pw, const struct spwd *sp)
{
#else
static void check_password(const struct passwd *pw)
{
#endif
	time_t now, last, ok;
	int exp_status;
#ifdef HAVE_USERSEC_H
	struct userpw *pu;
#endif

#ifdef SHADOW_PASSWORD
	exp_status = isexpired(pw, sp);
#else
	exp_status = isexpired(pw);
#endif

	now = time(NULL);

#ifdef SHADOW_PASSWORD
	/*
	 * If the force flag is set (for new users) this check is skipped.
	 */
	if (force == 1)
		return;

	/*
	 * Expired accounts cannot be changed ever.  Passwords
	 * which are locked may not be changed.  Passwords where
	 * min > max may not be changed.  Passwords which have
	 * been inactive too long cannot be changed.
	 */
	if (sp->sp_pwdp[0] == '!' || exp_status > 1 ||
		(sp->sp_max >= 0 && sp->sp_min > sp->sp_max)) {
		fprintf (stderr, "The password for %s cannot be changed.\n", sp->sp_namp);
		syslog(LOG_WARNING, "password locked for %s", sp->sp_namp);
		closelog();
		exit (E_NOPERM);
	}

	/*
	 * Passwords may only be changed after sp_min time is up.
	 */

	last = sp->sp_lstchg * SCALE;
	ok = last + (sp->sp_min > 0 ? sp->sp_min * SCALE : 0);
#else /* !SHADOW_PASSWORD */
	if (pw->pw_passwd[0] == '!' || exp_status > 1) {
		fprintf (stderr, "The password for %s cannot be changed.\n", pw->pw_name);
		syslog(LOG_WARNING, "password locked for %s", pw->pw_name);
		closelog();
		exit (E_NOPERM);
	}
#ifdef ATT_AGE
        /*
         * Can always be changed if there is no age info
         */

        if (! pw->pw_age[0])
                return;

        last = a64l (pw->pw_age + 2) * WEEK;
        ok = last + c64i (pw->pw_age[1]) * WEEK;
#else   /* !ATT_AGE */
#ifdef HAVE_USERSEC_H
        pu = getuserpw(pw->pw_name);
        last = pu ? pu->upw_lastupdate : 0L;
        ok = last + (minage > 0 ? minage * WEEK : 0);
#else
        last = 0;
        ok = 0;
#endif
#endif /* !ATT_AGE */
#endif /* !SHADOW_PASSWORD */
	if (now < ok) {
		fprintf(stderr, "Sorry, the password for %s cannot be changed yet.\n", pw->pw_name);
		syslog(LOG_WARNING, "now < minimum age for `%s'", pw->pw_name);
		closelog();
		exit (E_NOPERM);
	}
}



/*
 * pwd_to_spwd - create entries for new spwd structure
 *
 *      pwd_to_spwd() creates a new (struct spwd) containing the
 *      information in the pointed-to (struct passwd).
 *
 * This function is borrowed from the Shadow Password Suite.
 */
#ifdef SHADOW_PASSWORD
struct spwd *pwd_to_spwd(const struct passwd *pw)
{
        static struct spwd sp;

        /*
         * Nice, easy parts first.  The name and passwd map directly
         * from the old password structure to the new one.
         */
        sp.sp_namp = pw->pw_name;
        sp.sp_pwdp = pw->pw_passwd;

#ifdef ATT_AGE
        /*
         * AT&T-style password aging maps the sp_min, sp_max, and
         * sp_lstchg information from the pw_age field, which appears
         * after the encrypted password.
         */
        if (pw->pw_age[0]) {
                sp.sp_max = (c64i(pw->pw_age[0]) * WEEK) / SCALE;

                if (pw->pw_age[1])
                        sp.sp_min = (c64i(pw->pw_age[1]) * WEEK) / SCALE;
                else
                        sp.sp_min = (10000L * DAY) / SCALE;

                if (pw->pw_age[1] && pw->pw_age[2])
                        sp.sp_lstchg = (a64l(pw->pw_age + 2) * WEEK) / SCALE;
                else
                        sp.sp_lstchg = time((time_t *) 0) / SCALE;
        } else
#endif
	{
        	/*
        	 * Defaults used if there is no pw_age information.
        	 */
        	sp.sp_min = 0;
        	sp.sp_max = (10000L * DAY) / SCALE;
        	sp.sp_lstchg = time((time_t *) 0) / SCALE;
	}

        /*
         * These fields have no corresponding information in the password
         * file.  They are set to uninitialized values.
         */
        sp.sp_warn = -1;
        sp.sp_expire = -1;
        sp.sp_inact = -1;
        sp.sp_flag = -1;

        return &sp;
}
#endif



/*
 * new_password - validate old password and replace with new
 * (both old and new in global "char crypt_passwd[128]")
 */
static int new_password(const struct passwd *pw, char *newpasswd)
{
	char    *cp;            /* Pointer to getpass() response */
	char    pass[200];      /* New password */
#ifdef HAVE_LIBCRACK_HIST
	int HistUpdate P_((const char *, const char *));
#endif

	snprintf(pass, 200, "%s", newpasswd);

	/*
	 * Encrypt the password, then wipe the cleartext password.
	 */
	cp = pw_encrypt(pass, crypt_make_salt());
	memset(&pass, 0, sizeof(pass));

#ifdef HAVE_LIBCRACK_HIST
	HistUpdate(pw->pw_name, crypt_passwd);
#endif
	STRFCPY(crypt_passwd, cp);
	return 0;
}


#if !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__)

static void update_noshadow(int shadow_locked)
{
	const struct passwd	*pw;
	struct passwd		*npw;
#ifdef ATT_AGE
        char			age[5];
        long			week = time((time_t *) 0) / WEEK;
        char			*cp;
#endif

	/*
	 * call this with shadow_locked != 0 to avoid calling lckpwdf()
	 * twice (which will fail).  XXX - pw_lock(), pw_unlock(),
	 * spw_lock(), spw_unlock() really should track the lock count
	 * and call lckpwdf() only before the first lock, and ulckpwdf()
	 * after the last unlock.
	 */
	if (!pw_lock()) {
		fprintf(stderr, "Cannot lock the password file; try again later.\n");
		syslog(LOG_WARNING, "can't lock password file");
		exit(E_PWDBUSY);
	}
	if (!pw_open(O_RDWR)) {
		fprintf(stderr, "Cannot open the password file.\n");
		syslog(LOG_ERR, "can't open password file");
		fail_exit(E_MISSING);
	}
	pw = pw_locate(name);
	if (!pw) {
		fprintf(stderr, "mbpasswd: user %s not found in /etc/passwd\n", name);
		fail_exit(E_NOPERM);
	}
	npw = __pw_dup(pw);
	if (!npw)
		oom();
	npw->pw_passwd = update_crypt_pw(npw->pw_passwd);
#ifdef ATT_AGE
        memset(age, 0, sizeof(age));
        STRFCPY(age, npw->pw_age);

        /*
         * Just changing the password - update the last change date
         * if there is one, otherwise the age just disappears.
         */
        if (do_update_age) {
                if (strlen(age) > 2) {
                        cp = l64a(week);
                        age[2] = cp[0];
                        age[3] = cp[1];
                } else {
                        age[0] = '\0';
                }
        }

        if (xflg) {
                if (age_max > 0)
                        age[0] = i64c((age_max + 6) / 7);
                else
                        age[0] = '.';

                if (age[1] == '\0')
                        age[1] = '.';
        }
        if (nflg) {
                if (age[0] == '\0')
                        age[0] = 'z';

                if (age_min > 0)
                        age[1] = i64c((age_min + 6) / 7);
                else
                        age[1] = '.';
        }
        /*
         * The last change date is added by -n or -x if it's
         * not already there.
         */
        if ((nflg || xflg) && strlen(age) <= 2) {
                cp = l64a(week);
                age[2] = cp[0];
                age[3] = cp[1];
        }

        /*
         * Force password change - if last change date is
         * present, it will be set to (today - max - 1 week).
         * Otherwise, just set min = max = 0 (will disappear
         * when password is changed).
         */
        if (eflg) {
                if (strlen(age) > 2) {
                        cp = l64a(week - c64i(age[0]) - 1);
                        age[2] = cp[0];
                        age[3] = cp[1];
                } else {
                        strcpy(age, "..");
                }
        }

        npw->pw_age = age;
#endif

	if (!pw_update(npw)) {
		fprintf(stderr, "Error updating the password entry.\n");
		syslog(LOG_ERR, "error updating password entry");
		fail_exit(E_FAILURE);
	}
#ifdef NDBM
        if (pw_dbm_present() && !pw_dbm_update(npw)) {
                fprintf(stderr, _("Error updating the DBM password entry.\n"));
                SYSLOG((LOG_ERR, DBMERROR2));
                fail_exit(E_FAILURE);
        }
        endpwent();
#endif
	if (!pw_close()) {
		fprintf(stderr, "Cannot commit password file changes.\n");
		syslog(LOG_ERR, "can't rewrite password file");
		fail_exit(E_FAILURE);
	}
        pw_unlock();
}

#endif /* Not __FreeBSD__ && __NetBSD__ && __OpenBSD__ */


#ifdef SHADOW_PASSWORD
static void update_shadow(void)
{
        const struct spwd *sp;
        struct spwd *nsp;

        if (!spw_lock()) {
                fprintf(stderr, "Cannot lock the password file; try again later.\n");
                syslog(LOG_WARNING, "can't lock password file");
                exit(E_PWDBUSY);
        }
        if (!spw_open(O_RDWR)) {
                fprintf(stderr, "Cannot open the password file.\n");
                syslog(LOG_ERR, "can't open password file");
                fail_exit(E_FAILURE);
        }
        sp = spw_locate(name);
        if (!sp) {
                /* Try to update the password in /etc/passwd instead.  */
                spw_close();
                update_noshadow(1);
		spw_unlock();
                return;
        }
        nsp = __spw_dup(sp);
        if (!nsp)
                oom();
        nsp->sp_pwdp = update_crypt_pw(nsp->sp_pwdp);
        if (do_update_age)
                nsp->sp_lstchg = time((time_t *) 0) / SCALE;

        if (!spw_update(nsp)) {
                fprintf(stderr, "Error updating the password entry.\n");
                syslog(LOG_ERR, "error updating password entry");
                fail_exit(E_FAILURE);
        }
#ifdef NDBM
        if (sp_dbm_present() && !sp_dbm_update(nsp)) {
                fprintf(stderr, _("Error updating the DBM password entry.\n"));
                SYSLOG((LOG_ERR, DBMERROR2));
                fail_exit(E_FAILURE);
        }
        endspent();
#endif
        if (!spw_close()) {
                fprintf(stderr, "Cannot commit password file changes.\n");
                syslog(LOG_ERR, "can't rewrite password file");
                fail_exit(E_FAILURE);
        }
        spw_unlock();
}
#endif  /* SHADOWPWD */



/*
 *  Internal version of basename to make this better portable.
 */
char *Basename(char *str)
{
    char *cp = strrchr(str, '/');

    return cp ? cp+1 : str;
}



/*
 * Function will set a new password in the users password file.
 * Note that this function must run setuid root!
 */
int main(int argc, char *argv[])
{
#if !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__)
    const struct passwd	    *pw;
    const struct group	    *gr;
#ifdef SHADOW_PASSWORD
    const struct spwd	    *sp;
#endif
#else
    static struct passwd    *pw;
    static struct group	    *gr;
    int			    pfd, tfd;
#endif
    char		    *cp;
#ifdef _VPOPMAIL_PATH
    char		    *args[16];
#endif
    pid_t		    ppid;
    char		    *parent;
#if defined(__OpenBSD__)
#define ARG_SIZE 60
    static char		    **s, buf[ARG_SIZE];
    size_t		    siz = 100;
    char		    **p;
    int			    mib[4];
#elif defined(__NetBSD__)
#define ARG_SIZE 60
    static char             **s;
    size_t                  siz = 100;
    int                     mib[4];
#else
    char                    temp[PATH_MAX];
    FILE		    *fp;
#endif

    /*
     * Init $MBSE_ROOT/etc/login.defs file before the *pw gets overwritten.
     */
    def_load();

    /*
     * Get my username
     */
    pw = get_my_pwent();
    if (!pw) {
	fprintf(stderr, "mbpasswd: Cannot determine your user name.\n");
	exit(1);
    }
    myname = xstrdup(pw->pw_name);

    /*
     * Get my groupname, this must be "bbs", other users may not
     * use this program, not even root.
     */
    gr = getgrgid(pw->pw_gid);
    if (!gr) {
	fprintf(stderr, "mbpasswd: Cannot determine group name.\n");
	free(myname);
	exit(E_NOPERM);
    }
    if (strcmp(gr->gr_name, (char *)"bbs")) {
	fprintf(stderr, "mbpasswd: You are not a member of group \"bbs\".\n");
	free(myname);
	exit(E_NOPERM);
    }

    /*
     * We don't log into MBSE BBS logfiles but to the system logfiles,
     * because we are modifying system files not belonging to MBSE BBS.
     */
    openlog("mbpasswd", LOG_PID|LOG_CONS|LOG_NOWAIT, LOG_AUTH);

    /*
     * Find out the name of our parent.
     */
    ppid = getppid();

#if defined(__OpenBSD__)
    /*
     * Systems that use sysctl to get process information
     */
    mib[0] = CTL_KERN;
    mib[1] = KERN_PROC_ARGS;
    mib[2] = ppid;
    mib[3] = KERN_PROC_ARGV; 
    if ((s = realloc(s, siz)) == NULL) { 
	fprintf(stderr, "mbpasswd: no memory\n");
	exit(1);
    }
    if (sysctl(mib, 4, s, &siz, NULL, 0) == -1) {
	perror("");
	fprintf(stderr, "mbpasswd: sysctl call failed\n");
	exit(1);
    }
    buf[0] = '\0';
    for (p = s; *p != NULL; p++) {
	if (p != s)
	    strlcat(buf, " ", sizeof(buf));
	strlcat(buf, *p, sizeof(buf));
    }
    parent = xstrcpy(buf);
#elif defined(__NetBSD__)
    /*
     * Systems that use sysctl to get process information
     */
    mib[0] = CTL_KERN;
    mib[1] = KERN_PROC_ARGS;
    mib[2] = ppid;
    mib[3] = KERN_PROC_ARGV;
    if ((s = realloc(s, siz)) == NULL) {
        fprintf(stderr, "mbpasswd: no memory\n");
        exit(1);
    }
    if (sysctl(mib, 4, s, &siz, NULL, 0) == -1) {
        perror("");
        fprintf(stderr, "mbpasswd: sysctl call failed\n");
        exit(1);
    }
    parent = xstrcpy((char *)s);
#else
    /*
     *  Systems with /proc filesystem like Linux, FreeBSD
     */
    snprintf(temp, PATH_MAX, "/proc/%d/cmdline", ppid);
    if ((fp = fopen(temp, "r")) == NULL) {
	fprintf(stderr, "mbpasswd: can't read %s\n", temp);
	syslog(LOG_ERR, "mbpasswd: can't read %s", temp);
	free(myname);
	exit(E_FAILURE);
    }
    fgets(temp, PATH_MAX-1, fp);
    fclose(fp);
    parent = xstrcpy(Basename(temp));
#endif

    if (strcmp((char *)"mbsetup", parent) && strcmp((char *)"-mbsebbs", parent) && strcmp((char *)"-mbnewusr", parent)) {
	fprintf(stderr, "mbpasswd: illegal parent \"%s\"\n", parent);
	syslog(LOG_ERR, "mbpasswd: illegal parent \"%s\"", parent);
	free(myname);
	exit(E_FAILURE);
    }

    if (argc != 3) {
	fprintf(stderr, "\nmbpasswd commandline:\n\n");
	fprintf(stderr, "mbpasswd [username] [newpassword]\n");
	free(myname);
	exit(E_FAILURE);
    }

    if ((strcmp((char *)"-mbnewusr", parent) == 0) || (strcmp((char *)"mbsetup", parent) == 0)) {
	/*
	 * This is a new user setting his password, or the sysop resetting
	 * the password using mbsetup. This program runs under account mbse.
	 */
	force = 1;
	if (strcmp(pw->pw_name, (char *)"mbse") && strcmp(pw->pw_name, (char *)"bbs")) {
	    fprintf(stderr, "mbpasswd: only users `mbse' and `bbs' may do this.\n");
	    free(myname);
	    exit(E_NOPERM);
	}
    } else if (strcmp((char *)"-mbsebbs", parent) == 0) {
	/*
	 * Normal password change by user, check caller is the user.
	 * Calling program is only mbsebbs.
	 */
	force = 0;
	if (strcmp(pw->pw_name, argv[1])) {
	    fprintf(stderr, "mbpasswd: only owner may do this.\n");
	    free(myname);
	    exit(E_NOPERM);
	}
    } else {
	syslog(LOG_ERR, "mbpasswd: illegal called");
	free(myname);
	exit(E_NOPERM);
    }

    /*
     *  Check commandline arguments
     */
    if (strlen(argv[1]) > 32) {
	fprintf(stderr, "mbpasswd: Username too long\n");
	free(myname);
	exit(E_FAILURE);
    }
    if (strlen(argv[2]) > 32) {
	fprintf(stderr, "mbpasswd: Password too long\n");
	free(myname);
	exit(E_FAILURE);
    }

    name = strdup(argv[1]);
    if ((pw = getpwnam(name)) == NULL) {
	syslog(LOG_ERR, "mbpasswd: Unknown user %s", name);
	fprintf(stderr, "mbpasswd: Unknown user %s\n", name);
	free(myname);
	exit(E_FAILURE);
    }


#ifdef SHADOW_PASSWORD
    is_shadow_pwd = spw_file_present();

    sp = getspnam(name);
    if (!sp)
	sp = pwd_to_spwd(pw);

    cp = sp->sp_pwdp;
#else
    cp = pw->pw_passwd;
#endif
    /*
     * See if the user is permitted to change the password.
     * Otherwise, go ahead and set a new password.
     */
#ifdef SHADOW_PASSWORD
    check_password(pw, sp);
#else
    check_password(pw);
#endif

    if (new_password(pw, argv[2])) {
	fprintf(stderr, "The password for %s is unchanged.\n", name);
	syslog(LOG_ERR, "The password for %s is unchanged", name);
	closelog();
	free(myname);
	exit(E_FAILURE);
    }
    do_update_pwd = 1;
    do_update_age = 1;

    /*
     * Before going any further, raise the ulimit to prevent
     * colliding into a lowered ulimit, and set the real UID
     * to root to protect against unexpected signals.  Any
     * keyboard signals are set to be ignored.
     */
#if !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__)
    pwd_init();
#else
    pw_init();
#endif

    if (setuid(0)) {
	fprintf(stderr, "Cannot change ID to root.\n");
	syslog(LOG_ERR, "can't setuid(0)");
	closelog();
	free(myname);
	exit(E_FAILURE);
    }

#if !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__)

#ifdef  HAVE_USERSEC_H
    update_userpw(pw->pw_passwd);
#else  /* !HAVE_USERSEC_H */

#ifdef SHADOW_PASSWORD
    if (spw_file_present())
	update_shadow();
    else
#endif
	update_noshadow(0);
#endif /* !HAVE_USERSEC_H */

#else /* __FreeBSD__ && __NetBSD__ && __OpenBSD__ */
    /*
     *  FreeBSD password change, borrowed from the original FreeBSD sources
     */

    /*
     * Get the new password.  Reset passwd change time to zero by
     * default.
     */
    pw->pw_change = 0;
    pw->pw_passwd = crypt_passwd;

    pfd = pw_lock();
    tfd = pw_tmp();
    pw_copy(pfd, tfd, pw);

    if (!pw_mkdb(pw->pw_name))
	pw_error((char *)NULL, 0, 1);

#endif /* __FreeBSD__ && __NetBSD__ && __OpenBSD__ */

#ifdef _VPOPMAIL_PATH
    fflush(stdout);
    fflush(stdin);
    memset(args, 0, sizeof(args));
    
    snprintf(temp, PATH_MAX, "%s/vpasswd", (char *)_VPOPMAIL_PATH);
    args[0] = temp;
    args[1] = argv[1];
    args[2] = argv[2];
    args[3] = NULL;

    if (execute(args, (char *)"/dev/tty", (char *)"/dev/tty", (char *)"/dev/tty") != 0) {
	perror("mbpasswd: Failed to change vpopmail password\n");
	syslog(LOG_ERR, "Failed to change vpopmail password");
    }
#endif
	
    syslog(LOG_NOTICE, "password for `%s' changed by user `%s'", name, myname);
    closelog();
    free(myname);
    exit(E_SUCCESS);
}