#ifdef SMB_PASSWD

/* 
   Unix SMB/Netbios implementation.
   Version 1.9.
   smbpasswd module.
   Copyright (C) Jeremy Allison 1995.

   This program 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 of the License, or
   (at your option) any later version.
   
   This program 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 this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include "includes.h"
#include "des.h"

/* Static buffers we will return. */
static struct smb_passwd pw_buf;
static pstring user_name;
static unsigned char smbpwd[16];

struct smb_passwd *_my_get_smbpwnam(FILE *fp,char *name, BOOL *valid_old_pwd, long *pwd_seekpos)
{
  char linebuf[256];
  unsigned char c;
  unsigned char *p;
  long uidval;
  long linebuf_len;
  unsigned char lonybble, hinybble;
  int i;

  /* Scan the file, a line at a time and
     check if the name matches. */
  while(!feof(fp)) 
    {
      linebuf[0] = '\0';
      *pwd_seekpos = ftell(fp);

      fgets(linebuf, 256, fp);
      if(ferror(fp))
	return NULL;

      /* Check if the string is terminated with a newline -
	 if not then we must keep reading and discard until
	 we get one.
	 */
      linebuf_len = strlen(linebuf);
      if(linebuf[linebuf_len-1] != '\n')
	{
	  c = '\0';
	  while(!ferror(fp) && !feof(fp))
	    {
	      c = fgetc(fp);
	      if(c == '\n')
		break;
	    }
	}
      else
	linebuf[linebuf_len-1] = '\0';

      if((linebuf[0] == 0) && feof(fp))
	break;
      /* The line we have should be of the form :-

	 username:uid:[32hex bytes]:....other flags presently ignored....
	 */

      if(linebuf[0] == '#' || linebuf[0] == '\0')
	continue;
      p = (unsigned char *)strchr(linebuf, ':');
      if( p == NULL)
	continue;
      /* As 256 is shorter than a pstring we don't
	 need to check length here - if this ever changes.... */
      strncpy( user_name, linebuf, PTR_DIFF(p,linebuf));
      user_name[PTR_DIFF(p,linebuf)] = '\0';
      if(!strequal(user_name, name))
	continue;

      /* User name matches - get uid and password */
      p++; /* Go past ':' */
      if(!isdigit(*p))
	return(False);
      
      uidval = atoi((char *)p);
      while (*p && isdigit(*p)) p++;

      if(*p != ':')
	return(False);
      
      /* Now get the password value - this should be 32 hex digits which
	 are the ascii representations of a 16 byte string. Get two at
	 a time and put them into the password.
	 */
      p++;
      *pwd_seekpos += PTR_DIFF(p,linebuf); /* Save exact position of
					      passwd in file - this is used
					      by smbpasswd.c 
					      */
      if(*p == '*' || *p == 'X')
	{
	  /* Password deliberately invalid - end here. */
	  *valid_old_pwd = False;
	  pw_buf.smb_name = user_name;
	  pw_buf.smb_userid = uidval;
	  return(&pw_buf);
	}
      if(linebuf_len < (PTR_DIFF(p,linebuf) + 33))
	return(False);
      
      if(p[32] != ':')
	return(False);

      if(strequal(p,"NO PASSWORD")) {
	pw_buf.smb_passwd = NULL; /* No password */
      } else {
	char *hexchars = "0123456789ABCDEF";
	char *p1,*p2;	
	for(i = 0; i < 32; i += 2)
	  {
	    hinybble = toupper(p[i]);
	    lonybble = toupper(p[i+1]);
	    
	    p1 = strchr(hexchars,hinybble);
	    p2 = strchr(hexchars,lonybble);
	    if (!p1 || !p2)
	      return(False);

	    hinybble = PTR_DIFF(p1,hexchars);
	    lonybble = PTR_DIFF(p2,hexchars);

	    smbpwd[i/2] = (hinybble << 4) | lonybble;
	  }
	pw_buf.smb_passwd = smbpwd;
      }
      
      pw_buf.smb_name = user_name;
      pw_buf.smb_userid = uidval;
      *valid_old_pwd = True;
      return &pw_buf;
    }
  return NULL;
}

/*
 * Print command usage on stderr and die.
 */
void usage(char *name)
{
	fprintf(stderr, "Usage is : %s [username]\n", name);
	exit(1);
}

