/src/gnupg/g10/passphrase.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* passphrase.c - Get a passphrase |
2 | | * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, |
3 | | * 2005, 2006, 2007, 2009, 2011 Free Software Foundation, Inc. |
4 | | * |
5 | | * This file is part of GnuPG. |
6 | | * |
7 | | * GnuPG 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 3 of the License, or |
10 | | * (at your option) any later version. |
11 | | * |
12 | | * GnuPG 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, see <https://www.gnu.org/licenses/>. |
19 | | */ |
20 | | |
21 | | #include <config.h> |
22 | | #include <stddef.h> |
23 | | #include <stdio.h> |
24 | | #include <stdlib.h> |
25 | | #include <string.h> |
26 | | #include <unistd.h> |
27 | | #include <errno.h> |
28 | | #ifdef HAVE_LOCALE_H |
29 | | #include <locale.h> |
30 | | #endif |
31 | | #ifdef HAVE_LANGINFO_CODESET |
32 | | #include <langinfo.h> |
33 | | #endif |
34 | | |
35 | | #include "gpg.h" |
36 | | #include "../common/util.h" |
37 | | #include "options.h" |
38 | | #include "../common/ttyio.h" |
39 | | #include "keydb.h" |
40 | | #include "main.h" |
41 | | #include "../common/i18n.h" |
42 | | #include "../common/status.h" |
43 | | #include "call-agent.h" |
44 | | #include "../common/shareddefs.h" |
45 | | |
46 | | static char *fd_passwd = NULL; |
47 | | static char *next_pw = NULL; |
48 | | static char *last_pw = NULL; |
49 | | |
50 | | |
51 | | int |
52 | | have_static_passphrase (void) |
53 | 118k | { |
54 | 118k | return (!!fd_passwd |
55 | 118k | && (opt.batch || opt.pinentry_mode == PINENTRY_MODE_LOOPBACK)); |
56 | 118k | } |
57 | | |
58 | | |
59 | | /* Return a static passphrase. The returned value is only valid as |
60 | | long as no other passphrase related function is called. NULL may |
61 | | be returned if no passphrase has been set; better use |
62 | | have_static_passphrase first. */ |
63 | | const char * |
64 | | get_static_passphrase (void) |
65 | 0 | { |
66 | 0 | return fd_passwd; |
67 | 0 | } |
68 | | |
69 | | |
70 | | /**************** |
71 | | * Set the passphrase to be used for the next query and only for the next |
72 | | * one. |
73 | | */ |
74 | | void |
75 | | set_next_passphrase( const char *s ) |
76 | 0 | { |
77 | 0 | xfree(next_pw); |
78 | 0 | next_pw = NULL; |
79 | 0 | if ( s ) |
80 | 0 | { |
81 | 0 | next_pw = xmalloc_secure( strlen(s)+1 ); |
82 | 0 | strcpy (next_pw, s ); |
83 | 0 | } |
84 | 0 | } |
85 | | |
86 | | /**************** |
87 | | * Get the last passphrase used in passphrase_to_dek. |
88 | | * Note: This removes the passphrase from this modules and |
89 | | * the caller must free the result. May return NULL: |
90 | | */ |
91 | | char * |
92 | | get_last_passphrase (void) |
93 | 0 | { |
94 | 0 | char *p = last_pw; |
95 | 0 | last_pw = NULL; |
96 | 0 | return p; |
97 | 0 | } |
98 | | |
99 | | /* Here's an interesting question: since this passphrase was passed in |
100 | | on the command line, is there really any point in using secure |
101 | | memory for it? I'm going with 'yes', since it doesn't hurt, and |
102 | | might help in some small way (swapping). */ |
103 | | |
104 | | void |
105 | | set_passphrase_from_string(const char *pass) |
106 | 0 | { |
107 | 0 | xfree (fd_passwd); |
108 | 0 | fd_passwd = xmalloc_secure(strlen(pass)+1); |
109 | 0 | strcpy (fd_passwd, pass); |
110 | 0 | } |
111 | | |
112 | | |
113 | | void |
114 | | read_passphrase_from_fd( int fd ) |
115 | 0 | { |
116 | 0 | int i, len; |
117 | 0 | char *pw; |
118 | |
|
119 | 0 | if (! gnupg_fd_valid (fd)) |
120 | 0 | log_fatal ("passphrase-fd is invalid: %s\n", strerror (errno)); |
121 | | |
122 | 0 | if ( !opt.batch && opt.pinentry_mode != PINENTRY_MODE_LOOPBACK) |
123 | 0 | { /* Not used but we have to do a dummy read, so that it won't end |
124 | | up at the begin of the message if the quite usual trick to |
125 | | prepend the passphtrase to the message is used. */ |
126 | 0 | char buf[1]; |
127 | |
|
128 | 0 | while (!(read (fd, buf, 1) != 1 || *buf == '\n' )) |
129 | 0 | ; |
130 | 0 | *buf = 0; |
131 | 0 | return; |
132 | 0 | } |
133 | | |
134 | 0 | for (pw = NULL, i = len = 100; ; i++ ) |
135 | 0 | { |
136 | 0 | if (i >= len-1 ) |
137 | 0 | { |
138 | 0 | char *pw2 = pw; |
139 | 0 | len += 100; |
140 | 0 | pw = xmalloc_secure( len ); |
141 | 0 | if( pw2 ) |
142 | 0 | { |
143 | 0 | memcpy(pw, pw2, i ); |
144 | 0 | xfree (pw2); |
145 | 0 | } |
146 | 0 | else |
147 | 0 | i=0; |
148 | 0 | } |
149 | 0 | if (read( fd, pw+i, 1) != 1 || pw[i] == '\n' ) |
150 | 0 | break; |
151 | 0 | } |
152 | 0 | pw[i] = 0; |
153 | 0 | if (!opt.batch && opt.pinentry_mode != PINENTRY_MODE_LOOPBACK) |
154 | 0 | tty_printf("\b\b\b \n" ); |
155 | |
|
156 | 0 | xfree ( fd_passwd ); |
157 | 0 | fd_passwd = pw; |
158 | 0 | } |
159 | | |
160 | | |
161 | | /* |
162 | | * Ask the GPG Agent for the passphrase. |
163 | | * If NOCACHE is set the symmetric passpharse caching will not be used. |
164 | | * |
165 | | * If REPEAT is positive, a new passphrase is requested and the agent |
166 | | * shall require REPEAT times repetitions of the entered passphrase. |
167 | | * This is used for symmetric encryption. |
168 | | * |
169 | | * Note that TRYAGAIN_TEXT must not be translated. If CANCELED is not |
170 | | * NULL, the function does set it to 1 if the user canceled the |
171 | | * operation. If CACHEID is not NULL, it will be used as the cacheID |
172 | | * for the gpg-agent; if is NULL and a key fingerprint can be |
173 | | * computed, this will be used as the cacheid. |
174 | | * |
175 | | * For FLAGS see passphrase_to_dek; |
176 | | */ |
177 | | static char * |
178 | | passphrase_get (int newsymkey, int nocache, const char *cacheid, int repeat, |
179 | | const char *tryagain_text, unsigned int flags, int *canceled) |
180 | 118k | { |
181 | 118k | int rc; |
182 | 118k | char *pw = NULL; |
183 | 118k | char *orig_codeset; |
184 | 118k | const char *my_cacheid; |
185 | 118k | const char *desc; |
186 | | |
187 | 118k | if (canceled) |
188 | 118k | *canceled = 0; |
189 | | |
190 | 118k | orig_codeset = i18n_switchto_utf8 (); |
191 | | |
192 | 118k | if (!nocache && cacheid) |
193 | 5.87k | my_cacheid = cacheid; |
194 | 112k | else |
195 | 112k | my_cacheid = NULL; |
196 | | |
197 | 118k | if (tryagain_text) |
198 | 0 | tryagain_text = _(tryagain_text); |
199 | | |
200 | 118k | if ((flags & GETPASSWORD_FLAG_SYMDECRYPT)) |
201 | 118k | desc = _("Please enter the passphrase for decryption."); |
202 | 0 | else |
203 | 0 | desc = _("Enter passphrase\n"); |
204 | | |
205 | | /* Here we have: |
206 | | * REPEAT is set in create mode and if opt.passphrase_repeat is set. |
207 | | * (Thus it is not a clean indication that we want a new passphrase). |
208 | | * NOCACHE is set in create mode or if --no-symkey-cache is used. |
209 | | * CACHEID is only set if caching shall be used. |
210 | | * NEWSYMKEY has been added latter to make it clear that a new key |
211 | | * is requested. The whole chain of API is a bit too complex since |
212 | | * we we stripped things out over time; however, there is no time |
213 | | * for a full state analysis and thus this new parameter. |
214 | | */ |
215 | 118k | rc = agent_get_passphrase (my_cacheid, tryagain_text, NULL, |
216 | 118k | desc, |
217 | 118k | newsymkey, repeat, nocache, &pw); |
218 | | |
219 | 118k | i18n_switchback (orig_codeset); |
220 | | |
221 | | |
222 | 118k | if (!rc) |
223 | 0 | ; |
224 | 118k | else if (gpg_err_code (rc) == GPG_ERR_CANCELED |
225 | 118k | || gpg_err_code (rc) == GPG_ERR_FULLY_CANCELED) |
226 | 0 | { |
227 | 0 | log_info (_("cancelled by user\n") ); |
228 | 0 | if (canceled) |
229 | 0 | *canceled = 1; |
230 | 0 | } |
231 | 118k | else |
232 | 118k | { |
233 | 118k | log_error (_("problem with the agent: %s\n"), gpg_strerror (rc)); |
234 | | /* Due to limitations in the API of the upper layers they |
235 | | consider an error as no passphrase entered. This works in |
236 | | most cases but not during key creation where this should |
237 | | definitely not happen and let it continue without requiring a |
238 | | passphrase. Given that now all the upper layers handle a |
239 | | cancel correctly, we simply set the cancel flag now for all |
240 | | errors from the agent. */ |
241 | 118k | if (canceled) |
242 | 118k | *canceled = 1; |
243 | | |
244 | 118k | write_status_errcode ("get_passphrase", rc); |
245 | 118k | } |
246 | | |
247 | 118k | if (rc) |
248 | 118k | { |
249 | 118k | xfree (pw); |
250 | 118k | pw = NULL; |
251 | 118k | } |
252 | 118k | return pw; |
253 | 118k | } |
254 | | |
255 | | |
256 | | /* |
257 | | * Clear the cached passphrase with CACHEID. |
258 | | */ |
259 | | void |
260 | | passphrase_clear_cache (const char *cacheid) |
261 | 0 | { |
262 | 0 | int rc; |
263 | |
|
264 | 0 | rc = agent_clear_passphrase (cacheid); |
265 | 0 | if (rc) |
266 | 0 | log_error (_("problem with the agent: %s\n"), gpg_strerror (rc)); |
267 | 0 | } |
268 | | |
269 | | |
270 | | /* Return a new DEK object using the string-to-key specifier S2K. |
271 | | * Returns NULL if the user canceled the passphrase entry and if |
272 | | * CANCELED is not NULL, sets it to true. |
273 | | * |
274 | | * If CREATE is true a new passphrase will be created. If NOCACHE is |
275 | | * true the symmetric key caching will not be used. |
276 | | * FLAG bits are: |
277 | | * GETPASSWORD_FLAG_SYMDECRYPT := for symmetric decryption |
278 | | */ |
279 | | DEK * |
280 | | passphrase_to_dek (int cipher_algo, STRING2KEY *s2k, |
281 | | int create, int nocache, |
282 | | const char *tryagain_text, unsigned int flags, |
283 | | int *canceled) |
284 | 118k | { |
285 | 118k | char *pw = NULL; |
286 | 118k | DEK *dek; |
287 | 118k | STRING2KEY help_s2k; |
288 | 118k | int dummy_canceled; |
289 | 118k | char s2k_cacheidbuf[1+16+1]; |
290 | 118k | char *s2k_cacheid = NULL; |
291 | | |
292 | 118k | if (!canceled) |
293 | 7.85k | canceled = &dummy_canceled; |
294 | 118k | *canceled = 0; |
295 | | |
296 | 118k | if (opt.no_symkey_cache) |
297 | 0 | nocache = 1; /* Force no symmetric key caching. */ |
298 | | |
299 | 118k | if ( !s2k ) |
300 | 0 | { |
301 | 0 | log_assert (create && !nocache); |
302 | | /* This is used for the old rfc1991 mode |
303 | | * Note: This must match the code in encode.c with opt.rfc1991 set */ |
304 | 0 | memset (&help_s2k, 0, sizeof (help_s2k)); |
305 | 0 | s2k = &help_s2k; |
306 | 0 | s2k->hash_algo = S2K_DIGEST_ALGO; |
307 | 0 | } |
308 | | |
309 | | /* Create a new salt or what else to be filled into the s2k for a |
310 | | new key. */ |
311 | 118k | if (create && (s2k->mode == 1 || s2k->mode == 3)) |
312 | 0 | { |
313 | 0 | gcry_randomize (s2k->salt, 8, GCRY_STRONG_RANDOM); |
314 | 0 | if ( s2k->mode == 3 ) |
315 | 0 | { |
316 | | /* We delay the encoding until it is really needed. This is |
317 | | if we are going to dynamically calibrate it, we need to |
318 | | call out to gpg-agent and that should not be done during |
319 | | option processing in main(). */ |
320 | 0 | if (!opt.s2k_count) |
321 | 0 | opt.s2k_count = encode_s2k_iterations (agent_get_s2k_count ()); |
322 | 0 | s2k->count = opt.s2k_count; |
323 | 0 | } |
324 | 0 | } |
325 | | |
326 | | /* If we do not have a passphrase available in NEXT_PW and status |
327 | | information are request, we print them now. */ |
328 | 118k | if ( !next_pw && is_status_enabled() ) |
329 | 0 | { |
330 | 0 | char buf[50]; |
331 | |
|
332 | 0 | snprintf (buf, sizeof buf, "%d %d %d", |
333 | 0 | cipher_algo, s2k->mode, s2k->hash_algo ); |
334 | 0 | write_status_text ( STATUS_NEED_PASSPHRASE_SYM, buf ); |
335 | 0 | } |
336 | | |
337 | 118k | if ( next_pw ) |
338 | 0 | { |
339 | | /* Simply return the passphrase we already have in NEXT_PW. */ |
340 | 0 | pw = next_pw; |
341 | 0 | next_pw = NULL; |
342 | 0 | } |
343 | 118k | else if ( have_static_passphrase () ) |
344 | 0 | { |
345 | | /* Return the passphrase we have stored in FD_PASSWD. */ |
346 | 0 | pw = xmalloc_secure ( strlen(fd_passwd)+1 ); |
347 | 0 | strcpy ( pw, fd_passwd ); |
348 | 0 | } |
349 | 118k | else |
350 | 118k | { |
351 | 118k | if (!nocache && (s2k->mode == 1 || s2k->mode == 3)) |
352 | 5.87k | { |
353 | 5.87k | memset (s2k_cacheidbuf, 0, sizeof s2k_cacheidbuf); |
354 | 5.87k | *s2k_cacheidbuf = 'S'; |
355 | 5.87k | bin2hex (s2k->salt, 8, s2k_cacheidbuf + 1); |
356 | 5.87k | s2k_cacheid = s2k_cacheidbuf; |
357 | 5.87k | } |
358 | | |
359 | 118k | if (opt.pinentry_mode == PINENTRY_MODE_LOOPBACK) |
360 | 0 | { |
361 | 0 | char buf[32]; |
362 | |
|
363 | 0 | snprintf (buf, sizeof (buf), "%u", 100); |
364 | 0 | write_status_text (STATUS_INQUIRE_MAXLEN, buf); |
365 | 0 | } |
366 | | |
367 | | /* Divert to the gpg-agent. */ |
368 | 118k | pw = passphrase_get (create, create && nocache, s2k_cacheid, |
369 | 118k | create? opt.passphrase_repeat : 0, |
370 | 118k | tryagain_text, flags, canceled); |
371 | 118k | if (*canceled) |
372 | 118k | { |
373 | 118k | xfree (pw); |
374 | 118k | write_status( STATUS_CANCELED_BY_USER ); |
375 | 118k | return NULL; |
376 | 118k | } |
377 | 118k | } |
378 | | |
379 | 0 | if ( !pw || !*pw ) |
380 | 0 | write_status( STATUS_MISSING_PASSPHRASE ); |
381 | | |
382 | | /* Hash the passphrase and store it in a newly allocated DEK object. |
383 | | Keep a copy of the passphrase in LAST_PW for use by |
384 | | get_last_passphrase(). */ |
385 | 0 | dek = xmalloc_secure_clear ( sizeof *dek ); |
386 | 0 | dek->algo = cipher_algo; |
387 | 0 | if ( (!pw || !*pw) && create) |
388 | 0 | dek->keylen = 0; |
389 | 0 | else |
390 | 0 | { |
391 | 0 | gpg_error_t err; |
392 | |
|
393 | 0 | dek->keylen = openpgp_cipher_get_algo_keylen (dek->algo); |
394 | 0 | if (!(dek->keylen > 0 && dek->keylen <= DIM(dek->key))) |
395 | 0 | BUG (); |
396 | 0 | err = gcry_kdf_derive (pw, strlen (pw), |
397 | 0 | s2k->mode == 3? GCRY_KDF_ITERSALTED_S2K : |
398 | 0 | s2k->mode == 1? GCRY_KDF_SALTED_S2K : |
399 | 0 | /* */ GCRY_KDF_SIMPLE_S2K, |
400 | 0 | s2k->hash_algo, s2k->salt, 8, |
401 | 0 | S2K_DECODE_COUNT(s2k->count), |
402 | 0 | dek->keylen, dek->key); |
403 | 0 | if (err) |
404 | 0 | { |
405 | 0 | log_error ("gcry_kdf_derive failed: %s", gpg_strerror (err)); |
406 | 0 | xfree (pw); |
407 | 0 | xfree (dek); |
408 | 0 | write_status( STATUS_MISSING_PASSPHRASE ); |
409 | 0 | return NULL; |
410 | 0 | } |
411 | 0 | } |
412 | 0 | if (s2k_cacheid) |
413 | 0 | memcpy (dek->s2k_cacheid, s2k_cacheid, sizeof dek->s2k_cacheid); |
414 | 0 | xfree(last_pw); |
415 | 0 | last_pw = pw; |
416 | 0 | return dek; |
417 | 0 | } |
418 | | |
419 | | |
420 | | /* Emit the USERID_HINT and the NEED_PASSPHRASE status messages. |
421 | | MAINKEYID may be NULL. */ |
422 | | void |
423 | | emit_status_need_passphrase (ctrl_t ctrl, |
424 | | u32 *keyid, u32 *mainkeyid, int pubkey_algo) |
425 | 0 | { |
426 | 0 | char buf[50]; |
427 | 0 | char *us; |
428 | |
|
429 | 0 | us = get_long_user_id_string (ctrl, keyid); |
430 | 0 | write_status_text (STATUS_USERID_HINT, us); |
431 | 0 | xfree (us); |
432 | |
|
433 | 0 | snprintf (buf, sizeof buf, "%08lX%08lX %08lX%08lX %d 0", |
434 | 0 | (ulong)keyid[0], |
435 | 0 | (ulong)keyid[1], |
436 | 0 | (ulong)(mainkeyid? mainkeyid[0]:keyid[0]), |
437 | 0 | (ulong)(mainkeyid? mainkeyid[1]:keyid[1]), |
438 | 0 | pubkey_algo); |
439 | |
|
440 | 0 | write_status_text (STATUS_NEED_PASSPHRASE, buf); |
441 | 0 | } |
442 | | |
443 | | |
444 | | /* Return an allocated utf-8 string describing the key PK. If ESCAPED |
445 | | is true spaces and control characters are percent or plus escaped. |
446 | | MODE describes the use of the key description; use one of the |
447 | | FORMAT_KEYDESC_ macros. */ |
448 | | char * |
449 | | gpg_format_keydesc (ctrl_t ctrl, PKT_public_key *pk, int mode, int escaped) |
450 | 0 | { |
451 | 0 | char *uid; |
452 | 0 | size_t uidlen; |
453 | 0 | const char *algo_name; |
454 | 0 | const char *timestr; |
455 | 0 | char *orig_codeset; |
456 | 0 | char *maink; |
457 | 0 | char *desc; |
458 | 0 | const char *prompt; |
459 | 0 | const char *trailer = ""; |
460 | 0 | int is_subkey; |
461 | |
|
462 | 0 | if (mode == FORMAT_KEYDESC_KEYGRIP) |
463 | 0 | { |
464 | 0 | is_subkey = 0; |
465 | 0 | algo_name = NULL; |
466 | 0 | timestr = NULL; |
467 | 0 | uid = NULL; |
468 | 0 | } |
469 | 0 | else |
470 | 0 | { |
471 | 0 | is_subkey = (pk->main_keyid[0] && pk->main_keyid[1] |
472 | 0 | && pk->keyid[0] != pk->main_keyid[0] |
473 | 0 | && pk->keyid[1] != pk->main_keyid[1]); |
474 | 0 | algo_name = openpgp_pk_algo_name (pk->pubkey_algo); |
475 | 0 | timestr = strtimestamp (pk->timestamp); |
476 | 0 | uid = get_user_id (ctrl, is_subkey? pk->main_keyid:pk->keyid, |
477 | 0 | &uidlen, NULL); |
478 | 0 | } |
479 | |
|
480 | 0 | orig_codeset = i18n_switchto_utf8 (); |
481 | |
|
482 | 0 | if (is_subkey) |
483 | 0 | maink = xtryasprintf (_(" (main key ID %s)"), keystr (pk->main_keyid)); |
484 | 0 | else |
485 | 0 | maink = NULL; |
486 | |
|
487 | 0 | switch (mode) |
488 | 0 | { |
489 | 0 | case FORMAT_KEYDESC_NORMAL: |
490 | 0 | prompt = _("Please enter the passphrase to unlock the" |
491 | 0 | " OpenPGP secret key:"); |
492 | 0 | break; |
493 | 0 | case FORMAT_KEYDESC_IMPORT: |
494 | 0 | prompt = _("Please enter the passphrase to import the" |
495 | 0 | " OpenPGP secret key:"); |
496 | 0 | break; |
497 | 0 | case FORMAT_KEYDESC_EXPORT: |
498 | 0 | if (is_subkey) |
499 | 0 | prompt = _("Please enter the passphrase to export the" |
500 | 0 | " OpenPGP secret subkey:"); |
501 | 0 | else |
502 | 0 | prompt = _("Please enter the passphrase to export the" |
503 | 0 | " OpenPGP secret key:"); |
504 | 0 | break; |
505 | 0 | case FORMAT_KEYDESC_DELKEY: |
506 | 0 | if (is_subkey) |
507 | 0 | prompt = _("Do you really want to permanently delete the" |
508 | 0 | " OpenPGP secret subkey key:"); |
509 | 0 | else |
510 | 0 | prompt = _("Do you really want to permanently delete the" |
511 | 0 | " OpenPGP secret key:"); |
512 | 0 | trailer = "?"; |
513 | 0 | break; |
514 | 0 | case FORMAT_KEYDESC_KEYGRIP: |
515 | 0 | prompt = _("Please enter the passphrase to export the" |
516 | 0 | " secret key with keygrip:"); |
517 | 0 | break; |
518 | 0 | default: |
519 | 0 | prompt = "?"; |
520 | 0 | break; |
521 | 0 | } |
522 | | |
523 | 0 | if (mode == FORMAT_KEYDESC_KEYGRIP) |
524 | 0 | desc = xtryasprintf ("%s\n\n" |
525 | 0 | " %s\n", |
526 | 0 | prompt, |
527 | 0 | "<keygrip>"); |
528 | 0 | else |
529 | 0 | desc = xtryasprintf (_("%s\n" |
530 | 0 | "\"%.*s\"\n" |
531 | 0 | "%u-bit %s key, ID %s,\n" |
532 | 0 | "created %s%s.\n%s"), |
533 | 0 | prompt, |
534 | 0 | (int)uidlen, uid, |
535 | 0 | nbits_from_pk (pk), algo_name, |
536 | 0 | keystr (pk->keyid), timestr, |
537 | 0 | maink?maink:"", trailer); |
538 | 0 | xfree (maink); |
539 | 0 | xfree (uid); |
540 | |
|
541 | 0 | i18n_switchback (orig_codeset); |
542 | |
|
543 | 0 | if (escaped) |
544 | 0 | { |
545 | 0 | char *tmp = percent_plus_escape (desc); |
546 | 0 | xfree (desc); |
547 | 0 | desc = tmp; |
548 | 0 | } |
549 | |
|
550 | 0 | return desc; |
551 | 0 | } |