1 /*
2 * ProFTPD - FTP server daemon
3 * Copyright (c) 2004-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, The ProFTPD Project team and other respective
20 * copyright holders give permission to link this program with OpenSSL, and
21 * distribute the resulting executable, without including the source code for
22 * OpenSSL in the source distribution.
23 */
24
25 /* Display of files */
26
27 #include "conf.h"
28
29 static int first_msg_sent = FALSE;
30 static const char *first_msg = NULL;
31 static const char *prev_msg = NULL;
32
33 /* Note: The size provided by pr_fs_getsize2() is in KB, not bytes. */
format_size_str(char * buf,size_t buflen,off_t size)34 static void format_size_str(char *buf, size_t buflen, off_t size) {
35 char *units[] = {"K", "M", "G", "T", "P", "E", "Z", "Y"};
36 unsigned int nunits = 8;
37 register unsigned int i = 0;
38 int res;
39
40 /* Determine the appropriate units label to use. Do not exceed the max
41 * possible unit support (yottabytes), by ensuring that i maxes out at
42 * index 7 (of 8 possible units).
43 */
44 while (size > 1024 &&
45 i < (nunits - 1)) {
46 pr_signals_handle();
47
48 size /= 1024;
49 i++;
50 }
51
52 /* Now, prepare the buffer. */
53 res = pr_snprintf(buf, buflen, "%.3" PR_LU "%sB", (pr_off_t) size, units[i]);
54 if (res > 2) {
55 /* Check for leading zeroes; it's an aethetic choice. */
56 if (buf[0] == '0' && buf[1] != '.') {
57 memmove(&buf[0], &buf[1], res-1);
58 buf[res-1] = '\0';
59 }
60 }
61 }
62
display_add_line(pool * p,const char * resp_code,const char * resp_msg)63 static int display_add_line(pool *p, const char *resp_code,
64 const char *resp_msg) {
65
66 /* Handle the case where the data to Display might contain only one line. */
67
68 if (first_msg_sent == FALSE &&
69 first_msg == NULL) {
70 first_msg = pstrdup(p, resp_msg);
71 return 0;
72 }
73
74 if (first_msg != NULL) {
75 pr_response_send_raw("%s-%s", resp_code, first_msg);
76 first_msg = NULL;
77 first_msg_sent = TRUE;
78
79 prev_msg = pstrdup(p, resp_msg);
80 return 0;
81 }
82
83 if (prev_msg != NULL) {
84 if (session.multiline_rfc2228) {
85 pr_response_send_raw("%s-%s", resp_code, prev_msg);
86
87 } else {
88 pr_response_send_raw(" %s", prev_msg);
89 }
90 }
91
92 prev_msg = pstrdup(p, resp_msg);
93 return 0;
94 }
95
display_flush_lines(pool * p,const char * resp_code,int flags)96 static int display_flush_lines(pool *p, const char *resp_code, int flags) {
97 if (first_msg != NULL) {
98 if (session.auth_mech != NULL) {
99 if (flags & PR_DISPLAY_FL_NO_EOM) {
100 pr_response_send_raw("%s-%s", resp_code, first_msg);
101
102 } else {
103 pr_response_send_raw("%s %s", resp_code, first_msg);
104 }
105
106 } else {
107 /* There is a special case if the client has not yet authenticated; it
108 * means we are handling a DisplayConnect file. The server will send
109 * a banner as well, so we need to treat this is the start of a multiline
110 * response.
111 */
112 pr_response_send_raw("%s-%s", resp_code, first_msg);
113 }
114
115 } else {
116 if (prev_msg) {
117 if (session.multiline_rfc2228) {
118 pr_response_send_raw("%s-%s", resp_code, prev_msg);
119
120 } else {
121 if (flags & PR_DISPLAY_FL_NO_EOM) {
122 pr_response_send_raw(" %s", prev_msg);
123
124 } else {
125 pr_response_send_raw("%s %s", resp_code, prev_msg);
126 }
127 }
128 }
129 }
130
131 /* Reset state for the next set of lines. */
132 first_msg_sent = FALSE;
133 first_msg = NULL;
134 prev_msg = NULL;
135
136 return 0;
137 }
138
display_fh(pr_fh_t * fh,const char * fs,const char * resp_code,int flags)139 static int display_fh(pr_fh_t *fh, const char *fs, const char *resp_code,
140 int flags) {
141 struct stat st;
142 char buf[PR_TUNABLE_BUFFER_SIZE] = {'\0'};
143 int len, res;
144 const unsigned int *current_clients = NULL;
145 const unsigned int *max_clients = NULL;
146 off_t fs_size = 0;
147 pool *p;
148 const void *v;
149 xaset_t *s;
150 config_rec *c = NULL;
151 const char *mg_time, *outs = NULL, *rfc1413_ident = NULL, *user;
152 const char *serverfqdn = main_server->ServerFQDN;
153 char mg_size[12] = {'\0'}, mg_size_units[12] = {'\0'},
154 mg_max[12] = "unlimited";
155 char total_files_in[12] = {'\0'}, total_files_out[12] = {'\0'},
156 total_files_xfer[12] = {'\0'};
157 char mg_class_limit[12] = {'\0'}, mg_cur[12] = {'\0'},
158 mg_xfer_bytes[12] = {'\0'}, mg_cur_class[12] = {'\0'};
159 char mg_xfer_units[12] = {'\0'};
160
161 /* Stat the opened file to determine the optimal buffer size for IO. */
162 memset(&st, 0, sizeof(st));
163 if (pr_fsio_fstat(fh, &st) == 0) {
164 fh->fh_iosz = st.st_blksize;
165 }
166
167 /* Note: The size provided by pr_fs_getsize() is in KB, not bytes. */
168 res = pr_fs_fgetsize(fh->fh_fd, &fs_size);
169 if (res < 0 &&
170 errno != ENOSYS) {
171 (void) pr_log_debug(DEBUG7, "error getting filesystem size for '%s': %s",
172 fh->fh_path, strerror(errno));
173 fs_size = 0;
174 }
175
176 pr_snprintf(mg_size, sizeof(mg_size), "%" PR_LU, (pr_off_t) fs_size);
177 format_size_str(mg_size_units, sizeof(mg_size_units), fs_size);
178
179 p = make_sub_pool(session.pool);
180 pr_pool_tag(p, "Display Pool");
181
182 s = (session.anon_config ? session.anon_config->subset : main_server->conf);
183
184 mg_time = pr_strtime3(p, time(NULL), FALSE);
185
186 max_clients = get_param_ptr(s, "MaxClients", FALSE);
187
188 v = pr_table_get(session.notes, "client-count", NULL);
189 if (v != NULL) {
190 current_clients = v;
191 }
192
193 pr_snprintf(mg_cur, sizeof(mg_cur), "%u",
194 current_clients ? *current_clients : 1);
195
196 if (session.conn_class != NULL &&
197 session.conn_class->cls_name != NULL) {
198 const unsigned int *class_clients = NULL;
199 config_rec *maxc = NULL;
200 unsigned int maxclients = 0;
201
202 v = pr_table_get(session.notes, "class-client-count", NULL);
203 if (v != NULL) {
204 class_clients = v;
205 }
206
207 pr_snprintf(mg_cur_class, sizeof(mg_cur_class), "%u",
208 class_clients ? *class_clients : 0);
209
210 /* For the %z variable, first we scan through the MaxClientsPerClass,
211 * and use the first applicable one. If none are found, look for
212 * any MaxClients set.
213 */
214
215 maxc = find_config(main_server->conf, CONF_PARAM, "MaxClientsPerClass",
216 FALSE);
217
218 while (maxc != NULL) {
219 pr_signals_handle();
220
221 if (strcmp(maxc->argv[0], session.conn_class->cls_name) != 0) {
222 maxc = find_config_next(maxc, maxc->next, CONF_PARAM,
223 "MaxClientsPerClass", FALSE);
224 continue;
225 }
226
227 maxclients = *((unsigned int *) maxc->argv[1]);
228 break;
229 }
230
231 if (maxclients == 0) {
232 maxc = find_config(main_server->conf, CONF_PARAM, "MaxClients", FALSE);
233 if (maxc != NULL) {
234 maxclients = *((unsigned int *) maxc->argv[0]);
235 }
236 }
237
238 pr_snprintf(mg_class_limit, sizeof(mg_class_limit), "%u", maxclients);
239
240 } else {
241 pr_snprintf(mg_class_limit, sizeof(mg_class_limit), "%u",
242 max_clients ? *max_clients : 0);
243 pr_snprintf(mg_cur_class, sizeof(mg_cur_class), "%u", 0);
244 }
245
246 pr_snprintf(mg_xfer_bytes, sizeof(mg_xfer_bytes), "%" PR_LU,
247 (pr_off_t) session.total_bytes >> 10);
248 pr_snprintf(mg_xfer_units, sizeof(mg_xfer_units), "%" PR_LU "B",
249 (pr_off_t) session.total_bytes);
250
251 if (session.total_bytes >= 10240) {
252 pr_snprintf(mg_xfer_units, sizeof(mg_xfer_units), "%" PR_LU "kB",
253 (pr_off_t) session.total_bytes >> 10);
254
255 } else if ((session.total_bytes >> 10) >= 10240) {
256 pr_snprintf(mg_xfer_units, sizeof(mg_xfer_units), "%" PR_LU "MB",
257 (pr_off_t) session.total_bytes >> 20);
258
259 } else if ((session.total_bytes >> 20) >= 10240) {
260 pr_snprintf(mg_xfer_units, sizeof(mg_xfer_units), "%" PR_LU "GB",
261 (pr_off_t) session.total_bytes >> 30);
262 }
263
264 pr_snprintf(mg_max, sizeof(mg_max), "%u", max_clients ? *max_clients : 0);
265
266 user = pr_table_get(session.notes, "mod_auth.orig-user", NULL);
267 if (user == NULL) {
268 user = "";
269 }
270
271 c = find_config(main_server->conf, CONF_PARAM, "MasqueradeAddress", FALSE);
272 if (c != NULL) {
273 pr_netaddr_t *masq_addr = NULL;
274
275 if (c->argv[0] != NULL) {
276 masq_addr = c->argv[0];
277 }
278
279 if (masq_addr != NULL) {
280 serverfqdn = pr_netaddr_get_dnsstr(masq_addr);
281 }
282 }
283
284 /* "Stringify" the file number for this session. */
285 pr_snprintf(total_files_in, sizeof(total_files_in), "%u",
286 session.total_files_in);
287 total_files_in[sizeof(total_files_in)-1] = '\0';
288
289 pr_snprintf(total_files_out, sizeof(total_files_out), "%u",
290 session.total_files_out);
291 total_files_out[sizeof(total_files_out)-1] = '\0';
292
293 pr_snprintf(total_files_xfer, sizeof(total_files_xfer), "%u",
294 session.total_files_xfer);
295 total_files_xfer[sizeof(total_files_xfer)-1] = '\0';
296
297 rfc1413_ident = pr_table_get(session.notes, "mod_ident.rfc1413-ident", NULL);
298 if (rfc1413_ident == NULL) {
299 rfc1413_ident = "UNKNOWN";
300 }
301
302 while (pr_fsio_gets(buf, sizeof(buf), fh) != NULL) {
303 char *tmp;
304
305 pr_signals_handle();
306
307 buf[sizeof(buf)-1] = '\0';
308 len = strlen(buf);
309
310 while (len &&
311 (buf[len-1] == '\r' || buf[len-1] == '\n')) {
312 buf[len-1] = '\0';
313 len--;
314 }
315
316 /* Check for any Variable-type strings. */
317 tmp = strstr(buf, "%{");
318 while (tmp) {
319 char *key, *tmp2;
320 const char *val;
321
322 pr_signals_handle();
323
324 tmp2 = strchr(tmp, '}');
325 if (!tmp2) {
326 tmp = strstr(tmp + 1, "%{");
327 continue;
328 }
329
330 key = pstrndup(p, tmp, tmp2 - tmp + 1);
331
332 /* There are a couple of special-case keys to watch for:
333 *
334 * env:$var
335 * time:$fmt
336 *
337 * The Var API does not easily support returning values for keys
338 * where part of the value depends on part of the key. That's why
339 * these keys are handled here, instead of in pr_var_get().
340 */
341
342 if (strncmp(key, "%{time:", 7) == 0) {
343 char time_str[128], *fmt;
344 time_t now;
345 struct tm *tm;
346
347 fmt = pstrndup(p, key + 7, strlen(key) - 8);
348
349 time(&now);
350 memset(time_str, 0, sizeof(time_str));
351
352 tm = pr_localtime(p, &now);
353 if (tm != NULL) {
354 strftime(time_str, sizeof(time_str), fmt, tm);
355 }
356
357 val = pstrdup(p, time_str);
358
359 } else if (strncmp(key, "%{env:", 6) == 0) {
360 char *env_var;
361
362 env_var = pstrndup(p, key + 6, strlen(key) - 7);
363 val = pr_env_get(p, env_var);
364 if (val == NULL) {
365 pr_trace_msg("var", 4,
366 "no value set for environment variable '%s', using \"(none)\"",
367 env_var);
368 val = "(none)";
369 }
370
371 } else {
372 val = pr_var_get(key);
373 if (val == NULL) {
374 pr_trace_msg("var", 4,
375 "no value set for name '%s' [%s], using \"(none)\"", key,
376 strerror(errno));
377 val = "(none)";
378 }
379 }
380
381 outs = sreplace(p, buf, key, val, NULL);
382 sstrncpy(buf, outs, sizeof(buf));
383
384 tmp = strstr(outs, "%{");
385 }
386
387 outs = sreplace(p, buf,
388 "%C", (session.cwd[0] ? session.cwd : "(none)"),
389 "%E", main_server->ServerAdmin,
390 "%F", mg_size,
391 "%f", mg_size_units,
392 "%i", total_files_in,
393 "%K", mg_xfer_bytes,
394 "%k", mg_xfer_units,
395 "%L", serverfqdn,
396 "%M", mg_max,
397 "%N", mg_cur,
398 "%o", total_files_out,
399 "%R", (session.c && session.c->remote_name ?
400 session.c->remote_name : "(unknown)"),
401 "%T", mg_time,
402 "%t", total_files_xfer,
403 "%U", user,
404 "%u", rfc1413_ident,
405 "%V", main_server->ServerName,
406 "%x", session.conn_class ? session.conn_class->cls_name : "(unknown)",
407 "%y", mg_cur_class,
408 "%z", mg_class_limit,
409 NULL);
410
411 sstrncpy(buf, outs, sizeof(buf));
412
413 if (flags & PR_DISPLAY_FL_SEND_NOW) {
414 /* Normally we use pr_response_add(), and let the response code
415 * automatically handle all of the multiline response formatting.
416 * However, some of the Display files are at times waiting for the
417 * response chains to be flushed, which won't work (i.e. DisplayConnect
418 * and DisplayQuit).
419 */
420 display_add_line(p, resp_code, outs);
421
422 } else {
423 pr_response_add(resp_code, "%s", outs);
424 }
425 }
426
427 if (flags & PR_DISPLAY_FL_SEND_NOW) {
428 display_flush_lines(p, resp_code, flags);
429 }
430
431 destroy_pool(p);
432 return 0;
433 }
434
pr_display_fh(pr_fh_t * fh,const char * fs,const char * resp_code,int flags)435 int pr_display_fh(pr_fh_t *fh, const char *fs, const char *resp_code,
436 int flags) {
437 if (fh == NULL ||
438 resp_code == NULL) {
439 errno = EINVAL;
440 return -1;
441 }
442
443 return display_fh(fh, fs, resp_code, flags);
444 }
445
pr_display_file(const char * path,const char * fs,const char * resp_code,int flags)446 int pr_display_file(const char *path, const char *fs, const char *resp_code,
447 int flags) {
448 pr_fh_t *fh = NULL;
449 int res, xerrno;
450 struct stat st;
451
452 if (path == NULL ||
453 resp_code == NULL) {
454 errno = EINVAL;
455 return -1;
456 }
457
458 fh = pr_fsio_open_canon(path, O_RDONLY);
459 if (fh == NULL) {
460 return -1;
461 }
462
463 res = pr_fsio_fstat(fh, &st);
464 if (res < 0) {
465 xerrno = errno;
466
467 pr_fsio_close(fh);
468
469 errno = xerrno;
470 return -1;
471 }
472
473 if (S_ISDIR(st.st_mode)) {
474 pr_fsio_close(fh);
475 errno = EISDIR;
476 return -1;
477 }
478
479 res = display_fh(fh, fs, resp_code, flags);
480 xerrno = errno;
481
482 pr_fsio_close(fh);
483
484 errno = xerrno;
485 return res;
486 }
487