1/* Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000,
2   2001, 2003, 2004, 2005, 2006, 2007, 2009 Free Software Foundation,
3   Inc.
4
5   This file is part of the GNU C Library.
6
7   This program is free software; you can redistribute it and/or modify
8   it under the terms of the GNU General Public License as published by
9   the Free Software Foundation; either version 3, or (at your option)
10   any later version.
11
12   This program is distributed in the hope that it will be useful,
13   but WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   GNU General Public License for more details.
16
17   You should have received a copy of the GNU General Public License along
18   with this program; if not, write to the Free Software Foundation,
19   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
20
21#ifndef _LIBC
22# include <config.h>
23#endif
24
25#include "getpass.h"
26
27#include <stdio.h>
28
29#if !((defined _WIN32 || defined __WIN32__) && !defined __CYGWIN__)
30
31#include <stdbool.h>
32
33#if HAVE_DECL___FSETLOCKING && HAVE___FSETLOCKING
34# if HAVE_STDIO_EXT_H
35#  include <stdio_ext.h>
36# endif
37#else
38# define __fsetlocking(stream, type)	/* empty */
39#endif
40
41#if HAVE_TERMIOS_H
42# include <termios.h>
43#endif
44
45#if USE_UNLOCKED_IO
46# include "unlocked-io.h"
47#else
48# if !HAVE_DECL_FFLUSH_UNLOCKED
49#  undef fflush_unlocked
50#  define fflush_unlocked(x) fflush (x)
51# endif
52# if !HAVE_DECL_FLOCKFILE
53#  undef flockfile
54#  define flockfile(x) ((void) 0)
55# endif
56# if !HAVE_DECL_FUNLOCKFILE
57#  undef funlockfile
58#  define funlockfile(x) ((void) 0)
59# endif
60# if !HAVE_DECL_FPUTS_UNLOCKED
61#  undef fputs_unlocked
62#  define fputs_unlocked(str,stream) fputs (str, stream)
63# endif
64# if !HAVE_DECL_PUTC_UNLOCKED
65#  undef putc_unlocked
66#  define putc_unlocked(c,stream) putc (c, stream)
67# endif
68#endif
69
70/* It is desirable to use this bit on systems that have it.
71   The only bit of terminal state we want to twiddle is echoing, which is
72   done in software; there is no need to change the state of the terminal
73   hardware.  */
74
75#ifndef TCSASOFT
76# define TCSASOFT 0
77#endif
78
79static void
80call_fclose (void *arg)
81{
82  if (arg != NULL)
83    fclose (arg);
84}
85
86char *
87getpass (const char *prompt)
88{
89  FILE *tty;
90  FILE *in, *out;
91  struct termios s, t;
92  bool tty_changed = false;
93  static char *buf;
94  static size_t bufsize;
95  ssize_t nread;
96
97  /* Try to write to and read from the terminal if we can.
98     If we can't open the terminal, use stderr and stdin.  */
99
100  tty = fopen ("/dev/tty", "w+");
101  if (tty == NULL)
102    {
103      in = stdin;
104      out = stderr;
105    }
106  else
107    {
108      /* We do the locking ourselves.  */
109      __fsetlocking (tty, FSETLOCKING_BYCALLER);
110
111      out = in = tty;
112    }
113
114  flockfile (out);
115
116  /* Turn echoing off if it is on now.  */
117#if HAVE_TCGETATTR
118  if (tcgetattr (fileno (in), &t) == 0)
119    {
120      /* Save the old one. */
121      s = t;
122      /* Tricky, tricky. */
123      t.c_lflag &= ~(ECHO | ISIG);
124      tty_changed = (tcsetattr (fileno (in), TCSAFLUSH | TCSASOFT, &t) == 0);
125    }
126#endif
127
128  /* Write the prompt.  */
129  fputs_unlocked (prompt, out);
130  fflush_unlocked (out);
131
132  /* Read the password.  */
133  nread = getline (&buf, &bufsize, in);
134
135  /* According to the C standard, input may not be followed by output
136     on the same stream without an intervening call to a file
137     positioning function.  Suppose in == out; then without this fseek
138     call, on Solaris, HP-UX, AIX, OSF/1, the previous input gets
139     echoed, whereas on IRIX, the following newline is not output as
140     it should be.  POSIX imposes similar restrictions if fileno (in)
141     == fileno (out).  The POSIX restrictions are tricky and change
142     from POSIX version to POSIX version, so play it safe and invoke
143     fseek even if in != out.  */
144  fseeko (out, 0, SEEK_CUR);
145
146  if (buf != NULL)
147    {
148      if (nread < 0)
149	buf[0] = '\0';
150      else if (buf[nread - 1] == '\n')
151	{
152	  /* Remove the newline.  */
153	  buf[nread - 1] = '\0';
154	  if (tty_changed)
155	    {
156	      /* Write the newline that was not echoed.  */
157	      putc_unlocked ('\n', out);
158	    }
159	}
160    }
161
162  /* Restore the original setting.  */
163#if HAVE_TCSETATTR
164  if (tty_changed)
165    tcsetattr (fileno (in), TCSAFLUSH | TCSASOFT, &s);
166#endif
167
168  funlockfile (out);
169
170  call_fclose (tty);
171
172  return buf;
173}
174
175#else /* W32 native */
176
177/* Windows implementation by Martin Lambers <marlam@marlam.de>,
178   improved by Simon Josefsson. */
179
180/* For PASS_MAX. */
181#include <limits.h>
182/* For _getch(). */
183#include <conio.h>
184/* For strdup(). */
185#include <string.h>
186
187#ifndef PASS_MAX
188# define PASS_MAX 512
189#endif
190
191char *
192getpass (const char *prompt)
193{
194  char getpassbuf[PASS_MAX + 1];
195  size_t i = 0;
196  int c;
197
198  if (prompt)
199    {
200      fputs (prompt, stderr);
201      fflush (stderr);
202    }
203
204  for (;;)
205    {
206      c = _getch ();
207      if (c == '\r')
208	{
209	  getpassbuf[i] = '\0';
210	  break;
211	}
212      else if (i < PASS_MAX)
213	{
214	  getpassbuf[i++] = c;
215	}
216
217      if (i >= PASS_MAX)
218	{
219	  getpassbuf[i] = '\0';
220	  break;
221	}
222    }
223
224  if (prompt)
225    {
226      fputs ("\r\n", stderr);
227      fflush (stderr);
228    }
229
230  return strdup (getpassbuf);
231}
232#endif
233