view lib/password.c @ 1278:324306321d82 draft

Initial cleanup of last: mostly whitespace, move no record test to start of loop, don't bother to stat an empty file to report when an empty log was created (just report current time).
author Rob Landley <rob@landley.net>
date Sat, 10 May 2014 13:20:03 -0500
parents 63db77909fc8
children 1493a21160a4
line wrap: on
line source

/* password.c - password read/update helper functions.
 *
 * Copyright 2012 Ashwini Kumar <ak.ashwini@gmail.com>
 */

#include "toys.h"
#include <time.h>

int get_salt(char *salt, char *algo)
{      
  int i, len = 0, offset = 0;
  char buf[12];

  if (!strcmp(algo,"des")) len = 2;
  else {
    *salt++ = '$';
    if (!strcmp(algo,"md5")) {
      *salt++ = '1';
      len = 8;
    } else if (!strcmp(algo,"sha256")) {
      *salt++ = '5';
      len = 16;
    } else if (!strcmp(algo,"sha512")) {
      *salt++ = '6';
      len = 16;
    } else return -1;

    *salt++ = '$';
    offset = 3;
  }    

  // Read appropriate number of random bytes for salt
  i = xopen("/dev/urandom", O_RDONLY);
  xreadall(i, buf, ((len*6)+7)/8);
  close(i);

  // Grab 6 bit chunks and convert to characters in ./0-9a-zA-Z
  for (i=0; i<len; i++) {
    int bitpos = i*6, bits = bitpos/8;

    bits = ((buf[i]+(buf[i+1]<<8)) >> (bitpos&7)) & 0x3f;
    bits += 46;
    if (bits > 57) bits += 7;
    if (bits > 90) bits += 6;

    salt[i] = bits;
  }
  salt[i] = 0;

  return offset;
}

static void handle(int signo)
{
  //Dummy.. so that read breaks on the signal, 
  //instead of the applocation exit
}

int read_password(char * buff, int buflen, char* mesg)
{
  int i = 0;
  struct termios termio, oldtermio;
  struct sigaction sa, oldsa;

  tcgetattr(0, &oldtermio);
  tcflush(0, TCIFLUSH);
  termio = oldtermio;

  memset(&sa, 0, sizeof(sa));
  sa.sa_handler = handle;
  sigaction(SIGINT, &sa, &oldsa);

  termio.c_iflag &= ~(IUCLC|IXON|IXOFF|IXANY);
  termio.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|TOSTOP);
  tcsetattr(0, TCSANOW, &termio);

  fputs(mesg, stdout);
  fflush(stdout);

  while (1) {
    int ret = read(0, &buff[i], 1);
    if ( ret < 0 ) {
      buff[0] = 0;
      sigaction(SIGINT, &oldsa, NULL);
      tcsetattr(0, TCSANOW, &oldtermio);
      xputc('\n');
      fflush(stdout);
      return 1;
    } else if (ret == 0 || buff[i] == '\n' || buff[i] == '\r' || buflen == i+1)
    {
      buff[i] = '\0';
      break;
    }
    i++;
  }
  sigaction(SIGINT, &oldsa, NULL);
  tcsetattr(0, TCSANOW, &oldtermio);
  puts("");
  fflush(stdout);
  return 0;
}

static char *get_nextcolon(char *line, int cnt)
{
  while (cnt--) {
    if (!(line = strchr(line, ':'))) error_exit("Invalid Entry\n");
    line++; //jump past the colon
  }
  return line;
}

/*update_password is used by multiple utilities to update /etc/passwd,
 * /etc/shadow, /etc/group and /etc/gshadow files, 
 * which are used as user, group databeses
 * entry can be 
 * 1. encrypted password, when updating user password.
 * 2. complete entry for user details, when creating new user
 * 3. group members comma',' separated list, when adding user to group
 * 4. complete entry for group details, when creating new group
 * 5. entry = NULL, delete the named entry user/group
 */
