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













                                                                        




                    





                                  
                                                         



                                 
      

 
                          
                                                                           
 

                                             
         
                                  
                                 

                                



              
                                                                                       


      



                                                                                      
                                                                    
                                              
     
                                              




                              
                                                                           
                                
                                         
       
                                              

        

 


                                  
                                                                      
   
                           
 
                                                           
                                     











                                                             

        
                 
                
  
                                                          








                                                                  














































                                                                          

                                           
                              
                          
                                    
                          







                                           
                                            

                                           
                                
                          

                                                 








                                        
                                                              
                          

                                                     
                                                   
                                    






                                     
                                  

                                       
                                                                                          


                                      
                             

                                                          



                                           

                                           
                                    
                          






                                                  




                                               























                                                                         




                                               










                                                   
                                                            














                                                                         
                                


                                                               


                              



                                     
                                             
                       
                                


                         
                       
      
             
                                 
                               
                            




                            
                          
             
                                                                               


                            











                                               
                                                                                                             
                

                                                                                                 

           





                                
                                                         
                        
      




            
                                                                                            
   
                                  
 
                                                         

                      

                                 
                                  
                                                        
                                    
      
                                            
      



   
                                                
   
                                   
 
                                                         
                                                  
      

 
/**
 * libpassphrase – Personalisable library for TTY passphrase reading
 * 
 * Copyright © 2013, 2014  Mattias Andrée (maandree@member.fsf.org)
 * 
 * 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 <termios.h>
#include <unistd.h>

#include "passphrase.h"


#define START_PASSPHRASE_LIMIT  32


#if !defined(PASSPHRASE_ECHO) || defined(PASSPHRASE_MOVE)
/**
 * The original TTY settings
 */
static struct termios saved_stty;
#endif


#ifndef PASSPHRASE_REALLOC
static inline 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);
  for (i = 0; i < cur_size; i++)
    *(array + i) = 0;
  free(array);
  return rc;
}
#else
#define xrealloc(array, _cur_size, new_size)  realloc(array, (new_size) * sizeof(char))
#endif


#if defined(PASSPHRASE_MOVE) && !defined(PASSPHRASE_STAR) && !defined(PASSPHRASE_ECHO)
#  define xprintf(...)  /* do nothing */
#  define xflush()      /* do nothing */
#elif defined(PASSPHRASE_MOVE) || defined(PASSPHRASE_STAR)
#  define xprintf(FORMAT, ...)  fprintf(stderr, FORMAT, __VA_ARGS__)
#  define xflush()              fflush(stderr)
#else
#  define xflush()              fflush(stderr)
#endif


#ifdef PASSPHRASE_MOVE
#  if defined(PASSPHRASE_STAR)
#    define xputchar(C)  ({ if ((C & 0xC0) != 0x80)  fputc('*', stderr); })
#  elif defined(PASSPHRASE_ECHO)
#    define xputchar(C)  fputc(C, stderr)
#  else
#    define xputchar(C)  ({ /* be silent */ })
#  endif
#endif


/**
 * Reads the passphrase from stdin
 * 
 * @return  The passphrase, should be wiped `free`:ed, `NULL` on error
 */
