#!/usr/bin/perl # # SMArT # # Main program file # # Copyright 2001 Wilmer van der Gaast # # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # $redirected = 0; $server_id = 'Server: SMArT/Perl/@MARS_NWE_VERSION@'; do( '@MARS_NWE_INSTALL_FULL_CONFDIR@/smart.conf' ) or die "Could not load @MARS_NWE_INSTALL_FULL_CONFDIR@/smart.conf: $@ $!"; close( STDERR ); # Prefix all raw STDERR from helper tools with timestamp/component before it # reaches smart.log. This also catches output from nwbols/nwbpset/nwpasswd # and systemctl warnings. my $smart_stderr_filter = "perl -MPOSIX=strftime -ne 'chomp; " . "my \\$v=\\$ENV{SMART_VERSION}||q{0.99.pl28}; " . "my \\$f=\\$ENV{SMART_LOG_FILE}||q{stderr}; " . "print strftime(q{[%Y-%m-%d %H:%M:%S]}, localtime), qq{ [ERROR] [SMArT \\$v] [\\$f] \\$_\\n};' >> " . quotemeta( $smart_log_path ); $ENV{SMART_VERSION} = defined( $smart_version ) && $smart_version ne '' ? $smart_version : '0.99.pl28'; $ENV{SMART_LOG_FILE} = 'stderr'; open( STDERR, '|-', $smart_stderr_filter ) or open( STDERR, '>>' . $smart_log_path ) or die "Could not open $smart_log_path: $!"; $ENV{HOME} = '@MARS_NWE_INSTALL_FULL_CONFDIR@'; $smart_libexec_dir = '@MARS_NWE_INSTALL_FULL_LIBEXECDIR@'; $smart_libexec_dir =~ s#/*$##; $smart_control_path = $smart_libexec_dir . '/control' unless defined $smart_control_path; $mars_nwe_service = '@MARS_NWE_SYSTEMD_SERVICE@' unless defined $mars_nwe_service; $smart_systemctl_path = '@SYSTEMCTL_EXECUTABLE@' unless defined $smart_systemctl_path; $smart_admin_group = '@MARS_NWE_SMART_ADMIN_GROUP@' unless defined $smart_admin_group; $smart_admin_group = 'root' if ! defined( $smart_admin_group ) || $smart_admin_group eq '' || $smart_admin_group =~ /^\@MARS_NWE_SMART_ADMIN_GROUP\@$/; $l = ; $l =~ s/[\n\r]//g; $request_uri = ""; $post_body = ""; %hl = (); @c = split( ' ', $l ); if( scalar( @c ) > 2 ) { $request_uri = $c[1]; while( keys( %h ) < 50 ) { $l = ; $l =~ s/[\n\r]//g; if( $l eq '' ) { last; } $n = $l; $n =~ s/:[^:]*$//g; $v = $l; $v =~ s/^[^:]*://g; $v =~ s/^\s+//; $v =~ s/\s+$//; $h{$n} = $v; $hl{lc( $n )} = $v; } } $c[0] = uc( $c[0] ); $request_method = $c[0]; if( $request_method eq 'POST' ) { my $content_length = 0; if( defined( $hl{'content-length'} ) && $hl{'content-length'} =~ /^[0-9]+$/ ) { $content_length = int( $hl{'content-length'} ); } if( $content_length > 0 && $content_length < 8192 ) { read( STDIN, $post_body, $content_length ); } } if( $request_method ne 'GET' && $request_method ne 'POST' ) { error( 501 ); } @p = split( '\?', $c[1] ); $cc = $c[1]; $cc =~ s/[^\?]*\?//; $c = substr( shift( @p ), 1 ); parse_params( $p[0] ); parse_params( $post_body ) if $request_method eq 'POST'; @c = split( '/', $c ); if( $c[0] eq 'login' ) { handle_login_route(); exit; } if( $c[0] eq 'logout' ) { handle_logout_route(); exit; } # Static assets must be available before login, otherwise the login page # cannot load the SMArT logo and icons. if( $c[0] eq 'static' ) { do( $smart_libexec_dir . '/static.pl' ); handle_request(); exit; } if( ! valid_session() ) { redirect( '/login' ); exit; } if( ( $c[0] eq 'service' && $c[1] eq 'control' ) || ( $c[0] eq 'cgi-bin' && $c[1] eq 'control' ) ) { # Service control must run before drop_root(). # /service/control is the preferred path; /cgi-bin/control is kept as a legacy alias. my $rv = do( $smart_control_path ); if( ! defined $rv ) { error( 500 ); } handle_request(); exit; } elsif( $c[0] eq 'apply' ) { do( $smart_libexec_dir . '/readconfig.pl' ); do( $smart_libexec_dir . '/apply.pl' ); handle_request(); exit; } elsif( $c[0] eq 'settings' ) { do( $smart_libexec_dir . '/readconfig.pl' ); } drop_root(); if( $c[0] eq '' ) { print < SMArT EOF exit; } elsif( $c[0] eq 'static' ) { do( $smart_libexec_dir . '/static.pl' ); } elsif( $c[0] eq 'settings' ) { do( $smart_libexec_dir . '/settings.pl' ); } else { error( 500 ); } handle_request(); exit; ########################################## ##### END OF MAIN PROCEDURES FOLLOW ##### ########################################## sub smart_log_line( $$$ ) { my( $level, $file, $msg ) = @_; $level = 'INFO' unless defined( $level ) && $level ne ''; $file = 'smart' unless defined( $file ) && $file ne ''; $msg = '' unless defined( $msg ); my( $sec, $min, $hour, $mday, $mon, $year ) = localtime( time() ); my $ts = sprintf( "%04d-%02d-%02d %02d:%02d:%02d", $year + 1900, $mon + 1, $mday, $hour, $min, $sec ); my $version = defined( $smart_version ) && $smart_version ne '' ? $smart_version : '0.99.pl28'; if( open( my $fh, '>>', $smart_log_path ) ) { print( $fh '[' . $ts . '] [' . $level . '] [SMArT ' . $version . '] [' . $file . '] ' . $msg . "\n" ); close( $fh ); } } sub smart_auth_log( $ ) { my $msg = $_[0]; $msg = '' unless defined $msg; my( $sec, $min, $hour, $mday, $mon, $year ) = localtime( time() ); my $ts = sprintf( "%04d-%02d-%02d %02d:%02d:%02d", $year + 1900, $mon + 1, $mday, $hour, $min, $sec ); my $version = defined( $smart_version ) && $smart_version ne '' ? $smart_version : '0.99.pl28'; if( open( my $fh, '>>', $smart_log_path ) ) { print( $fh '[' . $ts . '] [INFO] [SMArT ' . $version . '] [smart] ' . $msg . "\n" ); close( $fh ); } } sub parse_params( $ ) { my $qs = $_[0]; return if ! defined( $qs ) || $qs eq ''; my @items = split( '&', $qs ); foreach my $item ( @items ) { my $n = $item; my $v = $item; $n =~ s/=.*//; $v =~ s/^[^=]*=?//; $n =~ s/\+/ /g; $v =~ s/\+/ /g; $n =~ s/%([0-9A-Fa-f][0-9A-Fa-f])/pack('c',hex($1))/gie; $v =~ s/%([0-9A-Fa-f][0-9A-Fa-f])/pack('c',hex($1))/gie; $p{$n} = $v; } } sub smart_html_escape( $ ) { my $s = $_[0]; $s = '' unless defined $s; $s =~ s/&/&/g; $s =~ s//>/g; $s =~ s/"/"/g; return $s; } sub session_timeout() { return $smart_session_timeout if defined( $smart_session_timeout ) && $smart_session_timeout =~ /^[0-9]+$/ && $smart_session_timeout > 0; return 3600; } sub session_dir() { my $dir = defined( $smart_session_dir ) && $smart_session_dir ne '' ? $smart_session_dir : '/run/mars-nwe-webui'; if( ! -d $dir ) { if( ! mkdir( $dir, 0700 ) ) { smart_auth_log( 'could not create session dir ' . $dir . ': ' . $! ); } } if( -d $dir ) { chmod( 0700, $dir ); } else { smart_auth_log( 'session dir is not available: ' . $dir ); } return $dir; } sub session_token() { my $token = ''; if( open( my $fh, '<', '/dev/urandom' ) ) { my $buf = ''; read( $fh, $buf, 24 ); close( $fh ); $token = unpack( 'H*', $buf ); } if( $token eq '' ) { $token = sprintf( "%08x%08x%08x%08x", time(), $$, int( rand( 0xffffffff ) ), int( rand( 0xffffffff ) ) ); } $token =~ s/[^A-Fa-f0-9]//g; return $token; } sub session_file( $ ) { my $token = $_[0]; $token = '' unless defined $token; $token =~ s/[^A-Fa-f0-9]//g; return '' if $token eq ''; return session_dir() . '/' . $token; } sub cookie_session_id() { my $cookie = defined( $hl{'cookie'} ) ? $hl{'cookie'} : ''; foreach my $part ( split( /;/, $cookie ) ) { $part =~ s/^\s+//; $part =~ s/\s+$//; if( $part =~ /^SMArT_SID=([A-Fa-f0-9]+)$/ ) { return $1; } } return ''; } sub valid_session() { my $token = cookie_session_id(); my $file = session_file( $token ); return 0 if $token eq ''; return 0 if $file eq ''; if( ! -f $file ) { smart_auth_log( 'session cookie exists but file is missing: ' . $file ); return 0; } my @st = stat( $file ); if( scalar( @st ) == 0 ) { smart_auth_log( 'could not stat session file: ' . $file ); return 0; } if( time() - $st[9] > session_timeout() ) { unlink( $file ); smart_auth_log( 'session expired: ' . $file ); return 0; } utime( time(), time(), $file ); return 1; } sub create_session( $ ) { my $user = $_[0]; my $token = session_token(); my $file = session_file( $token ); if( $file eq '' ) { smart_auth_log( 'could not build session file path' ); return ''; } if( open( my $fh, '>', $file ) ) { print( $fh $user . "\n" . time() . "\n" ); close( $fh ); chmod( 0600, $file ); smart_auth_log( 'created session for ' . $user . ' at ' . $file ); return $token; } smart_auth_log( 'could not create session file ' . $file . ': ' . $! ); return ''; } sub destroy_session() { my $token = cookie_session_id(); my $file = session_file( $token ); unlink( $file ) if $file ne '' && -f $file; } sub check_login_password( $$ ) { my( $user, $pass ) = @_; return 0 if ! defined( $user ) || ! defined( $pass ); return 0 if $user eq '' || $pass eq ''; if( ! defined( $smart_check_login ) || $smart_check_login eq '' || ! -x $smart_check_login ) { return -1; } my $admin_group = defined( $smart_admin_group ) && $smart_admin_group ne '' ? $smart_admin_group : 'root'; my $conf_path = defined( $smart_conf_path ) && $smart_conf_path ne '' ? $smart_conf_path : '@MARS_NWE_INSTALL_FULL_CONFDIR@/smart.conf'; my $rc = system( $smart_check_login, $user, $pass, $admin_group, $conf_path ); if( $rc == 0 ) { return 1; } my $exit = $rc >> 8; return -2 if $exit == 2; return 0; } sub print_login_page( $ ) { my $msg = smart_html_escape( $_[0] ); print < SMArT Login EOF } sub handle_login_route() { if( $request_method ne 'POST' ) { print_login_page( '' ); return; } my $rv = check_login_password( $p{user}, $p{pass} ); if( $rv == -1 ) { print_login_page( 'Login helper check_login is missing or not executable.' ); return; } if( $rv == -2 ) { my $admin_group = defined( $smart_admin_group ) && $smart_admin_group ne '' ? $smart_admin_group : 'root'; print_login_page( 'Login denied. User is not a member of required admin group: ' . $admin_group ); return; } if( $rv != 1 ) { print_login_page( 'Login failed.' ); return; } my $token = create_session( $p{user} ); if( $token eq '' ) { print_login_page( 'Could not create login session.' ); return; } print < $d[0], uid => $d[2] } ); } return( sort( { $a->{"name"} cmp $b->{"name"} } @c ) ); } sub unix_grouplist() { my( @c, @d, %e ); while( @d = getgrent ) { unshift( @c, { name => $d[0], gid => $d[2] } ); } return( sort( { $a->{"name"} cmp $b->{"name"} } @c ) ); } sub redirect( $ ) { if( $redirected != 0 ) { return( 0 ); } $redirected = 1; print < ); close( SFILE ); $line =~ s/\/.*//; return( $line ); } sub get_bindery_password { open( SFILE, '<' . $smart_nwclient_path ); chomp( $line = ); close( SFILE ); $line =~ s/.* //; return( $line ); } sub read_property_string { my @x = split( "\n", `nwbpvalues -c -o $_[0] -t $_[1] -p $_[2] -S $server` ); my( $i, $s ); $i = 5; while( hex( $x[$i] ) > 0 ) { $s .= pack( 'c', hex( $x[$i] ) ); $i ++; } return( $s ); } sub read_property_list { my @x = split( "\n", `nwbpvalues -c -o $_[0] -t $_[1] -p $_[2] -S $server` ); my( $i, @l ); $i = 6; while( $x[$i] ne '' ) { unshift( @l, $x[$i] ); $i += 2; } return( @l ); } sub write_property_string { open( FILE, '|' . 'nwbpset -S ' . $server ); print FILE < = getpwnam( $nonroot_user ); }