• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

proftpd / proftpd / 21452122833

28 Jan 2026 07:20PM UTC coverage: 92.996%. Remained the same
21452122833

push

github

51439 of 55313 relevant lines covered (93.0%)

213.27 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.44
/src/encode.c
1
/*
2
 * ProFTPD - FTP server daemon
3
 * Copyright (c) 2006-2026 The ProFTPD Project team
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation; either version 2 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License
16
 * along with this program; if not, write to the Free Software
17
 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
18
 *
19
 * As a special exemption, The ProFTPD Project team and other respective
20
 * copyright holders give permission to link this program with OpenSSL, and
21
 * distribute the resulting executable, without including the source code for
22
 * OpenSSL in the source distribution.
23
 */
24

25
/* UTF8/charset encoding/decoding. */
26

27
#include "conf.h"
28

29
#if defined(PR_USE_NLS)
30

31
#ifdef HAVE_ICONV_H
32
# include <iconv.h>
33
#endif
34

35
#ifdef HAVE_LANGINFO_H
36
# include <langinfo.h>
37
#endif
38

39
#ifdef HAVE_ICONV_H
40
static iconv_t decode_conv = (iconv_t) -1;
41
static iconv_t encode_conv = (iconv_t) -1;
42

43
static unsigned long encoding_policy = 0UL;
44
static const char *local_charset = NULL;
45
static const char *encoding = "UTF-8";
46
static int supports_telnet_iac = TRUE;
47

48
static const char *trace_channel = "encode";
49

50
static int str_convert(iconv_t conv, const char *inbuf, size_t *inbuflen,
4✔
51
    char *outbuf, size_t *outbuflen) {
52
# if defined(HAVE_ICONV)
53

54
  /* Reset the state machine before each conversion. */
55
  (void) iconv(conv, NULL, NULL, NULL, NULL);
4✔
56

57
  while (*inbuflen > 0) {
4✔
58
    size_t nconv;
4✔
59

60
    pr_signals_handle();
4✔
61

62
    /* Solaris/FreeBSD's iconv(3) takes a const char ** for the input buffer
63
     * (depending on version), whereas Linux/Mac OSX/GNU iconv(3) use char **
64
     * for the input buffer.
65
     */
66
#if defined(LINUX) || defined(DARWIN6) || defined(DARWIN7) || \
67
    defined(DARWIN8) || defined(DARWIN9) || defined(DARWIN10) || \
68
    defined(DARWIN11) || defined(DARWIN12) || defined(FREEBSD14) || \
69
    defined(GNU)
70

71
    nconv = iconv(conv, (char **) &inbuf, inbuflen, &outbuf, outbuflen);
4✔
72
#else
73
    nconv = iconv(conv, &inbuf, inbuflen, &outbuf, outbuflen);
74
#endif
75

76
    if (nconv == (size_t) -1) {
4✔
77

78
      /* Note: an errno of EILSEQ here can indicate badly encoded strings OR
79
       * (more likely) that the source character set used in the iconv_open(3)
80
       * call for this iconv_t descriptor does not accurately describe the
81
       * character encoding of the given string.  E.g. a filename may use
82
       * the ISO8859-1 character set, but iconv_open(3) was called using
83
       * US-ASCII.
84
       */
85

86
      return -1;
2✔
87
    }
88

89
    /* XXX We should let the loop condition work, rather than breaking out
90
     * of the loop here.
91
     */
92
    break;
93
  }
94

95
  return 0;
96
# else
97
  errno = ENOSYS;
98
  return -1;
99
# endif /* HAVE_ICONV */
100
}
101
#endif /* !HAVE_ICONV_H */
102