char* passphrase_read(void)
{
  char* rc = malloc(START_PASSPHRASE_LIMIT * sizeof(char));
  long size = START_PASSPHRASE_LIMIT;
  long len =  0;
#ifdef PASSPHRASE_MOVE
  long point = 0;
  long i = 0;
#  ifdef PASSPHRASE_OVERRIDE
#    if defined(PASSPHRASE_INSERT) && defined(DEFAULT_INSERT)
  char insert = 1;
#    elif defined(PASSPHRASE_INSERT)
  char insert = 0;
#    endif
#  endif
#endif
  int c;
  
  if (rc == NULL)
    return NULL;
  
  /* 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 = getchar();
      if ((c < 0) || (c == '\n'))
	break;
      if (c != 0)
        {
#if defined(PASSPHRASE_MOVE)
	  /* \e[1~  \eOH  ^A  -1  home   */
	  /* \e[2~            -2  insert */
	  /* \e[3~  ^D        -3  delete */
	  /* \e[4~  \eOF  ^E  -4  end    */
	  /* \8     \127      -5  erase  */
	  /* \e[C   ^F        -6  right  */
	  /* \e[D   ^B        -7  left   */
	  int cc = 0;
#ifdef PASSPHRASE_DEDICATED
	  if (c == '\033')
	    {
	      c = getchar();
	      if (c == 'O')
		{
		  c = getchar();
		  if      (c == 'H')  cc = -1;
		  else if (c == 'F')  cc = -4;
		}
	      else if (c == '[')
		{
		  c = getchar();
		  if      (c == 'C')  cc = -6;
		  else if (c == 'D')  cc = -7;
		  else if (('1' <= c) && (c <= '4') && (getchar() == '~'))
		    cc = -(c - '0');
		}
	    }
	  else
#endif
	    if ((c == 8) || (c == 127))
	      cc = -5;
	    else if ((c < 0) || (c >= ' '))
	      cc = ((int)c) & 255;
#ifdef PASSPHRASE_CONTROL
	    else if (c == 'A' - '@')  cc = -1;
	    else if (c == 'B' - '@')  cc = -7;
	    else if (c == 'D' - '@')  cc = -3;
	    else if (c == 'E' - '@')  cc = -4;
	    else if (c == 'F' - '@')  cc = -6;
#endif
	  
	  if (cc > 0)
	    {
	      c = (char)cc;
	      if (point == len)
		{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
		  xputchar(c);
#pragma GCC diagnostic pop
		  *(rc + len++) = c;
		  point++;
		}
#ifdef PASSPHRASE_INSERT
	      else
#ifdef PASSPHRASE_OVERRIDE
		if (insert)
#endif
		  {
		    if ((c & 0xC0) != 0x80)
		      { xprintf("\033[@"); }
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
		    xputchar(c);
#pragma GCC diagnostic pop
		    for (i = len; i > point; i--)
		      *(rc + i) = *(rc + i - 1);
		    len++;
		    *(rc + point++) = c;
		  }
#endif
#ifdef PASSPHRASE_OVERRIDE
		else
		  {
		    long n = 1;
		    char cn = c;
		    while ((*(rc + point + n) & 0xC0) == 0x80)
		      n++;
		    for (i = point + n; i < len; i++)
		      *(rc + i - n) = *(rc + i);
		    for (i = len - n; i < len; i++)
		      *(rc + i) = 0;
		    len -= n;
		    n = 0;
		    while (cn & 0x80)
		      {
			cn <<= 1;
			n++;
		      }
		    n = n ? n : 1;
		    if (len + n > size)
		      {
			if ((rc = xrealloc(rc, (size_t)size, (size_t)size << 1L)) == NULL)
			  return NULL;
			size <<= 1L;
		      }
		    len += n;
		    for (i = len - 1; i >= point + n; i--)
		      *(rc + i) = *(rc + i - n);
		    for (i = 0; i < n; i++)
		      {
			if (i)
			  c = getchar();
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
			xputchar(c);
#pragma GCC diagnostic pop
			*(rc + point++) = c;
		      }
		  }
#endif
	    }
	  else if ((cc == -1) && point) /* home */
	    {
	      long n = 0;
	      for (i = 0; i < point; i++)
		if ((*(rc + i) & 0xC0) != 0x80)
		  n++;
	      xprintf("\033[%liD", n);
	      point = 0;
	    }
#if defined(PASSPHRASE_INSERT) && defined(PASSPHRASE_OVERRIDE)
	  else if (cc == -2) /* insert */
	    insert ^= 1;
#endif
#ifdef PASSPHRASE_DELETE
	  else if ((cc == -3) && (len != point)) /* delete */
	    {
	      xprintf("\033[P");
#ifdef PASSPHRASE_INVALID
	      *(rc + len) = 0;
#endif
	      do
		{
		  for (i = point; i < len; i++)
		    *(rc + i) = *(rc + i + 1);
		  len--;
		}
	      while ((len != point) && ((*(rc + point) & 0xC0) == 0x80));
	    }
#endif
	  else if ((cc == -4) && (len != point)) /* end */
	    {
	      long n = 0;
	      for (i = point; i < len; i++)
		if ((*(rc + i) & 0xC0) != 0x80)
		  n++;
	      xprintf("\033[%liC", n);
	      point = len;
	    }
	  else if ((cc == -5) && point) /* erase */
	    {
	      char redo = 1;
	      xprintf("\033[D\033[P");
#ifdef PASSPHRASE_INVALID
	      *(rc + len) = 0;
#endif
	      while (redo)
		{
		  redo = (*(rc + point - 1) & 0xC0) == 0x80;
		  for (i = point; i <= len; i++)
		    *(rc + i - 1) = *(rc + i);
		  point--;
		  len--;
		}
	    }
	  else if ((cc == -6) && (len != point)) /* right */
	    {
	      xprintf("\033[C");
	      do
		point++;
	      while ((len != point) && ((*(rc + point) & 0xC0) == 0x80));
	    }
	  else if ((cc == -7) && point) /* left */
	    {
	      xprintf("\033[D");
	      point--;
	      while (point && ((*(rc + point) & 0xC0) == 0x80))
	        point--;
	    }
	  
#elif defined(PASSPHRASE_STAR)
	  if ((c == 8) || (c == 127))
	    {
	      if (len == 0)
		continue;
	      xprintf("%s", "\033[D \033[D");
	      xflush();
	      *(rc + --len) = 0;
#ifdef DEBUG
	      goto debug;
#else
	      continue;
#endif
	    }
	  if ((c & 0xC0) != 0x80)
	    fputc('*', stderr);
	  *(rc + len++) = c;
#else
	  *(rc + len++) = c;
#endif
	  
	  xflush();
	  if (len == size)
	    {
	      if ((rc = xrealloc(rc, (size_t)size, (size_t)size << 1)) == NULL)
		return NULL;
	      size <<= 1L;
	    }

#ifdef DEBUG
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-label"
	debug:
	  {
	    long 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[%liD\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
	}
    }
  
  /* NUL-terminate passphrase */
  *(rc + len) = 0;
  
#if !defined(PASSPHRASE_ECHO) || defined(PASSPHRASE_MOVE)
  fprintf(stderr, "\n");
#endif
  return rc;
}


/**
 * Disable echoing and do anything else to the terminal settnings `passphrase_read` requires
 */
void passphrase_disable_echo(void)
{
#if !defined(PASSPHRASE_ECHO) || defined(PASSPHRASE_MOVE)
  struct termios stty;
  
  tcgetattr(STDIN_FILENO, &stty);
  saved_stty = stty;
  stty.c_lflag &= (tcflag_t)~ECHO;
#if defined(PASSPHRASE_STAR) || defined(PASSPHRASE_MOVE)
  stty.c_lflag &= (tcflag_t)~ICANON;
#endif
  tcsetattr(STDIN_FILENO, TCSAFLUSH, &stty);
#endif
}


/**
 * Undo the actions of `passphrase_disable_echo`
 */
void passphrase_reenable_echo(void)
{
#if !defined(PASSPHRASE_ECHO) || defined(PASSPHRASE_MOVE)
  tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_stty);
#endif
}