4 /* this module implements a password cache: the goal is to
5 allow multiple wmbiff mailboxes that are otherwise
6 independent get all their passwords while only asking the
7 user for an account's password once. */
8 /* NOTE: it will fail if a user has different passwords for
9 pop vs. imap on the same server; this seems too far
10 fetched to be worth complexity */
12 /* NOTE: it verifies that the askpass program, which, if
13 given with a full path, must be owned either by the user
14 or by root. There may be decent reasons not to do
17 /* Intended properties: 1) exit()s if the user presses
18 cancel from askpass - this is detected by no output from
19 askpass. 2) allows the caller to remove a cached entry
20 if it turns out to be wrong, and prompt the user
21 again. This might be poor if the askpass program is
22 replaced with something non-interactive. */
29 #include "passwordMgr.h"
31 #include "charutil.h" /* chomp */
33 #include <sys/types.h>
37 #include <strings.h> /* index */
42 #define DEFROB(x) memfrob(x, x ## _len)
43 #define ENFROB(x) memfrob(x, x ## _len)
49 typedef struct password_binding_struct
{
50 struct password_binding_struct
*next
;
53 char password
[BUF_SMALL
]; /* may be frobnicated */
54 unsigned char password_len
; /* frobnicated *'s are nulls */
57 static password_binding pass_list
= NULL
;
59 /* verifies that askpass_fname, if it has no spaces, exists as
60 a file, is owned by the user or by root, and is not world
61 writeable. This is just a sanity check, and is not intended
62 to ensure the integrity of the password-asking program. */
63 /* would be static, but used in test_wmbiff */
64 int permissions_ok(Pop3 pc
, const char *askpass_fname
)
67 if (index(askpass_fname
, ' ')) {
69 "askpass has a space in it; not verifying ownership/permissions on '%s'\n",
73 if (stat(askpass_fname
, &st
)) {
74 DM(pc
, DEBUG_ERROR
, "Can't stat askpass program: '%s'\n",
76 if (askpass_fname
[0] != '/') {
78 "For your own good, use a full pathname.\n");
82 if (st
.st_uid
!= 0 && st
.st_uid
!= getuid()) {
84 "askpass program isn't owned by you or root: '%s'\n",
88 if (st
.st_mode
& S_IWOTH
) {
90 "askpass program is world writable: '%s'\n", askpass_fname
);
96 #ifdef HAVE_CORESERVICES_CORESERVICES_H
97 #ifdef HAVE_SECURITY_SECURITY_H
98 #define HAVE_APPLE_KEYCHAIN
103 #ifdef HAVE_APPLE_KEYCHAIN
104 /* routines to use apple's keychain to get a password
105 without a user having to type. this avoids some damage
106 where although ssh-askpass can grab focus within X, it
107 may not have a particularly secure keyboard. */
109 #include<CoreServices/CoreServices.h>
110 #include<Security/Security.h>
113 get_password_from_keychain(Pop3 pc
, const char *username
,
114 const char *servername
,
115 /*@out@ */ char *password
,
117 unsigned char *password_len
)
123 rc
= SecKeychainCopyDefault(&kc
);
125 DM(pc
, DEBUG_ERROR
, "passmgr: unable to open keychain, exiting\n");
128 rc
= SecKeychainFindInternetPassword(kc
, strlen(servername
),
130 strlen(username
), username
, 0,
132 kSecAuthenticationTypeDefault
,
133 &pwdlen
, (void **) &secpwd
, NULL
);
136 "passmgr: keychain password grab for %s at %s failed, exiting\n", username
, servername
);
137 DM(pc
, DEBUG_ERROR
, "passmgr: (perhaps you pressed 'deny')\n");
138 /* this seems like the sanest thing to do, for now */
142 if (pwdlen
< *password_len
) {
143 strncpy(password
, secpwd
, pwdlen
);
144 password
[pwdlen
] = '\0';
145 *password_len
= pwdlen
;
148 "passmgr: warning: your password appears longer (%lu) than expected (%d)\n",
149 strlen(secpwd
), *password_len
- 1);
151 rc
= SecKeychainItemFreeContent(NULL
, secpwd
);
154 #endif /* apple keychain */
158 get_password_from_command(Pop3 pc
, const char *username
,
159 const char *servername
,
160 /*@out@ */ char *password
,
162 unsigned char *password_len
)
164 password
[*password_len
- 1] = '\0';
166 /* check that the executed file is a good one. */
167 if (permissions_ok(pc
, pc
->askpass
)) {
171 strlen(pc
->askpass
) + strlen(username
) +
172 strlen(servername
) + 40;
173 command
= malloc(len
);
174 snprintf(command
, len
, "%s 'password for wmbiff: %s@%s'",
175 pc
->askpass
, username
, servername
);
177 (void) grabCommandOutput(pc
, command
, &password_ptr
, NULL
);
178 /* it's not clear what to do with the exit
179 status, though we can get it from
180 grabCommandOutput if needed to deal with some
181 programs that will print a message but exit
185 if (password_ptr
== NULL
) {
186 /* this likely means that the user cancelled, and doesn't
187 want us to keep asking about the password. */
189 "passmgr: fgets password failed, exiting\n");
191 "passmgr: (it looks like you pressed 'cancel')\n");
192 /* this seems like the sanest thing to do, for now */
195 strncpy(password
, password_ptr
, *password_len
);
196 if (password
[*password_len
- 1] != '\0') {
198 "passmgr: warning: your password appears longer (%lu) than expected (%d)\n",
199 (unsigned long) strlen(password_ptr
), *password_len
- 1);
202 password
[*password_len
- 1] = '\0';
203 *password_len
= strlen(password
);
205 /* consider this error to be particularly troublesome */
207 "passmgr: permissions check of '%s' failed.", pc
->askpass
);
212 char *passwordFor(const char *username
,
213 const char *servername
, Pop3 pc
, int bFlushCache
)
218 assert(username
!= NULL
);
219 assert(username
[0] != '\0');
221 /* find the binding */
224 && (strcmp(username
, p
->user
) != 0 ||
225 strcmp(servername
, p
->server
) != 0); p
= p
->next
);
227 /* if so, return the password */
229 if (p
->password
[0] != '\0') {
230 if (bFlushCache
== 0) {
231 char *ret
= strdup(p
->password
);
233 unsigned short ret_len
= p
->password_len
;
238 /* else fall through, overwrite */
240 /* if we've asked, but received nothing, disable this box */
241 pc
->checkMail
= NULL
;
245 p
= (password_binding
)
246 malloc(sizeof(struct password_binding_struct
));
249 /* else, try to get it. */
250 if (pc
->askpass
!= NULL
) {
253 p
->password_len
= 32;
254 #ifdef HAVE_APPLE_KEYCHAIN
255 if (strcmp(pc
->askpass
, "internal:apple:keychain") == 0) {
256 get_password_from_keychain(pc
, username
, servername
,
257 p
->password
, &p
->password_len
);
260 "you could change your askpass line to:\n"
261 " askpass = internal:apple:keychain\n"
262 "to use the OS X keychain instead of running a command\n");
264 get_password_from_command(pc
, username
, servername
,
265 p
->password
, &p
->password_len
);
266 #ifdef HAVE_APPLE_KEYCHAIN
269 retval
= strdup(p
->password
);
270 if (strlen(username
) + 1 > BUF_SMALL
) {
271 DM(pc
, DEBUG_ERROR
, "username is too long.\n");
272 memset(p
->user
, 0, BUF_SMALL
);
274 strncpy(p
->user
, username
, BUF_SMALL
- 1);
276 if (strlen(servername
) + 1 > BUF_BIG
) {
277 DM(pc
, DEBUG_ERROR
, "servername is too long.\n");
278 memset(p
->server
, 0, BUF_BIG
);
280 strncpy(p
->server
, servername
, BUF_BIG
- 1);