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