103
#ifdef HAVE_ICONV
104
static void set_supports_telnet_iac(const char *codeset) {
15✔
105

106
  /* The full list of character sets which use 0xFF could be obtained from
107
   * the libiconv sources; for now, this list should contain the most
108
   * commonly used character sets.
109
   */
110

111
  if (strncasecmp(codeset, "CP1251", 7) == 0 ||
15✔
112
      strncasecmp(codeset, "CP866", 6) == 0 ||
14✔
113
      strncasecmp(codeset, "ISO-8859-1", 11) == 0 ||
13✔
114
      strncasecmp(codeset, "KOI8-R", 7) == 0 ||
11✔
115
      strncasecmp(codeset, "WINDOWS-1251", 13) == 0) {
10✔
116
    supports_telnet_iac = FALSE;
6✔
117
    return;
6✔
118
  }
119

120
  supports_telnet_iac = TRUE;
9✔
121
}
122
#endif /* !HAVE_ICONV */
123

124
int encode_free(void) {
19✔
125
# ifdef HAVE_ICONV
126
  int res = 0;
19✔
127

128
  /* Close the iconv handles. */
129
  if (encode_conv != (iconv_t) -1) {
19✔
130
    if (iconv_close(encode_conv) < 0) {
15✔
131
      pr_trace_msg(trace_channel, 1,
×
132
        "error closing conversion handle from '%s' to '%s': %s",
133
        local_charset, encoding, strerror(errno));
×
134
      res = -1;
×
135
    }
136

137
    encode_conv = (iconv_t) -1;
15✔
138
  }
139

140
  if (decode_conv != (iconv_t) -1) {
19✔
141
    if (iconv_close(decode_conv) < 0) {
15✔
142
      pr_trace_msg(trace_channel, 1,
×
143
        "error closing conversion handle from '%s' to '%s': %s",
144
        encoding, local_charset, strerror(errno));
×
145
      res = -1;
×
146
    }
147

148
    decode_conv = (iconv_t) -1;
15✔
149
  }
150

151
  return res;
19✔
152
# else
153
  errno = ENOSYS;
154
  return -1;
155
# endif
156
}
157

158
int encode_init(void) {
18✔
159

160
  if (encoding == NULL) {
18✔
161
    pr_trace_msg(trace_channel, 3, "no encoding configured");
×
162
    return 0;
×
163
  }
164

165
  if (local_charset == NULL) {
18✔
166
    local_charset = pr_encode_get_local_charset();
6✔
167
  }
168

169
  pr_log_debug(DEBUG10, "using '%s' as local charset for %s conversion",
18✔
170
    local_charset, encoding);
171

172
# ifdef HAVE_ICONV
173

174
  /* If the local charset matches the remote charset, then there's no point
175
   * in converting; the charsets are the same.  Indeed, on some libiconv
176
   * implementations, attempting to convert between the same charsets results
177
   * in a tightly spinning CPU, or worse (see Bug#3272).
178
   */
179
  if (strcasecmp(local_charset, encoding) != 0) {
18✔
180

181
    /* Get the iconv handles. */
182
    encode_conv = iconv_open(encoding, local_charset);
18✔
183
    if (encode_conv == (iconv_t) -1) {
18✔
184
      int xerrno = errno;
3✔
185

186
      pr_log_pri(PR_LOG_NOTICE, "error opening conversion handle "
3✔
187
        "from '%s' to '%s': %s", local_charset, encoding, strerror(xerrno));
188

189
      errno = xerrno;
3✔
190
      return -1;
3✔
191
    }
192

193
    decode_conv = iconv_open(local_charset, encoding);
15✔
194
    if (decode_conv == (iconv_t) -1) {
15✔
195
      int xerrno = errno;
×
196

197
      pr_log_pri(PR_LOG_NOTICE, "error opening conversion handle "
×
198
        "from '%s' to '%s': %s", encoding, local_charset, strerror(xerrno));
199

200
      (void) iconv_close(encode_conv);
×
201
      encode_conv = (iconv_t) -1;
×
202

203
      errno = xerrno;
×
204
      return -1;
×
205
    }
206
  }
207

208
  set_supports_telnet_iac(encoding);
15✔
209
  return 0;
15✔
210
# else
211
  errno = ENOSYS;
212
  return -1;
213
# endif /* HAVE_ICONV */
214
}
215

