File: CommandLine\ArgumentsParser.cs
Project: ndp\cdf\src\WCF\Tools\WsatConfig\WsatConfig.csproj (WsatConfig)
//------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------------------------
 
namespace Microsoft.Tools.ServiceModel.WsatConfig
{
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Reflection;
    using System.Diagnostics;
    using System.Security.Principal;
    using System.Security.Cryptography.X509Certificates;
 
    class ArgumentsParser
    {
        Dictionary<string, ParserBase> parsersTable = new Dictionary<string, ParserBase>();
        Dictionary<string, string> optionsLookupTable = new Dictionary<string, string>();
 
        Dictionary<string, int> optionsHitCount = new Dictionary<string, int>();
        WsatConfiguration config;
 
        public ArgumentsParser(WsatConfiguration config)
        {
            this.config = config;
            InitializeParsers();
        }
 
        public bool ParseOptionAndArgument(string optionAndArgument)
        {
            string value;
            string lowerCaseOption = ExtractOption(optionAndArgument, out value);
            if (!string.IsNullOrEmpty(lowerCaseOption) && optionsLookupTable.ContainsKey(lowerCaseOption))
            {
                string normalizedOption = optionsLookupTable[lowerCaseOption];
                ParserBase parser = parsersTable[normalizedOption];
                parser.Parse(value, config);
                UpdateOptionHitCount(normalizedOption);
                return true;
            }
            return false;
        }
 
        public void ValidateArgumentsThrow()
        {
            if (optionsHitCount.Count >= 1)
            {
                foreach (string option in optionsHitCount.Keys)
                {
                    if (optionsHitCount[option] > 1)
                    {
                        throw new WsatAdminException(WsatAdminErrorCode.DUPLICATE_OPTION, SR.GetString(SR.ErrorDuplicateOption, option));
                    }
                }
 
                if (!config.TransactionBridgeEnabled)
                {
                    int optionsAllowed = 0;
                    if (optionsHitCount.ContainsKey(CommandLineOption.Network))
                    {
                        ++optionsAllowed;
                    }
 
                    if (optionsHitCount.ContainsKey(CommandLineOption.ClusterRemoteNode))
                    {
                        ++optionsAllowed;
                    }
 
                    if (optionsHitCount.ContainsKey(CommandLineOption.Restart))
                    {
                        ++optionsAllowed;
                    }
 
                    if (optionsHitCount.Count > optionsAllowed)
                    {
                        throw new WsatAdminException(WsatAdminErrorCode.CANNOT_UPDATE_SETTINGS_WHEN_NETWORK_IS_DISABLED, SR.GetString(SR.ErrorUpdateSettingsWhenNetworkIsDisabled));
                    }
                }
            }
        }
 
        void UpdateOptionHitCount(string option)
        {
            int hitCount = 0;
            if (optionsHitCount.TryGetValue(option, out hitCount))
            {
                optionsHitCount[option] = hitCount + 1;
            }
            else
            {
                optionsHitCount.Add(option, 1);
            }
        }
 
        static public string ExtractOption(string arg, out string value)
        {
            value = null;
            if (!string.IsNullOrEmpty(arg))
            {
                int separator = arg.IndexOf(':');
                if (separator != -1)
                {
                    value = arg.Substring(separator + 1);
                    return arg.Substring(0, separator).Trim().ToLowerInvariant();
                }
            }
            return string.Empty;
        }
 
