1 /*
2  * password.c
3  *
4  * This file is part of msmtp, an SMTP client, and of mpop, a POP3 client.
5  *
6  * Copyright (C) 2019, 2020, 2021  Martin Lambers <marlam@marlam.de>
7  * Jay Soffian <jaysoffian@gmail.com> (Mac OS X keychain support)
8  *
9  *   This program is free software; you can redistribute it and/or modify
10  *   it under the terms of the GNU General Public License as published by
11  *   the Free Software Foundation; either version 3 of the License, or
12  *   (at your option) any later version.
13  *
14  *   This program is distributed in the hope that it will be useful,
15  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
16  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  *   GNU General Public License for more details.
18  *
19  *   You should have received a copy of the GNU General Public License
20  *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 #ifdef HAVE_CONFIG_H
24 # include "config.h"
25 #endif
26 
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <unistd.h>
30 #include <errno.h>
31 #ifdef HAVE_LIBSECRET
32 # include <libsecret/secret.h>
33 #endif
34 #ifdef HAVE_MACOSXKEYRING
35 # include <Security/Security.h>
36 #endif
37 
38 #include "gettext.h"
39 #define _(string) gettext(string)
40 
41 #include "netrc.h"
42 #include "tools.h"
43 #include "xalloc.h"
44 #include "password.h"
45 
46 #ifdef W32_NATIVE
47 #define SYSNETRCFILE    "netrc.txt"
48 #define USERNETRCFILE   "netrc.txt"
49 #else /* UNIX */
50 #define SYSNETRCFILE    "netrc"
51 #define USERNETRCFILE   ".netrc"
52 #endif
53 
54 
55 /*
56  * password_get()
57  *
58  * see password.h
59  */
60 
61 #ifdef HAVE_LIBSECRET
get_schema(void)62 static const SecretSchema *get_schema(void)
63 {
64     static const SecretSchema schema = {
65         "de.marlam." PACKAGE_NAME ".password", SECRET_SCHEMA_DONT_MATCH_NAME,
66         {
67             {  "host", SECRET_SCHEMA_ATTRIBUTE_STRING },
68             {  "service", SECRET_SCHEMA_ATTRIBUTE_STRING },
69             {  "user", SECRET_SCHEMA_ATTRIBUTE_STRING },
70             {  "NULL", 0 },
71         }
72     };
73     return &schema;
74 }
service_string(password_service_t service)75 static const char *service_string(password_service_t service)
76 {
77     switch (service)
78     {
79     case password_service_smtp:
80         return "smtp";
81     case password_service_pop3:
82         return "pop3";
83     }
84     return NULL;
85 }
86 #endif
87 
password_get(const char * hostname,const char * user,password_service_t service,int consult_netrc,int getpass_only_via_tty)88 char *password_get(const char *hostname, const char *user,
89         password_service_t service,
90         int consult_netrc,
91         int getpass_only_via_tty)
92 {
93     char *password = NULL;
94 
95 #ifdef HAVE_LIBSECRET
96     if (!password)
97     {
98         gchar* libsecret_pw = secret_password_lookup_sync(
99                 get_schema(),
100                 NULL, NULL,
101                 "host", hostname,
102                 "service", service_string(service),
103                 "user", user,
104                 NULL);
105         if (!libsecret_pw)
106         {
107             /* for compatibility with passwords stored by the older
108              * libgnome-keyring */
109             libsecret_pw = secret_password_lookup_sync(
110                     SECRET_SCHEMA_COMPAT_NETWORK,
111                     NULL, NULL,
112                     "user", user,
113                     "protocol", service_string(service),
114                     "server", hostname,
115                     NULL);
116         }
117         if (libsecret_pw)
118         {
119             password = xstrdup(libsecret_pw);
120             secret_password_free(libsecret_pw);
121         }
122     }
123 #endif /* HAVE_LIBSECRET */
124 
125 #ifdef HAVE_MACOSXKEYRING
126     if (!password)
127     {
128         void *password_data;
129         UInt32 password_length;
130         if (SecKeychainFindInternetPassword(
131                     NULL,
132                     strlen(hostname), hostname,
133                     0, NULL,
134                     strlen(user), user,
135                     0, (char *)NULL,
136                     0,
137                     service == password_service_smtp ? kSecProtocolTypeSMTP : kSecProtocolTypePOP3,
138                     kSecAuthenticationTypeDefault,
139                     &password_length, &password_data,
140                     NULL) == noErr)
141         {
142             password = xmalloc((password_length + 1) * sizeof(char));
143             strncpy(password, password_data, (size_t)password_length);
144             password[password_length] = '\0';
145             SecKeychainItemFreeContent(NULL, password_data);
146         }
147     }
148 #endif /* HAVE_MACOSXKEYRING */
149 
150     if (!password && consult_netrc)
151     {
152         char *netrc_directory;
153         char *netrc_filename;
154         netrc_entry *netrc_hostlist;
155         netrc_entry *netrc_host;
156 
157         netrc_directory = get_homedir();
158         netrc_filename = get_filename(netrc_directory, USERNETRCFILE);
159         free(netrc_directory);
160         if ((netrc_hostlist = parse_netrc(netrc_filename)))
161         {
162             if ((netrc_host = search_netrc(netrc_hostlist, hostname, user)))
163             {
164                 password = xstrdup(netrc_host->password);
165             }
166             free_netrc(netrc_hostlist);
167         }
168         free(netrc_filename);
169         if (!password)
170         {
171             netrc_directory = get_sysconfdir();
172             netrc_filename = get_filename(netrc_directory, SYSNETRCFILE);
173             free(netrc_directory);
174             if ((netrc_hostlist = parse_netrc(netrc_filename)))
175             {
176                 if ((netrc_host = search_netrc(netrc_hostlist, hostname, user)))
177                 {
178                     password = xstrdup(netrc_host->password);
179                 }
180                 free_netrc(netrc_hostlist);
181             }
182             free(netrc_filename);
183         }
184     }
185 
186     if (!password)
187     {
188         int getpass_is_allowed = 1;
189         if (getpass_only_via_tty)
190         {
191             /* Do not let getpass() read from stdin, because we read the mail from
192              * there. Our W32 getpass() uses _getch(), which always reads from the
193              * 'console' and not stdin. On other systems, we test if /dev/tty can be
194              * opened before calling getpass(). */
195             int getpass_uses_tty;
196             FILE *tty;
197 #if defined W32_NATIVE || defined __CYGWIN__
198             getpass_uses_tty = 1;
199 #else
200             getpass_uses_tty = 0;
201             if ((tty = fopen("/dev/tty", "w+")))
202             {
203                 getpass_uses_tty = 1;
204                 fclose(tty);
205             }
206 #endif
207             if (!getpass_uses_tty)
208             {
209                 getpass_is_allowed = 0;
210             }
211         }
212         if (getpass_is_allowed)
213         {
214             char *prompt = xasprintf(_("password for %s at %s: "), user, hostname);
215             char *gpw = getpass(prompt);
216             free(prompt);
217             if (gpw)
218             {
219                 password = xstrdup(gpw);
220             }
221         }
222     }
223 
224     return password;
225 }
226 
227 
228 /*
229  * password_eval()
230  *
231  * see password.h
232  */
233 
234 #define LINEBUFSIZE 501
password_eval(const char * arg,char ** buf,char ** errstr)235 int password_eval(const char *arg, char **buf, char **errstr)
236 {
237     FILE *eval;
238     size_t bufsize;
239     size_t len;
240 
241     *buf = NULL;
242     *errstr = NULL;
243     errno = 0;
244     bufsize = 1; /* Account for the null character. */
245 
246     if (!(eval = popen(arg, "r")))
247     {
248         if (errno == 0)
249         {
250             errno = ENOMEM;
251         }
252         *errstr = xasprintf(_("cannot evaluate '%s': %s"), arg, strerror(errno));
253         return 1;
254     }
255 
256     do
257     {
258         bufsize += LINEBUFSIZE;
259         *buf = xrealloc(*buf, bufsize);
260         if (!fgets(&(*buf)[bufsize - LINEBUFSIZE - 1], LINEBUFSIZE + 1, eval))
261         {
262             *errstr = xasprintf(_("cannot read output of '%s'"), arg);
263             pclose(eval);
264             free(*buf);
265             *buf = NULL;
266             return 1;
267         }
268         len = strlen(*buf);
269         if (len > 0 && (*buf)[len - 1] == '\n')
270         {
271             /* Read only the first line. */
272             break;
273         }
274     }
275     while (!feof(eval));
276     pclose(eval);
277 
278     if (len > 0 && (*buf)[len - 1] == '\n')
279     {
280         (*buf)[len - 1] = '\0';
281         if (len - 1 > 0 && (*buf)[len - 2] == '\r')
282         {
283             (*buf)[len - 2] = '\0';
284         }
285     }
286 
287     return 0;
288 }
289