File Coverage

blib/lib/Terminal/Identify.pm
Criterion Covered Total %
statement 21 204 10.2
branch 0 62 0.0
condition 0 21 0.0
subroutine 7 23 30.4
pod 0 16 0.0
total 28 326 8.5


line stmt bran cond sub pod time code
1             package Terminal::Identify;
2              
3             # Load the basic Perl pragmas.
4 1     1   71824 use 5.026000;
  1         4  
5 1     1   8 use strict;
  1         2  
  1         33  
6 1     1   6 use warnings;
  1         9  
  1         31  
7              
8             # Load the Perl pragma Exporter.
9 1     1   5 use vars qw(@ISA @EXPORT @EXPORT_OK);
  1         2  
  1         89  
10 1     1   6 use Exporter 'import';
  1         9  
  1         82  
11              
12             # Export the implemented subroutines and the global variable.
13             our @EXPORT = qw(
14             whichterminalami
15             whichtermami
16             which_terminal
17             identify_terminal
18             terminal_identify
19             $OutputFormat
20             );
21              
22             # Base class of this module.
23             our @ISA = qw(Exporter);
24              
25             # Set the package version.
26             our $VERSION = '0.28';
27              
28             # Load the Perl modules.
29 1     1   587 use POSIX qw(ttyname);
  1         6847  
  1         6  