        void InitializeParsers()
        {
            AddOptionParserPair(CommandLineOption.Network, new NetworkParser());
            AddOptionParserPair(CommandLineOption.Port, new PortParser());
            AddOptionParserPair(CommandLineOption.MaxTimeout, new MaxTimeoutParser());
            AddOptionParserPair(CommandLineOption.DefaultTimeout, new DefaultTimeoutParser());
            AddOptionParserPair(CommandLineOption.TraceLevel, new TraceLevelParser());
            AddOptionParserPair(CommandLineOption.TraceActivity, new TraceActivityParser());
            AddOptionParserPair(CommandLineOption.TraceProp, new TracePropagationParser());
            AddOptionParserPair(CommandLineOption.TracePii, new TracePiiParser());
            AddOptionParserPair(CommandLineOption.EndpointCert, new EndpointCertificateParser());
            AddOptionParserPair(CommandLineOption.Accounts, new AccountsParser());
            AddOptionParserPair(CommandLineOption.ClusterRemoteNode, new ClusterRemoteNodeParser());
            AddOptionParserPair(CommandLineOption.AccountsCerts, new AccountsCertificatesParser());
        }
 
        void AddOptionParserPair(string standardOption, ParserBase parser)
        {
            optionsLookupTable.Add(standardOption.ToLowerInvariant(), standardOption);
            parsersTable.Add(standardOption, parser);
        }
 
        abstract class ParserBase
        {
            abstract protected void DoParse(string value, WsatConfiguration config);
 
            public void Parse(string value, WsatConfiguration config)
            {
                Debug.Assert(config != null, "config is null");
 
                DoParse(value, config);
            }
        }
 
        class NetworkParser : ParserBase
        {
            // -network:{enable|disable}
            protected override void DoParse(string value, WsatConfiguration config)
            {
                if (!String.IsNullOrEmpty(value))
                {
                    if (Utilities.SafeCompare(value, CommandLineOption.Enable))
                    {
                        QfeChecker.CheckQfe();
                        config.TransactionBridgeEnabled = true;
                        return;
                    }
                    else if (Utilities.SafeCompare(value, CommandLineOption.Disable))
                    {
                        config.TransactionBridgeEnabled = false;
                        return;
                    }
                }
                throw new WsatAdminException(WsatAdminErrorCode.INVALID_NETWORK_ARGUMENT, SR.GetString(SR.ErrorNetworkArgument));
            }
        }
 
        class PortParser : ParserBase
        {
            // -port:<portNum>
            protected override void DoParse(string value, WsatConfiguration config)
            {
                if (!String.IsNullOrEmpty(value))
                {
                    UInt16 parsedValue;
                    if (UInt16.TryParse(value, out parsedValue))
                    {
                        config.HttpsPort = parsedValue;
                        return;
                    }
                }
                throw new WsatAdminException(WsatAdminErrorCode.INVALID_HTTPS_PORT, SR.GetString(SR.ErrorHttpsPortRange));
            }
        }
 
        class MaxTimeoutParser : ParserBase
        {
            // -maxTimeout:<sec>
            override protected void DoParse(string value, WsatConfiguration config)
            {
                if (!String.IsNullOrEmpty(value))
                {
                    UInt16 parsedValue;
                    if (UInt16.TryParse(value, out parsedValue))
                    {
                        config.MaxTimeout = parsedValue;
                        return;
                    }
                }
                throw new WsatAdminException(WsatAdminErrorCode.INVALID_MAXTIMEOUT_ARGUMENT, SR.GetString(SR.ErrorMaximumTimeoutRange));
            }
        }
 
        class DefaultTimeoutParser : ParserBase
        {
            // -timeout:<sec>
            override protected void DoParse(string value, WsatConfiguration config)
            {
                if (!String.IsNullOrEmpty(value))
                {
                    UInt16 parsedValue;
                    if (UInt16.TryParse(value, out parsedValue))
                    {
                        config.DefaultTimeout = parsedValue;
                        return;
                    }
                }
                throw new WsatAdminException(WsatAdminErrorCode.INVALID_DEFTIMEOUT_ARGUMENT, SR.GetString(SR.ErrorDefaultTimeoutRange));
            }
        }
 
