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