wmclockmon: update change-log
[dockapps.git] / wmbiff / wmbiff / passwordMgr.c
blob917291bdd6990cd9c0a78e64e98e89e6f2a03187
1 /* passwordMgr.c
2 * Author: Neil Spring
3 */
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
15 this. */
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. */
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
29 #include "passwordMgr.h"
30 #include "Client.h"
31 #include "charutil.h" /* chomp */
32 #include <unistd.h>
33 #include <sys/types.h>
34 #include <stdlib.h>
35 #include <errno.h>
36 #include <string.h>
37 #include <strings.h> /* index */
38 #include <sys/stat.h>
39 #include "assert.h"
41 #ifdef HAVE_MEMFROB
42 #define DEFROB(x) memfrob(x, x ## _len)
43 #define ENFROB(x) memfrob(x, x ## _len)
44 #else
45 #define DEFROB(x)
46 #define ENFROB(x)
47 #endif
49 typedef struct password_binding_ {
50 struct password_binding_ *next;
51 char user[BUF_SMALL];
52 char server[BUF_BIG];
53 char password[BUF_SMALL]; /* may be frobnicated */
54 size_t password_len; /* frobnicated *'s are nulls */
55 } password_binding;
57 static password_binding *pass_list;
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)
66 struct stat st;
67 if (index(askpass_fname, ' ')) {
68 DM(pc, DEBUG_INFO,
69 "askpass has a space in it; not verifying ownership/permissions on '%s'\n",
70 askpass_fname);
71 return (1);
73 if (stat(askpass_fname, &st)) {
74 DM(pc, DEBUG_ERROR, "Can't stat askpass program: '%s'\n",
75 askpass_fname);
76 if (askpass_fname[0] != '/') {
77 DM(pc, DEBUG_ERROR,
78 "For your own good, use a full pathname.\n");
80 return (0);
82 if (st.st_uid != 0 && st.st_uid != getuid()) {
83 DM(pc, DEBUG_ERROR,
84 "askpass program isn't owned by you or root: '%s'\n",
85 askpass_fname);
86 return (0);
88 if (st.st_mode & S_IWOTH) {
89 DM(pc, DEBUG_ERROR,
90 "askpass program is world writable: '%s'\n", askpass_fname);
91 return (0);
93 return (1);
96 #ifdef HAVE_CORESERVICES_CORESERVICES_H
97 #ifdef HAVE_SECURITY_SECURITY_H
98 #define HAVE_APPLE_KEYCHAIN
99 #endif
100 #endif
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>
112 static void
113 get_password_from_keychain(Pop3 *pc, const char *username,
114 const char *servername,
115 /*@out@ */ char *password,
116 /*@out@ */ size_t *password_len)
118 SecKeychainRef kc;
119 OSStatus rc;
120 char *secpwd;
121 UInt32 pwdlen;
122 rc = SecKeychainCopyDefault(&kc);
123 if (rc != noErr) {
124 DM(pc, DEBUG_ERROR, "passmgr: unable to open keychain, exiting\n");
125 exit(EXIT_FAILURE);
127 rc = SecKeychainFindInternetPassword(kc, strlen(servername),
128 servername, 0, NULL,
129 strlen(username), username, 0,
130 NULL, 0, NULL,
131 kSecAuthenticationTypeDefault,
132 &pwdlen, (void **) &secpwd, NULL);
133 if (rc != noErr) {
134 DM(pc, DEBUG_ERROR,
135 "passmgr: keychain password grab for %s at %s failed, exiting\n", username, servername);
136 DM(pc, DEBUG_ERROR, "passmgr: (perhaps you pressed 'deny')\n");
137 /* this seems like the sanest thing to do, for now */
138 exit(EXIT_FAILURE);
141 if (pwdlen < *password_len) {
142 strncpy(password, secpwd, pwdlen);
143 password[pwdlen] = '\0';
144 *password_len = pwdlen;
145 } else {
146 DM(pc, DEBUG_ERROR,
147 "passmgr: warning: your password appears longer (%zu) than expected (%zu)\n",
148 strlen(secpwd), *password_len - 1);
150 rc = SecKeychainItemFreeContent(NULL, secpwd);
151 return;
153 #endif /* apple keychain */
156 static void
157 get_password_from_command(Pop3 *pc, const char *username,
158 const char *servername,
159 /*@out@ */ char *password,
160 /*@out@ */
161 size_t *password_len)
163 password[*password_len - 1] = '\0';
164 password[0] = '\0';
165 /* check that the executed file is a good one. */
166 if (permissions_ok(pc, pc->askpass)) {
167 char *command;
168 char *password_ptr;
169 int len =
170 strlen(pc->askpass) + strlen(username) +
171 strlen(servername) + 40;
172 command = malloc(len);
173 snprintf(command, len, "%s 'password for wmbiff: %s@%s'",
174 pc->askpass, username, servername);
176 (void) grabCommandOutput(pc, command, &password_ptr, NULL);
177 /* it's not clear what to do with the exit
178 status, though we can get it from
179 grabCommandOutput if needed to deal with some
180 programs that will print a message but exit
181 non-zero on error */
182 free(command);
184 if (password_ptr == NULL) {
185 /* this likely means that the user cancelled, and doesn't
186 want us to keep asking about the password. */
187 DM(pc, DEBUG_ERROR,
188 "passmgr: fgets password failed, exiting\n");
189 DM(pc, DEBUG_ERROR,
190 "passmgr: (it looks like you pressed 'cancel')\n");
191 /* this seems like the sanest thing to do, for now */
192 exit(EXIT_FAILURE);
194 strncpy(password, password_ptr, *password_len);
195 if (password[*password_len - 1] != '\0') {
196 DM(pc, DEBUG_ERROR,
197 "passmgr: warning: your password appears longer (%zu) than expected (%zu)\n",
198 strlen(password_ptr), *password_len - 1);
200 free(password_ptr);
201 password[*password_len - 1] = '\0';
202 *password_len = strlen(password);
203 } else {
204 /* consider this error to be particularly troublesome */
205 DM(pc, DEBUG_ERROR,
206 "passmgr: permissions check of '%s' failed.", pc->askpass);
207 exit(EXIT_FAILURE);
211 char *passwordFor(const char *username, const char *servername, Pop3 *pc,
212 int bFlushCache)
215 password_binding *p;
216 int p_allocked = 0;
218 assert(username != NULL);
219 assert(username[0] != '\0');
221 /* find the binding */
222 for (p = pass_list;
223 p != NULL && (strcmp(username, p->user) != 0 ||
224 strcmp(servername, p->server) != 0); p = p->next);
226 /* if so, return the password */
227 if (p != NULL) {
228 if (p->password[0] != '\0') {
229 if (bFlushCache == 0) {
230 char *ret = strdup(p->password);
231 #ifdef HAVE_MEMFROB
232 size_t ret_len = p->password_len;
233 DEFROB(ret);
234 #endif
235 return (ret);
237 /* else fall through, overwrite */
238 } else {
239 if (pc) {
240 /* if we've asked, but received nothing, disable this box */
241 pc->checkMail = NULL;
243 return (NULL);
245 } else {
246 p = malloc(sizeof *p);
247 p_allocked = 1;
250 /* else, try to get it. */
251 if (pc->askpass != NULL) {
252 char *retval;
254 p->password_len = 32;
255 #ifdef HAVE_APPLE_KEYCHAIN
256 if (strcmp(pc->askpass, "internal:apple:keychain") == 0) {
257 get_password_from_keychain(pc, username, servername,
258 p->password, &p->password_len);
259 } else {
260 DM(pc, DEBUG_ERROR,
261 "you could change your askpass line to:\n"
262 " askpass = internal:apple:keychain\n"
263 "to use the OS X keychain instead of running a command\n");
264 #endif
265 get_password_from_command(pc, username, servername,
266 p->password, &p->password_len);
267 #ifdef HAVE_APPLE_KEYCHAIN
269 #endif
270 retval = strdup(p->password);
271 if (strlen(username) + 1 > BUF_SMALL) {
272 DM(pc, DEBUG_ERROR, "username is too long.\n");
273 memset(p->user, 0, BUF_SMALL);
274 } else {
275 strncpy(p->user, username, BUF_SMALL - 1);
277 if (strlen(servername) + 1 > BUF_BIG) {
278 DM(pc, DEBUG_ERROR, "servername is too long.\n");
279 memset(p->server, 0, BUF_BIG);
280 } else {
281 strncpy(p->server, servername, BUF_BIG - 1);
283 ENFROB(p->password);
284 p->next = pass_list;
285 pass_list = p;
286 return (retval);
289 if (p_allocked) {
290 free(p);
292 return (NULL);
295 /* vim:set ts=4: */
297 * Local Variables:
298 * tab-width: 4
299 * c-indent-level: 4
300 * c-basic-offset: 4
301 * End: