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

proftpd / proftpd / 26127302613

19 May 2026 09:51PM UTC coverage: 93.024% (+0.4%) from 92.635%
26127302613

push

github

51329 of 55178 relevant lines covered (93.02%)

215.14 hits per line

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

91.67
/src/response.c
1
/*
2
 * ProFTPD - FTP server daemon
3
 * Copyright (c) 2001-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, Public Flood Software/MacGyver aka Habeeb J. Dihu
19
 * and other respective copyright holders give permission to link this program
20
 * with OpenSSL, and distribute the resulting executable, without including
21
 * the source code for OpenSSL in the source distribution.
22
 */
23

24
/* Command response routines. */
25

26
#include "conf.h"
27

28
pr_response_t *resp_list = NULL, *resp_err_list = NULL;
29

30
static int resp_blocked = FALSE;
31
static pool *resp_pool = NULL;
32

33
static char resp_buf[PR_RESPONSE_BUFFER_SIZE] = {'\0'};
34

35
static const char *resp_last_response_code = NULL;
36
static const char *resp_last_response_msg = NULL;
37

38
static char *(*resp_handler_cb)(pool *, const char *, ...) = NULL;
39

40
static const char *trace_channel = "response";
41

42
#define RESPONSE_WRITE_NUM_STR(strm, fmt, numeric, msg) \
43
  pr_trace_msg(trace_channel, 1, (fmt), (numeric), (msg)); \
44
  if (resp_handler_cb) \
45
    pr_netio_printf((strm), "%s", resp_handler_cb(resp_pool, (fmt), (numeric), \
46
      (msg))); \
47
  else \
48
    pr_netio_printf((strm), (fmt), (numeric), (msg));
49

50
#define RESPONSE_WRITE_STR(strm, fmt, msg) \
51
  pr_trace_msg(trace_channel, 1, (fmt), (msg)); \
52
  if (resp_handler_cb) \
53
    pr_netio_printf((strm), "%s", resp_handler_cb(resp_pool, (fmt), (msg))); \
54
  else \
55
    pr_netio_printf((strm), (fmt), (msg));
56

57
pool *pr_response_get_pool(void) {
58
  return resp_pool;
9✔
59
}
9✔
60

61
static void reset_last_response(void) {
62
  resp_last_response_code = NULL;
47✔
63
  resp_last_response_msg = NULL;
47✔
64
}
47✔
65

66
void pr_response_set_pool(pool *p) {
67
  resp_pool = p;
95✔
68

95✔
69
  if (p == NULL) {
70
    reset_last_response();
95✔
71
    return;
30✔
72
  }
30✔
73

74
  /* Copy any old "last" values out of the new pool. */
75
  if (resp_last_response_code != NULL) {
76
    const char *ptr;
65✔
77

14✔
78
    ptr = resp_last_response_code;
79
    resp_last_response_code = pstrdup(p, ptr);
14✔
80
  }
14✔
81

82
  if (resp_last_response_msg != NULL) {
83
    const char *ptr;
65✔
84

14✔
85
    ptr = resp_last_response_msg;
86
    resp_last_response_msg = pstrdup(p, ptr);
14✔
87
  }
14✔
88
}
89

90
int pr_response_get_last(pool *p, const char **response_code,
91
    const char **response_msg) {
32✔
92
  if (p == NULL) {
93
    errno = EINVAL;
32✔
94
    return -1;
1✔
95
  }
1✔
96

97
  if (response_code == NULL &&
98
      response_msg == NULL) {
31✔
99
    errno = EINVAL;
31✔
100
    return -1;
1✔
101
  }
1✔
102

103
  if (response_code != NULL) {
104
    *response_code = pstrdup(p, resp_last_response_code);
30✔
105
  }
26✔
106

107
  if (response_msg != NULL) {
108
    *response_msg = pstrdup(p, resp_last_response_msg);
30✔
109
  }
25✔
110

111
  return 0;
112
}
113

114
void pr_response_register_handler(char *(*handler_cb)(pool *, const char *,
115
    ...)) {
22✔
116
  resp_handler_cb = handler_cb;
117
}
22✔
118

