diff --git a/settings.pl b/settings.pl index 4cc511c..8e98e50 100644 --- a/settings.pl +++ b/settings.pl @@ -708,40 +708,44 @@ sub unix_user_defaults_from_query() my $want = ''; - # Preferred import route: - # /settings/users_import/ - if( defined( $c[1] ) && $c[1] eq 'users_import' && defined( $c[2] ) && $c[2] ne '' ) + # The router puts imported users here after /settings/users_import/ + # was normalized to /settings/users/add_new. + if( defined( $p{unix_user} ) && $p{unix_user} ne '' ) + { + $want = $p{unix_user}; + } + # Compatibility for direct /settings/users_import/, before normalization. + elsif( defined( $c[1] ) && $c[1] eq 'users_import' && defined( $c[2] ) && $c[2] ne '' ) { $want = $c[2]; } - # Compatibility route: - # /settings/users/add_new/ + # Compatibility for /settings/users/add_new/. elsif( defined( $c[3] ) && $c[3] ne '' ) { $want = $c[3]; } - elsif( defined( $p{unix_user} ) && $p{unix_user} ne '' ) - { - $want = $p{unix_user}; - } if( $want ne '' ) { $want =~ s/[^-_\.A-Za-z0-9]//g; - foreach my $u ( unix_userlist() ) - { - next if ! defined( $u->{name} ) || $u->{name} ne $want; + # Minimal import: use the Unix user parameter that reaches settings.pl. + $defaults{unix_user} = $want; - my $gecos = defined( $u->{gecos} ) ? $u->{gecos} : ''; - $gecos =~ s/,.*$//; + $defaults{name} = uc( $want ); + $defaults{name} =~ s/[^-_\.A-Za-z0-9]//g; - $defaults{name} = uc( $u->{name} ); - $defaults{name} =~ s/[^-_\.A-Za-z0-9]//g; - $defaults{unix_user} = $u->{name}; - $defaults{fullname} = $gecos; - last; - } + # Step 2: if no GECOS/fullname is available yet, use a friendly + # fallback from the Unix account name. "mario" -> "Mario", + # "mario_fetka" -> "Mario Fetka". + my $full = $want; + $full =~ s/[_\.\-]+/ /g; + $full =~ s/\s+/ /g; + $full =~ s/^\s+//; + $full =~ s/\s+$//; + $full = join( ' ', map { ucfirst( lc( $_ ) ) } split( / /, $full ) ); + + $defaults{fullname} = $full; } return %defaults; diff --git a/smart.cmake b/smart.cmake index 8846ffe..be19578 100644 --- a/smart.cmake +++ b/smart.cmake @@ -45,11 +45,14 @@ $smart_systemctl_path = '@SYSTEMCTL_EXECUTABLE@' unless defined $smart_systemctl $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 ) < 15 ) # Who would ever want to send more headers??? + while( keys( %h ) < 50 ) { $l = ; $l =~ s/[\n\r]//g; @@ -59,36 +62,31 @@ if( scalar( @c ) > 2 ) $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] ); -if( $h{Authorization} eq '' ) - { error( 401 ); } -else +if( $c[0] eq 'POST' ) { - @s = split( ' ', $h{Authorization} ); - if( $s[0] ne 'Basic' or length( $h{Authorization} ) > 80 ) # We can't be too careful, can we... - { error( 401 ); } - else + my $content_length = 0; + + if( defined( $hl{'content-length'} ) && $hl{'content-length'} =~ /^[0-9]+$/ ) { - $s[1] =~ tr#A-Za-z0-9+/##cd; - $s[1] =~ tr#A-Za-z0-9+/# -_#; - $s[1] = pack( 'c', 32 + 0.75 * length( $s[1] ) ) . $s[1]; - $s[1] = unpack( 'u', $s[1] ); - $s[1] =~ s/[\r\n]//g; - @l = split( ':', $s[1] ); - if( $l[0] ne 'root' ) - { error( 401 ); } - else - { if( $x = system( $smart_check_login, @l ) ) - { error( 401 ); } } + $content_length = int( $hl{'content-length'} ); + } + + if( $content_length > 0 && $content_length < 8192 ) + { + read( STDIN, $post_body, $content_length ); } } -if( $c[0] ne 'GET' ) +if( $c[0] ne 'GET' && $c[0] ne 'POST' ) { error( 501 ); } @@ -97,19 +95,30 @@ if( $c[0] ne 'GET' ) $cc = $c[1]; $cc =~ s/[^\?]*\?//; $c = substr( shift( @p ), 1 ); -@p = split( '&', $p[0] ); -foreach $p ( @p ) -{ - $n = $p; - $n =~ s/=.*//; - $v = $p; - $v =~ s/.*=//; - $v =~ s/\+/ /g; - $v =~ s/%([0-9A-F][0-9A-F])/pack('c',hex($1))/gie; - $p{$n} = $v; -} + +parse_params( $p[0] ); +parse_params( $post_body ) if $c[0] eq 'POST'; + @c = split( '/', $c ); +if( $c[0] eq 'login' ) +{ + handle_login_route(); + exit; +} + +if( $c[0] eq 'logout' ) +{ + handle_logout_route(); + 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' ) ) { @@ -176,6 +185,328 @@ exit; ##### END OF MAIN PROCEDURES FOLLOW ##### ########################################## +sub smart_auth_log( $ ) +{ + my $msg = $_[0]; + $msg = '' unless defined $msg; + + if( open( my $fh, '>>', $smart_log_path ) ) + { + print( $fh scalar( localtime() ) . " [AUTH] " . $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 ne 'root'; + + if( ! defined( $smart_check_login ) || $smart_check_login eq '' || ! -x $smart_check_login ) + { + return -1; + } + + return system( $smart_check_login, $user, $pass ) == 0 ? 1 : 0; +} + +sub print_login_page( $ ) +{ + my $msg = smart_html_escape( $_[0] ); + + print < + + + +SMArT Login + + + + + + +EOF +} + +sub handle_login_route() +{ + if( $c[0] 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 != 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 <