aboutsummaryrefslogblamecommitdiffstats
path: root/src/passphrase.c
blob: 5cf05fdde3499084daca892a4b86ede599edff84 (plain) (tree)
1
2
3
4
   
                                                                      
   
                                                                 













                                                                        

                   
                   
                   

                  
                   
                    
                     
 
                                 
                       
                              

 





                                              
 

 


                       
                    



                 


                                
                             
 

 
                          
                                                                    
 

                                             
         
                                  
                                 
                                   


              
                               
                                                                                        
                                

 














                                                                                   


                                                 



                                          



                                                        


























                                                                                                              
             


                                                                                     

                                      
                            
                                 




                                   

                                            
                              

                                     

           

                           
























                                                          












                                                       













                                                        
 









                                                         


                                  



                                                                   




                                                 

                   

 

                                                                                               
 





                               
                   
  


                        


































































                                                                                         



                                                                    
    
                                                 
                                                                                                   
      
                                                                                        
                 



                        



                             







                                   

                                                             
                                              
 
                       

               
                       




                                     
                       

                                      
                                                            





                                                    


                      
                                   
 
                            


                                                                      
                                  
                                                
                                              
                          




                                                 
                                



                            

 


                                  
                                            






                                                            



                                                                                 
                                                           

                                       
                      

                   
                                                               
                                     
                                                      



                            
        


                      


                                   
  
                 
                
  



                                     





                                                        
                                                          



                                                                  
                       



                                     




                             
      
                            
                            




                           
                         
              
                            
                       
                                  
                            

                               
                
                              
                                 
         
                                                               
                                                                  

                                                      
                                                                                        
                               
                                                                                      









                                                                                
                       
                        




                                                
                   
             
                     
                  
                   
                   
         





                                                                



                                            








                                                                           
                
                             

                                                  













                                                                                                         




                                
                                                         
                        
                                                
            
  


                              



   
                                  
   
                                                                          
   
                           
 
                                           

 
/**
 * libpassphrase – Personalisable library for TTY passphrase reading
 * 
 * Copyright © 2013, 2014, 2015  Mattias Andrée (m@maandree.se)
 * 
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <limits.h>
#include <termios.h>
#include <sys/wait.h>

#define PASSPHRASE_USE_DEPRECATED
#include "passphrase.h"
#include "passphrase_helper.h"


#ifndef START_PASSPHRASE_LIMIT
# define START_PASSPHRASE_LIMIT  32
#endif
#ifndef DEFAULT_PASSPHRASE_METER
# define DEFAULT_PASSPHRASE_METER  "passcheck"
#endif



#ifdef PASSPHRASE_METER
struct passcheck_state
{
  const char* label;
  int pipe_rw[2];
  pid_t pid;
  int flags;
};

static char* strength = NULL;
static size_t strength_size = 0;
#endif /* PASSPHRASE_METER */



#ifndef PASSPHRASE_REALLOC
static char* xrealloc(char* array, size_t cur_size, size_t new_size)
{
  char* rc = malloc(new_size * sizeof(char));
  size_t i;
  if (rc)
    for (i = 0; i < cur_size; i++)
	*(rc + i) = *(array + i);
  passphrase_wipe(array, cur_size);
  free(array);
  return rc;
}
#else /* !PASSPHRASE_REALLOC */
# define xrealloc(array, _cur_size, new_size)  realloc(array, (new_size) * sizeof(char))
#endif /* !PASSPHRASE_REALLOC */