22✔
119
int pr_response_block(int do_block) {
120
  if (do_block == TRUE ||
5✔
121
      do_block == FALSE) {
5✔
122
    resp_blocked = do_block;
123
    return 0;
4✔
124
  }
4✔
125

126
  errno = EINVAL;
127
  return -1;
1✔
128
}
1✔
129

130
int pr_response_blocked(void) {
131
  return resp_blocked;
2✔
132
}
2✔
133

134
void pr_response_clear(pr_response_t **head) {
135
  reset_last_response();
17✔
136

17✔
137
  if (head != NULL) {
138
    *head = NULL;
12✔
139
  }
11✔
140
}
141

17✔
142
void pr_response_flush(pr_response_t **head) {
143
  unsigned char ml = FALSE;
9✔
144
  const char *last_numeric = NULL;
9✔
145
  pr_response_t *resp = NULL;
9✔
146

9✔
147
  if (head == NULL ||
148
      *head == NULL) {
9✔
149
    return;
8✔
150
  }
151

152
  if (resp_blocked) {
153
    pr_trace_msg(trace_channel, 19,
5✔
154
      "responses blocked, not flushing response chain");
×
155
    pr_response_clear(head);
156
    return;
×
157
  }
×
158

159
  if (session.c == NULL) {
160
    /* Not sure what happened to the control connection, but since it's gone,
5✔
161
     * there's no need to flush any messages.
162
     */
163
    pr_response_clear(head);
164
    return;
×
165
  }
×
166

167
  for (resp = *head; resp; resp = resp->next) {
168
    if (ml) {
12✔
169
      /* Look for end of multiline */
7✔
170
      if (resp->next == NULL ||
171
          (resp->num != NULL &&
2✔
172
           strcmp(resp->num, last_numeric) != 0)) {
1✔
173
        RESPONSE_WRITE_NUM_STR(session.c->outstrm, "%s %s\r\n", last_numeric,
×
174
          resp->msg)
1✔
175
        ml = FALSE;
176

177
      } else {
178
        /* RFC2228's multiline responses are required for protected sessions. */
179
        if (session.sp_flags) {
180
          RESPONSE_WRITE_NUM_STR(session.c->outstrm, "%s-%s\r\n", last_numeric,
1✔
181
            resp->msg)
×
182

183
        } else {
184
          RESPONSE_WRITE_STR(session.c->outstrm, " %s\r\n" , resp->msg)
185
        }
1✔
186
      }
187

188
    } else {
189
      /* Look for start of multiline */
190
      if (resp->next &&
191
          (resp->next->num == NULL ||
5✔
192
           strcmp(resp->num, resp->next->num) == 0)) {
1✔
193
        RESPONSE_WRITE_NUM_STR(session.c->outstrm, "%s-%s\r\n", resp->num,
×
194
          resp->msg)
1✔
195
        ml = TRUE;
196
        last_numeric = resp->num;
1✔
197

1✔
198
      } else {
199
        RESPONSE_WRITE_NUM_STR(session.c->outstrm, "%s %s\r\n", resp->num,
200
          resp->msg)
4✔
201
      }
202
    }
203
  }
204

205
  pr_response_clear(head);
206
}
5✔
207