216
char *pr_decode_str(pool *p, const char *in, size_t inlen, size_t *outlen) {
75✔
217
#ifdef HAVE_ICONV
218
  size_t inbuflen, outbuflen, outbufsz;
75✔
219
  char *inbuf, outbuf[PR_TUNABLE_PATH_MAX*2], *res = NULL;
75✔
220

221
  if (p == NULL ||
75✔
222
      in == NULL ||
75✔
223
      outlen == NULL) {
224
    errno = EINVAL;
3✔
225
    return NULL;
3✔
226
  }
227

228
  /* If the local charset matches the remote charset, then there's no point
229
   * in converting; the charsets are the same.  Indeed, on some libiconv
230
   * implementations, attempting to convert between the same charsets results
231
   * in a tightly spinning CPU (see Bug#3272).
232
   */
233
  if (local_charset != NULL &&
72✔
234
      encoding != NULL &&
2✔
235
      strcasecmp(local_charset, encoding) == 0) {
2✔
236
    return pstrdup(p, in);
×
237
  }
238

239
  if (decode_conv == (iconv_t) -1) {
72✔
240
    pr_trace_msg(trace_channel, 1, "invalid decoding conversion handle, "
70✔
241
      "unable to decode string");
242
    return pstrdup(p, in);
70✔
243
  }
244

245
  inbuf = pcalloc(p, inlen);
2✔
246
  memcpy(inbuf, in, inlen);
2✔
247
  inbuflen = inlen;
2✔
248

249
  outbuflen = sizeof(outbuf);
2✔
250

251
  if (str_convert(decode_conv, inbuf, &inbuflen, outbuf, &outbuflen) < 0) {
2✔
252
    return NULL;
253
  }
254

255
  *outlen = sizeof(outbuf) - outbuflen;
1✔
256

257
  /* We allocate one byte more, for a terminating NUL. */
258
  outbufsz = sizeof(outbuf) - outbuflen + 1;
1✔
259
  res = pcalloc(p, outbufsz);
1✔
260

261
  memcpy(res, outbuf, *outlen);
1✔
262

263
  return res;
1✔
264
#else
265
  pr_trace_msg(trace_channel, 1,
266
    "missing iconv support, no %s decoding possible", encoding);
267
  return pstrdup(p, in);
268
#endif /* !HAVE_ICONV */
269
}
270

271
char *pr_encode_str(pool *p, const char *in, size_t inlen, size_t *outlen) {
7✔
272
#ifdef HAVE_ICONV
273
  size_t inbuflen, outbuflen, outbufsz;
7✔
274
  char *inbuf, outbuf[PR_TUNABLE_PATH_MAX*2], *res;
7✔
275

276
  if (p == NULL ||
7✔
277
      in == NULL ||
7✔
278
      outlen == NULL) {
279
    errno = EINVAL;
3✔
280
    return NULL;
3✔
281
  }
282

283
  /* If the local charset matches the remote charset, then there's no point
284
   * in converting; the charsets are the same.  Indeed, on some libiconv
285
   * implementations, attempting to convert between the same charsets results
286
   * in a tightly spinning CPU (see Bug#3272).
287
   */
288
  if (local_charset != NULL &&
4✔
289
      encoding != NULL &&
2✔
290
      strcasecmp(local_charset, encoding) == 0) {
2✔
291
    return pstrdup(p, in);
×
292
  }
293

294
  if (encode_conv == (iconv_t) -1) {
4✔
295
    pr_trace_msg(trace_channel, 1, "invalid encoding conversion handle, "
2✔
296
      "unable to encode string");
297
    return pstrdup(p, in);
2✔
298
  }
299

300
  inbuf = pcalloc(p, inlen);
2✔
301
  memcpy(inbuf, in, inlen);
2✔
302
  inbuflen = inlen;
2✔
303

304
  outbuflen = sizeof(outbuf);
2✔
305

306
  if (str_convert(encode_conv, inbuf, &inbuflen, outbuf, &outbuflen) < 0) {
2✔
307
    return NULL;
308
  }
309

310
  *outlen = sizeof(outbuf) - outbuflen;
1✔
311

312
  /* We allocate one byte more, for a terminating NUL. */
313
  outbufsz = sizeof(outbuf) - outbuflen + 1;
1✔
314

315
  res = pcalloc(p, outbufsz);
1✔
316
  memcpy(res, outbuf, *outlen);
1✔
317

318
  return res;
1✔
319
#else
320
  pr_trace_msg(trace_channel, 1,
321
    "missing iconv support, no %s encoding possible", encoding);
322
  return pstrdup(p, in);
323
#endif /* !HAVE_ICONV */
324
}
325