#ifdef PASSPHRASE_METER
static void passcheck_start(struct passcheck_state* state, int flags)
{
  const char* command;
  int pipe_rw[2] = { -1, -1 };
  int exec_rw[2] = { -1, -1 };
  pid_t pid;
  ssize_t n;
  int i = 0;
  
  state->pid = -1;
  state->flags = (flags & PASSPHRASE_READ_NEW) ? (flags ^ PASSPHRASE_READ_NEW) : 0;
  if (state->flags == 0)
    return;
  
  if (state->flags & PASSPHRASE_READ_BELOW_FREE)
    state->flags &= ~PASSPHRASE_READ_SCREEN_FREE;
  
  command = getenv("LIBPASSPHRASE_METER");
  if (!command || !*command)
    command = DEFAULT_PASSPHRASE_METER;
  
  state->label = getenv("LIBPASSPHRASE_STRENGTH_LABEL");
  if (!(state->label) || !*(state->label))
    state->label = PASSPHRASE_TEXT_STRENGTH;
  
  xpipe(state->pipe_rw);
  xpipe(pipe_rw);
  xpipe(exec_rw);
  /* ‘Their integer values shall be the two lowest available at the time of the pipe() call’ [man 3p pipe]
   * This guarantees (unless the application is doing something stupid) that the file desriptors
   * in exec_rw[1] is not stdin, stdout, stderr, or 0 (required by FD_CLOEXEC to take affect), assuming
   * stdin, stdout, and stderr are 0, 1, and 2, respectively, as specified in `man 3p stdin`. */
  
  if (fcntl(exec_rw[1], F_SETFD, FD_CLOEXEC) == -1)
    goto fail;
  
  pid = fork();
  if (pid == -1)
    {
      state->flags = 0;
      goto fail;
    }
  
  close(exec_rw[!!pid]), exec_rw[!!pid] = -1;
  close(state->pipe_rw[!!pid]), state->pipe_rw[!!pid] = -1;
  close(pipe_rw[!pid]), pipe_rw[!pid] = -1;
  state->pipe_rw[!!pid] = pipe_rw[!!pid], pipe_rw[!!pid] = -1;
  
  if (pid == 0)
    {
      gid_t gid = getgid(), egid = getegid();
      uid_t uid = getuid(), euid = geteuid();
      int fd;
      
      if ((state->pipe_rw[0] != STDIN_FILENO) && (state->pipe_rw[1] == STDIN_FILENO))
	{
	  fd = dup(state->pipe_rw[1]);
	  if (fd == -1)
	    goto child_fail;
	  state->pipe_rw[1] = fd;
	}
      for (i = 0; i <= 1; i++)
	if (state->pipe_rw[i] != i)
	  {
	    close(i);
	    fd = dup2(state->pipe_rw[i], i);
	    if (fd == -1)
	      goto child_fail;
	    close(state->pipe_rw[i]);
	    state->pipe_rw[i] = fd;
	  }
      
      close(STDERR_FILENO);
      
      if (egid != gid)
	if (setregid(gid, gid) && gid)
	  goto child_fail;
      if (euid != uid)
	if (setreuid(uid, uid) && uid)
	  goto child_fail;
      
      execlp(command, command, "-r", NULL);
    child_fail:
      n = write(exec_rw[1], &i, sizeof(i));
      _exit(!!n);
    }
  
 rewait:
  n = read(exec_rw[0], &i, sizeof(i));
  if ((n < 0) && (errno == EINTR))
    goto rewait;
  if (n)
    {
    rereap:
      if ((waitpid(pid, &i, 0) == -1) && (errno == EINTR))
	goto rereap;
      goto fail;
    }
  
  if (state->flags & PASSPHRASE_READ_SCREEN_FREE)
    {
      struct termios stty;
      struct termios saved_stty;
      tcgetattr(STDERR_FILENO, &stty);
      saved_stty = stty;
      stty.c_oflag &= (tcflag_t)~ONLCR;
      tcsetattr(STDERR_FILENO, TCSAFLUSH, &stty);
      fprintf(stderr, "\n\033[A");
      fflush(stderr);
      tcsetattr(STDERR_FILENO, TCSAFLUSH, &saved_stty);
    }
  
  close(exec_rw[0]);
  state->pid = pid;
  return;
 fail:
  if (state->pipe_rw[0] >= 0)  close(state->pipe_rw[0]);
  if (state->pipe_rw[1] >= 0)  close(state->pipe_rw[1]);
  if (pipe_rw[0] >= 0)  close(pipe_rw[0]);
  if (pipe_rw[1] >= 0)  close(pipe_rw[1]);
  if (exec_rw[0] >= 0)  close(exec_rw[0]);
  if (exec_rw[1] >= 0)  close(exec_rw[1]);
  state->pid = -1;
  state->flags = 0;
}


