1 /*
2  * ProFTPD - ftptop: a utility for monitoring proftpd sessions
3  * Copyright (c) 2000-2002 TJ Saunders <tj@castaglia.org>
4  * Copyright (c) 2003-2020 The ProFTPD Project team
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
19  *
20  * As a special exemption, TJ Saunders and other respective copyright holders
21  * give permission to link this program with OpenSSL, and distribute the
22  * resulting executable, without including the source code for OpenSSL in the
23  * source distribution.
24  */
25 
26 /* Shows who is online via proftpd, in a manner similar to top.  Uses the
27  * scoreboard files.
28  */
29 
30 #define FTPTOP_VERSION 		"ftptop/1.0"
31 
32 #include "utils.h"
33 
34 #include <ctype.h>
35 #include <stdlib.h>
36 #include <time.h>
37 
38 #if defined(PR_USE_NLS) && defined(HAVE_LOCALE_H)
39 # include <locale.h>
40 #endif
41 
42 static const char *program = "ftptop";
43 
44 /* ncurses is preferred...*/
45 
46 #if defined(HAVE_NCURSES_H) && \
47     ((defined(HAVE_LIBNCURSES) && defined(PR_USE_NCURSES) || \
48      (defined(HAVE_LIBNCURSESW) && defined(PR_USE_NCURSESW))))
49 # define HAVE_NCURSES 1
50 # include <ncurses.h>
51 #elif defined(HAVE_CURSES_H) && defined(HAVE_LIBCURSES) && \
52     defined(PR_USE_CURSES)
53 # define HAVE_CURSES 1
54 /* Sigh...portability.  It seems that Solaris' curses.h (at least for 2.8)
55  * steps on wide-character macros, generating compiler warnings.  This, then
56  * is just a hack to silence the compiler.
57  */
58 # ifdef SOLARIS2
59 #  define __lint
60 # endif
61 # include <curses.h>
62 #endif
63 
64 #if defined(HAVE_NCURSES) || defined(HAVE_CURSES)
65 
66 /* Display options */
67 
68 /* These are for displaying "PID S USER CLIENT SERVER TIME COMMAND" */
69 #define FTPTOP_REG_HEADER_FMT	"%-5s %s %-8s %-20s %-15s %-4s %-*s\n"
70 #define FTPTOP_REG_DISPLAY_FMT	"%-5u %s %-*.*s %-*.*s %-15s %-6.6s %4s %-*.*s\n"
71 
72 /* These are for displaying tranfer data: "PID S USER CLIENT KB/s %DONE" */
73 #define FTPTOP_XFER_HEADER_FMT	"%-5s %s %-8s %-44s %-10s %-*s\n"
74 #define FTPTOP_XFER_DISPLAY_FMT	"%-5u %s %-*.*s %-*.*s %-10.2f %-*.*s\n"
75 
76 #define FTPTOP_REG_ARG_MIN_SIZE		20
77 #define FTPTOP_XFER_DONE_MIN_SIZE	6
78 #define FTPTOP_REG_ARG_SIZE	\
79   (COLS - (80 - FTPTOP_REG_ARG_MIN_SIZE) < FTPTOP_REG_ARG_MIN_SIZE ? \
80   FTPTOP_REG_ARG_MIN_SIZE : COLS - (80 - FTPTOP_REG_ARG_MIN_SIZE))
81 #define FTPTOP_XFER_DONE_SIZE 	\
82   (COLS - (80 - FTPTOP_XFER_DONE_MIN_SIZE) < FTPTOP_XFER_DONE_MIN_SIZE ? \
83   FTPTOP_XFER_DONE_MIN_SIZE : COLS - (80 - FTPTOP_XFER_DONE_MIN_SIZE))
84 
85 #define FTPTOP_SHOW_DOWNLOAD		0x0001
86 #define FTPTOP_SHOW_UPLOAD		0x0002
87 #define FTPTOP_SHOW_IDLE		0x0004
88 #define FTPTOP_SHOW_AUTH		0x0008
89 #define	FTPTOP_SHOW_REG \
90   (FTPTOP_SHOW_DOWNLOAD|FTPTOP_SHOW_UPLOAD|FTPTOP_SHOW_IDLE)
91 #define FTPTOP_SHOW_RATES		0x0010
92 
93 static int delay = 2;
94 static unsigned int display_mode = FTPTOP_SHOW_REG;
95 static int batch_mode = FALSE;
96 static unsigned int max_iterations = 0;
97 
98 static char *config_filename = PR_CONFIG_FILE_PATH;
99 
100 /* Scoreboard variables */
101 static time_t ftp_uptime = 0;
102 static unsigned int ftp_nsessions = 0;
103 static unsigned int ftp_nuploads = 0;
104 static unsigned int ftp_ndownloads = 0;
105 static unsigned int ftp_nidles = 0;
106 
107 static char *server_name = NULL;
108 static char **ftp_sessions = NULL;
109 static unsigned int chunklen = 3;
110 
111 /* necessary prototypes */
112 static void scoreboard_close(void);
113 static int scoreboard_open(void);
114 
115 static void show_version(void);
116 static const char *show_ftpd_uptime(void);
117 static void usage(void);
118 
clear_counters(void)119 static void clear_counters(void) {
120 
121   if (ftp_sessions != NULL &&
122       ftp_nsessions > 0) {
123     register unsigned int i = 0;
124 
125     for (i = 0; i < ftp_nsessions; i++) {
126       free(ftp_sessions[i]);
127     }
128     free(ftp_sessions);
129     ftp_sessions = NULL;
130   }
131 
132   /* Reset the session counters. */
133   ftp_nsessions = 0;
134   ftp_nuploads = 0;
135   ftp_ndownloads = 0;
136   ftp_nidles = 0;
137 }
138 
finish(int signo)139 static void finish(int signo) {
140   endwin();
141   exit(0);
142 }
143 
calc_percent_done(off_t size,off_t done)144 static char *calc_percent_done(off_t size, off_t done) {
145   static char sbuf[32];
146 
147   memset(sbuf, '\0', sizeof(sbuf));
148 
149   if (done == 0) {
150     util_sstrncpy(sbuf, "0", sizeof(sbuf));
151 
152   } else if (size == 0) {
153     util_sstrncpy(sbuf, "Inf", sizeof(sbuf));
154 
155   } else if (done >= size) {
156     util_sstrncpy(sbuf, "100", sizeof(sbuf));
157 
158   } else {
159     snprintf(sbuf, sizeof(sbuf), "%.0f",
160       ((double) done / (double) size) * 100.0);
161     sbuf[sizeof(sbuf)-1] = '\0';
162   }
163 
164   return sbuf;
165 }
166 
167 /* Given a NUL-terminated string -- possibly UTF8-encoded -- and a maximum
168  * CHARACTER length, return the number of bytes in the string which can fit in
169  * that max length  without truncating a character.  This is needed since UTF8
170  * characters are variable-width.
171  */
str_getscreenlen(const char * str,size_t max_chars)172 static int str_getscreenlen(const char *str, size_t max_chars) {
173 #ifdef PR_USE_NLS
174   register unsigned int i = 0;
175   int nbytes = 0, nchars = 0;
176 
177   while (str[i] > 0 &&
178          i < max_chars) {
179 ascii:
180     i++;
181     nbytes++;
182     nchars++;
183   }
184 
185   while (str[i] &&
186          (size_t) nchars < max_chars) {
187     size_t len;
188 
189     if (str[i] > 0) {
190       goto ascii;
191     }
192 
193     len = 0;
194 
195     switch (str[i] & 0xF0) {
196       case 0xE0:
197         len = 3;
198         break;
199 
200       case 0xF0:
201         len = 4;
202         break;
203 
204       default:
205         len = 2;
206         break;
207     }
208 
209     /* Increment the index with the given length, but increment the
210      * character count only one.
211      */
212 
213     i += len;
214     nbytes += len;
215     nchars++;
216   }
217 
218   return nbytes;
219 #else
220   /* No UTF8 support in this proftpd build; just return the max characters. */
221   return (int) max_chars;
222 #endif /* !PR_USE_NLS */
223 }
224 
225 /* Borrowed from ftpwho.c */
show_time(time_t * i)226 static const char *show_time(time_t *i) {
227   time_t now = time(NULL);
228   unsigned long l;
229   static char buf[32];
230 
231   if (i == NULL ||
232      *i == 0) {
233     return "-";
234   }
235 
236   memset(buf, '\0', sizeof(buf));
237   l = now - *i;
238 
239   if (l < 3600) {
240     snprintf(buf, sizeof(buf), "%lum%lus", (l / 60), (l % 60));
241 
242   } else {
243     snprintf(buf, sizeof(buf), "%luh%lum", (l / 3600),
244       ((l - (l / 3600) * 3600) / 60));
245   }
246 
247   return buf;
248 }
249 
check_scoreboard_file(void)250 static int check_scoreboard_file(void) {
251   struct stat sbuf;
252 
253   if (stat(util_get_scoreboard(), &sbuf) < 0)
254     return -1;
255 
256   return 0;
257 }
258 
show_ftpd_uptime(void)259 static const char *show_ftpd_uptime(void) {
260   static char buf[128] = {'\0'};
261   time_t uptime_secs = time(NULL) - ftp_uptime;
262   int upminutes, uphours, updays;
263   int pos = 0;
264 
265   if (!ftp_uptime)
266     return "";
267 
268   memset(buf, '\0', sizeof(buf));
269   pos += snprintf(buf, sizeof(buf)-1, "%s", ", up for ");
270 
271   updays = (int) uptime_secs / (60 * 60 * 24);
272 
273   if (updays) {
274     pos += snprintf(buf + pos, sizeof(buf) - pos, "%d day%s, ", updays,
275       (updays != 1) ? "s" : "");
276   }
277 
278   upminutes = (int) uptime_secs / 60;
279 
280   uphours = upminutes / 60;
281   uphours = uphours % 24;
282 
283   upminutes = upminutes % 60;
284 
285   if (uphours) {
286     snprintf(buf + pos, sizeof(buf) - pos, "%2d hr%s %02d min", uphours,
287       (uphours != 1) ? "s" : "", upminutes);
288 
289   } else {
290     snprintf(buf + pos, sizeof(buf) - pos, "%d min", upminutes);
291   }
292 
293   return buf;
294 }
295 
process_opts(int argc,char * argv[])296 static void process_opts(int argc, char *argv[]) {
297   int optc = 0;
298   const char *prgopts = "AabDS:d:f:hIin:UV";
299 
300   while ((optc = getopt(argc, argv, prgopts)) != -1) {
301     switch (optc) {
302       case 'A':
303         display_mode = 0U;
304         display_mode |= FTPTOP_SHOW_AUTH;
305         break;
306 
307       case 'a':
308         display_mode &= ~FTPTOP_SHOW_AUTH;
309         break;
310 
311       case 'b':
312         batch_mode = TRUE;
313         break;
314 
315       case 'D':
316         display_mode = 0U;
317         display_mode |= FTPTOP_SHOW_DOWNLOAD;
318         break;
319 
320       case 'd':
321         delay = atoi(optarg);
322 
323         if (delay < 0) {
324           fprintf(stderr, "%s: negative delay illegal: %d\n", program,
325             delay);
326           exit(1);
327         }
328 
329         if (delay > 15) {
330           fprintf(stderr, "%s: delay of 0-15 seconds only supported\n",
331             program);
332           exit(1);
333         }
334 
335         break;
336 
337       case 'f':
338         if (util_set_scoreboard(optarg) < 0) {
339           fprintf(stderr, "%s: unable to use scoreboard '%s': %s\n",
340             program, optarg, strerror(errno));
341           exit(1);
342         }
343         break;
344 
345       case 'h':
346         usage();
347         break;
348 
349       case 'I':
350         display_mode = 0U;
351         display_mode |= FTPTOP_SHOW_IDLE;
352         break;
353 
354       case 'i':
355         display_mode &= ~FTPTOP_SHOW_IDLE;
356         break;
357 
358       case 'n': {
359         int count;
360 
361         count = atoi(optarg);
362         if (count <= 0) {
363           fprintf(stderr, "%s: bad iterations argument '%s'\n", program,
364             optarg);
365           exit(1);
366         }
367 
368         max_iterations = count;
369         break;
370       }
371 
372       case 'S':
373         if (server_name != NULL) {
374           free(server_name);
375           server_name = NULL;
376         }
377 
378         server_name = strdup(optarg);
379         break;
380 
381       case 'U':
382         display_mode = 0U;
383         display_mode |= FTPTOP_SHOW_UPLOAD;
384         break;
385 
386       case 'V':
387         show_version();
388         break;
389 
390       case '?':
391         break;
392 
393      default:
394         break;
395     }
396   }
397 
398   /* First attempt to check the supplied/default scoreboard path.  If this is
399    * incorrect, try the config file kludge.
400    */
401   if (check_scoreboard_file() < 0) {
402     char *path;
403 
404     path = util_scan_config(config_filename, "ScoreboardFile");
405     if (path != NULL) {
406       util_set_scoreboard(path);
407       free(path);
408     }
409 
410     if (check_scoreboard_file() < 0) {
411       fprintf(stderr, "%s: %s\n", util_get_scoreboard(), strerror(errno));
412       fprintf(stderr, "(Perhaps you need to specify the ScoreboardFile with -f, or change\n");
413       fprintf(stderr," the compile-time default directory?)\n");
414       exit(1);
415     }
416   }
417 }
418 
read_scoreboard(void)419 static void read_scoreboard(void) {
420 
421   /* NOTE: this buffer should probably be limited to the maximum window
422    * width, as it is used for display purposes.
423    */
424   static char buf[PR_TUNABLE_BUFFER_SIZE] = {'\0'};
425   pr_scoreboard_entry_t *score = NULL;
426 
427   ftp_sessions = calloc(chunklen, sizeof(char *));
428   if (ftp_sessions == NULL) {
429     exit(1);
430   }
431 
432   if (scoreboard_open() < 0) {
433     return;
434   }
435 
436   /* Iterate through the scoreboard. */
437   while ((score = util_scoreboard_entry_read()) != NULL) {
438 
439     /* Default status: "A" for "authenticating" */
440     char *status = "A";
441 
442     /* If a ServerName was given, skip unless the scoreboard entry matches. */
443     if (server_name != NULL &&
444         strcmp(server_name, score->sce_server_label) != 0) {
445       continue;
446     }
447 
448     /* Clear the buffer for this run. */
449     memset(buf, '\0', sizeof(buf));
450 
451     /* Has the user authenticated yet? */
452     if (strcmp(score->sce_user, "(none)") != 0) {
453 
454       /* Determine the status symbol to display. */
455       if (strcmp(score->sce_cmd, "idle") == 0) {
456         status = "I";
457         ftp_nidles++;
458 
459         if (display_mode != FTPTOP_SHOW_RATES &&
460             !(display_mode & FTPTOP_SHOW_IDLE)) {
461           continue;
462         }
463 
464       } else if (strcmp(score->sce_cmd, "RETR") == 0 ||
465                  strcmp(score->sce_cmd, "READ") == 0 ||
466                  strcmp(score->sce_cmd, "scp download") == 0) {
467         status = "D";
468         ftp_ndownloads++;
469 
470         if (display_mode != FTPTOP_SHOW_RATES &&
471             !(display_mode & FTPTOP_SHOW_DOWNLOAD)) {
472           continue;
473         }
474 
475       } else if (strcmp(score->sce_cmd, "STOR") == 0 ||
476                  strcmp(score->sce_cmd, "APPE") == 0 ||
477                  strcmp(score->sce_cmd, "STOU") == 0 ||
478                  strcmp(score->sce_cmd, "WRITE") == 0 ||
479                  strcmp(score->sce_cmd, "scp upload") == 0) {
480         status = "U";
481         ftp_nuploads++;
482 
483         if (display_mode != FTPTOP_SHOW_RATES &&
484             !(display_mode & FTPTOP_SHOW_UPLOAD)) {
485           continue;
486         }
487 
488       } else if (strcmp(score->sce_cmd, "LIST") == 0 ||
489                  strcmp(score->sce_cmd, "NLST") == 0 ||
490                  strcmp(score->sce_cmd, "MLST") == 0 ||
491                  strcmp(score->sce_cmd, "MLSD") == 0 ||
492                  strcmp(score->sce_cmd, "READDIR") == 0) {
493         status = "L";
494       }
495 
496     } else {
497       status = "A";
498 
499       /* Overwrite the "command", for display purposes */
500       util_sstrncpy(score->sce_cmd, "(authenticating)", sizeof(score->sce_cmd));
501     }
502 
503     if (display_mode != FTPTOP_SHOW_RATES) {
504       int user_namelen, client_namelen, cmd_arglen;
505 
506       user_namelen = str_getscreenlen(score->sce_user, 8);
507       client_namelen = str_getscreenlen(score->sce_client_name, 20);
508       cmd_arglen = str_getscreenlen(score->sce_cmd_arg, FTPTOP_REG_ARG_SIZE);
509 
510       snprintf(buf, sizeof(buf), FTPTOP_REG_DISPLAY_FMT,
511         (unsigned int) score->sce_pid, status,
512         user_namelen, user_namelen, score->sce_user,
513         client_namelen, client_namelen, score->sce_client_name,
514         score->sce_server_addr,
515         show_time(&score->sce_begin_session), score->sce_cmd,
516         cmd_arglen, cmd_arglen, score->sce_cmd_arg);
517       buf[sizeof(buf)-1] = '\0';
518 
519     } else {
520       int user_namelen, client_namelen;
521 
522       user_namelen = str_getscreenlen(score->sce_user, 8);
523       client_namelen = str_getscreenlen(score->sce_client_name, 44);
524 
525       /* Skip sessions unless they are actually transferring data */
526       if (*status != 'U' &&
527           *status != 'D') {
528         continue;
529       }
530 
531       snprintf(buf, sizeof(buf), FTPTOP_XFER_DISPLAY_FMT,
532         (unsigned int) score->sce_pid, status,
533         user_namelen, user_namelen, score->sce_user,
534         client_namelen, client_namelen, score->sce_client_name,
535         (score->sce_xfer_len / 1024.0) / (score->sce_xfer_elapsed / 1000),
536         FTPTOP_XFER_DONE_SIZE, FTPTOP_XFER_DONE_SIZE,
537         *status == 'D' ?
538           calc_percent_done(score->sce_xfer_size, score->sce_xfer_done) :
539           "(n/a)");
540       buf[sizeof(buf)-1] = '\0';
541     }
542 
543     /* Make sure there is enough memory allocated in the session list.
544      * Allocate more if needed.
545      */
546     if (ftp_nsessions > 0 &&
547         ftp_nsessions % chunklen == 0) {
548       ftp_sessions = realloc(ftp_sessions,
549         (ftp_nsessions + chunklen) * sizeof(char *));
550 
551       if (ftp_sessions == NULL) {
552         exit(1);
553       }
554     }
555 
556     ftp_sessions[ftp_nsessions] = calloc(1, strlen(buf) + 1);
557     if (ftp_sessions[ftp_nsessions] == NULL) {
558       exit(1);
559     }
560 
561     util_sstrncpy(ftp_sessions[ftp_nsessions++], buf, strlen(buf) + 1);
562   }
563 
564   scoreboard_close();
565 }
566 
scoreboard_close(void)567 static void scoreboard_close(void) {
568   util_close_scoreboard();
569 }
570 
scoreboard_open(void)571 static int scoreboard_open(void) {
572   int res = 0;
573 
574   res = util_open_scoreboard(O_RDONLY);
575   if (res < 0) {
576     switch (res) {
577       case UTIL_SCORE_ERR_BAD_MAGIC:
578         fprintf(stderr, "%s: error opening scoreboard: bad/corrupted file\n",
579           program);
580         return res;
581 
582       case UTIL_SCORE_ERR_OLDER_VERSION:
583         fprintf(stderr, "%s: error opening scoreboard: bad version (too old)\n",
584           program);
585         return res;
586 
587       case UTIL_SCORE_ERR_NEWER_VERSION:
588         fprintf(stderr, "%s: error opening scoreboard: bad version (too new)\n",
589           program);
590         return res;
591 
592       default:
593         fprintf(stderr, "%s: error opening scoreboard: %s\n",
594           program, strerror(errno));
595         return res;
596     }
597   }
598 
599   ftp_uptime = util_scoreboard_get_daemon_uptime();
600 
601   return 0;
602 }
603 
show_sessions(int use_attributes)604 static void show_sessions(int use_attributes) {
605   time_t now;
606 #if defined(HAVE_CTIME_R)
607   char now_str[32];
608 #else
609   char *now_str = NULL;
610 #endif /* HAVE_CTIME_R */
611   const char *uptime_str = NULL;
612 
613   clear_counters();
614   read_scoreboard();
615 
616   time(&now);
617 
618 #if defined(HAVE_CTIME_R)
619   memset(now_str, '\0', sizeof(now_str));
620   (void) ctime_r(&now, now_str);
621 #else
622   now_str = ctime(&now);
623 #endif /* HAVE_CTIME_R */
624 
625   /* Trim ctime(3)'s trailing newline. */
626   now_str[strlen(now_str)-1] = '\0';
627 
628   uptime_str = show_ftpd_uptime();
629 
630   if (use_attributes) {
631     wclear(stdscr);
632     move(0, 0);
633     attron(A_BOLD);
634   }
635 
636   printw(FTPTOP_VERSION ": %s%s\n", now_str, uptime_str);
637   printw("%u Total FTP Sessions: %u downloading, %u uploading, %u idle\n",
638     ftp_nsessions, ftp_ndownloads, ftp_nuploads, ftp_nidles);
639 
640   if (use_attributes) {
641     attroff(A_BOLD);
642   }
643 
644   printw("\n");
645 
646   if (use_attributes) {
647     attron(A_REVERSE);
648   }
649 
650   if (display_mode != FTPTOP_SHOW_RATES) {
651     printw(FTPTOP_REG_HEADER_FMT, "PID", "S", "USER", "CLIENT", "SERVER",
652       "TIME", FTPTOP_REG_ARG_SIZE, "COMMAND");
653 
654   } else {
655     printw(FTPTOP_XFER_HEADER_FMT, "PID", "S", "USER", "CLIENT", "KB/s", FTPTOP_XFER_DONE_SIZE, "%DONE");
656   }
657 
658   if (use_attributes) {
659     attroff(A_REVERSE);
660   }
661 
662   /* Write out the scoreboard entries. */
663   if (ftp_sessions != NULL &&
664       ftp_nsessions > 0) {
665     register unsigned int i = 0;
666 
667     for (i = 0; i < ftp_nsessions; i++) {
668       printw("%s", ftp_sessions[i]);
669     }
670   }
671 
672   wrefresh(stdscr);
673 }
674 
toggle_mode(void)675 static void toggle_mode(void) {
676   static unsigned int cached_mode = 0;
677 
678   if (cached_mode == 0)
679     cached_mode = display_mode;
680 
681   if (display_mode != FTPTOP_SHOW_RATES) {
682     display_mode = FTPTOP_SHOW_RATES;
683 
684   } else {
685     display_mode = cached_mode;
686   }
687 }
688 
show_version(void)689 static void show_version(void) {
690   fprintf(stdout, FTPTOP_VERSION "\n");
691   exit(0);
692 }
693 
usage(void)694 static void usage(void) {
695   fprintf(stdout, "usage: ftptop [options]\n\n");
696   fprintf(stdout, "\t-A      \t\tshow only authenticating sessions\n");
697   fprintf(stdout, "\t-a      \t\tignores authenticating connections when listing\n");
698   fprintf(stdout, "\t-b      \t\tbatch mode\n");
699   fprintf(stdout, "\t-D      \t\tshow only downloading sessions\n");
700   fprintf(stdout, "\t-d <num>\t\trefresh delay in seconds\n");
701   fprintf(stdout, "\t-f      \t\tconfigures the ScoreboardFile to use\n");
702   fprintf(stdout, "\t-h      \t\tdisplays this message\n");
703   fprintf(stdout, "\t-I      \t\tshow only idle connections\n");
704   fprintf(stdout, "\t-i      \t\tignores idle connections\n");
705   fprintf(stdout, "\t-n <num>\t\tnumber of iterations\n");
706   fprintf(stdout, "\t-S      \t\tshow only sessions for this ServerName\n");
707   fprintf(stdout, "\t-U      \t\tshow only uploading sessions\n");
708   fprintf(stdout, "\t-V      \t\tshows version\n");
709   fprintf(stdout, "\n");
710   fprintf(stdout, "  Use the 't' key to toggle between \"regular\" and \"transfer speed\"\n");
711   fprintf(stdout, "  display modes. Use the 'q' key to quit.\n\n");
712   exit(0);
713 }
714 
verify_scoreboard_file(void)715 static void verify_scoreboard_file(void) {
716   struct stat sbuf;
717 
718   if (stat(util_get_scoreboard(), &sbuf) < 0) {
719     fprintf(stderr, "%s: unable to stat '%s': %s\n", program,
720       util_get_scoreboard(), strerror(errno));
721     exit(1);
722   }
723 }
724 
main(int argc,char * argv[])725 int main(int argc, char *argv[]) {
726   unsigned int iteration_count = 0;
727 
728   /* Process command line options. */
729   process_opts(argc, argv);
730 
731   /* Verify that the scoreboard file is usable. */
732   verify_scoreboard_file();
733 
734   /* Install signal handlers. */
735   signal(SIGINT, finish);
736   signal(SIGTERM, finish);
737 
738 #if defined(PR_USE_NLS) && defined(HAVE_LOCALE_H)
739   (void) setlocale(LC_ALL, "");
740 #endif
741 
742   /* Initialize the display. */
743   initscr();
744   if (batch_mode == TRUE) {
745     scrollok(stdscr, TRUE);
746 
747   } else {
748     cbreak();
749   }
750   noecho();
751 #if !defined(HAVE_NCURSES)
752   nodelay(stdscr, TRUE);
753 #endif /* HAVE_NCURSES */
754   curs_set(0);
755 
756   /* Paint the initial display. */
757   show_sessions(batch_mode == FALSE);
758 
759   /* Loop endlessly. */
760   while (TRUE) {
761     if (batch_mode == TRUE) {
762       sleep(delay);
763 
764     } else {
765       int c = -1;
766 
767 #if defined(HAVE_NCURSES)
768       (void) halfdelay(delay * 10);
769 #else
770       sleep(delay);
771 #endif /* HAVE_NCURSES */
772 
773       c = getch();
774       if (c != -1) {
775         if (tolower(c) == 'q') {
776           break;
777         }
778 
779         if (tolower(c) == 't') {
780           toggle_mode();
781         }
782       }
783     }
784 
785     show_sessions(batch_mode == FALSE);
786 
787     if (batch_mode == TRUE) {
788       /* Only worry about the number of iterations if a limit has been
789        * set via `-n`.
790        */
791       if (max_iterations > 0) {
792         iteration_count++;
793 
794         if (iteration_count >= max_iterations) {
795           break;
796         }
797       }
798     }
799   }
800 
801   /* done */
802   finish(0);
803   return 0;
804 }
805 
806 #else /* defined(HAVE_CURSES) || defined(HAVE_NCURSES) */
807 
808 #include <stdio.h>
809 
main(int argc,char * argv[])810 int main(int argc, char *argv[]) {
811   fprintf(stdout, "%s: no curses or ncurses library on this system\n", program);
812   return 1;
813 }
814 
815 #endif /* defined(HAVE_CURSES) || defined(HAVE_NCURSES) */
816