/*
 *   Copyright (c) 2003 David Parsons. All rights reserved.
 *   
 *   Redistribution and use in source and binary forms, with or without
 *   modification, are permitted provided that the following conditions
 *   are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in
 *     the documentation and/or other materials provided with the
 *     distribution.
 *  3. All advertising materials mentioning features or use of this
 *     software must display the following acknowledgement:
 *     
 *   This product includes software developed by David Parsons
 *   (orc@pell.chi.il.us)
 *
 *  4. My name may not be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *     
 *  THIS SOFTWARE IS PROVIDED BY DAVID PARSONS ``AS IS'' AND ANY
 *  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 *  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DAVID
 *  PARSONS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 *  TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 *  IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 *  THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * poppasswd: change passwords for a pop account
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <pwd.h>
#include <errno.h>
#include <unistd.h>
#include <sys/utsname.h>
#include <syslog.h>

#include "config.h"

#include "vpw.h"
#include "post.h"
#include "transaction.h"


/*
 * 200 hello.  Who are you? ->
   <-- USER foo@domain
   200 What is your clearance, citizen? ->
   <-- PASS bar
   200 Pass, friend ->
   <-- NEWPASS baz
   200 OK ->
   <-- QUIT
   200 Keep your laser handy ->
 */

enum command { USER, PASS, NEWPASS, QUIT, FINAL };

int state = USER;		/* The next command we want */
char *username = 0;		/* username */
char *vdomain = 0;		/* @domain part of username */
char *vpasswd = 0;		/* virtual password directory */

struct vlock *xact = 0;		/* password change transaction */

/*
 * the commands we recognise
 */
struct cmd {
	char *text;
        enum command state;
} cmds[] = {
	{ "USER",    USER },
	{ "PASS",    PASS },
	{ "NEWPASS", NEWPASS },
	{ "QUIT",    QUIT },
};
#define NRCMDS (sizeof cmds/sizeof cmds[0])


/*
 * message() makes happy noises
 */
void
message(char *fmt, ...)
{
    va_list ap;

    printf("%c00 ", strchr(fmt, '?') ? '3' : '2');

    va_start(ap, fmt);
    vprintf(fmt, ap);
    va_end(ap);

    putchar('\r');
    putchar('\n');
} /* message */


/*
 * err() complain bitterly, then abandon ship
 */
void
err(char *fmt, ...)
{
    va_list ptr;

    printf("500 ");
    va_start(ptr,fmt);
    vprintf(fmt,ptr);
    va_end(ptr);
    putchar('\r');
    putchar('\n');
    exit(1);
} /* err */


/*
 * at exit, roll back any unfinished transactions
 */
void
abend()
{
    if (xact)
	vpwrollback(xact);
} /* abend */


/*
 * e_to_sys() dummy function for xmalloc/xrealloc/concatenate
 */
void
e_to_sys(int errno)
{
    ;
}


/*
 * argument() picks one argument off the command line
 */
char *
argument(char *str, int one_word_only)
{
    char *ret;

    while (isspace(*str))
	++str;
    if (*str) {
	ret = str;

	if (one_word_only)
	    while (*str && !isspace(*str))
		++str;
	else
	    while (*str && *str != '\n' && *str != '\r')
		++str;

	*str = 0;
	return ret;
    }
    return 0;
} /* argument */


/*
 * user() handles a USER command
 */
void
user(char *logon)
{
    char *p;

    if (logon == 0 || *logon == 0)
	err("Who did you say you were again?");


    /* pick off the virtual domain;  if it's not there,
     * we'll still happily grab the password, but it
     * won't mean anything.
     */
    if (p = strchr(logon, '@')) {
	*p++ = 0;
	username = xstrdup(logon);
	vdomain  = xstrdup(p);
    }
    else
	username = logon;
} /* user */


/*
 * password() validates a PASS command
 */