static void passcheck_stop(struct passcheck_state* state)
{
  int _status;
  
  if (state->flags == 0)
    return;
  
  close(state->pipe_rw[0]);
  close(state->pipe_rw[1]);
  
  free(strength), strength = NULL;
  strength_size = 0;
  
rereap:
  if ((waitpid(state->pid, &_status, 0) == -1) && (errno == EINTR))
    goto rereap;
  
  if (state->flags & PASSPHRASE_READ_SCREEN_FREE)
    fprintf(stderr, "\033[s\033[E\033[0K\033[u");
  else
    fprintf(stderr, "\033[B\033[0K\033[A");
  fflush(stderr);
  
  state->flags = 0;
}


static void passcheck_update(struct passcheck_state* state, const char* passphrase, size_t len)
{
  size_t strength_ptr = 0;
  ssize_t n;
  int i;
  void* new;
  char* p;
  unsigned long long int value;
  const char* desc;
  
  if (state->flags == 0)
    return;
  
  for (i = 0; i < 2; i++, passphrase = "\n", len = 1)
    while (len)
      {
	n = write(state->pipe_rw[1], passphrase, len);
	if (n < 0)
	  {
	    if (errno == EINTR)
	      continue;
	    goto fail;
	  }
	passphrase += (size_t)n;
	len -= (size_t)n;
      }
  
 again:
  if (strength_ptr == strength_size)
    {
      strength_size = strength_size ? (strength_size << 1) : 8;
      new = realloc(strength, strength_size * sizeof(char));
      if (new == NULL)
	goto fail;
      strength = new;
    }
  n = read(state->pipe_rw[0], strength + strength_ptr, strength_size - 1 - strength_ptr);
  if (n <= 0)
    {
      if (n && (errno == EINTR))
	goto again;
      goto fail;
    }
  strength_ptr += (size_t)n;
  strength[strength_ptr] = '\0';
  p = strpbrk(strength, " \t\r\f\n\v");
  if (p == NULL)
    goto again;
  if (*p == '\033')
    {
      p = strpbrk(strength, "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm~");
      if (p == NULL)
	goto fail;
      p++;
    }
  else
    p = strength;
  *(strpbrk(p, " \t\r\f\n\v\033")) = '\0';
  errno = 0;
  value = strtoull(p, NULL, 10);
  if ((value == 0) && errno)
    {
      if (errno != ERANGE)
	goto fail;
      value = ULLONG_MAX;
    }
  while (memchr(strength, '\n', strength_ptr) == NULL)
    {
      strength_ptr = 0;
      n = read(state->pipe_rw[0], strength, strength_size);
      if (n <= 0)
	{
	  if (n && (errno == EINTR))
	    goto again;
	  goto fail;
	}
      strength_ptr = (size_t)n;
    }
  strength_ptr = 0;
  
  if (0);
#define X(COND, COLOUR, DESC)  else if (COND)  desc = COLOUR"m"DESC;
  LIST_PASSPHRASE_STRENGTH_LIMITS(value)
#undef X
    
  if (state->flags & PASSPHRASE_READ_SCREEN_FREE)
    fprintf(stderr, "\033[s\033[E\033[0K%s \033[%s\033[m (%lli)\033[u", state->label, desc, value);
  else
    fprintf(stderr, "\033[B\033[s\033[0K\033[%s\033[m (%lli)\033[u\033[A", desc, value);
  fflush(stderr);
  
  return;
 fail:
  passcheck_stop(state);
}
#endif /* PASSPHRASE_METER */


static int fdgetc(int fd)
{
  unsigned char c;
  if (read(fd, &c, sizeof(c)) <= 0)
    return -1;
  return (int)c;
}