        // -traceLevel:{Off|Error|Critical|Warning|Information|Verbose|All}
        class TraceLevelParser : ParserBase
        {
            override protected void DoParse(string value, WsatConfiguration config)
            {
                if (!String.IsNullOrEmpty(value))
                {
                    SourceLevels level;
                    bool parsedCorrectly = Utilities.ParseSourceLevel(value, out level);
                    if (parsedCorrectly)
                    {
                        config.TraceLevel = level;
                        return;
                    }
                    else
                    {
                        // check to see if it's a number
                        uint temp;
                        if (UInt32.TryParse(value, out temp))
                        {
                            config.TraceLevel = (SourceLevels)temp;
                            return;
                        }
                    }
                }
                throw new WsatAdminException(WsatAdminErrorCode.INVALID_TRACELEVEL_ARGUMENT,
                                    SR.GetString(SR.ErrorTraceLevelArgument));
            }
        }
 
        class TraceActivityParser : ParserBase
        {
            // -traceActivity:{enable|disable}
            protected override void DoParse(string value, WsatConfiguration config)
            {
                if (!String.IsNullOrEmpty(value))
                {
                    if (Utilities.SafeCompare(value, CommandLineOption.Enable))
                    {
                        config.ActivityTracing = true;
                        return;
                    }
                    else if (Utilities.SafeCompare(value, CommandLineOption.Disable))
                    {
                        config.ActivityTracing = false;
                        return;
                    }
                }
                throw new WsatAdminException(WsatAdminErrorCode.INVALID_TRACE_ACTIVITY_ARGUMENT,
                                                    SR.GetString(SR.ErrorTraceActivityArgument));
            }
        }
 
        class TracePropagationParser : ParserBase
        {
            // -traceProp:{enable|disable}
            protected override void DoParse(string value, WsatConfiguration config)
            {
                if (!String.IsNullOrEmpty(value))
                {
                    if (Utilities.SafeCompare(value, CommandLineOption.Enable))
                    {
                        config.ActivityPropagation = true;
                        return;
                    }
                    else if (Utilities.SafeCompare(value, CommandLineOption.Disable))
                    {
                        config.ActivityPropagation = false;
                        return;
                    }
                }
                throw new WsatAdminException(WsatAdminErrorCode.INVALID_TRACE_PROPAGATION_ARGUMENT,
                                        SR.GetString(SR.ErrorTracePropArgument));
            }
        }
 
        class TracePiiParser : ParserBase
        {
            // -tracePII:{enable|disable}
            protected override void DoParse(string value, WsatConfiguration config)
            {
                if (!String.IsNullOrEmpty(value))
                {
                    if (Utilities.SafeCompare(value, CommandLineOption.Enable))
                    {
                        config.TracePii = true;
                        return;
                    }
                    else if (Utilities.SafeCompare(value, CommandLineOption.Disable))
                    {
                        config.TracePii = false;
                        return;
                    }
                }
                throw new WsatAdminException(WsatAdminErrorCode.INVALID_TRACE_PII_ARGUMENT,
                                    SR.GetString(SR.ErrorTracePiiArgument));
            }
        }
 
        // -endpointCert:{machine|<thumb>|"Issuer\SubjectName"}
        class EndpointCertificateParser : ParserBase
        {
            protected override void DoParse(string value, WsatConfiguration config)
            {
                if (!String.IsNullOrEmpty(value))
                {
                    if (Utilities.SafeCompare(value, CommandLineOption.CertMachine))
                    {
                        config.X509Certificate = CertificateManager.GetMachineIdentityCertificate();
                        if (config.X509Certificate == null)
                        {
                            throw new WsatAdminException(WsatAdminErrorCode.CANNOT_FIND_MACHINE_CERTIFICATE,
                                                            SR.GetString(SR.ErrorCannotFindMachineCertificate));
                        }
                    }
                    else if (value.IndexOf('\\') >= 0) // "Issuer\SubjectName" e.g. "*\This is a distinguished subject"
                    {
                        config.X509Certificate = CertificateManager.GetCertificateFromIssuerAndSubjectName(value);
                    }
                    else // thumbprint
                    {
                        config.X509Certificate = CertificateManager.GetCertificateFromThumbprint(value, string.Empty);
                    }
 
                    if (config.X509Certificate != null)
                    {
                        return;
                    }
                }
                throw new WsatAdminException(WsatAdminErrorCode.INVALID_OR_MISSING_SSL_CERTIFICATE,
                                    SR.GetString(SR.ErrorMissingSSLCert));
            }
        }
 