void
password(char *password)
{
    FILE *cf;
    struct passwd *owner = 0;
    struct vpwent *vu = 0;
    char *username, *domain;
    int rc;

    if (vdomain) {
	/* get the user who owns the domain, so we can give away
	 * privileges before we actually start writing to files
	 */
	if (cf = fopen(PATH_VHOST "/domains.cf", "r")) {
	    while ( (rc = getrecord(cf, &username, &domain, 0)) >= 0) {
		if (rc >= 2 && strcmp(domain, vdomain) == 0) {
		    if (owner = getpwnam(username)) {
			if (owner->pw_uid == 0)	/* NOT ALLOWED*/
			    owner = 0;
			else if (setuid(owner->pw_uid) == -1) {
			    syslog(LOG_ERR, "setuid: %m");
			    err(strerror(errno));
			}
		    }
		    break;
		}
	    }
	    fclose(cf);
	}

	if (owner) {
	    vpasswd = concatenate(PATH_VHOST, "/" , vdomain, "/passwd", 0);

	    setvpw(vpasswd);
	    vu = getvpwnam(username);
	    endvpw();
	    if (vu && strcmp(vu->v_pwd, crypt(password, vu->v_pwd)) == 0)
		return;
	}
    }
err:syslog(LOG_AUTH, vdomain ? "Bad password for %s@%s"
                             : "Bad password for %s", username, vdomain);
    err("You do not have the proper security clearance, citizen");
} /* password */


/*
 * chpass() actually changes the password
 */
void
chpass(char *newpass)
{
    char *passdir = concatenate(PATH_VHOST, "/", vdomain, 0);
    struct vpwent *vu;
    int done = 0;

    /* don't even THINK of writing to a file if we're root
     */
    if (getuid() == 0) {
	syslog(LOG_ERR, "chpass() called with uid 0");
	err("The Computer has detected mutant powers.");
    }

    if ( xact = vpwlock(passdir, "passwd") ) {
	if (setvpw(xact->filename)) {
	    while (vu = getvpwent()) {
		if (strcmp(vu->v_name, username) == 0) {
		    vu->v_pwd = crypt(newpass, vu->v_pwd);
		    done = 1;
		}
		if (putvpwent(xact->fd, vu) != 0) {
		    vpwrollback(xact);
		    goto err;
		}
	    }
	    endvpw();
	    if (vpwcommit(xact) == 0) {
		xact = 0;
		syslog(LOG_AUTH, "%s@%s changed their password", username, vdomain);
		return;
	    }
	}
    }
err:syslog(LOG_ERR, "%m");
    err(strerror(errno));
} /* chpass */


/*
 * expect() get a command, parse and execute it
 */
expect()
{
    char ans[200];
    char *p, *q;
    int i;

    fflush(stdout);
    fgets(ans, sizeof ans, stdin);

    for (p = ans; isspace(*p); ++p)
	;
    for (q = p; *q && !isspace(*q); ++q)
	;
    if (*q)
	*q++ = 0;

    for (i=0; i<NRCMDS; i++)
	if (strcasecmp(p, cmds[i].text) == 0)
	    break;
    if ((i >= NRCMDS) || (cmds[i].state != state && cmds[i].state != QUIT)) {
	sleep(1);
	err("Sorry. Try again.");
    }
    switch (cmds[i].state) {
    case QUIT:
	message("Keep your laser handy!");
	exit(0);
    case USER:
	user(argument(q, 1));
	message("What is your clearance, %s?", username);
	break;
    case PASS:
	password(argument(q, 0));
	message("Welcome, %s.", username);
	break;
    case NEWPASS:
	chpass(argument(q, 0));
	message("OK");
	break;
    }
    state++;
} /* expect */


int
main()
{
    struct utsname sys;
    struct sockaddr_in sin;
    struct hostent *ha;
#if HAVE_SOCKLEN_T
    socklen_t sval;
#else
    int sval;
#endif
    char *ip = 0;
    char *host = 0;

#ifdef LOG_SECURITY
    openlog("vpoppasswd", LOG_PID, LOG_SECURITY);
#else
    openlog("vpoppasswd", LOG_PID, LOG_AUTH);
#endif

    sval = sizeof(sin);
    if (getpeername(0, &sin, &sval) == 0) { /* IPV4 only! */
	ip = inet_ntoa(sin.sin_addr);
	if (ha = gethostbyaddr((char*) &sin.sin_addr, 4, sin.sin_family))
	    host = strdup(ha->h_name);
    }
    if (host || ip)
	syslog(LOG_INFO, "connection from %s", host ? host : ip);

    uname(&sys);
    message("This is %s.  State your name, citizen.", sys.nodename);

    atexit(abend);

    while (1)
	expect();
}