#if defined(PASSPHRASE_DEDICATED) && defined(PASSPHRASE_MOVE)
static int get_dedicated_control_key(int fdin)
{
  int c = fdgetc(fdin);
  if (c == 'O')
    {
      c = fdgetc(fdin);
      if (c == 'H')  return KEY_HOME;
      if (c == 'F')  return KEY_END;
    }
  else if (c == '[')
    {
      c = fdgetc(fdin);
      if (c == 'C')  return KEY_RIGHT;
      if (c == 'D')  return KEY_LEFT;
      if (('1' <= c) && (c <= '4') && (fdgetc(fdin) == '~'))
	return -(c - '0');
    }
  return 0;
}
#endif /* PASSPHRASE_DEDICATED && PASSPHRASE_MOVE */



#ifdef PASSPHRASE_MOVE
static int get_key(int c, int fdin)
{
# ifdef PASSPHRASE_DEDICATED
  if (c == '\033')             return get_dedicated_control_key(fdin);
# else /* PASSPHRASE_DEDICATED */
  (void) fdin;
# endif /* PASSPHRASE_DEDICATED */
  if ((c == 8) || (c == 127))  return KEY_ERASE;
  if ((c < 0) || (c >= ' '))   return c & 255;
# ifdef PASSPHRASE_CONTROL
  if (c == 'A' - '@')          return KEY_HOME;
  if (c == 'B' - '@')          return KEY_LEFT;
  if (c == 'D' - '@')          return KEY_DELETE;
  if (c == 'E' - '@')          return KEY_END;
  if (c == 'F' - '@')          return KEY_RIGHT;
# endif /* PASSPHRASE_CONTROL */
  return 0;
}
#endif /* PASSPHRASE_MOVE */



/**
 * Reads the passphrase from stdin
 * 
 * @param   fdin   File descriptor for input
 * @param   flags  Settings, a combination of the constants:
 *                 * PASSPHRASE_READ_EXISTING
 *                 * PASSPHRASE_READ_NEW
 *                 * PASSPHRASE_READ_SCREEN_FREE
 *                 * PASSPHRASE_READ_BELOW_FREE
 *                 Invalid input is ignored, to make use the
 *                 application will work.
 * @return         The passphrase, should be wiped and `free`:ed, `NULL` on error
 */