        // -accounts:<account,>
        class AccountsParser : ParserBase
        {
            protected override void DoParse(string value, WsatConfiguration config)
            {
                // Empty value is allowed and means then users want to remove all accounts
                config.KerberosGlobalAcl = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                List<string> acceptedAccounts = new List<string>(config.KerberosGlobalAcl);
 
                List<string> validAccounts = new List<string>();
                foreach (string account in acceptedAccounts)
                {
                    string normalizedAccount = account.Trim();
                    if (!string.IsNullOrEmpty(normalizedAccount))
                    {
                        if (IsValidAccount(normalizedAccount))
                        {
                            validAccounts.Add(normalizedAccount);
                        }
                        else
                        {
                            throw new WsatAdminException(WsatAdminErrorCode.INVALID_ACCOUNT, SR.GetString(SR.ErrorAccountArgument, normalizedAccount));
                        }
                    }
                }
                config.KerberosGlobalAcl = validAccounts.ToArray();
            }
 
            bool IsValidAccount(string account)
            {
                bool isValid = false;
                try
                {
                    NTAccount ntAccount = new NTAccount(account);
                    IdentityReference identityRef = ntAccount.Translate(typeof(SecurityIdentifier));
                    isValid = identityRef != null;
                }
                catch (IdentityNotMappedException)
                {
                }
                catch (Exception e)
                {
                    if (Utilities.IsCriticalException(e))
                    {
                        throw;
                    }
                }
 
                return isValid;
            }
        }
 
        class ClusterRemoteNodeParser : ParserBase
        {
            // private switch: -clusterRemoteNode
            protected override void DoParse(string value, WsatConfiguration config)
            {
                if (!String.IsNullOrEmpty(value))
                {
                    if (Utilities.SafeCompare(value, CommandLineOption.Enable))
                    {
                        config.IsClusterRemoteNode = true;
                        return;
                    }
                    else if (Utilities.SafeCompare(value, CommandLineOption.Disable))
                    {
                        config.IsClusterRemoteNode = false;
                        return;
                    }
                }
                throw new WsatAdminException(WsatAdminErrorCode.INVALID_CLUSTER_REMOTE_NODE_ARGUMENT, SR.GetString(SR.ErrorClusterRemoteNodeArgument));
            }
        }
 
        class AccountsCertificatesParser : ParserBase
        {
            // -accountsCerts:<base64hash|"Issuer\SubjectName",>
            protected override void DoParse(string value, WsatConfiguration config)
            {
                // Empty value is allowed and means then users want to remove all accounts certs
                config.X509GlobalAcl = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                // newConfig.X509GlobalAcl can be empty, but never null
 
                int i = 0;
                foreach (string certString in config.X509GlobalAcl)
                {
                    if (!string.IsNullOrEmpty(certString))
                    {
                        X509Certificate2 cert = null;
                        if (certString.IndexOf('\\') >= 0)
                        {
                            // issuer\subject
                            cert = CertificateManager.GetCertificateFromIssuerAndSubjectName(certString);
                        }
                        else
                        {
                            // thumbprint
                            cert = CertificateManager.GetCertificateFromThumbprint(certString, string.Empty);
                        }
 
                        if (cert != null)
                        {
                            config.X509GlobalAcl[i] = cert.Thumbprint;
                        }
                        else
                        {
                            throw new WsatAdminException(WsatAdminErrorCode.INVALID_OR_MISSING_CLIENT_CERTIFICATE,
                                                            SR.GetString(SR.ErrorInvalidOrMissingClientCert));
                        }
                    }
                    i++;
                }
            }
        }
    }
}