30              
31             # Set the global output format.
32             our $OutputFormat = "";
33              
34             # Set the global term hash variable.
35             our %termhash = ();
36              
37             # Define the Perl BEGIN block.
38             BEGIN {
39             # Set the subroutine aliases.
40 1     1   1552 *whichtermami = \&whichterminalami;
41 1         3 *which_terminal = \&whichterminalami;
42 1         2 *identify_terminal = \&whichterminalami;
43 1         2845 *terminal_identify = \&whichterminalami;
44             };
45              
46             # ++++++++++++++++++++++++++++++++
47             # Perform some preliminary checks.
48             # ++++++++++++++++++++++++++++++++
49              
50             # Check if the operating system is Windows.
51             if ($^O eq "MSWin32") {
52             # Print a message into the terminal window.
53             print "This module works not on Windows. Bye.\n";
54             # Exit the script with error code 1.
55             exit 1;
56             };
57              
58             # Check if the Linux command 'which' exists.
59             my $exitcode = system("which which > /dev/null 2>&1");
60             if ($exitcode != 0) {
61             # Print a message into the terminal window.
62             print "The Linux command 'which' does not exist. Bye.\n";
63             # Exit the script with error code 2.
64             exit 1;
65             };
66              
67             # Check if the Linux command 'grep' exists.
68             if (!defined `which grep 2>/dev/null` || `which grep 2>/dev/null` eq "") {
69             # Print a message into the terminal window.
70             print "The Linux command 'grep' does not exist. Bye.\n";
71             # Exit the script with error code 3.
72             exit 3;
73             };
74              
75             # Check if the Linux command 'users' exists.
76             if (!defined `which users 2>/dev/null` || `which users 2>/dev/null` eq "") {
77             # Print a message into the terminal window.
78             print "The Linux command 'grep' does not exist. Bye.\n";
79             # Exit the script with error code 4.
80             exit 4;
81             };
82              
83             # Check if the Linux command 'ps' exists.
84             if (!defined `which ps 2>/dev/null` || `which ps 2>/dev/null` eq "") {
85             # Print a message into the terminal window.
86             print "The Linux command 'ps' does not exist. Bye.\n";
87             # Exit the script with error code 5.
88             exit 5;
89             };
90              
91             # Set some variables.
92             our $FN_PASSWD = "/etc/passwd";
93             our $FN_SHELLS = "/etc/shells";
94              
95             # Check if passwd exists.
96             if (! -e $FN_PASSWD) {
97             die "$!, $FN_PASSWD not exists.\n";
98             };
99              
100             # Check if shells exists.
101             if (! -e $FN_SHELLS) {
102             die "$!, $FN_SHELLS not exists.\n";
103             };
104              
105             # ++++++++++++++++++++++++++++++++++++++++++
106             # Create a dictionary from __DATA__ section.
107             # ++++++++++++++++++++++++++++++++++++++++++
108             my $line = "";
109             my $key = "";
110             my $value = "";
111             while () {
112             if (/# Begin terminals/../# End terminals/) {
113             next if /# Begin terminals/ || /# End terminals/;
114             $line = $_;
115             my @parts = split /=>/, $line, 2;
116             $key = $parts[0];
117             $key =~ s/^\s+|\s+$//g;
118             $value = $parts[1];
119             $value =~ s/^\s+|\s+$//g;
120             $termhash{$key} = $value;
121             };
122             };
123              
124             # ============================================================================ #
125             # Subroutine trim #
126             # #
127             # Description: #
128             # The subroutine removes white spaces from both ends of a string. This is done #
129             # by a logical or operation and using \s from regular expressions. Anchors are #
130             # begin ^ of string and end $ of string. #
131             # #
132             # @argument: $_[0] => $string String to trim (scalar) #
133             # @return: $string Trimmed string (scalar) #
134             # ============================================================================ #
135             sub trim {
136             # Assign the function argument to the local string variable $str.
137 0 0 0 0 0   my $string = ((defined $_[0] && $_[0] ne "") ? $_[0] : "");
138             # Trim the string from the left side and the right side.
139 0           $string =~ s/^\s+|\s+$//g;
140             # Return the trimmed string.
141 0           return $string;
142             };
143              
144             # ============================================================================ #
145             # Subroutine search_brackets #
146             # #
147             # Description: #
148             # The subroutine adds the square brackets [ and ] to the searchstring for #
149             # use with grep. E.g. shell becomes [s]hell. Therefor the string must have #
150             # a minumum length of 1. #
151             # #
152             # @argument: $_[0] => $str Searchstring (scalar) #
153             # @return: $str Searchstring with square brackets (scalar) #
154             # ============================================================================ #
155             sub search_brackets {
156             # Initialise the local variable $searchstring.
157 0     0 0   my $searchstring = "";
158             # Check if $searchstring is defined and is not empty.
159 0 0 0       if ((defined $_[0]) && (length($_[0]) > 0)) {
160             # Assign the subroutine argument to the local variable.
161 0           $searchstring = $_[0];
162             } else {
163             # Return string of length 0.
164 0           return "";
165             }
166             # Add square brackets [ and ] to the given string.
167 0           substr($searchstring, 0, 0) = '[';
168 0           substr($searchstring, 2, 0) = ']';
169             # Return the modified string.
170 0           return $searchstring;
171             };
172              
173             # ============================================================================ #
174             # Subroutine read_file #
175             # #
176             # Description: #
177             # Read the complete content of a file in one chunk. The retrieved content is #
178             # stored in a string variable. #
179             # #
180             # @argument: $_[0] => $file Text filename (scalar) #
181             # @return: $content File content (scalar) #
182             # ============================================================================ #
183             sub read_file {
184             # Assign the function argument to the local variable.
185 0     0 0   my $file = $_[0];
186             # Initialise the return variable.
187 0           my $content = "";
188             # If file not exists, return an empty string.
189 0 0         if (! -f $file) {
190             # Return an empty string.
191 0           return $content;
192             };
193             # Open a file handler for reading the file.
194 0           open(my $fh, "<", $file);
195             # Read the complete content from the file.
196 0           $content = do {local $/; <$fh>};
  0            
  0            
197             # Close the file handler.
198 0           close($fh);
199             # Return the file content.
200 0           return $content;
201             };
202              
203             # ============================================================================ #
204             # Subroutine login_users #
205             # #
206             # Description: #
207             # Split the logged-in users by sperator space and store them to an array. #
208             # #
209             # @argument: None #
210             # @return: $user_arr Array with the logged-in users (array) #
211             # ============================================================================ #
212             sub login_users {
213             # Get the logged-in users from Linux command users.
214 0     0 0   my $user = `users`;
215             # Split the logged-in users and store them to the array.
216 0           my @user_array = split ' ', $user;
217             # Return the array with the logged-in users.
218 0           return @user_array;
219             };
220              
221             # ============================================================================ #
222             # Subroutine login_shells #
223             # #
224             # Description: #
225             # Read the content of the file /etc/shells and store the content in a string #
226             # variable. Then the content is splited up into lines. In a loop the lines #
227             # are used where a valid login shell is given. Then the path is removed from #
228             # the line with the login shell. The login shell is added to an array. In a #
229             # last step douple entries are removed from the array. #
230             # #
231             # @argument: None #
232             # @return: @login_shells Array with valid login shells (array) #
233             # ============================================================================ #
234             sub login_shells {
235             # Declare the login shells array.
236 0     0 0   my @login_shells;
237             # Set the file for reading.
238 0           my $file = $FN_SHELLS;
239             # Read the content from the file.
240 0           my $content = read_file($file);
241             # Loop over the lines of the file content.
242 0           foreach (split '\n', $content) {
243             # Trim the new line.
244 0           my $line = trim($_);
245             # Use only a line with a valid login shell.
246 0 0         if ($line =~ /^\/.*\/(.*)$/) {
247             # Remove the path from the line with the login shell.
248 0           my ($shell) = $line =~ /^\/.*\/(.*)$/;
249             # Add the login shell to the array.
250 0           push(@login_shells, $shell);
251             };
252             };
253             # Remove the double entries from the array.
254 0           my %login_hash = map({$_, 1} @login_shells);
  0            
255 0           @login_shells = keys %login_hash;
256             # Return the array with the unique login shells.
257 0           return @login_shells;
258             };
259              
260             # ============================================================================ #
261             # Subroutine get_ppid #
262             # #
263             # Description: #
264             # Determine the PPID of the calling Perl script using the Linux command ps. #
265             # #
266             # @argument: $_[0] => $pid PID (scalar) #
267             # @return: $ppid PPID (scalar) #
268             # ============================================================================ #
269             sub get_ppid {
270             # Assign the subroutine argument to the local variable $pid.
271 0     0 0   my $pid = $_[0];
272             # Store the output of the Linux command ps in the local variable $ppid.
273 0           my $ppid = `ps --no-headers -o ppid:1 -p $pid --sort lstart 2>/dev/null`;
274             # Split a multiline Perl scalar with the PPID's in parts.
275 0           my @parts = split /\s+/, $ppid;
276             # If there is more than one PPID, use the first PPID.
277 0           $ppid = $parts[0];
278             # Trim the Perl scalar with the PPID.
279 0           $ppid = trim($ppid);
280             # Return the PPID.
281 0           return $ppid;
282             };
283              
284             # ============================================================================ #
285             # Subroutine get_termpath #
286             # ============================================================================ #
287             sub get_termpath {
288             # Initialise the local variables.
289 0     0 0   my $fileno = fileno(STDIN);
290 0           my $term_path = "";
291             # Get the terminal path.
292             # $term_path = TermPath();
293 0           $term_path = ttyname($fileno);
294             # Check the terminal path
295 0 0         $term_path = (defined $term_path ? $term_path : "");
296             # Return the terminal path.
297 0           return $term_path;
298             };
299              
300             # ============================================================================ #
301             # Subroutine get_user_name #
302             # ============================================================================ #
303             sub get_username {
304             # Initialise the username.
305 0     0 0   my $username = "";
306             # Define the methods for getting the username.
307 0           my $method1 = getlogin();
308 0           my $method2 = (getpwuid($<))[0];
309 0           my $method3 = $ENV{LOGNAME};
310 0           my $method4 = $ENV{USER};
311             # Extract the username.
312 0   0       $username = ($method1 || $method2 || $method3 || $method4);
313             # Return the username.
314 0           return $username;
315             };
316              
317             # ============================================================================ #
318             # Subroutine term_user_shell #
319             # #
320             # Description: #
321             # Create a multi dimensional array with term, user and shell. #
322             # #
323             # @arguments: $_[0] => $passwd_content Content of passwd (scalar) #
324             # @{$_[1]} => @user_array User array (array) #
325             # @{$_[2]} => @shell_array Shell array (array) #
326             # @return: @result_array Array with the result (array) #
327             # ============================================================================ #
328             sub term_user_shell {
329             # Assign the subroutine arguments to the local variables.
330 0     0 0   my $passwd_content = $_[0];
331 0           my @user_array = @{$_[1]};
  0            
332 0           my @shell_array = @{$_[2]};
  0            
333             # Initialise variable $term.
334 0           my $term = "";
335             # Get the username related to the terminal.
336 0           my $username = get_username();
337             # Declare the return array.
338 0           my @result_array = ();
339             # Get the terminal path.
340 0           my $term_path = get_termpath();
341             # If $term_path is not defined or empty set $term to ?.
342 0 0 0       if (!defined $term_path || $term_path eq "") {
343             # Set variable $term_path.
344 0           $term = "?";
345             } else {
346             # Check on pts and tty.
347 0 0         if ($term_path =~ /^.*\/dev\/(pts\/\d+)$/) {
    0          
348             # Extract the pseudo terminal slave.
349 0           ($term) = $term_path =~ /^.*\/dev\/(pts\/\d+)$/;
350             } elsif ($term_path =~ /^.*\/dev\/(tty\d+)$/) {
351             # Extract the terminal TeleTYpewriter.
352 0           ($term) = $term_path =~ /^.*\/dev\/(tty\d+)$/;
353             } else {
354 0           $term = "?";
355             };
356             };
357             # Loop over the array with the lines of $passwd_content.
358 0           foreach (split '\n', $passwd_content) {
359             # Get user and shell from each line of the content.
360 0           my ($user) = $_ =~ /^(.*?):.*/;
361 0           my ($shell) = $_ =~ /^.*\/(.*)$/;
362 0 0 0       if ($shell ne "nologin" && $shell ne "sync" && $shell ne "false") {
      0        
363             # Check user and shell against the given arrays.
364 0 0 0       if (grep(/^$user$/, @user_array) &&
    0          
365             grep(/^$shell$/, @shell_array)) {
366             # Assemble a new list.
367 0           my @tmp = ($term, $user, $shell);
368             # Add data to array.
369 0           push(@result_array, \@tmp);
370             } elsif ($user eq $username) {
371             # Assemble a new list.
372 0           my @tmp = ($term, $user, $shell);
373             # Add data to array.
374 0           push(@result_array, \@tmp);
375             };
376             };
377             };
378             # Return the array.
379 0           return @result_array;
380             };
381              
382             # ============================================================================ #
383             # Subroutine terminal_process #
384             # #
385             # Description: #
386             # Get the process related to term, user and shell. #
387             # #
388             # @argument: @{$_[0]} => @data Array with term, user and shell (array) #
389             # @return: @result Array with matching processes (array) #
390             # ============================================================================ #
391             sub terminal_process {
392             # Assign the subroutine argument to the local variable.
393 0     0 0   my @data = @{$_[0]};
  0            
394             # Initialise the return array.
395 0           my @match = ();
396             # Set command.
397 0           my $ps_cmd = "ps aux --sort lstart 2>/dev/null";
398             # Loop over the elements of the array.
399 0           foreach my $item (@data) {
400             # Get term, user and shell from the array.
401 0           my $term = $item->[0];
402 0           my $user = $item->[1];
403 0           my $shell = $item->[2];
404 0           $term = search_brackets($term);
405 0           $user = search_brackets($user);
406 0           $shell = search_brackets($shell);
407             # Search for processes which is matching shell, user and terminal.
408 0           my $grep_shell = "grep $shell 2>/dev/null";
409 0           my $grep_user = "grep $user 2>/dev/null";
410 0           my $grep_term = "grep $term 2>/dev/null";
411 0           my $process = `${ps_cmd} | ${grep_shell} | ${grep_user} | ${grep_term}`;
412             # Check if the result is not empty.
413 0 0         if ($process ne "") {
414             # Add the process to the return array.
415 0           push(@match, $process);
416             };
417             };
418             # If array @match is empty, it is not a local process.
419 0 0         if (@match == 0) {
420             # Loop over the elements of the array.
421 0           foreach my $item (@data) {
422             # Get term and user from the array.
423 0           my $term = $item->[0];
424 0           my $user = $item->[1];
425             # Add square brackets to searchstrings.
426 0           $term = search_brackets($term);
427 0           $user = search_brackets($user);
428             # Search for processes which is matching shell and terminal.
429 0           my $grep_term = "grep $term 2>/dev/null";
430 0           my $grep_user = "grep $user 2>/dev/null";
431 0           my $process = `${ps_cmd} | ${grep_user} | ${grep_term} | grep "?" | grep "sshd"`;
432             # Check if the result is not empty.
433 0 0         if ($process ne "") {
434             # Add the process to the return array.
435 0           push(@match, $process);
436             };
437             };
438             };
439             # Return the array.
440 0           return @match;
441             };
442              
443             # ============================================================================ #
444             # Subroutine terminal_command_line #
445             # #
446             # Description: #
447             # Use the Linux command ps to get the command line of the process which is #
448             # related to the terminal in use. #
449             # #
450             # @argument $_[0] => $ppid PPID (scalar) #
451             # @return $termproc Terminal command line (scalar) #
452             # ============================================================================ #
453             sub terminal_command_line {
454             # Assign the subroutine argument to the local variable $ppid.
455 0     0 0   my $ppid = $_[0];
456             # Get the command column from the ps output.
457 0           my $termproc = `ps --no-headers -o cmd:1 -p $ppid --sort lstart 2>/dev/null`;
458             # Trim the command line output string.
459 0           $termproc = trim($termproc);
460             # Return the process related to the terminal in use.
461 0           return $termproc
462             };
463              
464             # ============================================================================ #
465             # Subroutine terminal_identifier #
466             # #
467             # Description: #
468             # Identify the process command line of the terminal in use. #
469             # #
470             # @argument: None #
471             # @return: $terminal_command Process command line (scalar) #
472             # ============================================================================ #
473             sub terminal_identifier {
474             # Declare the return variable $terminal_command.
475 0     0 0   my $terminal_command;
476             # Set the filename.
477 0           my $filename = $FN_PASSWD;
478             # Get the logged-in users.
479 0           my @user_arr = login_users();
480             # Get the available login shells.
481 0           my @shell_arr = login_shells();
482             # Read the file /etc/passwd in and store it in the variable $content.
483 0           my $content = read_file($filename);
484             # Create the array with user and shell.
485 0           my @result = term_user_shell($content, \@user_arr, \@shell_arr);
486             # check if @result is empty.
487 0 0         if (@result == 0) {
488             # Set terminal command.
489 0           $terminal_command = "";
490             # Return an empty string.
491 0           return $terminal_command;
492             };
493             # Create the array with user and term.
494 0           my @match = terminal_process(\@result);
495             # Check if array is empty.
496 0 0         if (@match > 0) {
497             # Split up the process by lines and white spaces.
498 0           my @columns = split /\s+/, $match[0];
499             # Extract the PID from the array.
500 0           my $pid = $columns[1];
501             # Get the PPID from the command ps.
502 0           my $ppid = get_ppid($pid);
503             # Get the terminal in use from the command ps.
504 0           $terminal_command = terminal_command_line($ppid);
505             } else {
506             # Set terminal command.
507 0           $terminal_command = "";
508             };
509             # Return the terminal process command.
510 0           return $terminal_command;
511             };
512              
513             # ============================================================================ #
514             # Subroutine get_terminal_path #
515             # ============================================================================ #
516             sub get_terminal_path {
517             # Assign the subroutine argument to the local variable.
518 0     0 0   my $terminal_command = $_[0];
519             # Initialise the return variable.
520 0           my $terminal_path = "";
521             # Define the regular expression for white spaces.
522 0           my $re_ws = qr/\s+/;
523             # Define the regular expression for command line arguments.
524 0           my $re_args = qr/(?<= )(-.*?)(?= |$)/;
525             # Check terminal command on white spaces.
526 0 0         if ($terminal_command =~ /$re_ws/) {
527             # Initialise the progs array.
528 0           my @progs = ();
529             # Assign terminal command to raw string.
530 0           my $raw_string = $terminal_command;
531             # Remove all terminal command line arguments.
532 0           $raw_string =~ s/$re_args//g;
533             # Split the terminal command line by white spaces.
534 0           my @parts = split /$re_ws/, $raw_string;
535             # Loop over the elements of the array.
536 0           foreach (@parts) {
537             # Check if the element is an executable.
538 0 0         if (`which $_` ne "") {
539             # Add executable to new array.
540 0           push(@progs, $_);
541             };
542             };
543             # Check number of executables.
544 0 0         if (@progs == 0) {
545             # Assign first element to path.
546 0           $terminal_path = $parts[0];
547             } else {
548 0 0         if (@progs > 1) {
549             # Assign first element to path.
550 0           $terminal_path = $progs[1];
551             } else {
552             # Assign second element to path.
553 0           $terminal_path = $progs[0];
554             };
555             };
556             } else {
557             # Set variable $terminal_path.
558 0           $terminal_path = $terminal_command;
559             };
560             # Return the terminal name.
561 0           return $terminal_path;
562             };
563              
564             # ============================================================================ #
565             # Subroutine get_terminal_name #
566             # ============================================================================ #
567             sub get_terminal_name {
568             # Assign the subroutine argument to the local variable.
569 0     0 0   my $terminal_path = $_[0];
570             # Initialise the return variable.
571 0           my $terminal_name = "";
572             # Define the regular expression for program pathes.
573 0           my $re_pp = qr/^\/.*\//;
574             # Check the variable $terminal_path on the existence of a path.
575 0 0         if ($terminal_path =~ /$re_pp/) {
576             # Remove the path from variable $terminal_path.
577 0           $terminal_name = $terminal_path =~ s/$re_pp//r;
578             } else {
579             # Assign variable $terminal_path to variable $terminal_name.
580 0           $terminal_name = $terminal_path;
581             };
582             # Return the terminal name.
583 0           return $terminal_name;
584             };
585              
586             # ============================================================================ #
587             # Subroutine get_terminal_ftn #
588             # ============================================================================ #
589             sub get_terminal_ftn {
590             # Assign the subroutine argument to the local variable.
591 0     0 0   my $terminal_name = $_[0];
592             # Initialise the return variable.
593 0           my $terminal_ftn = "";
594             # Check terminal name.
595 0 0         if (defined $termhash{$terminal_name}) {
596             # Get the friendly terminal name.
597 0           $terminal_ftn = $termhash{$terminal_name};
598             } else {
599             # Set the friendly terminal name.
600 0           $terminal_ftn = $terminal_name;
601             };
602             # Return the friendly terminal name.
603 0           return $terminal_ftn;
604             };
605              
606             # ============================================================================ #
607             # Subroutine whichterminalami #
608             # #
609             # Description: #
610             # Identify the terminal emulator. #
611             # #
612             # @argument: $_[0] => $flag Output format flag (scalar) #
613             # @return: $terminal Terminal name (scalar) #
614             # ============================================================================ #
615             sub whichterminalami {
616             # Initialise the output format flag variable.
617 0     0 0   my $flag = "";
618             # Set the output format flag.
619 0 0         if ($OutputFormat ne "") {
620             # Set the output format flag variable based on the global variable.
621 0           $flag = $OutputFormat;
622             } else {
623             # Get the output format flag variable from the subroutine argument.
624 0 0         $flag = (defined $_[0] ? $_[0] : '');
625             };
626             # Initialise the terminal variables.
627 0           my $terminal = "";
628 0           my $terminal_ftn = "";
629 0           my $terminal_name = "";
630 0           my $terminal_path = "";
631             # Identify the terminal process command line.
632 0           my $terminal_command = terminal_identifier();
633             # If terminal process command line is an empty string it is an unknown terminal.
634 0 0         if ($terminal_command eq "") {
635             # Return unknown terminal.
636 0           return "Unknown Terminal";
637             };
638             # Get the terminal path.
639 0           $terminal_path = get_terminal_path($terminal_command);
640             # Get the terminal name.
641 0           $terminal_name = get_terminal_name($terminal_path);
642             # Get the friendly terminal name.
643 0           $terminal_ftn = get_terminal_ftn($terminal_name);
644             # Return remote console or system console based on sshd or login.
645 0 0         if (($terminal_name) =~ /^sshd.*/) {
    0          
646 0           return "Remote Console";
647             } elsif (($terminal_name) =~ /^login.*/) {
648 0           return "System Console";
649             };
650             # Return the terminal string based on the flag setting.
651 0 0         if ($flag eq "") {
    0          
    0          
    0          
652 0           $terminal = $terminal_name;
653             } elsif ($flag eq "FTN") {
654 0           $terminal = $terminal_ftn;
655             } elsif ($flag eq "PATH") {
656 0           $terminal = $terminal_path;
657             } elsif ($flag eq "PROC") {
658 0           $terminal = $terminal_command;
659             };
660             # Return the found terminal string.
661 0           return $terminal;
662             };
663              
664             1;
665              
666             __DATA__