208
void pr_response_add_err(const char *numeric, const char *fmt, ...) {
209
  pr_response_t *resp = NULL, **head = NULL;
15✔
210
  int res;
15✔
211
  va_list msg;
15✔
212

15✔
213
  if (fmt == NULL) {
214
    return;
15✔
215
  }
1✔
216

217
  if (resp_pool == NULL) {
218
    pr_trace_msg(trace_channel, 1,
14✔
219
      "no response pool set, ignoring added %s error response", numeric);
×
220
    return;
221
  }
×
222

223
  va_start(msg, fmt);
224
  res = pr_vsnprintf(resp_buf, sizeof(resp_buf), fmt, msg);
14✔
225
  va_end(msg);
14✔
226

14✔
227
  resp_buf[sizeof(resp_buf) - 1] = '\0';
228

14✔
229
  resp = (pr_response_t *) pcalloc(resp_pool, sizeof(pr_response_t));
230
  resp->num = (numeric ? pstrdup(resp_pool, numeric) : NULL);
14✔
231
  resp->msg = pstrndup(resp_pool, resp_buf, res);
14✔
232

14✔
233
  if (numeric != R_DUP) {
234
    resp_last_response_code = pstrdup(resp_pool, resp->num);
14✔
235
  }
14✔
236
  resp_last_response_msg = pstrndup(resp_pool, resp->msg, res);
237

14✔
238
  pr_trace_msg(trace_channel, 7, "error response added to pending list: %s %s",
239
    resp->num ? resp->num : "(null)", resp->msg);
14✔
240

14✔
241
  if (numeric != NULL) {
242
    pr_response_t *iter;
14✔
243

14✔
244
    /* Handle the case where the first messages in the list may have R_DUP
245
     * for the numeric response code (i.e. NULL).  To do this, we scan the
246
     * list for the first non-null response code, and use that for any R_DUP
247
     * messages.
248
     */
249
    for (iter = resp_err_list; iter; iter = iter->next) {
250
      if (iter->num == NULL) {
24✔
251
        iter->num = resp->num;
10✔
252
      }
×
253
    }
254
  }
255

256
  for (head = &resp_err_list;
257
    *head &&
14✔
258
    (!numeric || !(*head)->num || strcmp((*head)->num, numeric) <= 0) &&
10✔
259
    !(numeric && !(*head)->num && head == &resp_err_list);
34✔
260
    head = &(*head)->next) {
10✔
261
  }
10✔
262

10✔
263
  resp->next = *head;
264
  *head = resp;
14✔
265
}
14✔
266

267
void pr_response_add(const char *numeric, const char *fmt, ...) {
268
  pr_response_t *resp = NULL, **head = NULL;
35✔
269
  int res;
35✔
270
  va_list msg;
35✔
271

35✔
272
  if (fmt == NULL) {
273
    return;
35✔
274
  }
3✔
275

276
  if (resp_pool == NULL) {
277
    pr_trace_msg(trace_channel, 1,
34✔
278
      "no response pool set, ignoring added %s response", numeric);
2✔
279
    return;
280
  }
2✔
281

282
  va_start(msg, fmt);
283
  res = pr_vsnprintf(resp_buf, sizeof(resp_buf), fmt, msg);
32✔
284
  va_end(msg);
32✔
285

32✔
286
  resp_buf[sizeof(resp_buf) - 1] = '\0';
287

32✔
288
  resp = (pr_response_t *) pcalloc(resp_pool, sizeof(pr_response_t));
289
  resp->num = (numeric ? pstrdup(resp_pool, numeric) : NULL);
32✔
290
  resp->msg = pstrndup(resp_pool, resp_buf, res);
32✔
291

32✔
292
  if (numeric != R_DUP) {
293
    resp_last_response_code = pstrdup(resp_pool, resp->num);
32✔
294
  }
26✔
295
  resp_last_response_msg = pstrndup(resp_pool, resp->msg, res);
296

32✔
297
  pr_trace_msg(trace_channel, 7, "response added to pending list: %s %s",
298
    resp->num ? resp->num : "(null)", resp->msg);
32✔
299

32✔
300
  if (numeric != NULL) {
301
    pr_response_t *iter;
32✔
302

26✔
303
    /* Handle the case where the first messages in the list may have R_DUP
304
     * for the numeric response code (i.e. NULL).  To do this, we scan the
305
     * list for the first non-null response code, and use that for any R_DUP
306
     * messages.
307
     */
308
    for (iter = resp_list; iter; iter = iter->next) {
309
      if (iter->num == NULL) {
46✔
310
        iter->num = resp->num;
20✔
311
      }
3✔
312
    }
313
  }
314

315
  for (head = &resp_list;
316
    *head &&
32✔
317
    (!numeric || !(*head)->num || strcmp((*head)->num, numeric) <= 0) &&
28✔
318
    !(numeric && !(*head)->num && head == &resp_list);
88✔
319
    head = &(*head)->next) {
20✔
320
  }
28✔
321

28✔
322
  resp->next = *head;
323
  *head = resp;
32✔
324
}
32✔
325

