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

proftpd / proftpd / 26182518137

20 May 2026 06:49PM UTC coverage: 93.024% (+0.4%) from 92.635%
26182518137

push

github

51329 of 55178 relevant lines covered (93.02%)

226.63 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, see <https://www.gnu.org/licenses/>.
17
 *
18
 * As a special exemption, The ProFTPD Project team and other respective
19
 * copyright holders give permission to link this program with OpenSSL, and
20
 * distribute the resulting executable, without including the source code for
21
 * OpenSSL in the source distribution.
22
 */
23

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

26
#include "conf.h"
27

28
#if defined(PR_USE_NLS)
29

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

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

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

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

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

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

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

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

4✔
59
    pr_signals_handle();
60

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

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

75
    if (nconv == (size_t) -1) {
76

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

85
      return -1;
86
    }
2✔
87

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

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

102
#ifdef HAVE_ICONV
103
static void set_supports_telnet_iac(const char *codeset) {
104

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

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

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

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

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

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

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

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

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

157
int encode_init(void) {
158

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

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

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

171
# ifdef HAVE_ICONV
172

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2✔
248
  outbuflen = sizeof(outbuf);
249

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

254
  *outlen = sizeof(outbuf) - outbuflen;
255

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

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

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

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

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

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

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

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

2✔
303
  outbuflen = sizeof(outbuf);
304

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

309
  *outlen = sizeof(outbuf) - outbuflen;
310

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

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

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

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

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

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

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

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

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

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

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

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

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

375
  return res;
376

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

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

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

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

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

407
  } else {
408

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

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

427
  return charset;
428
}
6✔
429

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

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

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

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

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

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

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

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

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

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

478
  (void) encode_free();
479

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

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

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

2✔
493
    errno = xerrno;
494
  }
2✔
495

496
  return res;
497

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

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

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

515
  return FALSE;
516
}
517

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

522
#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