1 /*
2  * ProFTPD - mod_sftp Display files
3  * Copyright (c) 2010-2020 TJ Saunders
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, TJ Saunders and other respective copyright holders
20  * give permission to link this program with OpenSSL, and distribute the
21  * resulting executable, without including the source code for OpenSSL in the
22  * source distribution.
23  */
24 
25 /* Display of files */
26 
27 #include "mod_sftp.h"
28 #include "display.h"
29 #include "packet.h"
30 #include "msg.h"
31 
32 /* Note: The size provided by pr_fs_getsize2() is in KB, not bytes. */
format_size_str(char * buf,size_t buflen,off_t size)33 static void format_size_str(char *buf, size_t buflen, off_t size) {
34   char *units[] = {"K", "M", "G", "T", "P", "E", "Z", "Y"};
35   unsigned int nunits = 8;
36   register unsigned int i = 0;
37   int res;
38 
39   /* Determine the appropriate units label to use. Do not exceed the max
40    * possible unit support (yottabytes), by ensuring that i maxes out at
41    * index 7 (of 8 possible units).
42    */
43   while (size > 1024 &&
44          i < (nunits - 1)) {
45     pr_signals_handle();
46 
47     size /= 1024;
48     i++;
49   }
50 
51   /* Now, prepare the buffer. */
52   res = pr_snprintf(buf, buflen, "%.3" PR_LU "%sB", (pr_off_t) size, units[i]);
53   if (res > 2) {
54     /* Check for leading zeroes; it's an aethetic choice. */
55     if (buf[0] == '0' && buf[1] != '.') {
56       memmove(&buf[0], &buf[1], res-1);
57       buf[res-1] = '\0';
58     }
59   }
60 }
61 
sftp_display_fh_get_msg(pool * p,pr_fh_t * fh)62 const char *sftp_display_fh_get_msg(pool *p, pr_fh_t *fh) {
63   struct stat st;
64   char buf[PR_TUNABLE_BUFFER_SIZE], *msg = "";
65   int len, res;
66   const unsigned int *current_clients = NULL;
67   const unsigned int *max_clients = NULL;
68   off_t fs_size = 0;
69   const void *v;
70   const char *outs, *rfc1413_ident, *user;
71   const char *serverfqdn = main_server->ServerFQDN;
72   char mg_size[12] = {'\0'}, mg_size_units[12] = {'\0'},
73     mg_max[12] = "unlimited";
74   char mg_class_limit[12] = {'\0'}, mg_cur[12] = {'\0'},
75     mg_cur_class[12] = {'\0'};
76   const char *mg_time;
77 
78   /* Stat the opened file to determine the optimal buffer size for IO. */
79   memset(&st, 0, sizeof(st));
80   if (pr_fsio_fstat(fh, &st) == 0) {
81     fh->fh_iosz = st.st_blksize;
82   }
83 
84   res = pr_fs_fgetsize(fh->fh_fd, &fs_size);
85   if (res < 0 &&
86       errno != ENOSYS) {
87     (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
88       "error getting filesystem size for '%s': %s", fh->fh_path,
89       strerror(errno));
90     fs_size = 0;
91   }
92 
93   pr_snprintf(mg_size, sizeof(mg_size), "%" PR_LU, (pr_off_t) fs_size);
94   format_size_str(mg_size_units, sizeof(mg_size_units), fs_size);
95 
96   mg_time = pr_strtime3(p, time(NULL), FALSE);
97 
98   max_clients = get_param_ptr(main_server->conf, "MaxClients", FALSE);
99 
100   v = pr_table_get(session.notes, "client-count", NULL);
101   if (v != NULL) {
102     current_clients = v;
103   }
104 
105   pr_snprintf(mg_cur, sizeof(mg_cur), "%u",
106     current_clients ? *current_clients: 1);
107 
108   if (session.conn_class != NULL &&
109       session.conn_class->cls_name) {
110     const unsigned int *class_clients = NULL;
111     config_rec *maxc = NULL;
112     unsigned int maxclients = 0;
113 
114     v = pr_table_get(session.notes, "class-client-count", NULL);
115     if (v != NULL) {
116       class_clients = v;
117     }
118 
119     pr_snprintf(mg_cur_class, sizeof(mg_cur_class), "%u",
120       class_clients ? *class_clients : 0);
121 
122     /* For the %z variable, first we scan through the MaxClientsPerClass,
123      * and use the first applicable one.  If none are found, look for
124      * any MaxClients set.
125      */
126 
127     maxc = find_config(main_server->conf, CONF_PARAM, "MaxClientsPerClass",
128       FALSE);
129     while (maxc != NULL) {
130       pr_signals_handle();
131 
132       if (strcmp(maxc->argv[0], session.conn_class->cls_name) != 0) {
133         maxc = find_config_next(maxc, maxc->next, CONF_PARAM,
134           "MaxClientsPerClass", FALSE);
135         continue;
136       }
137 
138       maxclients = *((unsigned int *) maxc->argv[1]);
139       break;
140     }
141 
142     if (maxclients == 0) {
143       maxc = find_config(main_server->conf, CONF_PARAM, "MaxClients", FALSE);
144       if (maxc) {
145         maxclients = *((unsigned int *) maxc->argv[0]);
146       }
147     }
148 
149     pr_snprintf(mg_class_limit, sizeof(mg_class_limit), "%u", maxclients);
150 
151   } else {
152     pr_snprintf(mg_class_limit, sizeof(mg_class_limit), "%u",
153       max_clients ? *max_clients : 0);
154     pr_snprintf(mg_cur_class, sizeof(mg_cur_class), "%u", 0);
155   }
156 
157   pr_snprintf(mg_max, sizeof(mg_max), "%u", max_clients ? *max_clients : 0);
158 
159   user = pr_table_get(session.notes, "mod_auth.orig-user", NULL);
160   if (user == NULL) {
161     user = "";
162   }
163 
164   rfc1413_ident = pr_table_get(session.notes, "mod_ident.rfc1413-ident", NULL);
165   if (rfc1413_ident == NULL) {
166     rfc1413_ident = "UNKNOWN";
167   }
168 
169   memset(buf, '\0', sizeof(buf));
170   while (pr_fsio_gets(buf, sizeof(buf), fh) != NULL) {
171     char *tmp;
172 
173     pr_signals_handle();
174 
175     buf[sizeof(buf)-1] = '\0';
176     len = strlen(buf);
177 
178     while (len > 0 &&
179            (buf[len-1] == '\r' || buf[len-1] == '\n')) {
180       pr_signals_handle();
181 
182       buf[len-1] = '\0';
183       len--;
184     }
185 
186     /* Check for any Variable-type strings. */
187     tmp = strstr(buf, "%{");
188     while (tmp) {
189       char *key, *tmp2;
190       const char *val;
191 
192       pr_signals_handle();
193 
194       tmp2 = strchr(tmp, '}');
195       if (tmp2 == NULL) {
196         /* No closing '}' found in this string, so no need to look for any
197          * another '%{' opening sequence.  Just move on.
198          */
199         tmp = NULL;
200         break;
201       }
202 
203       key = pstrndup(p, tmp, tmp2 - tmp + 1);
204 
205       /* There are a couple of special-case keys to watch for:
206        *
207        *   env:$var
208        *   time:$fmt
209        *
210        * The Var API does not easily support returning values for keys
211        * where part of the value depends on part of the key.  That's why
212        * these keys are handled here, instead of in pr_var_get().
213        */
214 
215       if (strncmp(key, "%{time:", 7) == 0) {
216         char time_str[128], *fmt;
217         time_t now;
218         struct tm *tm;
219 
220         fmt = pstrndup(p, key + 7, strlen(key) - 8);
221 
222         now = time(NULL);
223         memset(time_str, 0, sizeof(time_str));
224 
225         tm = pr_localtime(p, &now);
226         if (tm != NULL) {
227           strftime(time_str, sizeof(time_str), fmt, tm);
228         }
229 
230         val = pstrdup(p, time_str);
231 
232       } else if (strncmp(key, "%{env:", 6) == 0) {
233         char *env_var;
234 
235         env_var = pstrndup(p, key + 6, strlen(key) - 7);
236         val = pr_env_get(p, env_var);
237         if (val == NULL) {
238           pr_trace_msg("var", 4,
239             "no value set for environment variable '%s', using \"(none)\"",
240             env_var);
241           val = "(none)";
242         }
243 
244       } else {
245         val = pr_var_get(key);
246         if (val == NULL) {
247           pr_trace_msg("var", 4,
248             "no value set for name '%s', using \"(none)\"", key);
249           val = "(none)";
250         }
251       }
252 
253       outs = sreplace(p, buf, key, val, NULL);
254       sstrncpy(buf, outs, sizeof(buf));
255 
256       tmp = strstr(outs, "%{");
257     }
258 
259     outs = sreplace(p, buf,
260       "%C", (session.cwd[0] ? session.cwd : "(none)"),
261       "%E", main_server->ServerAdmin,
262       "%F", mg_size,
263       "%f", mg_size_units,
264       "%i", "0",
265       "%K", "0",
266       "%k", "0B",
267       "%L", serverfqdn,
268       "%M", mg_max,
269       "%N", mg_cur,
270       "%o", "0",
271       "%R", (session.c && session.c->remote_name ?
272         session.c->remote_name : "(unknown)"),
273       "%T", mg_time,
274       "%t", "0",
275       "%U", user,
276       "%u", rfc1413_ident,
277       "%V", main_server->ServerName,
278       "%x", session.conn_class ? session.conn_class->cls_name : "(unknown)",
279       "%y", mg_cur_class,
280       "%z", mg_class_limit,
281       NULL);
282 
283     /* Always make sure that the lines we send are CRLF-terminated. */
284     msg = pstrcat(p, msg, outs, "\r\n", NULL);
285 
286     /* Clear the buffer for the next read. */
287     memset(buf, '\0', sizeof(buf));
288   }
289 
290   return msg;
291 }
292