char* passphrase_read2(int fdin, int flags)
{
  char* rc = malloc(START_PASSPHRASE_LIMIT * sizeof(char));
  size_t size = START_PASSPHRASE_LIMIT;
  size_t len =  0;
#ifdef PASSPHRASE_MOVE
  size_t point = 0;
  size_t i = 0;
# if defined(PASSPHRASE_OVERRIDE) && defined(PASSPHRASE_INSERT)
  char insert = DEFAULT_INSERT_VALUE;
# endif /* PASSPHRASE_OVERRIDE && PASSPHRASE_INSERT */
#endif /* PASSPHRASE_MOVE */
#ifdef PASSPHRASE_TEXT
  size_t printed_len = 0;
#endif /* PASSPHRASE_TEXT */
  int c;
#ifdef PASSPHRASE_MOVE
  int cc;
#endif
#ifdef PASSPHRASE_METER
  struct passcheck_state passcheck;
#endif /* PASSPHRASE_METER */
  
  if (rc == NULL)
    return NULL;
  
#ifdef PASSPHRASE_METER
  passcheck_start(&passcheck, flags);
#endif /* PASSPHRASE_METER */
  
#ifdef PASSPHRASE_TEXT
  xprintf("%s%zn", PASSPHRASE_TEXT_EMPTY, &printed_len);
  if (printed_len)
    xprintf("\e[%zuD", printed_len);
#endif /* PASSPHRASE_TEXT */
  
  /* Read password until EOF or Enter, skip all \0 as that
     is probably not a part of the passphrase (good luck typing
     that in X.org) and can be echoed into stdin by the kernel. */
  for (;;)
    {
      c = fdgetc(fdin);
      if ((c < 0) || (c == '\n'))
	{
#ifdef PASSPHRASE_METER
	  passcheck_stop(&passcheck);
#endif /* PASSPHRASE_METER */
	  break;
	}
      if (c == 0)
	continue;
      
#if defined(PASSPHRASE_MOVE)
      cc = get_key(c, fdin);
      if (cc > 0)
	{
	  c = (char)cc;
	  if (point == len)
	    append_char();
# ifdef PASSPHRASE_INSERT
	  else
#  ifdef PASSPHRASE_OVERRIDE
	    if (insert)
#  endif /* PASSPHRASE_OVERRIDE */
	      insert_char();
# endif /* PASSPHRASE_INSERT */
# ifdef PASSPHRASE_OVERRIDE
	    else
	      override_char();
# endif /* PASSPHRASE_OVERRIDE */
	}
# if defined(PASSPHRASE_INSERT) && defined(PASSPHRASE_OVERRIDE)
      else if (cc == KEY_INSERT)                      insert ^= 1;
# endif /* PASSPHRASE_INSERT && PASSPHRASE_OVERRIDE */
# ifdef PASSPHRASE_DELETE
      else if ((cc == KEY_DELETE) && (len != point))  { delete_next(); print_delete(); }
# endif /* PASSPHRASE_DELETE */
      else if ((cc == KEY_ERASE) && point)            { erase_prev(); print_erase(); }
      else if ((cc == KEY_HOME)  && (point != 0))     move_home();
      else if ((cc == KEY_END)   && (point != len))   move_end();
      else if ((cc == KEY_RIGHT) && (point != len))   move_right();
      else if ((cc == KEY_LEFT)  && (point != 0))     move_left();
      
#elif defined(PASSPHRASE_STAR) || defined(PASSPHRASE_TEXT) /* PASSPHRASE_MOVE */
      if ((c == 8) || (c == 127))
	{
	  if (len == 0)
	    continue;
	  erase_prev();
	  print_erase();
	  
#ifdef PASSPHRASE_METER
	  passcheck_update(&passcheck, rc, len);
#endif /* PASSPHRASE_METER */
	  
	  xflush();
# ifdef DEBUG
	  goto debug;
# else /* DEBUG */
	  continue;
# endif /* DEBUG */
	}
      append_char();
      
#else /* PASSPHRASE_MOVE, PASSPHRASE_STAR || PASSPHRASE_TEXT */
      append_char();
#endif /* PASSPHRASE_MOVE, PASSPHRASE_STAR || PASSPHRASE_TEXT */
      
#ifdef PASSPHRASE_METER
      passcheck_update(&passcheck, rc, len);
#endif /* PASSPHRASE_METER */
      
      xflush();
      if (len == size)
	{
	  if ((rc = xrealloc(rc, (size_t)size, (size_t)size << 1)) == NULL)
	    return NULL;
	  size <<= 1L;
	}
      
#ifdef DEBUG
# ifdef __GNUC__
#  pragma GCC diagnostic push
#   pragma GCC diagnostic ignored "-Wunused-label"
# endif
    debug:
      {
	size_t n = 0;
	for (i = point; i < len; i++)
	  if ((*(rc + i) & 0xC0) != 0x80)
	    n++;
	*(rc + len) = 0;
	if (n)
	  fprintf(stderr, "\033[s\033[H\033[K%s\033[%zuD\033[01;34m%s\033[00m\033[u", rc, n, rc + point);
	else
	  fprintf(stderr, "\033[s\033[H\033[K%s\033[01;34m%s\033[00m\033[u", rc, rc + point);
	fflush(stderr);
      }
#endif /* DEBUG */
    }
  
  /* NUL-terminate passphrase */
  *(rc + len) = 0;
  
#if !defined(PASSPHRASE_ECHO) || defined(PASSPHRASE_MOVE)
  fprintf(stderr, "\n");
#endif /* !PASSPHRASE_ECHO || PASSPHRASE_MOVE */
  return rc;
  
#ifndef PASSPHRASE_METER
  (void) flags;
#endif /* !PASSPHRASE_METER */
}


/**
 * Reads the passphrase from stdin
 * 
 * @return  The passphrase, should be wiped and `free`:ed, `NULL` on error
 */
char* passphrase_read(void)
{
  return passphrase_read2(STDIN_FILENO, 0);
}