326
void pr_response_send_async(const char *resp_numeric, const char *fmt, ...) {
327
  int res;
5✔
328
  char buf[PR_TUNABLE_BUFFER_SIZE] = {'\0'};
5✔
329
  va_list msg;
5✔
330
  size_t len, max_len;
5✔
331

5✔
332
  if (resp_blocked) {
333
    pr_trace_msg(trace_channel, 19,
5✔
334
      "responses blocked, not sending async response");
×
335
    return;
336
  }
4✔
337

338
  if (session.c == NULL) {
339
    /* Not sure what happened to the control connection, but since it's gone,
5✔
340
     * there's no need to flush any messages.
341
     */
342
    return;
343
  }
344

345
  sstrncpy(buf, resp_numeric, sizeof(buf));
346

1✔
347
  len = strlen(resp_numeric);
348
  sstrcat(buf + len, " ", sizeof(buf) - len);
1✔
349

1✔
350
  max_len = sizeof(buf) - (len + 1);
351

1✔
352
  va_start(msg, fmt);
353
  res = pr_vsnprintf(buf + len + 1, max_len, fmt, msg);
1✔
354
  va_end(msg);
1✔
355

1✔
356
  buf[sizeof(buf) - 1] = '\0';
357

1✔
358
  resp_last_response_code = pstrdup(resp_pool, resp_numeric);
359
  resp_last_response_msg = pstrdup(resp_pool, buf + len + 1);
1✔
360

1✔
361
  sstrcat(buf + res, "\r\n", max_len - res);
362

1✔
363
  pr_trace_msg(trace_channel, 1, "async: %s", buf);
364
  if (resp_handler_cb != NULL) {
1✔
365
    pr_netio_printf_async(session.c->outstrm, "%s",
1✔
366
      resp_handler_cb(resp_pool, "%s", buf));
1✔
367

368
  } else {
369
    pr_netio_printf_async(session.c->outstrm, "%s", buf);
370
  }
×
371
}
372

373
void pr_response_send(const char *resp_numeric, const char *fmt, ...) {
374
  va_list msg;
8✔
375

8✔
376
  if (resp_blocked) {
377
    pr_trace_msg(trace_channel, 19, "responses blocked, not sending response");
8✔
378
    return;
×
379
  }
×
380

381
  if (session.c == NULL) {
382
    /* Not sure what happened to the control connection, but since it's gone,
8✔
383
     * there's no need to flush any messages.
384
     */
385
    return;
386
  }
387

388
  va_start(msg, fmt);
389
  pr_vsnprintf(resp_buf, sizeof(resp_buf), fmt, msg);
8✔
390
  va_end(msg);
8✔
391

8✔
392
  resp_buf[sizeof(resp_buf) - 1] = '\0';
393

8✔
394
  resp_last_response_code = pstrdup(resp_pool, resp_numeric);
395
  resp_last_response_msg = pstrdup(resp_pool, resp_buf);
8✔
396

8✔
397
  RESPONSE_WRITE_NUM_STR(session.c->outstrm, "%s %s\r\n", resp_numeric,
398
    resp_buf)
8✔
399
}
400

401
void pr_response_send_raw(const char *fmt, ...) {
402
  va_list msg;
25✔
403

25✔
404
  if (resp_blocked) {
405
    pr_trace_msg(trace_channel, 19,
25✔
406
      "responses blocked, not sending raw response");
×
407
    return;
408
  }
24✔
409

410
  if (session.c == NULL) {
411
    /* Not sure what happened to the control connection, but since it's gone,
25✔
412
     * there's no need to flush any messages.
413
     */
414
    return;
415
  }
416

417
  va_start(msg, fmt);
418
  pr_vsnprintf(resp_buf, sizeof(resp_buf), fmt, msg);
1✔
419
  va_end(msg);
1✔
420

1✔
421
  resp_buf[sizeof(resp_buf) - 1] = '\0';
422

1✔
423
  RESPONSE_WRITE_STR(session.c->outstrm, "%s\r\n", resp_buf)
424
}
1✔
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