326
void pr_encode_disable_encoding(void) {
1✔
327
#ifdef HAVE_ICONV_H
328
  pr_trace_msg(trace_channel, 8, "%s encoding disabled", encoding);
1✔
329
  (void) encode_free();
1✔
330
  encoding = NULL;
1✔
331
#endif
332
}
1✔
333

334
/* Enables runtime use of encoding using the specified character set (assuming
335
 * NLS is supported).  Note that "UTF8", "utf8", "utf-8", and "UTF-8" are
336
 * accepted "character set" designations.
337
 */
338
int pr_encode_enable_encoding(const char *codeset) {
5✔
339
#ifdef HAVE_ICONV_H
340
  int res;
5✔
341

342
  if (codeset == NULL) {
5✔
343
    errno = EINVAL;
1✔
344
    return -1;
1✔
345
  }
346

347
  if (encoding != NULL &&
4✔
348
      strcasecmp(encoding, codeset) == 0) {
3✔
349
    pr_trace_msg(trace_channel, 5, "'%s' encoding already being used", codeset);
1✔
350
    return 0;
1✔
351
  }
352

353
  if (encoding) {
3✔
354
    pr_trace_msg(trace_channel, 5,
2✔
355
      "attempting to switch encoding from %s to %s", encoding, codeset);
356

357
  } else {
358
    pr_trace_msg(trace_channel, 5, "attempting to enable %s encoding", codeset);
1✔
359
  }
360

361
  (void) encode_free();
3✔
362
  encoding = pstrdup(permanent_pool, codeset);
3✔
363

364
  res = encode_init();
3✔
365
  if (res < 0) {
3✔
366
    int xerrno = errno;
1✔
367

368
    pr_trace_msg(trace_channel, 1,
1✔
369
      "failed to initialize encoding for %s, disabling encoding: %s", codeset,
370
      strerror(xerrno));
371

372
    encoding = NULL;
1✔
373
    errno = xerrno;
1✔
374
  }
375

376
  return res;
377

378
#else
379
  errno = ENOSYS;
380
  return -1;
381
#endif /* !HAVE_ICONV_H */
382
}
383

384
unsigned long pr_encode_get_policy(void) {
2✔
385
  return encoding_policy;
2✔
386
}
387

388
int pr_encode_set_policy(unsigned long policy) {
2✔
389
  encoding_policy = policy;
2✔
390
  return 0;
2✔
391
}
392

