1 |
/* wptImportList.cpp |
2 |
* Copyright (C) 2001-2005 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 |
* You should have received a copy of the GNU General Public License |
18 |
* along with WinPT; if not, write to the Free Software Foundation, |
19 |
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
20 |
*/ |
21 |
|
22 |
#ifdef HAVE_CONFIG_H |
23 |
#include <config.h> |
24 |
#endif |
25 |
|
26 |
#include <windows.h> |
27 |
#include <windows.h> |
28 |
#include <sys/types.h> |
29 |
|
30 |
#include "wptTypes.h" |
31 |
#include "wptGPG.h" |
32 |
#include "wptCommonCtl.h" |
33 |
#include "wptKeylist.h" |
34 |
#include "wptNLS.h" |
35 |
#include "wptErrors.h" |
36 |
#include "wptUTF8.h" |
37 |
#include "wptW32API.h" |
38 |
#include "wptRegistry.h" |
39 |
#include "openpgp.h" |
40 |
|
41 |
|
42 |
/* Symbolic column ids */ |
43 |
enum impl_col_t { |
44 |
IMPL_COL_UID = 0, |
45 |
IMPL_COL_SIZE = 1, |
46 |
IMPL_COL_KEYID = 2, |
47 |
IMPL_COL_CREATION = 3, |
48 |
IMPL_COL_KTYPE = 4 |
49 |
}; |
50 |
|
51 |
/* context to hold information about a key. */ |
52 |
struct import_key_s { |
53 |
/* primary userid */ |
54 |
char *uid; |
55 |
|
56 |
/* 1 = secret key is available. */ |
57 |
int secret; |
58 |
|
59 |
/* flags */ |
60 |
unsigned int disabled:1; |
61 |
unsigned int expired:1; |
62 |
unsigned int revoked:1; |
63 |
|
64 |
/* primary key attributes */ |
65 |
char keyid[16+1]; |
66 |
gpgme_pubkey_algo_t pubkey_algo; |
67 |
size_t length; |
68 |
time_t timestamp; |
69 |
|
70 |
/* secondary key attributes */ |
71 |
char subkey_keyid[16+1]; |
72 |
gpgme_pubkey_algo_t subkey_algo; |
73 |
size_t subkey_length; |
74 |
time_t subkey_timestamp; |
75 |
}; |
76 |
typedef struct import_key_s *import_key_t; |
77 |
|
78 |
|
79 |
/* Release the key in @c. */ |
80 |
static void |
81 |
import_key_release (import_key_t c) |
82 |
{ |
83 |
if (!c) |
84 |
return; |
85 |
if (c->uid) |
86 |
free (c->uid); |
87 |
free (c); |
88 |
} |
89 |
|
90 |
|
91 |
/* Decode the %-encoded userid in @pend and store it as the first |
92 |
userid in the key @c. */ |
93 |
static void |
94 |
decode_userid (char *pend, import_key_t c) |
95 |
{ |
96 |
c->uid = (char*)calloc (1, strlen (pend) + 1); |
97 |
if (!c->uid) |
98 |
BUG (NULL); |
99 |
gpg_decode_c_string (pend, &c->uid, strlen (pend) + 1); |
100 |
c->uid[strlen (c->uid) -3] = '\0'; |
101 |
|
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 *s = buf+3; |
131 |
while (s && *s == ':') |
132 |
s++; |
133 |
decode_userid ((char *)s, 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, import_key_t *r_key) |
205 |
{ |
206 |
import_key_t key; |
207 |
int in_cert, got_block = 0; |
208 |
char buf[384]; |
209 |
int n=0; |
210 |
|
211 |
if (!r_key) |
212 |
return gpg_error (GPG_ERR_INV_ARG); |
213 |
|
214 |
key = (import_key_t) calloc (1, sizeof *key); |
215 |
if (!key) |
216 |
return gpg_error (GPG_ERR_ENOMEM); |
217 |
|
218 |
if (*pending_line) { |
219 |
parse_colon_key (*pending_line, key); |
220 |
safe_free (*pending_line); |
221 |
*pending_line = NULL; |
222 |
in_cert = 1; |
223 |
} |
224 |
else |
225 |
in_cert = 0; |
226 |
|
227 |
while ((n=gpg_data_readline (out, buf, sizeof buf-1)) > 0) { |
228 |
if (!strncmp (buf, "pub", 3) || !strncmp (buf, "sec", 3)) { |
229 |
if (in_cert) { |
230 |
*pending_line = strdup (buf); |
231 |
goto ready; |
232 |
} |
233 |
in_cert = 1; |
234 |
got_block = 1; |
235 |
parse_colon_key (buf, key); |
236 |
} |
237 |
else if (in_cert) { |
238 |
parse_colon_key (buf, key); |
239 |
got_block = 1; |
240 |
} |
241 |
} |
242 |
ready: |
243 |
*r_key = key; |
244 |
if (got_block && n == 0) |
245 |
return 0; |
246 |
return n == 0? gpg_error (GPG_ERR_EOF) : 0; |
247 |
} |
248 |
|
249 |
|
250 |
/* Return a humand readable key description of @key. */ |
251 |
static char* |
252 |
key_description (import_key_t key) |
253 |
{ |
254 |
gpgme_pubkey_algo_t pkalgo; |
255 |
gpgme_pubkey_algo_t subalgo = (gpgme_pubkey_algo_t)0; |
256 |
const char *type, *state; |
257 |
char *p; |
258 |
int n=0; |
259 |
|
260 |
pkalgo = key->pubkey_algo; |
261 |
if (key->subkey_algo) |
262 |
subalgo = key->subkey_algo; |
263 |
if (key->revoked) |
264 |
state = _("Revoked" ); |
265 |
else if (key->expired) |
266 |
state = _("Expired" ); |
267 |
else |
268 |
state = ""; |
269 |
if (key->secret) |
270 |
type = _("secret key"); |
271 |
else |
272 |
type = _("public key"); |
273 |
|
274 |
n = strlen (state) + strlen (type) + 2*8 + 8; |
275 |
p = new char[n+1]; |
276 |
if (!p) |
277 |
BUG (NULL); |
278 |
|
279 |
if (!subalgo) |
280 |
_snprintf (p, 64, "%s %s %s", state, |
281 |
get_key_pubalgo (pkalgo),type); |
282 |
else |
283 |
_snprintf (p, 64, "%s %s/%s %s", state, |
284 |
get_key_pubalgo (pkalgo), |
285 |
get_key_pubalgo (subalgo), type); |
286 |
return p; |
287 |
} |
288 |
|
289 |
|
290 |
/* Add the key @key to the list view control @lv at position @pos. */ |
291 |
static int |
292 |
implist_add_key (listview_ctrl_t lv, int pos, import_key_t key) |
293 |
{ |
294 |
char *uid = NULL; |
295 |
char buf[128], * desc; |
296 |
const char *t; |
297 |
u32 tt, tt2=0; |
298 |
|
299 |
if (listview_add_item( lv, " " )) |
300 |
return WPTERR_GENERAL; |
301 |
t = key->uid; |
302 |
if (!t || strlen (t) < 5) { |
303 |
t = _("Invalid user ID"); |
304 |
listview_add_sub_item (lv, pos, IMPL_COL_UID, t); |
305 |
} |
306 |
else { |
307 |
uid = utf8_to_wincp (t, strlen (t)); |
308 |
if (uid) { |
309 |
listview_add_sub_item (lv, pos, IMPL_COL_UID, uid); |
310 |
free (uid); |
311 |
} |
312 |
} |
313 |
|
314 |
tt = key->length; |
315 |
if (key->subkey_length > 0) |
316 |
tt2 = key->subkey_length; |
317 |
if (tt && tt2) |
318 |
_snprintf (buf, sizeof buf - 1, "%d/%d", tt, tt2); |
319 |
else |
320 |
_snprintf (buf, sizeof buf-1, "%d", tt); |
321 |
listview_add_sub_item (lv, pos, IMPL_COL_SIZE, buf); |
322 |
|
323 |
t = key->keyid; |
324 |
if (!t || strlen (t) < 8) |
325 |
t = "????????????????"; |
326 |
_snprintf (buf, sizeof buf -1, "0x%s", t+8); |
327 |
listview_add_sub_item (lv, pos, IMPL_COL_KEYID, buf); |
328 |
|
329 |
|
330 |
tt = key->timestamp; |
331 |
t = get_key_created (tt); |
332 |
if( !t ) |
333 |
t = "????-??-??"; |
334 |
listview_add_sub_item (lv, pos, IMPL_COL_CREATION, (char *)t); |
335 |
|
336 |
desc = key_description (key); |
337 |
if (desc) { |
338 |
listview_add_sub_item (lv, pos, IMPL_COL_KTYPE, desc); |
339 |
free_if_alloc (desc); |
340 |
} |
341 |
|
342 |
return 0; |
343 |
} |
344 |
|
345 |
|
346 |
/* Create a list view for list keys. */ |
347 |
int |
348 |
implist_build (listview_ctrl_t *lv, HWND ctrl) |
349 |
{ |
350 |
struct listview_ctrl_s *c; |
351 |
struct listview_column_s implist[] = { |
352 |
{0, 190, (char *)_("User ID")}, |
353 |
{1, 66, (char *)_("Size")}, |
354 |
{2, 80, (char *)_("Key ID")}, |
355 |
{3, 72, (char *)_("Creation")}, |
356 |
{4, 132, (char *)_("Type")}, |
357 |
{0, 0, NULL} |
358 |
}; |
359 |
int j, rc = 0; |
360 |
|
361 |
rc = listview_new (&c); |
362 |
if( rc ) |
363 |
return rc; |
364 |
c->ctrl = ctrl; |
365 |
for (j = 0; implist[j].fieldname != NULL; j++) |
366 |
listview_add_column (c, &implist[j]); |
367 |
listview_set_ext_style (c); |
368 |
*lv = c; |
369 |
return 0; |
370 |
} |
371 |
|
372 |
|
373 |
static int |
374 |
check_import_data (const char *fname, int *r_revcert) |
375 |
{ |
376 |
FILE *f; |
377 |
char buf[512]; |
378 |
int n; |
379 |
|
380 |
f = fopen (fname, "rb"); |
381 |
if (!f) |
382 |
return -1; |
383 |
|
384 |
*r_revcert = 0; |
385 |
n = fread (buf, 1, 511, f); |
386 |
if (n > 0) { |
387 |
buf[n] = 0; |
388 |
if (strstr (buf, "A revocation certificate should follow")) |
389 |
*r_revcert = 1; |
390 |
} |
391 |
|
392 |
fclose (f); |
393 |
return 0; |
394 |
} |
395 |
|
396 |
|
397 |
/* Load the list view @lv with the contents of the file @file. |
398 |
@r_revcerts contains the number of revocation certs and |
399 |
@r_seckeys the number of imported secret keys. */ |
400 |
int |
401 |
implist_load (listview_ctrl_t lv, const char *file, |
402 |
int *r_revcerts, int *r_seckeys) |
403 |
{ |
404 |
gpgme_data_t list; |
405 |
gpgme_error_t err; |
406 |
import_key_t key; |
407 |
int rc = 0, is_tmp=0; |
408 |
char *pending_line = NULL; |
409 |
char fname[300], *out; |
410 |
|
411 |
if (!file) { |
412 |
err = gpg_data_new_from_clipboard (&list, 0); |
413 |
if (err) { |
414 |
msg_box (NULL, gpgme_strerror( err ), _("Import"), MB_ERR); |
415 |
return WPTERR_CLIP_OPEN; |
416 |
} |
417 |
GetTempPath (299, fname); |
418 |
strcat (fname, "in_gpg_keys"); |
419 |
err = gpg_data_release_and_set_file (list, fname); |
420 |
if (err) { |
421 |
msg_box (NULL, gpgme_strerror (err), _("Import"), MB_ERR); |
422 |
return WPTERR_FILE_CREAT; |
423 |
} |
424 |
file = fname; |
425 |
is_tmp = 1; |
426 |
} |
427 |
|
428 |
if (check_import_data (file, r_revcerts)) { |
429 |
msg_box (NULL, _("It is possible that the ASCII-Armor is damaged\n" |
430 |
"and thus a CRC error occurs."), _("Import"), MB_ERR); |
431 |
if (is_tmp) unlink (file); |
432 |
return WPTERR_GENERAL; |
433 |
} |
434 |
|
435 |
err = gpg_import_key_list (file, &out); |
436 |
if (err) { |
437 |
msg_box (NULL, gpgme_strerror (err), _("Import"), MB_ERR); |
438 |
if (is_tmp) unlink (file); |
439 |
return WPTERR_GENERAL; |
440 |
} |
441 |
|
442 |
err = gpgme_data_new_from_mem (&list, out, strlen (out), 1); |
443 |
free (out); |
444 |
if (err) { |
445 |
msg_box (NULL, gpgme_strerror (err), _("Import"), MB_ERR); |
446 |
if (is_tmp) unlink (file); |
447 |
return WPTERR_GENERAL; |
448 |
} |
449 |
|
450 |
if (r_seckeys) |
451 |
*r_seckeys = 0; |
452 |
while (!rc) { |
453 |
/* XXX if the key has a direct key signature, the user-id field |
454 |
in the --with-colons mode is empty! */ |
455 |
rc = gpg_import_next_key (list, &pending_line, &key); |
456 |
if (!rc) |
457 |
rc = implist_add_key (lv, 0, key); |
458 |
if (key->secret) |
459 |
(*r_seckeys)++; |
460 |
import_key_release (key); |
461 |
} |
462 |
|
463 |
|
464 |
gpgme_data_release (list); |
465 |
if (is_tmp) unlink (file); |
466 |
return rc; |
467 |
} |
468 |
|
469 |
|
470 |
/* Release the list view @lv. */ |
471 |
void |
472 |
implist_delete (listview_ctrl_t lv) |
473 |
{ |
474 |
if (lv) { |
475 |
listview_release (lv); |
476 |
} |
477 |
} |