aboutsummaryrefslogtreecommitdiffstats
path: root/src/unistd/getpass.c
blob: b764aed7691d83023925e68e697142b98ab09d65 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
/**
 * slibc — Yet another C library
 * Copyright © 2015, 2016  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;
}