393
const char *pr_encode_get_local_charset(void) {
6✔
394
  const char *charset = NULL;
6✔
395

396
#ifdef HAVE_NL_LANGINFO
397
  /* Look up the current charset.  If there's a problem, default to
398
   * UCS-2.  Make sure we pick up the locale of the environment.
399
   */
400
  charset = nl_langinfo(CODESET);
6✔
401
  if (charset == NULL ||
6✔
402
      strlen(charset) == 0) {
6✔
403
    charset = "UTF-8";
×
404
    pr_trace_msg(trace_channel, 1,
×
405
      "unable to determine locale, defaulting to 'UTF-8' for %s conversion",
406
      encoding);
407

408
  } else {
409

410
    /* Workaround a stupid bug in many implementations where nl_langinfo()
411
     * returns "646" to mean "US-ASCII".  The problem is that iconv_open(3)
412
     * doesn't accept "646" as an acceptable encoding.
413
     */
414
    if (strncmp(charset, "646", 4) == 0) {
6✔
415
      charset = "US-ASCII";
×
416
    }
417

418
    pr_trace_msg(trace_channel, 1,
6✔
419
      "converting %s to local character set '%s'", encoding, charset);
420
    }
421
#else
422
  charset = "UTF-8";
423
  pr_trace_msg(trace_channel, 1,
424
    "nl_langinfo(3) not supported, defaulting to using 'UTF-8' for "
425
    "%s conversion", encoding);
426
#endif /* HAVE_NL_LANGINFO */
427

428
  return charset;
6✔
429
}
430

431
const char *pr_encode_get_charset(void) {
1✔
432
#ifdef HAVE_ICONV_H
433
  return local_charset;
1✔
434

435
#else
436
  errno = ENOSYS;
437
  return NULL;
438
#endif /* !HAVE_ICONV_H */
439
}
440

441
const char *pr_encode_get_encoding(void) {
1✔
442
#ifdef HAVE_ICONV_H
443
  return encoding;
1✔
444

445
#else
446
  errno = ENOSYS;
447
  return NULL;
448
#endif /* !HAVE_ICONV_H */
449
}
450

451
int pr_encode_set_charset_encoding(const char *charset, const char *codeset) {
11✔
452
#ifdef HAVE_ICONV_H
453
  int res;
11✔
454

455
  if (charset == NULL ||
11✔
456
      codeset == NULL) {
11✔
457
    errno = EINVAL;
2✔
458
    return -1;
2✔
459
  }
460

461
  if (local_charset) {
9✔
462
    pr_trace_msg(trace_channel, 5,
8✔
463
      "attempting to switch local charset from %s to %s", local_charset,
464
      charset);
465

466
  } else {
467
    pr_trace_msg(trace_channel, 5, "attempting to use %s as local charset",
1✔
468
      charset);
469
  }
470

471
  if (encoding) {
9✔
472
    pr_trace_msg(trace_channel, 5,
8✔
473
      "attempting to switch encoding from %s to %s", encoding, codeset);
474

475
  } else {
476
    pr_trace_msg(trace_channel, 5, "attempting to use %s encoding", codeset);
1✔
477
  }
478

479
  (void) encode_free();
9✔
480

481
  local_charset = pstrdup(permanent_pool, charset);
9✔
482
  encoding = pstrdup(permanent_pool, codeset);
9✔
483

484
  res = encode_init();
9✔
485
  if (res < 0) {
9✔
486
    int xerrno = errno;
2✔
487

488
    pr_trace_msg(trace_channel, 1,
2✔
489
      "failed to initialize encoding for local charset %s, encoding %s, "
490
      "disabling encoding", charset, codeset);
491
    local_charset = NULL;
2✔
492
    encoding = NULL;
2✔
493

494
    errno = xerrno;
2✔
495
  }
496

497
  return res;
498

499
#else
500
  errno = ENOSYS;
501
  return -1;
502
#endif /* !HAVE_ICONV_H */
503
}
504

505
int pr_encode_is_utf8(const char *codeset) {
4✔
506
  if (codeset == NULL) {
4✔
507
    errno = EINVAL;
1✔
508
    return -1;
1✔
509
  }
510

511
  if (strncasecmp(codeset, "UTF8", 5) == 0 ||
3✔
512
      strncasecmp(codeset, "UTF-8", 6) == 0) {
2✔
513
    return TRUE;
2✔
514
  }
515

516
  return FALSE;
517
}
518

519
int pr_encode_supports_telnet_iac(void) {
33✔
520
  return supports_telnet_iac;
33✔
521
}
522

523
#endif /* PR_USE_NLS */
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc