maint: update copyright
[gnulib.git] / lib / getpass.c
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 2, 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
76 static void
77 call_fclose (void *arg)
78 {
79   if (arg != NULL)
80     fclose (arg);
81 }
82
83 char *
84 getpass (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
188 char *
189 getpass (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