1 /*
2 * ProFTPD - FTP server daemon
3 * Copyright (c) 2001-2020 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 #define RESPONSE_WRITE_STR_ASYNC(strm, fmt, msg) \
59 pr_trace_msg(trace_channel, 1, pstrcat(session.pool, "async: ", (fmt), NULL), \
60 (msg)); \
61 if (resp_handler_cb) \
62 pr_netio_printf_async((strm), "%s", resp_handler_cb(resp_pool, (fmt), \
63 (msg))); \
64 else \
65 pr_netio_printf_async((strm), (fmt), (msg));
66
pr_response_get_pool(void)67 pool *pr_response_get_pool(void) {
68 return resp_pool;
69 }
70
reset_last_response(void)71 static void reset_last_response(void) {
72 resp_last_response_code = NULL;
73 resp_last_response_msg = NULL;
74 }
75
pr_response_set_pool(pool * p)76 void pr_response_set_pool(pool *p) {
77 resp_pool = p;
78
79 if (p == NULL) {
80 reset_last_response();
81 return;
82 }
83
84 /* Copy any old "last" values out of the new pool. */
85 if (resp_last_response_code != NULL) {
86 const char *ptr;
87
88 ptr = resp_last_response_code;
89 resp_last_response_code = pstrdup(p, ptr);
90 }
91
92 if (resp_last_response_msg != NULL) {
93 const char *ptr;
94
95 ptr = resp_last_response_msg;
96 resp_last_response_msg = pstrdup(p, ptr);
97 }
98 }
99
pr_response_get_last(pool * p,const char ** response_code,const char ** response_msg)100 int pr_response_get_last(pool *p, const char **response_code,
101 const char **response_msg) {
102 if (p == NULL) {
103 errno = EINVAL;
104 return -1;
105 }
106
107 if (response_code == NULL &&
108 response_msg == NULL) {
109 errno = EINVAL;
110 return -1;
111 }
112
113 if (response_code != NULL) {
114 *response_code = pstrdup(p, resp_last_response_code);
115 }
116
117 if (response_msg != NULL) {
118 *response_msg = pstrdup(p, resp_last_response_msg);
119 }
120
121 return 0;
122 }
123
pr_response_register_handler(char * (* handler_cb)(pool *,const char *,...))124 void pr_response_register_handler(char *(*handler_cb)(pool *, const char *,
125 ...)) {
126 resp_handler_cb = handler_cb;
127 }
128
pr_response_block(int bool)129 int pr_response_block(int bool) {
130 if (bool == TRUE ||
131 bool == FALSE) {
132 resp_blocked = bool;
133 return 0;
134 }
135
136 errno = EINVAL;
137 return -1;
138 }
139
pr_response_blocked(void)140 int pr_response_blocked(void) {
141 return resp_blocked;
142 }
143
pr_response_clear(pr_response_t ** head)144 void pr_response_clear(pr_response_t **head) {
145 reset_last_response();
146
147 if (head != NULL) {
148 *head = NULL;
149 }
150 }
151
pr_response_flush(pr_response_t ** head)152 void pr_response_flush(pr_response_t **head) {
153 unsigned char ml = FALSE;
154 const char *last_numeric = NULL;
155 pr_response_t *resp = NULL;
156
157 if (head == NULL ||
158 *head == NULL) {
159 return;
160 }
161
162 if (resp_blocked) {
163 pr_trace_msg(trace_channel, 19,
164 "responses blocked, not flushing response chain");
165 pr_response_clear(head);
166 return;
167 }
168
169 if (session.c == NULL) {
170 /* Not sure what happened to the control connection, but since it's gone,
171 * there's no need to flush any messages.
172 */
173 pr_response_clear(head);
174 return;
175 }
176
177 for (resp = *head; resp; resp = resp->next) {
178 if (ml) {
179 /* Look for end of multiline */
180 if (resp->next == NULL ||
181 (resp->num != NULL &&
182 strcmp(resp->num, last_numeric) != 0)) {
183 RESPONSE_WRITE_NUM_STR(session.c->outstrm, "%s %s\r\n", last_numeric,
184 resp->msg)
185 ml = FALSE;
186
187 } else {
188 /* RFC2228's multiline responses are required for protected sessions. */
189 if (session.multiline_rfc2228 || session.sp_flags) {
190 RESPONSE_WRITE_NUM_STR(session.c->outstrm, "%s-%s\r\n", last_numeric,
191 resp->msg)
192
193 } else {
194 RESPONSE_WRITE_STR(session.c->outstrm, " %s\r\n" , resp->msg)
195 }
196 }
197
198 } else {
199 /* Look for start of multiline */
200 if (resp->next &&
201 (resp->next->num == NULL ||
202 strcmp(resp->num, resp->next->num) == 0)) {
203 RESPONSE_WRITE_NUM_STR(session.c->outstrm, "%s-%s\r\n", resp->num,
204 resp->msg)
205 ml = TRUE;
206 last_numeric = resp->num;
207
208 } else {
209 RESPONSE_WRITE_NUM_STR(session.c->outstrm, "%s %s\r\n", resp->num,
210 resp->msg)
211 }
212 }
213 }
214
215 pr_response_clear(head);
216 }
217
pr_response_add_err(const char * numeric,const char * fmt,...)218 void pr_response_add_err(const char *numeric, const char *fmt, ...) {
219 pr_response_t *resp = NULL, **head = NULL;
220 int res;
221 va_list msg;
222
223 if (fmt == NULL) {
224 return;
225 }
226
227 if (resp_pool == NULL) {
228 pr_trace_msg(trace_channel, 1,
229 "no response pool set, ignoring added %s error response", numeric);
230 return;
231 }
232
233 va_start(msg, fmt);
234 res = pr_vsnprintf(resp_buf, sizeof(resp_buf), fmt, msg);
235 va_end(msg);
236
237 resp_buf[sizeof(resp_buf) - 1] = '\0';
238
239 resp = (pr_response_t *) pcalloc(resp_pool, sizeof(pr_response_t));
240 resp->num = (numeric ? pstrdup(resp_pool, numeric) : NULL);
241 resp->msg = pstrndup(resp_pool, resp_buf, res);
242
243 if (numeric != R_DUP) {
244 resp_last_response_code = pstrdup(resp_pool, resp->num);
245 }
246 resp_last_response_msg = pstrndup(resp_pool, resp->msg, res);
247
248 pr_trace_msg(trace_channel, 7, "error response added to pending list: %s %s",
249 resp->num ? resp->num : "(null)", resp->msg);
250
251 if (numeric != NULL) {
252 pr_response_t *iter;
253
254 /* Handle the case where the first messages in the list may have R_DUP
255 * for the numeric response code (i.e. NULL). To do this, we scan the
256 * list for the first non-null response code, and use that for any R_DUP
257 * messages.
258 */
259 for (iter = resp_err_list; iter; iter = iter->next) {
260 if (iter->num == NULL) {
261 iter->num = resp->num;
262 }
263 }
264 }
265
266 for (head = &resp_err_list;
267 *head &&
268 (!numeric || !(*head)->num || strcmp((*head)->num, numeric) <= 0) &&
269 !(numeric && !(*head)->num && head == &resp_err_list);
270
271 head = &(*head)->next);
272
273 resp->next = *head;
274 *head = resp;
275 }
276
pr_response_add(const char * numeric,const char * fmt,...)277 void pr_response_add(const char *numeric, const char *fmt, ...) {
278 pr_response_t *resp = NULL, **head = NULL;
279 int res;
280 va_list msg;
281
282 if (fmt == NULL) {
283 return;
284 }
285
286 if (resp_pool == NULL) {
287 pr_trace_msg(trace_channel, 1,
288 "no response pool set, ignoring added %s response", numeric);
289 return;
290 }
291
292 va_start(msg, fmt);
293 res = pr_vsnprintf(resp_buf, sizeof(resp_buf), fmt, msg);
294 va_end(msg);
295
296 resp_buf[sizeof(resp_buf) - 1] = '\0';
297
298 resp = (pr_response_t *) pcalloc(resp_pool, sizeof(pr_response_t));
299 resp->num = (numeric ? pstrdup(resp_pool, numeric) : NULL);
300 resp->msg = pstrndup(resp_pool, resp_buf, res);
301
302 if (numeric != R_DUP) {
303 resp_last_response_code = pstrdup(resp_pool, resp->num);
304 }
305 resp_last_response_msg = pstrndup(resp_pool, resp->msg, res);
306
307 pr_trace_msg(trace_channel, 7, "response added to pending list: %s %s",
308 resp->num ? resp->num : "(null)", resp->msg);
309
310 if (numeric != NULL) {
311 pr_response_t *iter;
312
313 /* Handle the case where the first messages in the list may have R_DUP
314 * for the numeric response code (i.e. NULL). To do this, we scan the
315 * list for the first non-null response code, and use that for any R_DUP
316 * messages.
317 */
318 for (iter = resp_list; iter; iter = iter->next) {
319 if (iter->num == NULL) {
320 iter->num = resp->num;
321 }
322 }
323 }
324
325 for (head = &resp_list;
326 *head &&
327 (!numeric || !(*head)->num || strcmp((*head)->num, numeric) <= 0) &&
328 !(numeric && !(*head)->num && head == &resp_list);
329 head = &(*head)->next);
330
331 resp->next = *head;
332 *head = resp;
333 }
334
pr_response_send_async(const char * resp_numeric,const char * fmt,...)335 void pr_response_send_async(const char *resp_numeric, const char *fmt, ...) {
336 int res;
337 char buf[PR_TUNABLE_BUFFER_SIZE] = {'\0'};
338 va_list msg;
339 size_t len, max_len;
340
341 if (resp_blocked) {
342 pr_trace_msg(trace_channel, 19,
343 "responses blocked, not sending async response");
344 return;
345 }
346
347 if (session.c == NULL) {
348 /* Not sure what happened to the control connection, but since it's gone,
349 * there's no need to flush any messages.
350 */
351 return;
352 }
353
354 sstrncpy(buf, resp_numeric, sizeof(buf));
355
356 len = strlen(resp_numeric);
357 sstrcat(buf + len, " ", sizeof(buf) - len);
358
359 max_len = sizeof(buf) - len;
360
361 va_start(msg, fmt);
362 res = pr_vsnprintf(buf + len + 1, max_len, fmt, msg);
363 va_end(msg);
364
365 buf[sizeof(buf) - 1] = '\0';
366
367 resp_last_response_code = pstrdup(resp_pool, resp_numeric);
368 resp_last_response_msg = pstrdup(resp_pool, buf + len + 1);
369
370 sstrcat(buf + res, "\r\n", sizeof(buf));
371 RESPONSE_WRITE_STR_ASYNC(session.c->outstrm, "%s", buf)
372 }
373
pr_response_send(const char * resp_numeric,const char * fmt,...)374 void pr_response_send(const char *resp_numeric, const char *fmt, ...) {
375 va_list msg;
376
377 if (resp_blocked) {
378 pr_trace_msg(trace_channel, 19, "responses blocked, not sending response");
379 return;
380 }
381
382 if (session.c == NULL) {
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);
390 pr_vsnprintf(resp_buf, sizeof(resp_buf), fmt, msg);
391 va_end(msg);
392
393 resp_buf[sizeof(resp_buf) - 1] = '\0';
394
395 resp_last_response_code = pstrdup(resp_pool, resp_numeric);
396 resp_last_response_msg = pstrdup(resp_pool, resp_buf);
397
398 RESPONSE_WRITE_NUM_STR(session.c->outstrm, "%s %s\r\n", resp_numeric,
399 resp_buf)
400 }
401
pr_response_send_raw(const char * fmt,...)402 void pr_response_send_raw(const char *fmt, ...) {
403 va_list msg;
404
405 if (resp_blocked) {
406 pr_trace_msg(trace_channel, 19,
407 "responses blocked, not sending raw response");
408 return;
409 }
410
411 if (session.c == NULL) {
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);
419 pr_vsnprintf(resp_buf, sizeof(resp_buf), fmt, msg);
420 va_end(msg);
421
422 resp_buf[sizeof(resp_buf) - 1] = '\0';
423
424 RESPONSE_WRITE_STR(session.c->outstrm, "%s\r\n", resp_buf)
425 }
426