1 /*
2 * ProFTPD - FTP server daemon
3 * Copyright (c) 1997, 1998 Public Flood Software
4 * Copyright (c) 1999, 2000 MacGyver aka Habeeb J. Dihu <macgyver@tos.net>
5 * Copyright (c) 2001-2020 The ProFTPD Project team
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
20 *
21 * As a special exemption, Public Flood Software/MacGyver aka Habeeb J. Dihu
22 * and other respective copyright holders give permission to link this program
23 * with OpenSSL, and distribute the resulting executable, without including
24 * the source code for OpenSSL in the source distribution.
25 */
26
27 /* Shows a count of "who" is online via proftpd. Uses the scoreboard file. */
28
29 #include "utils.h"
30 #include "ccan-json.h"
31
32 #define MAX_CLASSES 100
33 struct scoreboard_class {
34 char *score_class;
35 unsigned long score_count;
36 };
37
38 #define OF_COMPAT 0x001
39 #define OF_ONELINE 0x002
40 #define OF_JSON 0x004
41
42 static const char *config_filename = PR_CONFIG_FILE_PATH;
43
percent_complete(off_t size,off_t done)44 static char *percent_complete(off_t size, off_t done) {
45 static char sbuf[32];
46
47 memset(sbuf, '\0', sizeof(sbuf));
48
49 if (done == 0) {
50 util_sstrncpy(sbuf, "0", sizeof(sbuf));
51
52 } else if (size == 0) {
53 util_sstrncpy(sbuf, "Inf", sizeof(sbuf));
54
55 } else if (done >= size) {
56 util_sstrncpy(sbuf, "100", sizeof(sbuf));
57
58 } else {
59 snprintf(sbuf, sizeof(sbuf), "%.0f",
60 ((double) done / (double) size) * 100.0);
61 sbuf[sizeof(sbuf)-1] = '\0';
62 }
63
64 return sbuf;
65 }
66
get_server_json(void)67 static JsonNode *get_server_json(void) {
68 JsonNode *server;
69 pid_t daemon_pid;
70 time_t daemon_uptime;
71
72 server = json_mkobject();
73
74 daemon_pid = util_scoreboard_get_daemon_pid();
75 if (daemon_pid != 0) {
76 json_append_member(server, "server_type", json_mkstring("standalone"));
77 json_append_member(server, "pid", json_mknumber((double) daemon_pid));
78
79 } else {
80 json_append_member(server, "server_type", json_mkstring("inetd"));
81 }
82
83 daemon_uptime = util_scoreboard_get_daemon_uptime();
84 json_append_member(server, "started_ms",
85 json_mknumber(((double) (daemon_uptime * 1000L))));
86
87 return server;
88 }
89
get_conns_json(void)90 static JsonNode *get_conns_json(void) {
91 JsonNode *conns;
92 pr_scoreboard_entry_t *score = NULL;
93
94 conns = json_mkarray();
95
96 while ((score = util_scoreboard_entry_read()) != NULL) {
97 JsonNode *conn;
98 int authenticating = FALSE, downloading = FALSE, uploading = FALSE;
99
100 conn = json_mkobject();
101
102 json_append_member(conn, "pid", json_mknumber((double) score->sce_pid));
103 json_append_member(conn, "connected_since_ms",
104 json_mknumber(((double) (score->sce_begin_session * 1000L))));
105 json_append_member(conn, "remote_name",
106 json_mkstring(score->sce_client_name));
107 json_append_member(conn, "remote_address",
108 json_mkstring(score->sce_client_addr));
109
110 if (score->sce_server_addr[0]) {
111 char *ptr, server_addr[80];
112
113 /* Trim off the port portion of the server_addr field; we report that
114 * separately.
115 */
116 memset(server_addr, '\0', sizeof(server_addr));
117
118 ptr = strrchr(score->sce_server_addr, ':');
119 if (ptr != NULL) {
120 memcpy(server_addr, score->sce_server_addr,
121 (ptr - score->sce_server_addr));
122 } else {
123 memcpy(server_addr, score->sce_server_addr, sizeof(server_addr)-1);
124 }
125
126 json_append_member(conn, "local_address", json_mkstring(server_addr));
127 }
128
129 json_append_member(conn, "local_port",
130 json_mknumber((double) score->sce_server_port));
131
132 if (strcmp(score->sce_user, "(none)") == 0) {
133 authenticating = TRUE;
134 }
135
136 if (authenticating) {
137 json_append_member(conn, "authenticating", json_mkbool(TRUE));
138
139 } else {
140 json_append_member(conn, "user", json_mkstring(score->sce_user));
141 }
142
143 if (score->sce_class[0]) {
144 json_append_member(conn, "class", json_mkstring(score->sce_class));
145 }
146
147 if (score->sce_protocol[0]) {
148 json_append_member(conn, "protocol", json_mkstring(score->sce_protocol));
149 }
150
151 if (score->sce_cwd[0]) {
152 json_append_member(conn, "location", json_mkstring(score->sce_cwd));
153 }
154
155 if (score->sce_cmd[0]) {
156 if (strcmp(score->sce_cmd, "idle") == 0) {
157 json_append_member(conn, "idling", json_mkbool(TRUE));
158
159 if (!authenticating) {
160 json_append_member(conn, "idle_since_ms",
161 json_mknumber(((double) (score->sce_begin_idle * 1000L))));
162 }
163
164 } else {
165 json_append_member(conn, "command", json_mkstring(score->sce_cmd));
166
167 if (score->sce_cmd_arg[0]) {
168 json_append_member(conn, "command_args",
169 json_mkstring(score->sce_cmd_arg));
170 }
171 }
172
173 } else {
174 json_append_member(conn, "idling", json_mkbool(TRUE));
175
176 if (!authenticating) {
177 json_append_member(conn, "idle_since_ms",
178 json_mknumber(((double) (score->sce_begin_idle * 1000L))));
179 }
180 }
181
182 if (strncmp(score->sce_cmd, "RETR", 5) == 0 ||
183 strncmp(score->sce_cmd, "READ", 5) == 0 ||
184 strcmp(score->sce_cmd, "scp download") == 0) {
185 downloading = TRUE;
186
187 } else {
188 if (strncmp(score->sce_cmd, "STOR", 5) == 0 ||
189 strncmp(score->sce_cmd, "STOU", 5) == 0 ||
190 strncmp(score->sce_cmd, "APPE", 5) == 0 ||
191 strncmp(score->sce_cmd, "WRITE", 6) == 0 ||
192 strcmp(score->sce_cmd, "scp upload") == 0) {
193 uploading = TRUE;
194 }
195 }
196
197 if (downloading) {
198 json_append_member(conn, "downloading", json_mkbool(TRUE));
199 json_append_member(conn, "transfer_completed",
200 json_mkstring(percent_complete(score->sce_xfer_size,
201 score->sce_xfer_done)));
202 }
203
204 if (uploading) {
205 json_append_member(conn, "uploading", json_mkbool(TRUE));
206 }
207
208 if (score->sce_xfer_done > 0) {
209 json_append_member(conn, "transfer_bytes",
210 json_mknumber((double) score->sce_xfer_done));
211 }
212
213 if (score->sce_xfer_elapsed > 0) {
214 json_append_member(conn, "transfer_duration_ms",
215 json_mknumber(((double) (score->sce_xfer_elapsed * 1000L))));
216 }
217
218 json_append_element(conns, conn);
219 }
220
221 return conns;
222 }
223
get_json(void)224 static JsonNode *get_json(void) {
225 JsonNode *json, *server = NULL, *conns = NULL;
226
227 server = get_server_json();
228 conns = get_conns_json();
229 json = json_mkobject();
230
231 if (server != NULL) {
232 json_append_member(json, "server", server);
233 }
234
235 if (conns != NULL) {
236 json_append_member(json, "connections", conns);
237 }
238
239 return json;
240 }
241
strtime(time_t * then)242 static const char *strtime(time_t *then) {
243 time_t now = time(NULL);
244 unsigned long since;
245 static char time_str[32];
246
247 if (then == NULL ||
248 *then == 0) {
249 return "-";
250 }
251
252 memset(time_str, '\0', sizeof(time_str));
253 since = now - *then;
254
255 if (since < 3600) {
256 snprintf(time_str, sizeof(time_str)-1, "%lum%lus", (since / 60),
257 (since % 60));
258
259 } else {
260 snprintf(time_str, sizeof(time_str)-1, "%luh%lum", (since / 3600),
261 ((since - (since / 3600) * 3600) / 60));
262 }
263
264 return time_str;
265 }
266
check_scoreboard_file(void)267 static int check_scoreboard_file(void) {
268 struct stat st;
269
270 if (stat(util_get_scoreboard(), &st) < 0) {
271 return -1;
272 }
273
274 return 0;
275 }
276
show_uptime(time_t uptime_since)277 static const char *show_uptime(time_t uptime_since) {
278 static char buf[128] = {'\0'};
279 time_t uptime_secs = time(NULL) - uptime_since;
280 int upminutes, uphours, updays;
281 int pos = 0;
282
283 memset(buf, '\0', sizeof(buf));
284
285 updays = (int) uptime_secs / (60 * 60 * 24);
286 if (updays > 0) {
287 pos += snprintf(buf + pos, sizeof(buf) - pos, "%d %s, ", updays,
288 updays != 1 ? "days" : "day");
289 }
290
291 upminutes = (int) uptime_secs / 60;
292
293 uphours = upminutes / 60;
294 uphours = uphours % 24;
295
296 upminutes = upminutes % 60;
297
298 if (uphours) {
299 snprintf(buf + pos, sizeof(buf) - pos, "%2d %s %02d min", uphours,
300 uphours != 1 ? "hrs" : "hr", upminutes);
301
302 } else {
303 snprintf(buf + pos, sizeof(buf) - pos, "%d min", upminutes);
304 }
305
306 return buf;
307 }
308
309 static struct option_help {
310 const char *long_opt,*short_opt,*desc;
311 } opts_help[] = {
312 { "--config", "-c", "specify full path to proftpd configuration file" },
313 { "--file", "-f", "specify full path to scoreboard file" },
314 { "--help", "-h", NULL },
315 { "--outform","-o", "specify an output format" },
316 { "--verbose","-v", "display additional information for each connection" },
317 { "--server", "-S", "show users only for specified ServerName" },
318 { NULL }
319 };
320
321 #ifdef HAVE_GETOPT_LONG
322 static struct option opts[] = {
323 { "config", 1, NULL, 'c' },
324 { "file", 1, NULL, 'f' },
325 { "help", 0, NULL, 'h' },
326 { "outform", 1, NULL, 'o' },
327 { "verbose", 0, NULL, 'v' },
328 { "server", 1, NULL, 'S' },
329 { NULL, 0, NULL, 0 }
330 };
331 #endif /* HAVE_GETOPT_LONG */
332
show_usage(const char * progname,int exit_code)333 static void show_usage(const char *progname, int exit_code) {
334 struct option_help *h = NULL;
335
336 printf("usage: %s [options]\n", progname);
337 for (h = opts_help; h->long_opt; h++) {
338 #ifdef HAVE_GETOPT_LONG
339 printf(" %s, %s\n", h->short_opt, h->long_opt);
340 #else /* HAVE_GETOPT_LONG */
341 printf(" %s\n", h->short_opt);
342 #endif
343 if (h->desc == NULL) {
344 printf(" display %s usage\n", progname);
345
346 } else {
347 printf(" %s\n", h->desc);
348 }
349 }
350
351 exit(exit_code);
352 }
353
main(int argc,char ** argv)354 int main(int argc, char **argv) {
355 pr_scoreboard_entry_t *score = NULL;
356 pid_t mpid = 0;
357 time_t uptime = 0;
358 unsigned int count = 0, total = 0;
359 int c = 0, res = 0;
360 char *server_name = NULL;
361 struct scoreboard_class classes[MAX_CLASSES];
362 char *cp, *progname = *argv;
363 const char *cmdopts = "S:c:f:ho:v";
364 unsigned char verbose = FALSE;
365 unsigned long outform = 0;
366
367 memset(classes, 0, MAX_CLASSES * sizeof(struct scoreboard_class));
368
369 cp = strrchr(progname, '/');
370 if (cp != NULL)
371 progname = cp+1;
372
373 opterr = 0;
374 while ((c =
375 #ifdef HAVE_GETOPT_LONG
376 getopt_long(argc, argv, cmdopts, opts, NULL)
377 #else /* HAVE_GETOPT_LONG */
378 getopt(argc, argv, cmdopts)
379 #endif /* HAVE_GETOPT_LONG */
380 ) != -1) {
381 switch (c) {
382 case 'h':
383 show_usage(progname, 0);
384 break;
385
386 case 'v':
387 verbose = TRUE;
388 break;
389
390 case 'f':
391 util_set_scoreboard(optarg);
392 break;
393
394 case 'c':
395 config_filename = strdup(optarg);
396 break;
397
398 case 'o':
399 /* Check the given outform parameter. */
400 if (strcasecmp(optarg, "compat") == 0) {
401 outform |= OF_COMPAT;
402 break;
403
404 } else if (strcasecmp(optarg, "oneline") == 0) {
405 outform |= OF_ONELINE;
406 break;
407
408 } else if (strcasecmp(optarg, "json") == 0) {
409 outform = OF_JSON;
410 break;
411 }
412
413 fprintf(stderr, "unknown outform value: '%s'\n", optarg);
414 return 1;
415
416 case 'S':
417 server_name = strdup(optarg);
418 break;
419
420 case '?':
421 fprintf(stderr, "unknown option: %c\n", (char) optopt);
422 show_usage(progname, 1);
423 break;
424 }
425 }
426
427 /* First attempt to check the supplied/default scoreboard path. If this is
428 * incorrect, try the config file kludge.
429 */
430 if (check_scoreboard_file() < 0) {
431 char *path;
432
433 path = util_scan_config(config_filename, "ScoreboardFile");
434 if (path != NULL) {
435 util_set_scoreboard(path);
436 free(path);
437 }
438
439 if (check_scoreboard_file() < 0) {
440 fprintf(stderr, "%s: %s\n", util_get_scoreboard(), strerror(errno));
441 fprintf(stderr, "(Perhaps you need to specify the ScoreboardFile with -f, or change\n");
442 fprintf(stderr, " the compile-time default directory?)\n");
443 exit(1);
444 }
445 }
446
447 res = util_open_scoreboard(O_RDONLY);
448 if (res < 0) {
449 if (server_name != NULL) {
450 free(server_name);
451 }
452
453 switch (res) {
454 case -1:
455 fprintf(stderr, "unable to open scoreboard: %s\n", strerror(errno));
456 return 1;
457
458 case UTIL_SCORE_ERR_BAD_MAGIC:
459 fprintf(stderr, "scoreboard is corrupted or old\n");
460 return 1;
461
462 case UTIL_SCORE_ERR_OLDER_VERSION:
463 fprintf(stderr, "scoreboard version is too old\n");
464 return 1;
465
466 case UTIL_SCORE_ERR_NEWER_VERSION:
467 fprintf(stderr, "scoreboard version is too new\n");
468 return 1;
469 }
470 }
471
472 if (outform == OF_JSON) {
473 JsonNode *json;
474
475 json = get_json();
476 if (json != NULL) {
477 char *json_str;
478
479 json_str = json_stringify(json, " ");
480 fprintf(stdout, "%s\n", json_str);
481 free(json_str);
482 json_delete(json);
483 }
484
485 if (server_name != NULL) {
486 free(server_name);
487 server_name = NULL;
488 }
489
490 return 0;
491 }
492
493 mpid = util_scoreboard_get_daemon_pid();
494 uptime = util_scoreboard_get_daemon_uptime();
495 count = 0;
496
497 if (!mpid) {
498 printf("inetd FTP daemon:\n");
499
500 } else {
501 printf("standalone FTP daemon [%u], up for %s\n", (unsigned int) mpid,
502 show_uptime(uptime));
503 }
504
505 if (server_name != NULL) {
506 printf("ProFTPD Server '%s'\n", server_name);
507 }
508
509 while ((score = util_scoreboard_entry_read()) != NULL) {
510 int downloading = FALSE, uploading = FALSE;
511 register unsigned int i = 0;
512
513 /* If a ServerName was given, skip unless the scoreboard entry matches. */
514 if (server_name != NULL &&
515 strcmp(server_name, score->sce_server_label) != 0) {
516 continue;
517 }
518
519 if (!count++) {
520 if (total > 0) {
521 printf(" - %u user%s\n\n", total, total > 1 ? "s" : "");
522 }
523 total = 0;
524 }
525
526 /* Tally up per-Class counters. */
527 for (i = 0; i != MAX_CLASSES; i++) {
528 if (classes[i].score_class == 0) {
529 classes[i].score_class = strdup(score->sce_class);
530 classes[i].score_count++;
531 break;
532 }
533
534 if (strcasecmp(classes[i].score_class, score->sce_class) == 0) {
535 classes[i].score_count++;
536 break;
537 }
538 }
539
540 total++;
541
542 if (strncmp(score->sce_cmd, "RETR", 5) == 0 ||
543 strncmp(score->sce_cmd, "READ", 5) == 0 ||
544 strcmp(score->sce_cmd, "scp download") == 0) {
545 downloading = TRUE;
546
547 } else {
548 if (strncmp(score->sce_cmd, "STOR", 5) == 0 ||
549 strncmp(score->sce_cmd, "STOU", 5) == 0 ||
550 strncmp(score->sce_cmd, "APPE", 5) == 0 ||
551 strncmp(score->sce_cmd, "WRITE", 6) == 0 ||
552 strcmp(score->sce_cmd, "scp upload") == 0) {
553 uploading = TRUE;
554 }
555 }
556
557 if (outform & OF_COMPAT) {
558 if ((downloading || uploading) &&
559 score->sce_xfer_size > 0) {
560 if (downloading) {
561 printf("%5d %-6s (%s%%) %s %s\n", (int) score->sce_pid,
562 strtime(&score->sce_begin_idle),
563 percent_complete(score->sce_xfer_size, score->sce_xfer_done),
564 score->sce_cmd, score->sce_cmd_arg);
565
566 } else {
567 printf("%5d %-6s (n/a) %s %s\n", (int) score->sce_pid,
568 strtime(&score->sce_begin_idle), score->sce_cmd,
569 score->sce_cmd_arg);
570 }
571
572 } else {
573 printf("%5d %-6s %s %s\n", (int) score->sce_pid,
574 strtime(&score->sce_begin_idle), score->sce_cmd,
575 score->sce_cmd_arg);
576 }
577
578 if (verbose) {
579 if (score->sce_client_addr[0]) {
580 printf(" (host: %s [%s])\n", score->sce_client_name,
581 score->sce_client_addr);
582 }
583
584 if (score->sce_protocol[0]) {
585 printf(" (protocol: %s)\n", score->sce_protocol);
586 }
587
588 if (score->sce_cwd[0]) {
589 printf(" (cwd: %s)\n", score->sce_cwd);
590 }
591
592 if (score->sce_class[0]) {
593 printf(" (class: %s)\n", score->sce_class);
594 }
595 }
596
597 continue;
598 }
599
600 /* Has the client authenticated yet, or not? */
601 if (strcmp(score->sce_user, "(none)")) {
602
603 /* Is the client idle? */
604 if (strncmp(score->sce_cmd, "idle", 5) == 0) {
605
606 /* These printf() calls needs to be split up, as strtime() returns
607 * a pointer to a static buffer, and pushing two invocations onto
608 * the stack means that the times thus formatted will be incorrect.
609 */
610 printf("%5d %-8s [%6s] ", (int) score->sce_pid,
611 score->sce_user, strtime(&score->sce_begin_session));
612 printf("%6s %s", strtime(&score->sce_begin_idle), score->sce_cmd);
613
614 if (verbose &&
615 !(outform & OF_ONELINE)) {
616 printf("\n");
617 }
618
619 } else {
620 if (downloading) {
621 printf("%5d %-8s [%6s] (%3s%%) %s %s", (int) score->sce_pid,
622 score->sce_user, strtime(&score->sce_begin_session),
623 percent_complete(score->sce_xfer_size, score->sce_xfer_done),
624 score->sce_cmd, score->sce_cmd_arg);
625
626 } else {
627 printf("%5d %-8s [%6s] (n/a) %s %s", (int) score->sce_pid,
628 score->sce_user, strtime(&score->sce_begin_session),
629 score->sce_cmd, score->sce_cmd_arg);
630 }
631
632 if (verbose) {
633 printf("%sKB/s: %3.2f%s",
634 (outform & OF_ONELINE) ? " " : "\n\t",
635 (score->sce_xfer_len / 1024.0) /
636 (score->sce_xfer_elapsed / 1000),
637 (outform & OF_ONELINE) ? "" : "\n");
638 }
639 }
640
641 /* Display additional information, if requested. */
642 if (verbose) {
643 if (score->sce_client_addr[0]) {
644 printf("%sclient: %s [%s]%s",
645 (outform & OF_ONELINE) ? " " : "\t",
646 score->sce_client_name, score->sce_client_addr,
647 (outform & OF_ONELINE) ? "" : "\n");
648 }
649
650 if (score->sce_server_addr[0]) {
651 printf("%sserver: %s (%s)%s",
652 (outform & OF_ONELINE) ? " " : "\t",
653 score->sce_server_addr, score->sce_server_label,
654 (outform & OF_ONELINE) ? "" : "\n");
655 }
656
657 if (score->sce_protocol[0]) {
658 printf("%sprotocol: %s%s",
659 (outform & OF_ONELINE) ? " " : "\t",
660 score->sce_protocol,
661 (outform & OF_ONELINE) ? "" : "\n");
662 }
663
664 if (score->sce_cwd[0]) {
665 printf("%slocation: %s%s",
666 (outform & OF_ONELINE) ? " " : "\t",
667 score->sce_cwd,
668 (outform & OF_ONELINE) ? "" : "\n");
669 }
670
671 if (score->sce_class[0]) {
672 printf("%sclass: %s",
673 (outform & OF_ONELINE) ? " " : "\t",
674 score->sce_class);
675 }
676
677 printf("%s", "\n");
678
679 } else {
680 printf("%s", "\n");
681 }
682
683 } else {
684 printf("%5d %-8s [%6s] (authenticating)", (int) score->sce_pid,
685 score->sce_user, strtime(&score->sce_begin_session));
686
687 /* Display additional information, if requested. */
688 if (verbose) {
689 if (score->sce_client_addr[0]) {
690 printf("%sclient: %s [%s]%s",
691 (outform & OF_ONELINE) ? " " : "\n\t",
692 score->sce_client_name, score->sce_client_addr,
693 (outform & OF_ONELINE) ? "" : "\n");
694 }
695
696 if (score->sce_server_addr[0]) {
697 printf("%sserver: %s (%s)%s",
698 (outform & OF_ONELINE) ? " " : "\t",
699 score->sce_server_addr, score->sce_server_label,
700 (outform & OF_ONELINE) ? "" : "\n");
701 }
702
703 if (score->sce_protocol[0]) {
704 printf("%sprotocol: %s%s",
705 (outform & OF_ONELINE) ? " " : "\t",
706 score->sce_protocol,
707 (outform & OF_ONELINE) ? "" : "\n");
708 }
709
710 if (score->sce_class[0]) {
711 printf("%sclass: %s",
712 (outform & OF_ONELINE) ? " " : "\t",
713 score->sce_class);
714 }
715 }
716
717 printf("%s", "\n");
718 }
719 }
720 util_close_scoreboard();
721
722 if (total > 0) {
723 register unsigned int i = 0;
724
725 for (i = 0; i != MAX_CLASSES; i++) {
726 if (classes[i].score_class == 0) {
727 break;
728 }
729
730 printf("Service class %-20s - %3lu user%s\n", classes[i].score_class,
731 classes[i].score_count, classes[i].score_count > 1 ? "s" : "");
732 }
733
734 } else {
735 printf("no users connected\n");
736 }
737
738 if (server_name != NULL) {
739 free(server_name);
740 }
741
742 return 0;
743 }
744