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

proftpd / proftpd / 28064770019

23 Jun 2026 11:42PM UTC coverage: 92.468% (-0.6%) from 93.032%
28064770019

push

github

web-flow
If the non-default (and undocumented) "xattr" SFTP extensions are enabled, the "flistxattr" extension can return heap memory to the unsuspecting client.

Thanks to Fabian Wahle of Hap Security for reporting this issue.

48692 of 52658 relevant lines covered (92.47%)

243.18 hits per line

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

89.02
/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,
4✔
50
    char *outbuf, size_t *outbuflen) {
51
# if defined(HAVE_ICONV)
52

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

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

59
    pr_signals_handle();
4✔
60

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);
4✔
71
#else
72
    nconv = iconv(conv, &inbuf, inbuflen, &outbuf, outbuflen);
73
#endif
74

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

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
    }
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) {
15✔
104

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 ||
29✔
111
      strncasecmp(codeset, "CP866", 6) == 0 ||
27✔
112
      strncasecmp(codeset, "ISO-8859-1", 11) == 0 ||
24✔
113
      strncasecmp(codeset, "KOI8-R", 7) == 0 ||
21✔
114
      strncasecmp(codeset, "WINDOWS-1251", 13) == 0) {
10✔
115
    supports_telnet_iac = FALSE;
6✔
116
    return;
6✔
117
  }
118

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

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

127
  /* Close the iconv handles. */
128
  if (encode_conv != (iconv_t) -1) {
19✔
129
    if (iconv_close(encode_conv) < 0) {
15✔
130
      pr_trace_msg(trace_channel, 1,
×
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;
15✔
137
  }
138

139
  if (decode_conv != (iconv_t) -1) {
19✔
140
    if (iconv_close(decode_conv) < 0) {
15✔
141
      pr_trace_msg(trace_channel, 1,
×
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;
15✔
148
  }
149

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

157
int encode_init(void) {
18✔
158

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

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

168
  pr_log_debug(DEBUG10, "using '%s' as local charset for %s conversion",
18✔
169
    local_charset, encoding);
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) {
18✔
179

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

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

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

192
    decode_conv = iconv_open(local_charset, encoding);
15✔
193
    if (decode_conv == (iconv_t) -1) {
15✔
194
      int xerrno = errno;
×
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);
15✔
208
  return 0;
15✔
209
# else
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) {
75✔
216
#ifdef HAVE_ICONV
217
  size_t inbuflen, outbuflen, outbufsz;
218
  char *inbuf, outbuf[PR_TUNABLE_PATH_MAX*2], *res = NULL;
75✔
219

220
  if (p == NULL ||
150✔
221
      in == NULL ||
148✔
222
      outlen == NULL) {
223
    errno = EINVAL;
3✔
224
    return NULL;
3✔
225
  }
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 &&
74✔
233
      encoding != NULL &&
4✔
234
      strcasecmp(local_charset, encoding) == 0) {
2✔
235
    return pstrdup(p, in);
×
236
  }
237

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

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

248
  outbuflen = sizeof(outbuf);
2✔
249

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

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

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

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

262
  return res;
1✔
263
#else
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) {
7✔
271
#ifdef HAVE_ICONV
272
  size_t inbuflen, outbuflen, outbufsz;
273
  char *inbuf, outbuf[PR_TUNABLE_PATH_MAX*2], *res;
274

275
  if (p == NULL ||
14✔
276
      in == NULL ||
12✔
277
      outlen == NULL) {
278
    errno = EINVAL;
3✔
279
    return NULL;
3✔
280
  }
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 &&
6✔
288
      encoding != NULL &&
4✔
289
      strcasecmp(local_charset, encoding) == 0) {
2✔
290
    return pstrdup(p, in);
×
291
  }
292

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

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

303
  outbuflen = sizeof(outbuf);
2✔
304

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

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

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

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

317
  return res;
1✔
318
#else
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) {
1✔
326
#ifdef HAVE_ICONV_H
327
  pr_trace_msg(trace_channel, 8, "%s encoding disabled", encoding);
1✔
328
  (void) encode_free();
1✔
329
  encoding = NULL;
1✔
330
#endif
331
}
1✔
332

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) {
5✔
338
#ifdef HAVE_ICONV_H
339
  int res;
340

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

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

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

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

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

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

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

371
    encoding = NULL;
1✔
372
    errno = xerrno;
1✔
373
  }
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) {
2✔
384
  return encoding_policy;
2✔
385
}
386

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

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

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);
6✔
400
  if (charset == NULL ||
12✔
401
      strlen(charset) == 0) {
6✔
402
    charset = "UTF-8";
×
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) {
6✔
414
      charset = "US-ASCII";
×
415
    }
416

417
    pr_trace_msg(trace_channel, 1,
6✔
418
      "converting %s to local character set '%s'", encoding, charset);
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;
6✔
428
}
429

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

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

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

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) {
11✔
451
#ifdef HAVE_ICONV_H
452
  int res;
453

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

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

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

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

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

478
  (void) encode_free();
9✔
479

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

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

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

493
    errno = xerrno;
2✔
494
  }
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) {
4✔
505
  if (codeset == NULL) {
4✔
506
    errno = EINVAL;
1✔
507
    return -1;
1✔
508
  }
509

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

515
  return FALSE;
1✔
516
}
517

518
int pr_encode_supports_telnet_iac(void) {
33✔
519
  return supports_telnet_iac;
33✔
520
}
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