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

proftpd / proftpd / 14526507026

17 Apr 2025 11:25PM UTC coverage: 93.03% (+0.4%) from 92.667%
14526507026

push

github

51358 of 55206 relevant lines covered (93.03%)

234.02 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-2024 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, Public Flood Software/MacGyver aka Habeeb J. Dihu
20
 * and other respective copyright holders give permission to link this program
21
 * with OpenSSL, and distribute the resulting executable, without including
22
 * the source code for OpenSSL in the source distribution.
23
 */
24

25
/* Command response routines. */
26

27
#include "conf.h"
28

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

112
  return 0;
113
}
114

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

206
  pr_response_clear(head);
5✔
207
}
208

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

346
  sstrncpy(buf, resp_numeric, sizeof(buf));
1✔
347

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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