aboutsummaryrefslogblamecommitdiffstats
path: root/src/unistd/getpass.c
blob: 5968d3810f19ca31f9333e98d44fc4174166b48b (plain) (tree)
































































































































































































                                                                                         
/**
 * slibc — Yet another C library
 * Copyright © 2015  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 <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <termios.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <slibc-alloca.h>


#define t(...)  do { if (__VA_ARGS__) goto fail; } while (0)



/**
 * This function performs the actual password-reading
 * part of `getpass`. It will read into a statically
 * allocated buffer until that buffer is full (8196 bytes,
 * including termination), and switch to a dynamically
 * allocated buffer when the it [the statically allocated
 * buffer] is full.
 * 
 * @prarm   fd  File descriptor to the, already configured,
 *              controlling terminal.
 * @return      The entered line. You should override it with zeroes as
 *              soon as possible to avoid leaving cleartest passphrases
 *              visible in memory, or potentially stored to unencrypted
 *              swap. The returned string is statically allocated, and
 *              should not be deallocated, if the length of the string,
 *              is (strictly) less than 8196, otherwise it is dynamically
 *              allocated, and should be deallocated.
 * 
 * @throws  Any error specified for malloc(3).
 * @throws  Any error specified for read(3).
 */
static char* _getpass(int fd)
{
  static char pass_s[8196];
  char* pass_d = NULL;
  size_t ptr_s = 0, ptr_d = 8196, size_d = (8196 << 1);
  ssize_t got = 1;
  void* new;
  
  /* Read into statically allocated buffer until it is full. */
  while (ptr_s < sizeof(pass_s) / sizeof(char))
    {
      /* Read. */
      t (got = read(fd, pass_s + ptr_s, sizeof(pass_s) - ptr_s * sizeof(char)), got < 0);
      ptr_s += (size_t)got;
      /* End of line? */
      if ((got == 0) || (pass_s[ptr_s - 1] == '\n'))
	{
	  pass_s[ptr_s - !!got] = '\0';
	  return pass_s;
	}
    }
  
  /* Transfer input to dynamically allocated buffer. */
  t (pass_d = secure_realloc(pass_d, size_d * sizeof(char)), pass_d == NULL);
  memcpy(pass_d, pass_s, sizeof(pass_s)), explicit_bzero(pass_s, sizeof(pass_s));
  
  /* Read into dynamically allocated buffer. */
  for (;;)
    {
      /* Do we need to grow the buffer? */
      if (ptr_d + 1 == size_d)
	{
	  t (new = secure_realloc(pass_d, (size_d <<= 1) * sizeof(char)), new == NULL);
	  pass_d = new;
	}
      /* Read. */
      t (got = read(fd, pass_d + ptr_d, (size_d - ptr_d - 1) * sizeof(char)), got < 0);
      ptr_d += (size_t)got;
      /* End of line? */
      if ((got == 0) || (pass_d[ptr_d - 1] == '\n'))
	{
	  pass_d[ptr_d - !!got] = '\0';
	  return pass_d;
	}
    }
  
 fail:
  saved_errno = errno;
  explicit_bzero(pass_s, sizeof(pass_s));
  secure_free(pass_d);
  errno = saved_errno;
  return NULL;
}


/**
 * Get password input from the terminal.
 * 
 * The exact behaviour of this function depends on the implementations.
 * However you can assume that, the controlling terminal (/dev/tty) is
 * opened and used for input and output, and that echoing is disabled.
 * You cannot assume that arbitrary lengths are supported. However, in
 * this implementation, line editing is enabled and arbitrary lengths
 * are supported. If the length of the input (excluding termination) is
 * less than 8192 a statically allocated string is returned, otherwise
 * a dynamically allocated string is returned.
 * 
 * @etymology  (Get) (pass)word from terminal!
 * 
 * @param   prompt  Text to print at the beginning of the line.
 *                  Used to tell the user what is expected of her.
 *                  Must not be `NULL`.
 * @return          The entered line. You should override it with zeroes as
 *                  soon as possible to avoid leaving cleartest passphrases
 *                  visible in memory, or potentially stored to unencrypted
 *                  swap. The returned string is statically allocated, do
 *                  not deallocate it, unless you know that you are using
 *                  slibc and the length of the string is at least 8192.
 *                  `NULL` on error. If a statically allocated string is
 *                  returned, it will be overwritten at the next call.
 * 
 * @throws  Any error specified for open(3).
 * @throws  Any error specified for malloc(3).
 * @throws  Any error specified for read(3).
 * @throws  Any error specified for tcgetattr(3).
 * @throws  Any error specified for tcsetattr(3).
 * @throws  Any error specified for tcdrain(3).
 * 
 * @since  Always.
 */
char* getpass(const char* prompt)
{
  struct termios saved_stty, stty;
  char* pass;
  int fd = -1, stty_saved = 0, saved_errno;
  
  /* Open controlling terminal. */
  t (fd = open("/dev/tty", O_RDWR | O_NOCTTY | O_CLOEXEC), fd < 0);
  
  /* Configure terminal settings. */
  t (tcgetattr(fd, &stty));
  saved_stty = stty;
  stty_saved = 1;
  stty.c_lflag = (stty.c_lflag & (tcflag_t)~(ECHO  | ISIG )) | (tcflag_t)ICANON;
  stty.c_iflag = (stty.c_iflag & (tcflag_t)~(INLCR | IGNCR)) | (tcflag_t)ICRNL;
  t (tcsetattr(fd, TCSAFLUSH, &stty));
  t (tcdrain(fd));
  
  /* Prompt the user. */
  (void) dprintf(fd, "%s", prompt);
  
  /* Get password. */
  pass = _getpass(fd);
  if (pass == NULL)
    goto fail;
  
  /* Move cursor to next line. */
  (void) dprintf(fd, "\n");
  
  /* Restore terminal settings. */
  (void) tcsetattr(fd, TCSAFLUSH, &saved_stty);
  (void) tcdrain(fd);
  
  /* Close controlling terminal. */
  close(fd);
  
  return pass;
  
 fail:
  saved_errno = errno;
  if (fd >= 0)
    close(fd);
  if (stty_saved)
    {
      tcsetattr(fd, TCSAFLUSH, &saved_stty);
      tcdrain(fd);
    }
  errno = saved_errno;
  return NULL;
}