lib/threads/Start-IcingaWindowsRESTThread.psm1

function Start-IcingaWindowsRESTThread()
{
    param (
        [int]$ThreadId       = 0,
        [switch]$RequireAuth = $FALSE
    );

    # Our ScriptBlock for the code being executed inside the thread
    [ScriptBlock]$IcingaRestClientScript = {
        # Allow us to parse the framework global data to this thread
        param($IcingaDaemonData, $RequireAuth, $ThreadId);

        # Import the framework library components and initialise it
        # as daemon
        Use-Icinga -LibOnly -Daemon;

        # Initialise our performance counter categories
        Show-IcingaPerformanceCounterCategories | Out-Null;

        while ($TRUE) {

            try {
                if ($IcingaDaemonData.IcingaThreadContent.RESTApi.ApiRequests.ContainsKey($ThreadId) -eq $FALSE) {
                    Start-Sleep -Milliseconds 10;
                    continue;
                }

                if ($IcingaDaemonData.IcingaThreadContent.RESTApi.ApiRequests.$ThreadId.Count -eq 0) {
                    Start-Sleep -Milliseconds 10;
                    continue;
                }
    
                $Connection = $IcingaDaemonData.IcingaThreadContent.RESTApi.ApiRequests.$ThreadId.Dequeue();

                if ($null -eq $Connection) {
                    Start-Sleep -Milliseconds 10;
                    continue;
                }

                # Read the received message from the stream by using our smart functions
                [string]$RestMessage = Read-IcingaTCPStream -Client $Connection.Client -Stream $Connection.Stream;
                # Now properly translate the entire rest message to a parseable hashtable
                $RESTRequest         = Read-IcingaRestMessage -RestMessage $RestMessage -Connection $Connection;

                if ($null -ne $RESTRequest) {

                    # Check if we require to authenticate the user
                    if ($RequireAuth) {
                        # If no authentication header is provided we should show the prompt
                        if ([string]::IsNullOrEmpty($RESTRequest.Header.Authorization)) {
                            # In case we do not send an authentication header increase the blacklist counter
                            # to ensure we are not spammed and "attacked" by a client with useless requests
                            Add-IcingaRESTClientBlacklistCount `
                                -Client $Connection.Client `
                                -ClientList $IcingaDaemonData.BackgroundDaemon.IcingaPowerShellRestApi.ClientBlacklist;
                            # Send the authentication prompt
                            Send-IcingaWebAuthMessage -Connection $Connection;
                            # Close the connection
                            Close-IcingaTCPConnection -Client $Connection.Client;
                            continue;
                        }

                        $Credentials        = Convert-Base64ToCredentials -AuthString $RESTRequest.Header.Authorization;
                        [bool]$LoginSuccess = Test-IcingaRESTCredentials -UserName $Credentials.user -Password $Credentials.password -Domain $Credentials.domain;
                        $Credentials        = $null;

                        # Handle login failures
                        if ($LoginSuccess -eq $FALSE) {
                            # Failed attempts should increase the blacklist counter
                            Add-IcingaRESTClientBlacklistCount `
                                -Client $Connection.Client `
                                -ClientList $IcingaDaemonData.BackgroundDaemon.IcingaPowerShellRestApi.ClientBlacklist;
                            # Re-send the authentication prompt
                            Send-IcingaWebAuthMessage -Connection $Connection;
                            # Close the connection
                            Close-IcingaTCPConnection -Client $Connection.Client;
                            continue;
                        }
                    }

                    # We should remove clients from the blacklist who are sending valid requests
                    Remove-IcingaRESTClientBlacklist -Client $Connection.Client -ClientList $IcingaDaemonData.BackgroundDaemon.IcingaPowerShellRestApi.ClientBlacklist;
                    switch (Get-IcingaRESTPathElement -Request $RESTRequest -Index 0) {
                        'v1' {
                            Invoke-IcingaRESTAPIv1Calls -Request $RESTRequest -Connection $Connection;
                            break;
                        };
                        default {
                            Write-IcingaDebugMessage -Message ('Invalid API call - no version specified' + ($RESTRequest.RequestPath | Out-String));
                            Send-IcingaTCPClientMessage -Message (
                                New-IcingaTCPClientRESTMessage `
                                    -HTTPResponse ($IcingaHTTPEnums.HTTPResponseType.'Not Found') `
                                    -ContentBody 'Invalid API call received. No version specified.'
                            ) -Stream $Connection.Stream;
                        };
                    }
                }
            } catch {
                $ExMsg = $_.Exception.Message;

                Send-IcingaTCPClientMessage -Message (
                                New-IcingaTCPClientRESTMessage `
                                    -HTTPResponse ($IcingaHTTPEnums.HTTPResponseType.'Internal Server Error') `
                                    -ContentBody $ExMsg
                            ) -Stream $Connection.Stream;

                Write-IcingaEventMessage -Namespace 'RESTApi' -EventId 2051 -Objects $ExMsg;
            }

            # Finally close the clients connection as we are done here and
            # ensure this thread will close by simply leaving the ScriptBlock
            if ($null -ne $Connection) {
                Close-IcingaTCPConnection -Client $Connection.Client;
            }

            # Cleanup the error stack and remove not required data
            $Error.Clear();
            # Force PowerShell to call the garbage collector to free memory
            [System.GC]::Collect();
        }
    }

    # Now create a new thread for our ScriptBlock, assign a name and
    # parse all required arguments to it. Last but not least start it
    # directly
    New-IcingaThreadInstance `
        -Name ([string]::Format("Icinga_Windows_REST_Api_Thread_{0}", $ThreadId)) `
        -ThreadPool $IcingaDaemonData.IcingaThreadPool.BackgroundPool `
        -ScriptBlock $IcingaRestClientScript `
        -Arguments @( $IcingaDaemonData, $RequireAuth, $ThreadId) `
        -Start;
}