int main(int argc, char **argv)
{
  int real_uid;
  struct passwd *pwd;
  fstring old_passwd;
  uchar old_p16[16];
  fstring new_passwd;
  uchar new_p16[16];
  char *p;
  struct smb_passwd *smb_pwent;
  FILE *fp;
  BOOL valid_old_pwd = False;
  long seekpos;
  int pwfd;
  char ascii_p16[33];
  char c;
  int ret, i, err;
  int lockfd=-1;
  char *pfile = SMB_PASSWD_FILE;

  charset_initialise();

#ifndef DEBUG_PASSWORD
  /* Check the effective uid */
  if(geteuid() != 0) {
    fprintf(stderr, "%s: Must be setuid root.\n", argv[0]);
    exit(1);
  }
#endif

  /* Get the real uid */
  real_uid = getuid();
  
  /* Deal with usage problems */
  if( real_uid == 0) {
    /* As root we can change anothers password. */
    if(argc != 1 && argc != 2)
      usage(argv[0]);
  } else if(argc != 1)
    usage(argv[0]);
		

  if(real_uid == 0 && argc == 2) {
    /* If we are root we can change anothers password. */
    strncpy( user_name, argv[1], sizeof(user_name)-1);
    user_name[sizeof(user_name)-1] = '\0';
    pwd = getpwnam( user_name );
  } else {
    pwd = getpwuid( real_uid );
  }

  if(pwd == 0) {
    fprintf(stderr, "%s: Unable to get UNIX password entry for user.\n", argv[0]);
    exit(1);
  }
  
  /* If we are root we don't ask for the old password. */
  old_passwd[0] = '\0';
  if(real_uid != 0) {
    p = getpass("Old SMB password:");
    strncpy(old_passwd, p, 14);
    old_passwd[14] = '\0';
    strupper(old_passwd);
  }
  
  new_passwd[0] = '\0';
  p = getpass("New SMB password:");
  strncpy(new_passwd, p, 14);
  new_passwd[14] = '\0';
  p = getpass("Retype new SMB password:");
  if(strncmp(p, new_passwd, 14)) {
    fprintf(stderr, "%s: Mismatch - password unchanged.\n", argv[0]);
    exit(1);
  }
  strupper(new_passwd);
  
  if(new_passwd[0] == '\0') {
    printf("Password not set\n");
    exit(0);
  }
  
  /* Calculate the SMB hash functions of
     both old an new passwords. */
  
  memset(old_p16,'\0',16);
  E_P16((uchar *)old_passwd,old_p16);
  
  memset(new_p16,'\0',16);
  E_P16((uchar *)new_passwd,new_p16);
  
  /* Open the smbpaswd file XXXX - we need to parse smb.conf to
     get the filename */
  if((fp = fopen(pfile, "r+")) == NULL) {
    err = errno;
    fprintf(stderr, "%s: Failed to open password file %s.\n", 
	    argv[0], pfile);
    errno = err;
    perror(argv[0]);
    exit(err);
  }
  
  /* make sure it is only rw by the owner */
  chmod(pfile,0600);
  
  /* Lock the smbpasswd file for write. */
  if((lockfd=file_lock(pfile,5))<0) {
    err = errno;
    fprintf(stderr, "%s: Failed to lock password file %s.\n", 
	    argv[0], pfile);
    fclose(fp);
    errno = err;
    perror(argv[0]);
    exit(err);
  }

  /* Get the smb passwd entry for this user */
  smb_pwent = _my_get_smbpwnam(fp, pwd->pw_name, &valid_old_pwd, &seekpos);
  if(smb_pwent == NULL) {
    fprintf(stderr, "%s: Failed to find entry for user %s in file %s.\n",
	    argv[0], pwd->pw_name, pfile);
    fclose(fp);
    file_unlock(lockfd);
    exit(1);
  }
  
  /* If we are root we don't need to check the old password. */
  if( real_uid != 0) {
    if( valid_old_pwd == False) {
      fprintf(stderr, "%s: User %s is disabled, plase contact your administrator to enable it.\n", argv[0], pwd->pw_name);
      fclose(fp);
      file_unlock(lockfd);
      exit(1);
    }
    /* Check the old passwd (if there was one). */
    if(smb_pwent->smb_passwd != NULL) {
      if( memcmp( old_p16, smb_pwent->smb_passwd, 16)) {
	fprintf(stderr, "%s: Couldn't change password.\n", argv[0]);
	fclose(fp);
	file_unlock(lockfd);
	exit(1);
      }
    }
  }
  /* If we get here either we were root or the old password
     checked out ok. */
  /* Create the 32 byte representation of the new p16 */
  for(i = 0; i < 16; i++) {
    sprintf(&ascii_p16[i*2], "%02X", (uchar)new_p16[i]);
  }
  /* Do an atomic write into the file at the position
     defined by seekpos. */
  pwfd = fileno(fp);
  ret = lseek(pwfd, seekpos - 1, SEEK_SET);
  if(ret != seekpos -1) {
    err = errno;
    fprintf(stderr, "%s: seek fail on file %s.\n", 
	    argv[0], pfile);
    fclose(fp);
    errno = err;
    perror(argv[0]);
    file_unlock(lockfd);
    exit(1);
  }
  /* Sanity check - ensure the character is a ':' */
  if(read(pwfd,&c,1) != 1) {
    err = errno;
    fprintf(stderr, "%s: read fail on file %s.\n", 
	    argv[0], pfile);
    fclose(fp);
    errno = err;
    perror(argv[0]);
    file_unlock(lockfd);
    exit(1);
  }
  if(c != ':') {
    fprintf(stderr, "%s: sanity check on passwd file %s failed.\n",
	    argv[0], pfile);
    fclose(fp);
    file_unlock(lockfd);
    exit(1);
  }
  if(write(pwfd,ascii_p16,32)!=32) {
    err = errno;
    fprintf(stderr, "%s: write fail in file %s.\n", 
	    argv[0], pfile);
    fclose(fp);
    errno = err;
    perror(argv[0]);
    file_unlock(lockfd);
    exit(err);
  }
  fclose(fp);
  file_unlock(lockfd);
  printf("Password changed\n");
  return 0;
}

#else

#include "includes.h"

int main(int argc, char **argv)
{
  printf("smb password encryption not selected in Makefile\n");
  return 0;
}
#endif


