1 |
/* wptImportList.cpp - Key import list |
2 |
* Copyright (C) 2001-2007 Timo Schulz |
3 |
* Copyright (C) 2005 g10 Code GmbH |
4 |
* |
5 |
* This file is part of WinPT. |
6 |
* |
7 |
* WinPT is free software; you can redistribute it and/or |
8 |
* modify it under the terms of the GNU General Public License |
9 |
* as published by the Free Software Foundation; either version 2 |
10 |
* of the License, or (at your option) any later version. |
11 |
* |
12 |
* WinPT is distributed in the hope that it will be useful, |
13 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
15 |
* General Public License for more details. |
16 |
*/ |
17 |
#ifdef HAVE_CONFIG_H |
18 |
#include <config.h> |
19 |
#endif |
20 |
|
21 |
#include <windows.h> |
22 |
#include <sys/types.h> |
23 |
#include <ctype.h> |
24 |
|
25 |
#include "resource.h" |
26 |
#include "wptTypes.h" |
27 |
#include "wptGPG.h" |
28 |
#include "wptCommonCtl.h" |
29 |
#include "wptKeylist.h" |
30 |
#include "wptNLS.h" |
31 |
#include "wptErrors.h" |
32 |
#include "wptUTF8.h" |
33 |
#include "wptW32API.h" |
34 |
#include "wptRegistry.h" |
35 |
#include "wptVersion.h" |
36 |
#include "StringBuffer.h" |
37 |
#include "openpgp.h" |
38 |
|
39 |
|
40 |
/* Symbolic column ids */ |
41 |
enum impl_col_t { |
42 |
IMPL_COL_UID = 0, |
43 |
IMPL_COL_SIZE = 1, |
44 |
IMPL_COL_KEYID = 2, |
45 |
IMPL_COL_CREATION = 3, |
46 |
IMPL_COL_KTYPE = 4 |
47 |
}; |
48 |
|
49 |
/* context to hold information about a key. */ |
50 |
struct import_key_s { |
51 |
/* primary userid */ |
52 |
char *uid; |
53 |
|
54 |
/* 1 = secret key is available. */ |
55 |
int secret; |
56 |
|
57 |
/* flags */ |
58 |
unsigned int disabled:1; |
59 |
unsigned int expired:1; |
60 |
unsigned int revoked:1; |
61 |
|
62 |
/* primary key attributes */ |
63 |
char keyid[16+1]; |
64 |
gpgme_pubkey_algo_t pubkey_algo; |
65 |
size_t length; |
66 |
time_t timestamp; |
67 |
|
68 |
/* secondary key attributes */ |
69 |
char subkey_keyid[16+1]; |
70 |
gpgme_pubkey_algo_t subkey_algo; |
71 |
size_t subkey_length; |
72 |
time_t subkey_timestamp; |
73 |
}; |
74 |
typedef struct import_key_s *import_key_t; |
75 |
|
76 |
|
77 |
/* Release the key in @c. */ |
78 |
static void |
79 |
import_key_release (import_key_t c) |
80 |
{ |
81 |
if (!c) |
82 |
return; |
83 |
safe_free (c->uid); |
84 |
safe_free (c); |
85 |
} |
86 |
|
87 |
|
88 |
/* Decode the %-encoded userid in @pend and store it as the first |
89 |
userid in the key @c. */ |
90 |
static void |
91 |
decode_userid (char *pend, import_key_t c) |
92 |
{ |
93 |
char *uid; |
94 |
|
95 |
uid = (char*)calloc (1, strlen (pend) + 1); |
96 |
if (!uid) |
97 |
BUG (NULL); |
98 |
gpg_decode_c_string (pend, &uid, strlen (pend) + 1); |
99 |
uid[strlen (uid) -3] = '\0'; |
100 |
c->uid = utf8_to_native (uid); |
101 |
safe_free (uid); |
102 |
} |
103 |
|
104 |
|
105 |
/* Parse the output of 'gpg --with-colons <data'. */ |
106 |
static void |
107 |
parse_colon_key (char *buf, import_key_t c) |
108 |
{ |
109 |
const char * s; |
110 |
char *p, *pend; |
111 |
int field = 0, rectype = 0; |
112 |
enum key_t { |
113 |
KEY_none = 0, |
114 |
KEY_primary = 1, |
115 |
KEY_secondary = 2, |
116 |
}; |
117 |
|
118 |
if (!buf) |
119 |
return; /* EOF */ |
120 |
|
121 |
if( !strncmp (buf, "pub", 3) || !strncmp (buf, "sec", 3)) { |
122 |
rectype = KEY_primary; |
123 |
if (*buf == 's') |
124 |
c->secret = 1; |
125 |
} |
126 |
else if (!strncmp (buf, "sub", 3) || !strncmp (buf, "ssb", 3)) |
127 |
rectype = KEY_secondary; |
128 |
else if (!strncmp (buf, "uid", 3)) { |
129 |
if (!c->uid) { |
130 |
const char *uid = buf+3; |
131 |
while (uid && *uid == ':') |
132 |
uid++; |
133 |
decode_userid ((char *)uid, c); |
134 |
} |
135 |
return; |
136 |
} |
137 |
else |
138 |
return; |
139 |
|
140 |
for (p = buf; p; p = pend) { |
141 |
field++; |
142 |
pend = strchr (p, ':'); |
143 |
if (pend) |
144 |
*pend++ = 0; |
145 |
|
146 |
switch (field) { |
147 |
case 1: |
148 |
if (rectype != KEY_primary) |
149 |
break; |
150 |
for (s = pend; *s && !isdigit (*s); s++) { |
151 |
switch (*s) { |
152 |
case 'd': c->disabled = 1;break; |
153 |
case 'e': c->expired = 1; break; |
154 |
case 'r': c->revoked = 1; break; |
155 |
} |
156 |
} |
157 |
break; |
158 |
|
159 |
case 2: |
160 |
if (rectype == KEY_primary) |
161 |
c->length = atoi (pend); |
162 |
else if (rectype == KEY_secondary) |
163 |
c->subkey_length = atoi (pend); |
164 |
break; |
165 |
|
166 |
case 3: |
167 |
if (rectype == KEY_primary) |
168 |
c->pubkey_algo = (gpgme_pubkey_algo_t)atoi (pend); |
169 |
else if (rectype == KEY_secondary) |
170 |
c->subkey_algo = (gpgme_pubkey_algo_t)atoi (pend); |
171 |
break; |
172 |
|
173 |
case 4: |
174 |
if (rectype == KEY_primary) { |
175 |
strncpy (c->keyid, pend, 16); |
176 |
c->keyid[16] = 0; |
177 |
} |
178 |
else if (rectype == KEY_secondary) { |
179 |
strncpy (c->subkey_keyid, pend, 16); |
180 |
c->subkey_keyid[16] = 0; |
181 |
} |
182 |
break; |
183 |
|
184 |
case 5: |
185 |
if (rectype == KEY_primary) |
186 |
c->timestamp = strtoul (pend, NULL, 10); |
187 |
else if (rectype == KEY_secondary) |
188 |
c->subkey_timestamp = strtoul (pend, NULL, 10); |
189 |
break; |
190 |
|
191 |
case 9: |
192 |
if (rectype == KEY_primary && !c->uid && strlen (pend) > 2) { |
193 |
if (!strchr (pend, '[') && !strchr (pend, ']')) |
194 |
decode_userid (pend, c); |
195 |
} |
196 |
break; |
197 |
} |
198 |
} |
199 |
} |
200 |
|
201 |
/* Read the next key from data stream @out. The result is stored in @r_key. |
202 |
Return value: 0 on success. */ |
203 |
gpgme_error_t |
204 |
gpg_import_next_key (gpgme_data_t out, char **pending_line, |
205 |
import_key_t *r_key) |
206 |
{ |
207 |
import_key_t key; |
208 |
int in_cert, got_block = 0; |
209 |
char buf[384]; |
210 |
int n; |
211 |
|
212 |
if (!r_key) |
213 |
return gpg_error (GPG_ERR_INV_ARG); |
214 |
|
215 |
key = (import_key_t) calloc (1, sizeof *key); |
216 |
if (!key) |
217 |
BUG (0); |
218 |
|
219 |
if (*pending_line) { |
220 |
parse_colon_key (*pending_line, key); |
221 |
safe_free (*pending_line); |
222 |
*pending_line = NULL; |
223 |
in_cert = 1; |
224 |
} |
225 |
else |
226 |
in_cert = 0; |
227 |
|
228 |
while ((n=gpg_data_readline (out, buf, DIM (buf)-1)) > 0) { |
229 |
if (!strncmp (buf, "pub", 3) || !strncmp (buf, "sec", 3)) { |
230 |
if (in_cert) { |
231 |
*pending_line = strdup (buf); |
232 |
goto ready; |
233 |
} |
234 |
in_cert = 1; |
235 |
got_block = 1; |
236 |
parse_colon_key (buf, key); |
237 |
} |
238 |
else if (in_cert) { |
239 |
parse_colon_key (buf, key); |
240 |
got_block = 1; |
241 |
} |
242 |
} |
243 |
ready: |
244 |
*r_key = key; |
245 |
if (got_block && n == 0) |
246 |
return 0; |
247 |
return n == 0? gpg_error (GPG_ERR_EOF) : 0; |
248 |
} |
249 |
|
250 |
|
251 |
/* Return a humand readable key description of @key. */ |
252 |
static char* |
253 |
key_description (import_key_t key) |
254 |
{ |
255 |
gpgme_pubkey_algo_t subalgo = (gpgme_pubkey_algo_t)0; |
256 |
StringBuffer p; |
257 |
const char *state; |
258 |
char *desc; |
259 |
|
260 |
if (key->subkey_algo) |
261 |
subalgo = key->subkey_algo; |
262 |
if (key->revoked) |
263 |
state = _("Revoked"); |
264 |
else if (key->expired) |
265 |
state = _("Expired"); |
266 |
else |
267 |
state = " "; |
268 |
p = state; |
269 |
p = p + " " + get_key_pubalgo (key->pubkey_algo) + " "; |
270 |
if (subalgo != 0) |
271 |
p = p + "/" + get_key_pubalgo (subalgo) + " "; |
272 |
p = p + (key->secret? _("secret key") : _("public key")) ; |
273 |
desc = p.getBufferCopy (); |
274 |
return desc; |
275 |
} |
276 |
|
277 |
|
278 |
/* Add the key @key to the list view control @lv at position @pos. */ |
279 |
static int |
280 |
implist_add_key (listview_ctrl_t lv, int pos, import_key_t key) |
281 |
{ |
282 |
char buf[64], *desc; |
283 |
const char *t; |
284 |
u32 tt, tt2=0; |
285 |
|
286 |
if (listview_add_item_image (lv, " ", key->secret? 1 : 0)) |
287 |
return WPTERR_GENERAL; |
288 |
t = key->uid; |
289 |
if (!t || strlen (t) < 5) |
290 |
t = _("Invalid user ID"); |
291 |
listview_add_sub_item (lv, pos, IMPL_COL_UID, t); |
292 |
|
293 |
tt = key->length; |
294 |
if (key->subkey_length > 0) |
295 |
tt2 = key->subkey_length; |
296 |
if (tt > 0 && tt2 > 0) |
297 |
_snprintf (buf, DIM (buf) - 1, "%d/%d", tt, tt2); |
298 |
else |
299 |
_snprintf (buf, DIM (buf)-1, "%d", tt); |
300 |
listview_add_sub_item (lv, pos, IMPL_COL_SIZE, buf); |
301 |
|
302 |
t = key->keyid; |
303 |
if (!t || strlen (t) < 8) |
304 |
t = "????????????????"; |
305 |
_snprintf (buf, DIM (buf) -1, "0x%s", t+8); |
306 |
listview_add_sub_item (lv, pos, IMPL_COL_KEYID, buf); |
307 |
|
308 |
t = get_key_created (key->timestamp); |
309 |
if (!t) |
310 |
t = "????" "-??" "-??"; |
311 |
listview_add_sub_item (lv, pos, IMPL_COL_CREATION, (char *)t); |
312 |
|
313 |
desc = key_description (key); |
314 |
listview_add_sub_item (lv, pos, IMPL_COL_KTYPE, desc); |
315 |
free_if_alloc (desc); |
316 |
|
317 |
return 0; |
318 |
} |
319 |
|
320 |
|
321 |
/* Create a list view for list keys. */ |
322 |
void |
323 |
implist_build (listview_ctrl_t *lv, HWND ctrl) |
324 |
{ |
325 |
HICON ico[2]; |
326 |
struct listview_ctrl_s *c; |
327 |
struct listview_column_s implist[] = { |
328 |
{0, 190, (char *)_("User ID")}, |
329 |
{1, 66, (char *)_("Size")}, |
330 |
{2, 80, (char *)_("Key ID")}, |
331 |
{3, 72, (char *)_("Creation")}, |
332 |
{4, 132, (char *)_("Type")}, |
333 |
{0, 0, NULL} |
334 |
}; |
335 |
int i; |
336 |
|
337 |
listview_new (&c, ctrl); |
338 |
for (i = 0; implist[i].fieldname != NULL; i++) |
339 |
listview_add_column (c, &implist[i]); |
340 |
listview_set_ext_style (c); |
341 |
listview_set_grid_style (c); |
342 |
ico[0] = LoadIcon (glob_hinst, (LPCTSTR)IDI_PUBKEY); |
343 |
ico[1] = LoadIcon (glob_hinst, (LPCTSTR)IDI_KEYPAIR); |
344 |
listview_set_image_list (c, 22, 14, ico, 2); |
345 |
*lv = c; |
346 |
} |
347 |
|
348 |
|
349 |
/* Check to verify that the file can be access and |
350 |
set @r_revcert to '1' in case of an revocation cert. */ |
351 |
static int |
352 |
check_import_data (const char *fname, int *r_revcert) |
353 |
{ |
354 |
FILE *fp; |
355 |
char buf[512]; |
356 |
int n; |
357 |
|
358 |
fp = fopen (fname, "rb"); |
359 |
if (!fp) |
360 |
return -1; |
361 |
|
362 |
*r_revcert = 0; |
363 |
n = fread (buf, 1, DIM (buf)-1, fp); |
364 |
fclose (fp); |
365 |
if (!n) |
366 |
return 0; |
367 |
buf[n] = 0; |
368 |
if (strstr (buf, "A revocation certificate should follow")) |
369 |
*r_revcert = 1; |
370 |
return 0; |
371 |
} |
372 |
|
373 |
|
374 |
/* Load the list view @lv with the contents of the file @file. |
375 |
@r_revcerts contains the number of revocation certs and |
376 |
@r_seckeys the number of imported secret keys. */ |
377 |
int |
378 |
implist_load (listview_ctrl_t lv, const char *file, |
379 |
int *r_revcerts, int *r_seckeys) |
380 |
{ |
381 |
gpgme_data_t list; |
382 |
gpgme_error_t err; |
383 |
import_key_t key; |
384 |
char *pending_line = NULL; |
385 |
char fname[MAX_PATH+32+1], *out; |
386 |
int rc = 0, is_tmp=0; |
387 |
|
388 |
if (!file) { |
389 |
err = gpg_data_new_from_clipboard (&list, 0); |
390 |
if (err) { |
391 |
msg_box (NULL, gpgme_strerror (err), _("Import"), MB_ERR); |
392 |
return WPTERR_CLIP_OPEN; |
393 |
} |
394 |
get_temp_name (fname, DIM (fname)-1, "tmp_in_gpg_keys"); |
395 |
err = gpg_data_release_and_set_file (list, fname); |
396 |
if (err) { |
397 |
gpgme_data_release (list); |
398 |
msg_box (NULL, gpgme_strerror (err), _("Import"), MB_ERR); |
399 |
return WPTERR_FILE_CREAT; |
400 |
} |
401 |
file = fname; |
402 |
is_tmp = 1; |
403 |
} |
404 |
|
405 |
if (check_import_data (file, r_revcerts)) { |
406 |
msg_box (NULL, _("It is possible that the ASCII-Armor is damaged\n" |
407 |
"and thus causing a CRC error."), |
408 |
_("Import"), MB_ERR); |
409 |
if (is_tmp) |
410 |
DeleteFile (file); |
411 |
return WPTERR_GENERAL; |
412 |
} |
413 |
|
414 |
err = gpg_import_key_list (file, &out); |
415 |
if (err) { |
416 |
msg_box (NULL, gpgme_strerror (err), _("Import"), MB_ERR); |
417 |
if (is_tmp) |
418 |
DeleteFile (file); |
419 |
return WPTERR_GENERAL; |
420 |
} |
421 |
|
422 |
err = gpgme_data_new_from_mem (&list, out, strlen (out), 1); |
423 |
safe_free (out); |
424 |
if (err) { |
425 |
msg_box (NULL, gpgme_strerror (err), _("Import"), MB_ERR); |
426 |
if (is_tmp) |
427 |
DeleteFile (file); |
428 |
return WPTERR_GENERAL; |
429 |
} |
430 |
|
431 |
if (r_seckeys) |
432 |
*r_seckeys = 0; |
433 |
while (!rc) { |
434 |
/* XXX if the key has a direct key signature, the user-id field |
435 |
in the --with-colons mode is empty! */ |
436 |
rc = gpg_import_next_key (list, &pending_line, &key); |
437 |
if (!rc) |
438 |
rc = implist_add_key (lv, 0, key); |
439 |
if (key->secret) |
440 |
(*r_seckeys)++; |
441 |
import_key_release (key); |
442 |
} |
443 |
|
444 |
gpgme_data_release (list); |
445 |
if (is_tmp) |
446 |
DeleteFile (file); |
447 |
return rc; |
448 |
} |
449 |
|
450 |
|
451 |
/* Release the list view @lv. */ |
452 |
void |
453 |
implist_delete (listview_ctrl_t lv) |
454 |
{ |
455 |
if (lv) { |
456 |
listview_release (lv); |
457 |
} |
458 |
} |