1 |
/* encrypt.c - Encrypt functions |
2 |
* Copyright (C) 2000, 2001 Werner Koch (dd9jn), g10 Code GmbH |
3 |
* Copyright (C) 2002-2005 Timo Schulz |
4 |
* |
5 |
* This file is part of MyGPGME. |
6 |
* |
7 |
* MyGPGME is free software; you can redistribute it and/or modify |
8 |
* it under the terms of the GNU General Public License as published by |
9 |
* the Free Software Foundation; either version 2 of the License, or |
10 |
* (at your option) any later version. |
11 |
* |
12 |
* MyGPGME 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 |
15 |
* GNU General Public License for more details. |
16 |
* |
17 |
* You should have received a copy of the GNU General Public License |
18 |
* along with this program; if not, write to the Free Software Foundation, |
19 |
* Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA |
20 |
*/ |
21 |
|
22 |
#include <stdio.h> |
23 |
#include <stdlib.h> |
24 |
#include <string.h> |
25 |
#include <assert.h> |
26 |
|
27 |
#include "util.h" |
28 |
#include "context.h" |
29 |
#include "ops.h" |
30 |
|
31 |
|
32 |
#define use_symmetric(rset) (!(rset) || !gpgme_recipients_count ((rset))) |
33 |
|
34 |
struct recp_info_s { |
35 |
struct recp_info_s * next; |
36 |
gpgme_error_t code; |
37 |
char recp[1]; |
38 |
}; |
39 |
|
40 |
struct encrypt_result_s { |
41 |
int okay; |
42 |
int no_recp; |
43 |
int inv_recp; |
44 |
int file_start; |
45 |
int file_done; |
46 |
struct recp_info_s * inf; |
47 |
}; |
48 |
|
49 |
|
50 |
static gpgme_error_t |
51 |
create_result_struct( gpgme_ctx_t ctx ) |
52 |
{ |
53 |
assert ( !ctx->result.encrypt ); |
54 |
ctx->result.encrypt = calloc( 1, sizeof *ctx->result.encrypt ); |
55 |
if( !ctx->result.encrypt ) |
56 |
return mk_error( Out_Of_Core ); |
57 |
ctx->result_type = RESULT_TYPE_ENCRYPT; |
58 |
return 0; |
59 |
} /* create_result_struct */ |
60 |
|
61 |
|
62 |
static gpgme_error_t |
63 |
add_recp_info( _encrypt_result_t res, int code, const char * name ) |
64 |
{ |
65 |
struct recp_info_s * r; |
66 |
|
67 |
r = calloc( 1, sizeof * r + strlen( name ) + 1 ); |
68 |
if( !r ) |
69 |
return mk_error( Out_Of_Core ); |
70 |
r->code = code; |
71 |
strcpy (r->recp , name); |
72 |
r->next = res->inf; |
73 |
res->inf = r; |
74 |
|
75 |
return 0; |
76 |
} /* add_recp_info */ |
77 |
|
78 |
|
79 |
void |
80 |
_gpgme_release_encrypt_result( _encrypt_result_t res ) |
81 |
{ |
82 |
struct recp_info_s * r, * r2; |
83 |
|
84 |
if( !res ) |
85 |
return; |
86 |
r = res->inf; |
87 |
while( r ) { |
88 |
r2 = r->next; |
89 |
safe_free( r ); |
90 |
r = r2; |
91 |
} |
92 |
safe_free( res->inf ); |
93 |
safe_free( res ); |
94 |
} /* _gpgme_release_encrypt_result */ |
95 |
|
96 |
|
97 |
void |
98 |
_gpgme_encrypt_add_cipher (gpgme_ctx_t ctx) |
99 |
{ |
100 |
if (ctx->cipher_algo == -1) |
101 |
return; |
102 |
_gpgme_gpg_add_arg (ctx->gpg, "--cipher-algo"); |
103 |
switch (ctx->cipher_algo) { |
104 |
case GPGME_CIPHER_3DES: _gpgme_gpg_add_arg (ctx->gpg, "3DES");break; |
105 |
case GPGME_CIPHER_CAST5: _gpgme_gpg_add_arg (ctx->gpg, "CAST5");break; |
106 |
case GPGME_CIPHER_BLOWFISH: _gpgme_gpg_add_arg (ctx->gpg, "BLOWFISH");break; |
107 |
case GPGME_CIPHER_AES128: _gpgme_gpg_add_arg (ctx->gpg, "AES");break; |
108 |
case GPGME_CIPHER_AES192: _gpgme_gpg_add_arg (ctx->gpg, "AES192");break; |
109 |
case GPGME_CIPHER_AES256: _gpgme_gpg_add_arg (ctx->gpg, "AES256");break; |
110 |
case GPGME_CIPHER_TWOFISH: _gpgme_gpg_add_arg (ctx->gpg, "TWOFISH");break; |
111 |
default: _gpgme_gpg_add_arg (ctx->gpg, "AES");break; |
112 |
} |
113 |
if (ctx->s2k.used) { |
114 |
char buf[32]; |
115 |
if (ctx->s2k.mode < 0 || ctx->s2k.mode > 3) |
116 |
ctx->s2k.mode = GPGME_S2K_ITERSALTED; |
117 |
sprintf( buf, "%d", ctx->s2k.mode ); |
118 |
_gpgme_gpg_add_arg (ctx->gpg, "--s2k-mode"); |
119 |
_gpgme_gpg_add_arg (ctx->gpg, buf); |
120 |
_gpgme_gpg_add_arg (ctx->gpg, "--s2k-digest-algo"); |
121 |
switch (ctx->s2k.digest_algo) { |
122 |
case GPGME_MD_SHA1: _gpgme_gpg_add_arg (ctx->gpg, "SHA1"); break; |
123 |
case GPGME_MD_RMD160:_gpgme_gpg_add_arg (ctx->gpg, "RIPEMD160"); break; |
124 |
default: _gpgme_gpg_add_arg (ctx->gpg, "SHA1"); break; |
125 |
} |
126 |
} |
127 |
} /* _gpgme_encrypt_add_cipher */ |
128 |
|
129 |
|
130 |
static void |
131 |
encrypt_status_handler( gpgme_ctx_t ctx, gpg_statcode_t code, char * args ) |
132 |
{ |
133 |
int i = 0; |
134 |
gpgme_error_t err; |
135 |
|
136 |
if (ctx->out_of_core) |
137 |
return; |
138 |
|
139 |
if( ctx->result_type == RESULT_TYPE_NONE ) { |
140 |
if( create_result_struct ( ctx ) ) { |
141 |
ctx->out_of_core = 1; |
142 |
return; |
143 |
} |
144 |
} |
145 |
assert( ctx->result_type == RESULT_TYPE_ENCRYPT ); |
146 |
|
147 |
switch( code ) { |
148 |
case STATUS_END_ENCRYPTION: |
149 |
ctx->result.encrypt->okay = 1; |
150 |
break; |
151 |
|
152 |
case STATUS_NO_RECP: |
153 |
ctx->result.encrypt->no_recp = 1; |
154 |
break; |
155 |
|
156 |
case STATUS_INV_RECP: |
157 |
while( args[i] && args[i] != ' ' ) |
158 |
i++; |
159 |
while( args[i] && args[i] != ' ' ) |
160 |
i++; |
161 |
err = add_recp_info( ctx->result.encrypt, atol( args ), args + i ); |
162 |
if( err ) |
163 |
ctx->out_of_core = 1; |
164 |
ctx->result.encrypt->inv_recp++; |
165 |
break; |
166 |
|
167 |
case STATUS_FILE_START: |
168 |
ctx->result.encrypt->file_start++; |
169 |
if( ctx->cb.interactiv ) |
170 |
ctx->cb.interactiv( ctx->cb.interactiv_value, code, NULL, args+2 ); |
171 |
break; |
172 |
|
173 |
case STATUS_FILE_DONE: |
174 |
ctx->result.encrypt->file_done++; |
175 |
if( ctx->cb.interactiv ) |
176 |
ctx->cb.interactiv( ctx->cb.interactiv_value, code, NULL, NULL ); |
177 |
break; |
178 |
|
179 |
case STATUS_PROGRESS: |
180 |
if (ctx->cb.progress) |
181 |
_gpgme_progress_handler (ctx, args); |
182 |
break; |
183 |
} |
184 |
} /* encrypt_status_handler */ |
185 |
|
186 |
|
187 |
static const char * |
188 |
encrypt_command_handler (void * opaque, gpg_statcode_t code, const char * key) |
189 |
{ |
190 |
gpgme_ctx_t ctx = opaque; |
191 |
|
192 |
if (!ctx) |
193 |
return NULL; |
194 |
if (!strcmp (key, "untrusted_key.override")) |
195 |
return "N"; |
196 |
if (!ctx->cb.interactiv) |
197 |
return NULL; |
198 |
if ((code == STATUS_GET_BOOL && !strcmp (key, "openfile.overwrite.okay")) |
199 |
||(code == STATUS_GET_LINE && !strcmp (key, "openfile.askoutname" )) ) |
200 |
return ctx->cb.interactiv (ctx->cb.interactiv_value, code, key, NULL); |
201 |
|
202 |
return NULL; |
203 |
} /* encrypt_command_handler */ |
204 |
|
205 |
|
206 |
static gpgme_error_t |
207 |
encrypt_start( gpgme_ctx_t ctx, gpgme_recipients_t recp, |
208 |
gpgme_data_t plain, gpgme_data_t ciph ) |
209 |
|
210 |
{ |
211 |
gpgme_error_t rc = 0; |
212 |
|
213 |
fail_on_pending_request( ctx ); |
214 |
ctx->pending = 1; |
215 |
|
216 |
/* do some checks */ |
217 |
if( !gpgme_recipients_count ( recp ) ) { |
218 |
rc = mk_error( No_Recipients ); |
219 |
goto leave; |
220 |
} |
221 |
|
222 |
/* create a process object */ |
223 |
_gpgme_gpg_release( &ctx->gpg ); |
224 |
rc = _gpgme_gpg_new( &ctx->gpg ); |
225 |
if( rc ) |
226 |
goto leave; |
227 |
|
228 |
_gpgme_gpg_set_status_handler (ctx->gpg, encrypt_status_handler, ctx); |
229 |
_gpgme_gpg_set_command_handler (ctx->gpg, encrypt_command_handler, ctx); |
230 |
|
231 |
/* build the commandline */ |
232 |
_gpgme_gpg_add_arg( ctx->gpg, "--encrypt" ); |
233 |
if( ctx->use_armor ) |
234 |
_gpgme_gpg_add_arg( ctx->gpg, "--armor" ); |
235 |
if (ctx->no_compress) |
236 |
_gpgme_gpg_add_arg (ctx->gpg, "-z 0"); |
237 |
if (ctx->force_mdc) |
238 |
_gpgme_gpg_add_arg (ctx->gpg, "--force-mdc"); |
239 |
_gpgme_add_comment (ctx); |
240 |
if (ctx->cb.progress) |
241 |
_gpgme_gpg_add_arg (ctx->gpg, "--enable-progress-filter"); |
242 |
/* If we know that all recipients are valid (full or ultimate trust) |
243 |
we can pass suppress further checks */ |
244 |
if( ctx->force_trust || _gpgme_recipients_all_valid( recp ) ) |
245 |
_gpgme_gpg_add_arg( ctx->gpg, "--always-trust" ); |
246 |
|
247 |
_gpgme_append_gpg_args_from_recipients( recp, ctx->gpg ); |
248 |
if (ctx->use_logging) |
249 |
_gpgme_gpg_set_logging_handler (ctx->gpg, ctx); |
250 |
|
251 |
/* Check the supplied data */ |
252 |
if( gpgme_data_get_type( plain ) == GPGME_DATA_TYPE_NONE ) { |
253 |
rc = mk_error( No_Data ); |
254 |
goto leave; |
255 |
} |
256 |
_gpgme_data_set_mode( plain, GPGME_DATA_MODE_OUT ); |
257 |
if( !ciph || gpgme_data_get_type( ciph ) != GPGME_DATA_TYPE_NONE ) { |
258 |
rc = mk_error( Invalid_Value ); |
259 |
goto leave; |
260 |
} |
261 |
_gpgme_data_set_mode( ciph, GPGME_DATA_MODE_IN ); |
262 |
/* Tell the gpg object about the data */ |
263 |
if( ctx->use_tmpfiles ) { |
264 |
_gpgme_gpg_add_arg( ctx->gpg, "--yes" ); |
265 |
_gpgme_gpg_add_arg( ctx->gpg, "--output" ); |
266 |
_gpgme_gpg_add_arg( ctx->gpg, _gpgme_get_tmpfile( 0 ) ); |
267 |
_gpgme_data_write_to_tmpfile( plain ); |
268 |
_gpgme_gpg_add_arg( ctx->gpg, _gpgme_get_tmpfile( 1 ) ); |
269 |
} |
270 |
else { |
271 |
_gpgme_gpg_add_arg( ctx->gpg, "--output" ); |
272 |
_gpgme_gpg_add_arg( ctx->gpg, "-" ); |
273 |
_gpgme_gpg_add_data( ctx->gpg, ciph, 1 ); |
274 |
_gpgme_gpg_add_arg( ctx->gpg, "--" ); |
275 |
_gpgme_gpg_add_data( ctx->gpg, plain, 0 ); |
276 |
} |
277 |
|
278 |
/* and kick off the process */ |
279 |
rc = _gpgme_gpg_spawn ( ctx->gpg, ctx ); |
280 |
|
281 |
leave: |
282 |
if( rc ) { |
283 |
ctx->pending = 0; |
284 |
_gpgme_gpg_release( &ctx->gpg ); |
285 |
} |
286 |
|
287 |
return rc; |
288 |
} /* encrypt_start */ |
289 |
|
290 |
|
291 |
gpgme_error_t |
292 |
file_encrypt_start( gpgme_ctx_t ctx, gpgme_recipients_t recp, |
293 |
const char ** input, size_t nfiles, const char * output ) |
294 |
{ |
295 |
gpgme_error_t rc = 0; |
296 |
int symmetric = 0; |
297 |
|
298 |
if( !input ) |
299 |
return mk_error( Invalid_Value ); |
300 |
|
301 |
fail_on_pending_request( ctx ); |
302 |
ctx->pending = 1; |
303 |
|
304 |
if( use_symmetric( recp ) ) |
305 |
symmetric = 1; |
306 |
|
307 |
_gpgme_gpg_release( &ctx->gpg ); |
308 |
rc = _gpgme_gpg_new( &ctx->gpg ); |
309 |
if( rc ) { |
310 |
gpgme_release( ctx ); |
311 |
return rc; |
312 |
} |
313 |
|
314 |
_gpgme_gpg_set_status_handler( ctx->gpg, encrypt_status_handler, ctx ); |
315 |
if( ctx->cb.interactiv ) |
316 |
_gpgme_gpg_set_command_handler( ctx->gpg, encrypt_command_handler, ctx ); |
317 |
else |
318 |
_gpgme_gpg_add_arg (ctx->gpg, "--yes"); |
319 |
if( ctx->use_armor ) |
320 |
_gpgme_gpg_add_arg (ctx->gpg, "--armor"); |
321 |
if (ctx->no_compress) |
322 |
_gpgme_gpg_add_arg (ctx->gpg, "-z 0"); |
323 |
if (ctx->force_mdc) |
324 |
_gpgme_gpg_add_arg (ctx->gpg, "--force-mdc"); |
325 |
_gpgme_add_comment (ctx); |
326 |
if( ctx->cb.progress ) |
327 |
_gpgme_gpg_add_arg( ctx->gpg, "--enable-progress-filter" ); |
328 |
if( !output ) |
329 |
_gpgme_gpg_add_arg( ctx->gpg, "--no-mangle-dos-filenames" ); |
330 |
|
331 |
if( symmetric ) { |
332 |
_gpgme_gpg_add_arg( ctx->gpg, "--symmetric" ); |
333 |
_gpgme_encrypt_add_cipher( ctx ); |
334 |
rc = _gpgme_add_passphrase( ctx ); |
335 |
if( rc ) { |
336 |
_gpgme_gpg_release( &ctx->gpg ); |
337 |
return rc; |
338 |
} |
339 |
} |
340 |
else { |
341 |
if( ctx->use_throwkeyid ) |
342 |
_gpgme_gpg_add_arg( ctx->gpg, "--throw-keyid" ); |
343 |
if( ctx->force_trust ) |
344 |
_gpgme_gpg_add_arg( ctx->gpg, "--always-trust" ); |
345 |
if( recp ) |
346 |
_gpgme_append_gpg_args_from_recipients( recp, ctx->gpg ); |
347 |
if( ctx->pipemode || nfiles > 1 ) |
348 |
_gpgme_gpg_add_arg( ctx->gpg, "--encrypt-files" ); |
349 |
else |
350 |
_gpgme_gpg_add_arg( ctx->gpg, "--encrypt" ); |
351 |
|
352 |
} |
353 |
|
354 |
/* we cannot use --output for --encrypt-files */ |
355 |
if( nfiles == 1 && !ctx->pipemode && output ) { |
356 |
_gpgme_gpg_add_arg( ctx->gpg, "--output" ); |
357 |
_gpgme_gpg_add_arg( ctx->gpg, output ); |
358 |
} |
359 |
while( nfiles-- ) |
360 |
_gpgme_gpg_add_arg( ctx->gpg, *input++ ); |
361 |
rc = _gpgme_gpg_spawn( ctx->gpg, ctx ); |
362 |
if( rc ) { |
363 |
ctx->pending = 0; |
364 |
_gpgme_gpg_release( &ctx->gpg ); |
365 |
} |
366 |
|
367 |
return rc; |
368 |
} /* file_encrypt_start */ |
369 |
|
370 |
|
371 |
static gpgme_error_t |
372 |
get_encrypt_result (gpgme_ctx_t ctx) |
373 |
{ |
374 |
struct encrypt_result_s * res; |
375 |
gpgme_error_t err = 0; |
376 |
int rc; |
377 |
|
378 |
assert (ctx->result.encrypt); |
379 |
res = ctx->result.encrypt; |
380 |
if (res->okay) |
381 |
return mk_error( No_Error ); |
382 |
if (res->no_recp) |
383 |
err = mk_error (No_Recipients); |
384 |
else if (res->inv_recp) |
385 |
err = mk_error (Inv_Recipients); |
386 |
else if ((rc = gpgme_get_process_rc (ctx ))) { |
387 |
DEBUG1 ("gpg return code=%d\n", rc); |
388 |
err = mk_error (Interal_GPG_Problem); |
389 |
} |
390 |
else if (!res->okay || (res->file_start != res->file_done)) |
391 |
err = mk_error (Encryption_Failed); |
392 |
return err; |
393 |
} /* get_encrypt_result */ |
394 |
|
395 |
|
396 |
/** |
397 |
* gpgme_op_encrypt: |
398 |
* @ctx: The context |
399 |
* @recp: A set of recipients |
400 |
* @in: plaintext input |
401 |
* @out: ciphertext output |
402 |
* |
403 |
* This function encrypts @in to @out for all recipients from |
404 |
* @recp. Other parameters are take from the context @c. |
405 |
* The function does wait for the result. |
406 |
* |
407 |
* Return value: 0 on success or an errorcode. |
408 |
**/ |
409 |
gpgme_error_t |
410 |
gpgme_op_encrypt( gpgme_ctx_t ctx, gpgme_recipients_t recp, |
411 |
gpgme_data_t in, gpgme_data_t out ) |
412 |
|
413 |
{ |
414 |
gpgme_error_t err; |
415 |
|
416 |
err = encrypt_start( ctx, recp, in, out ); |
417 |
if( !err ) { |
418 |
gpgme_wait( ctx, 1 ); |
419 |
ctx->pending = 0; |
420 |
if( ctx->use_tmpfiles ) { |
421 |
_gpgme_data_read_from_tmpfile( out ); |
422 |
_gpgme_del_tmpfiles( ctx->wipe_fnc ); |
423 |
} |
424 |
err = get_encrypt_result( ctx ); |
425 |
} |
426 |
|
427 |
return err; |
428 |
} /* gpgme_op_encrypt */ |
429 |
|
430 |
|
431 |
gpgme_error_t |
432 |
gpgme_op_file_encrypt( gpgme_ctx_t ctx, gpgme_recipients_t rset, |
433 |
const char * input, const char * output ) |
434 |
{ |
435 |
gpgme_error_t err; |
436 |
const char * files[1]; |
437 |
|
438 |
files[0] = input; |
439 |
err = file_encrypt_start( ctx, rset, files, 1, output ); |
440 |
if( !err ) { |
441 |
gpgme_wait( ctx, 1 ); |
442 |
ctx->pending = 0; |
443 |
err = get_encrypt_result( ctx ); |
444 |
} |
445 |
|
446 |
return err; |
447 |
} /* gpgme_op_file_encrypt */ |
448 |
|
449 |
|
450 |
gpgme_error_t |
451 |
gpgme_op_files_encrypt( gpgme_ctx_t ctx, gpgme_recipients_t rset, |
452 |
const char ** files, size_t nfiles ) |
453 |
{ |
454 |
gpgme_error_t err; |
455 |
|
456 |
/* fixme: the result is actually only for the last file. we need |
457 |
a notification system which informs us with a callback |
458 |
after each file is processed. */ |
459 |
if( use_symmetric( rset ) ) { |
460 |
const char * s[1]; |
461 |
size_t i; |
462 |
|
463 |
for( i=0; i < nfiles; i++ ) { |
464 |
s[0] = files[i]; |
465 |
err = file_encrypt_start( ctx, NULL, s, 1, NULL ); |
466 |
if( !err ) { |
467 |
gpgme_wait( ctx, 1 ); |
468 |
ctx->pending = 0; |
469 |
err = get_encrypt_result( ctx ); |
470 |
} |
471 |
if( err ) |
472 |
break; |
473 |
} |
474 |
} |
475 |
else { |
476 |
err = file_encrypt_start( ctx, rset, files, nfiles, NULL ); |
477 |
if( !err ) { |
478 |
gpgme_wait( ctx, 1 ); |
479 |
ctx->pending = 0; |
480 |
err = get_encrypt_result( ctx ); |
481 |
} |
482 |
} |
483 |
return err; |
484 |
} |
485 |
|
486 |
|
487 |
gpgme_error_t |
488 |
gpgme_op_clip_encrypt( gpgme_recipients_t rset, int opts, gpgme_ctx_t *r_ctx ) |
489 |
{ |
490 |
gpgme_error_t err; |
491 |
gpgme_ctx_t ctx = NULL; |
492 |
gpgme_data_t plain = NULL; |
493 |
gpgme_data_t ciph = NULL; |
494 |
|
495 |
err = gpgme_new( &ctx ); |
496 |
if( err ) |
497 |
return err; |
498 |
|
499 |
if( opts & GPGME_CTRL_TMPFILES ) |
500 |
gpgme_control( ctx, GPGME_CTRL_TMPFILES, 1 ); |
501 |
if( opts & GPGME_CTRL_FORCETRUST ) |
502 |
gpgme_control( ctx, GPGME_CTRL_FORCETRUST, 1 ); |
503 |
gpgme_control( ctx, GPGME_CTRL_ARMOR, 1 ); |
504 |
|
505 |
err = gpgme_data_new_from_clipboard (&plain); |
506 |
if( !err ) |
507 |
err = gpgme_data_new( &ciph ); |
508 |
if( !err ) |
509 |
err = gpgme_op_encrypt( ctx, rset, plain, ciph ); |
510 |
if( !err ) { |
511 |
gpgme_data_change_version( &ciph ); |
512 |
gpgme_data_release_and_set_clipboard( ciph ); |
513 |
} |
514 |
if( r_ctx ) |
515 |
*r_ctx = ctx; |
516 |
else |
517 |
gpgme_release( ctx ); |
518 |
gpgme_data_release( plain ); |
519 |
return err; |
520 |
} /* gpgme_op_clip_encrypt */ |
521 |
|
522 |
|
523 |
int |
524 |
gpgme_recperr_count_items( gpgme_ctx_t ctx ) |
525 |
{ |
526 |
struct recp_info_s * r; |
527 |
int ncount = 0; |
528 |
|
529 |
if( !ctx ) |
530 |
return 0; |
531 |
if( ctx->result_type != RESULT_TYPE_ENCRYPT ) |
532 |
return -1; |
533 |
for( r = ctx->result.encrypt->inf; r; r = r->next ) |
534 |
ncount++; |
535 |
|
536 |
return ncount; |
537 |
} /* gpgme_recperr_count_items */ |
538 |
|
539 |
|
540 |
const char* |
541 |
gpgme_recperr_get( gpgme_ctx_t ctx, int idx, gpgme_error_t *r_code ) |
542 |
{ |
543 |
struct recp_info_s * r; |
544 |
|
545 |
if( !ctx ) |
546 |
return NULL; |
547 |
if( idx > gpgme_recperr_count_items( ctx ) ) |
548 |
return NULL; |
549 |
for( r = ctx->result.encrypt->inf; r && idx--; r = r->next ) |
550 |
; |
551 |
if( r ) { |
552 |
if( r_code ) |
553 |
*r_code = r->code; |
554 |
return r->recp; |
555 |
} |
556 |
|
557 |
return NULL; |
558 |
} /* gpgme_recperr_get_code */ |