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