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