int update_password(char *filename, char* username, char* entry)
{
  char *filenamesfx = NULL, *namesfx = NULL, *shadow = NULL,
       *sfx = NULL, *line = NULL;
  FILE *exfp, *newfp;
  int ret = -1, found = 0;
  struct flock lock;

  shadow = strstr(filename, "shadow");
  filenamesfx = xmprintf("%s+", filename);
  sfx = strchr(filenamesfx, '+');

  exfp = fopen(filename, "r+");
  if (!exfp) {
    perror_msg("Couldn't open file %s",filename);
    goto free_storage;
  }

  *sfx = '-';
  ret = unlink(filenamesfx);
  ret = link(filename, filenamesfx);
  if (ret < 0) error_msg("can't create backup file");

  *sfx = '+';
  lock.l_type = F_WRLCK;
  lock.l_whence = SEEK_SET;
  lock.l_start = 0;
  lock.l_len = 0;

  ret = fcntl(fileno(exfp), F_SETLK, &lock);
  if (ret < 0) perror_msg("Couldn't lock file %s",filename);

  lock.l_type = F_UNLCK; //unlocking at a later stage

  newfp = fopen(filenamesfx, "w+");
  if (!newfp) {
    error_msg("couldn't open file for writing");
    ret = -1;
    fclose(exfp);
    goto free_storage;
  }

  ret = 0;
  namesfx = xmprintf("%s:",username);
  while ((line = get_line(fileno(exfp))) != NULL)
  {
    if (strncmp(line, namesfx, strlen(namesfx)))
      fprintf(newfp, "%s\n", line);
    else if (entry) {
      char *current_ptr = NULL;

      found = 1;
      if (!strcmp(toys.which->name, "passwd")) {
        fprintf(newfp, "%s%s:",namesfx, entry);
        current_ptr = get_nextcolon(line, 2); //past passwd
        if (shadow) {
          fprintf(newfp, "%u:",(unsigned)(time(NULL))/(24*60*60));
          current_ptr = get_nextcolon(current_ptr, 1);
          fprintf(newfp, "%s\n",current_ptr);
        } else fprintf(newfp, "%s\n",current_ptr);
      } else if (!strcmp(toys.which->name, "groupadd") || 
          !strcmp(toys.which->name, "addgroup") ||
          !strcmp(toys.which->name, "delgroup") ||
          !strcmp(toys.which->name, "groupdel")){
        current_ptr = get_nextcolon(line, 3); //past gid/admin list
        *current_ptr = '\0';
        fprintf(newfp, "%s", line);
        fprintf(newfp, "%s\n", entry);
      }
    }
    free(line);
  }
  free(namesfx);
  if (!found && entry) fprintf(newfp, "%s\n", entry);
  fcntl(fileno(exfp), F_SETLK, &lock);
  fclose(exfp);

  errno = 0;
  fflush(newfp);
  fsync(fileno(newfp));
  fclose(newfp);
  rename(filenamesfx, filename);
  if (errno) {
    perror_msg("File Writing/Saving failed: ");
    unlink(filenamesfx);
    ret = -1;
  }

free_storage:
  free(filenamesfx);
  return ret;
}

void is_valid_username(const char *name)                                                                                              
{
  regex_t rp;
  regmatch_t rm[1]; 
  int eval;
  char *regex = "^[_.A-Za-z0-9][-_.A-Za-z0-9]*"; //User name REGEX

  xregcomp(&rp, regex, REG_NEWLINE);

  /* compare string against pattern --  remember that patterns 
     are anchored to the beginning of the line */
  eval = regexec(&rp, name, 1, rm, 0);
  regfree(&rp);
  if (!eval && !rm[0].rm_so) {
    int len = strlen(name);
    if ((rm[0].rm_eo == len) ||
        (rm[0].rm_eo == len - 1 && name[len - 1] == '$')) {
      if (len >= LOGIN_NAME_MAX) error_exit("name is too long");
      else return;
    }
  }
  error_exit("'%s', not valid %sname",name,
      (((toys.which->name[3] == 'g') || 
        (toys.which->name[0] == 'g'))? "group" : "user"));
}