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 <sys/types.h> |
28 |
#include <ctype.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 |
/* Parse the output of 'gpg --with-colons <data'. */ |
105 |
static void |
106 |
parse_colon_key (char *buf, import_key_t c) |
107 |
{ |
108 |
const char * s; |
109 |
char *p, *pend; |
110 |
int field = 0, rectype = 0; |
111 |
enum key_t { |
112 |
KEY_none = 0, |
113 |
KEY_primary = 1, |
114 |
KEY_secondary = 2, |
115 |
}; |
116 |
|
117 |
if (!buf) |
118 |
return; /* EOF */ |
119 |
|
120 |
if( !strncmp (buf, "pub", 3) || !strncmp (buf, "sec", 3)) { |
121 |
rectype = KEY_primary; |
122 |
if (*buf == 's') |
123 |
c->secret = 1; |
124 |
} |
125 |
else if (!strncmp (buf, "sub", 3) || !strncmp (buf, "ssb", 3)) |
126 |
rectype = KEY_secondary; |
127 |
else if (!strncmp (buf, "uid", 3)) { |
128 |
if (!c->uid) { |
129 |
const char *uid = buf+3; |
130 |
while (uid && *uid == ':') |
131 |
uid++; |
132 |
decode_userid ((char *)uid, c); |
133 |
} |
134 |
return; |
135 |
} |
136 |
else |
137 |
return; |
138 |
|
139 |
for (p = buf; p; p = pend) { |
140 |
field++; |
141 |
pend = strchr (p, ':'); |
142 |
if (pend) |
143 |
*pend++ = 0; |
144 |
|
145 |
switch (field) { |
146 |
case 1: |
147 |
if (rectype != KEY_primary) |
148 |
break; |
149 |
for (s = pend; *s && !isdigit ((unsigned int)*s); s++) { |
150 |
switch (*s) { |
151 |
case 'd': c->disabled = 1;break; |
152 |
case 'e': c->expired = 1; break; |
153 |
case 'r': c->revoked = 1; break; |
154 |
} |
155 |
} |
156 |
break; |
157 |
|
158 |
case 2: |
159 |
if (rectype == KEY_primary) |
160 |
c->length = atoi (pend); |
161 |
else if (rectype == KEY_secondary) |
162 |
c->subkey_length = atoi (pend); |
163 |
break; |
164 |
|
165 |
case 3: |
166 |
if (rectype == KEY_primary) |
167 |
c->pubkey_algo = (gpgme_pubkey_algo_t)atoi (pend); |
168 |
else if (rectype == KEY_secondary) |
169 |
c->subkey_algo = (gpgme_pubkey_algo_t)atoi (pend); |
170 |
break; |
171 |
|
172 |
case 4: |
173 |
if (rectype == KEY_primary) { |
174 |
strncpy (c->keyid, pend, 16); |
175 |
c->keyid[16] = 0; |
176 |
} |
177 |
else if (rectype == KEY_secondary) { |
178 |
strncpy (c->subkey_keyid, pend, 16); |
179 |
c->subkey_keyid[16] = 0; |
180 |
} |
181 |
break; |
182 |
|
183 |
case 5: |
184 |
if (rectype == KEY_primary) |
185 |
c->timestamp = strtoul (pend, NULL, 10); |
186 |
else if (rectype == KEY_secondary) |
187 |
c->subkey_timestamp = strtoul (pend, NULL, 10); |
188 |
break; |
189 |
|
190 |
case 9: |
191 |
if (rectype == KEY_primary && !c->uid && strlen (pend) > 2) { |
192 |
if (!strchr (pend, '[') && !strchr (pend, ']')) |
193 |
decode_userid (pend, c); |
194 |
} |
195 |
break; |
196 |
} |
197 |
} |
198 |
} |
199 |
|
200 |
/* Read the next key from data stream @out. The result is stored in @r_key. |
201 |
Return value: 0 on success. */ |
202 |
gpgme_error_t |
203 |
gpg_import_next_key (gpgme_data_t out, char **pending_line, import_key_t *r_key) |
204 |
{ |
205 |
import_key_t key; |
206 |
int in_cert, got_block = 0; |
207 |
char buf[384]; |
208 |
int n=0; |
209 |
|
210 |
if (!r_key) |
211 |
return gpg_error (GPG_ERR_INV_ARG); |
212 |
|
213 |
key = (import_key_t) calloc (1, sizeof *key); |
214 |
if (!key) |
215 |
return gpg_error (GPG_ERR_ENOMEM); |
216 |
|
217 |
if (*pending_line) { |
218 |
parse_colon_key (*pending_line, key); |
219 |
safe_free (*pending_line); |
220 |
*pending_line = NULL; |
221 |
in_cert = 1; |
222 |
} |
223 |
else |
224 |
in_cert = 0; |
225 |
|
226 |
while ((n=gpg_data_readline (out, buf, sizeof buf-1)) > 0) { |
227 |
if (!strncmp (buf, "pub", 3) || !strncmp (buf, "sec", 3)) { |
228 |
if (in_cert) { |
229 |
*pending_line = strdup (buf); |
230 |
goto ready; |
231 |
} |
232 |
in_cert = 1; |
233 |
got_block = 1; |
234 |
parse_colon_key (buf, key); |
235 |
} |
236 |
else if (in_cert) { |
237 |
parse_colon_key (buf, key); |
238 |
got_block = 1; |
239 |
} |
240 |
} |
241 |
ready: |
242 |
*r_key = key; |
243 |
if (got_block && n == 0) |
244 |
return 0; |
245 |
return n == 0? gpg_error (GPG_ERR_EOF) : 0; |
246 |
} |
247 |
|
248 |
|
249 |
/* Return a humand readable key description of @key. */ |
250 |
static char* |
251 |
key_description (import_key_t key) |
252 |
{ |
253 |
gpgme_pubkey_algo_t pkalgo; |
254 |
gpgme_pubkey_algo_t subalgo = (gpgme_pubkey_algo_t)0; |
255 |
const char *type, *state; |
256 |
char *p; |
257 |
int n=0; |
258 |
|
259 |
pkalgo = key->pubkey_algo; |
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 |
if (key->secret) |
269 |
type = _("secret key"); |
270 |
else |
271 |
type = _("public key"); |
272 |
|
273 |
n = strlen (state) + strlen (type) + 2*8 + 8; |
274 |
p = new char[n+1]; |
275 |
if (!p) |
276 |
BUG (NULL); |
277 |
|
278 |
if (!subalgo) |
279 |
_snprintf (p, 64, "%s %s %s", state, |
280 |
get_key_pubalgo (pkalgo),type); |
281 |
else |
282 |
_snprintf (p, 64, "%s %s/%s %s", state, |
283 |
get_key_pubalgo (pkalgo), |
284 |
get_key_pubalgo (subalgo), type); |
285 |
return p; |
286 |
} |
287 |
|
288 |
|
289 |
/* Add the key @key to the list view control @lv at position @pos. */ |
290 |
static int |
291 |
implist_add_key (listview_ctrl_t lv, int pos, import_key_t key) |
292 |
{ |
293 |
char *uid = NULL; |
294 |
char buf[128], * desc; |
295 |
const char *t; |
296 |
u32 tt, tt2=0; |
297 |
|
298 |
if (listview_add_item( lv, " " )) |
299 |
return WPTERR_GENERAL; |
300 |
t = key->uid; |
301 |
if (!t || strlen (t) < 5) { |
302 |
t = _("Invalid user ID"); |
303 |
listview_add_sub_item (lv, pos, IMPL_COL_UID, t); |
304 |
} |
305 |
else { |
306 |
uid = utf8_to_native (t); |
307 |
if (uid) { |
308 |
listview_add_sub_item (lv, pos, IMPL_COL_UID, uid); |
309 |
free (uid); |
310 |
} |
311 |
} |
312 |
|
313 |
tt = key->length; |
314 |
if (key->subkey_length > 0) |
315 |
tt2 = key->subkey_length; |
316 |
if (tt && tt2) |
317 |
_snprintf (buf, sizeof buf - 1, "%d/%d", tt, tt2); |
318 |
else |
319 |
_snprintf (buf, sizeof buf-1, "%d", tt); |
320 |
listview_add_sub_item (lv, pos, IMPL_COL_SIZE, buf); |
321 |
|
322 |
t = key->keyid; |
323 |
if (!t || strlen (t) < 8) |
324 |
t = "????????????????"; |
325 |
_snprintf (buf, sizeof buf -1, "0x%s", t+8); |
326 |
listview_add_sub_item (lv, pos, IMPL_COL_KEYID, buf); |
327 |
|
328 |
|
329 |
tt = key->timestamp; |
330 |
t = get_key_created (tt); |
331 |
if( !t ) |
332 |
t = "????" "-??" "-??"; |
333 |
listview_add_sub_item (lv, pos, IMPL_COL_CREATION, (char *)t); |
334 |
|
335 |
desc = key_description (key); |
336 |
if (desc) { |
337 |
listview_add_sub_item (lv, pos, IMPL_COL_KTYPE, desc); |
338 |
free_if_alloc (desc); |
339 |
} |
340 |
|
341 |
return 0; |
342 |
} |
343 |
|
344 |
|
345 |
/* Create a list view for list keys. */ |
346 |
int |
347 |
implist_build (listview_ctrl_t *lv, HWND ctrl) |
348 |
{ |
349 |
struct listview_ctrl_s *c; |
350 |
struct listview_column_s implist[] = { |
351 |
{0, 190, (char *)_("User ID")}, |
352 |
{1, 66, (char *)_("Size")}, |
353 |
{2, 80, (char *)_("Key ID")}, |
354 |
{3, 72, (char *)_("Creation")}, |
355 |
{4, 132, (char *)_("Type")}, |
356 |
{0, 0, NULL} |
357 |
}; |
358 |
int j, rc = 0; |
359 |
|
360 |
rc = listview_new (&c); |
361 |
if( rc ) |
362 |
return rc; |
363 |
c->ctrl = ctrl; |
364 |
for (j = 0; implist[j].fieldname != NULL; j++) |
365 |
listview_add_column (c, &implist[j]); |
366 |
listview_set_ext_style (c); |
367 |
*lv = c; |
368 |
return 0; |
369 |
} |
370 |
|
371 |
|
372 |
static int |
373 |
check_import_data (const char *fname, int *r_revcert) |
374 |
{ |
375 |
FILE *f; |
376 |
char buf[512]; |
377 |
int n; |
378 |
|
379 |
f = fopen (fname, "rb"); |
380 |
if (!f) |
381 |
return -1; |
382 |
|
383 |
*r_revcert = 0; |
384 |
n = fread (buf, 1, 511, f); |
385 |
if (n > 0) { |
386 |
buf[n] = 0; |
387 |
if (strstr (buf, "A revocation certificate should follow")) |
388 |
*r_revcert = 1; |
389 |
} |
390 |
|
391 |
fclose (f); |
392 |
return 0; |
393 |
} |
394 |
|
395 |
|
396 |
/* Load the list view @lv with the contents of the file @file. |
397 |
@r_revcerts contains the number of revocation certs and |
398 |
@r_seckeys the number of imported secret keys. */ |
399 |
int |
400 |
implist_load (listview_ctrl_t lv, const char *file, |
401 |
int *r_revcerts, int *r_seckeys) |
402 |
{ |
403 |
gpgme_data_t list; |
404 |
gpgme_error_t err; |
405 |
import_key_t key; |
406 |
int rc = 0, is_tmp=0; |
407 |
char *pending_line = NULL; |
408 |
char fname[300], *out; |
409 |
|
410 |
if (!file) { |
411 |
err = gpg_data_new_from_clipboard (&list, 0); |
412 |
if (err) { |
413 |
msg_box (NULL, gpgme_strerror( err ), _("Import"), MB_ERR); |
414 |
return WPTERR_CLIP_OPEN; |
415 |
} |
416 |
get_temp_name (fname, 299, "in_gpg_keys"); |
417 |
err = gpg_data_release_and_set_file (list, fname); |
418 |
if (err) { |
419 |
msg_box (NULL, gpgme_strerror (err), _("Import"), MB_ERR); |
420 |
return WPTERR_FILE_CREAT; |
421 |
} |
422 |
file = fname; |
423 |
is_tmp = 1; |
424 |
} |
425 |
|
426 |
if (check_import_data (file, r_revcerts)) { |
427 |
msg_box (NULL, _("It is possible that the ASCII-Armor is damaged\n" |
428 |
"and thus a CRC error occurs."), |
429 |
_("Import"), MB_ERR); |
430 |
if (is_tmp) |
431 |
DeleteFile (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) |
439 |
DeleteFile (file); |
440 |
return WPTERR_GENERAL; |
441 |
} |
442 |
|
443 |
err = gpgme_data_new_from_mem (&list, out, strlen (out), 1); |
444 |
free (out); |
445 |
if (err) { |
446 |
msg_box (NULL, gpgme_strerror (err), _("Import"), MB_ERR); |
447 |
if (is_tmp) |
448 |
DeleteFile (file); |
449 |
return WPTERR_GENERAL; |
450 |
} |
451 |
|
452 |
if (r_seckeys) |
453 |
*r_seckeys = 0; |
454 |
while (!rc) { |
455 |
/* XXX if the key has a direct key signature, the user-id field |
456 |
in the --with-colons mode is empty! */ |
457 |
rc = gpg_import_next_key (list, &pending_line, &key); |
458 |
if (!rc) |
459 |
rc = implist_add_key (lv, 0, key); |
460 |
if (key->secret) |
461 |
(*r_seckeys)++; |
462 |
import_key_release (key); |
463 |
} |
464 |
|
465 |
gpgme_data_release (list); |
466 |
if (is_tmp) |
467 |
DeleteFile (file); |
468 |
return rc; |
469 |
} |
470 |
|
471 |
|
472 |
/* Release the list view @lv. */ |
473 |
void |
474 |
implist_delete (listview_ctrl_t lv) |
475 |
{ |
476 |
if (lv) { |
477 |
listview_release (lv); |
478 |
} |
479 |
} |