diff --git a/.github/workflows/c_release.yml b/.github/workflows/c_release.yml index 9efaab995..0e98f1985 100644 --- a/.github/workflows/c_release.yml +++ b/.github/workflows/c_release.yml @@ -125,7 +125,7 @@ jobs: with: ref: c_release-${{github.run_number}} - uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 - - uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 + - uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 - run: | docker buildx build -t atsigncompany/sshnpdc -f sshnpd/tools/Dockerfile.package \ --platform ${{ matrix.platform }} -o type=tar,dest=bins.tar . @@ -163,7 +163,7 @@ jobs: with: ref: c_release-${{github.run_number}} - uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 - - uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 + - uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 - run: | docker buildx build -t atsigncompany/sshnpdcmusl -f sshnpd/tools/Dockerfile.musl \ --platform ${{ matrix.platform }} -o type=tar,dest=bins.tar . diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 219ea20e1..83beb12f3 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -50,7 +50,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@aa578102511db1f4524ed59b8cc2bae4f6e88195 # v3.27.6 + uses: github/codeql-action/init@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -60,7 +60,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@aa578102511db1f4524ed59b8cc2bae4f6e88195 # v3.27.6 + uses: github/codeql-action/autobuild@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9 # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -73,6 +73,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@aa578102511db1f4524ed59b8cc2bae4f6e88195 # v3.27.6 + uses: github/codeql-action/analyze@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/dockerhub_sshnpd.yml b/.github/workflows/dockerhub_sshnpd.yml index 9790195b9..641fe40bc 100644 --- a/.github/workflows/dockerhub_sshnpd.yml +++ b/.github/workflows/dockerhub_sshnpd.yml @@ -35,7 +35,7 @@ jobs: - name: Set up QEMU uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 - name: Login to Docker Hub uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: diff --git a/.github/workflows/multibuild.yaml b/.github/workflows/multibuild.yaml index ca3b628a3..1d1b4fca8 100644 --- a/.github/workflows/multibuild.yaml +++ b/.github/workflows/multibuild.yaml @@ -189,7 +189,7 @@ jobs: - if: ${{ ! inputs.main_build_only }} uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 - if: ${{ ! inputs.main_build_only }} - uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 - if: ${{ ! inputs.main_build_only }} run: | docker buildx build -t atsigncompany/sshnptarball -f ./tools/multibuild/Dockerfile.package \ diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index dd59e3e55..707003c17 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@aa578102511db1f4524ed59b8cc2bae4f6e88195 # v3.27.6 + uses: github/codeql-action/upload-sarif@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9 with: sarif_file: results.sarif diff --git a/.github/workflows/unit_tests.yaml b/.github/workflows/unit_tests.yaml index 7409894cf..0d603dd96 100644 --- a/.github/workflows/unit_tests.yaml +++ b/.github/workflows/unit_tests.yaml @@ -31,7 +31,7 @@ jobs: - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94 # v1.7.0 with: sdk: ${{ matrix.dart-channel}} - - uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0 + - uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 with: go-version: "stable" cache-dependency-path: tools/osv-scanner/go.sum diff --git a/packages/dart/sshnoports/pubspec.lock b/packages/dart/sshnoports/pubspec.lock index 2b9a48967..42481c0b0 100644 --- a/packages/dart/sshnoports/pubspec.lock +++ b/packages/dart/sshnoports/pubspec.lock @@ -5,23 +5,23 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "45cfa8471b89fb6643fe9bf51bd7931a76b8f5ec2d65de4fb176dba8d4f22c77" + sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" url: "https://pub.dev" source: hosted - version: "73.0.0" + version: "76.0.0" _macros: dependency: transitive description: dart source: sdk - version: "0.3.2" + version: "0.3.3" analyzer: dependency: transitive description: name: analyzer - sha256: "4959fec185fe70cce007c57e9ab6983101dbe593d2bf8bbfb4453aaec0cf470a" + sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" url: "https://pub.dev" source: hosted - version: "6.8.0" + version: "6.11.0" archive: dependency: transitive description: @@ -59,10 +59,10 @@ packages: dependency: transitive description: name: at_auth - sha256: "4fba41a421cc55e0f53b2a506500d61d7a374bcf073e57927b9aeb457676cb5d" + sha256: "4d23e7ffd799104ccc74ce006a8a286dcefaeafcc8f72c6bcbbc6a893244abeb" url: "https://pub.dev" source: hosted - version: "2.0.9" + version: "2.0.10" at_base2e15: dependency: transitive description: @@ -99,10 +99,10 @@ packages: dependency: transitive description: name: at_commons - sha256: "08a7be508c85f9b21d09a1d95225bb5164d20426c6d8564e1c31a74843b67ca7" + sha256: "02e9c06edf0d7fc9b68ee9df79c801a0e5e877c5eb7505d76adbd6fe691e6101" url: "https://pub.dev" source: hosted - version: "5.1.0" + version: "5.1.1" at_demo_data: dependency: transitive description: @@ -123,10 +123,10 @@ packages: dependency: "direct main" description: name: at_onboarding_cli - sha256: f3eb8789671fd1580607d94b045992e9fee2ac3eb19797decc47466ac8a7d242 + sha256: b5a4b5595f37ae4dfc5ffdec9302b5bd67309dbf29ecbb4483c7cfe17c260223 url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.8.1" at_persistence_secondary_server: dependency: transitive description: @@ -211,18 +211,18 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" + sha256: "74691599a5bc750dc96a6b4bfd48f7d9d66453eab04c7f4063134800d6a5c573" url: "https://pub.dev" source: hosted - version: "2.4.13" + version: "2.4.14" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 + sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" url: "https://pub.dev" source: hosted - version: "7.3.2" + version: "8.0.0" build_version: dependency: "direct dev" description: @@ -524,10 +524,10 @@ packages: dependency: "direct dev" description: name: lints - sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413" + sha256: "4a16b3f03741e1252fda5de3ce712666d010ba2122f8e912c94f9f7b90e1a4c3" url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "5.1.0" logging: dependency: "direct main" description: @@ -540,10 +540,10 @@ packages: dependency: transitive description: name: macros - sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" url: "https://pub.dev" source: hosted - version: "0.1.2-main.4" + version: "0.1.3-main.0" matcher: dependency: transitive description: @@ -811,10 +811,10 @@ packages: dependency: "direct dev" description: name: test - sha256: "22eb7769bee38c7e032d532e8daa2e1cc901b799f603550a4db8f3a5f5173ea2" + sha256: "43490fe4c0f5ecb898f3fa1cdcdad8d521d7f6ff17ebdc4e8cd32b2e99524a20" url: "https://pub.dev" source: hosted - version: "1.25.12" + version: "1.25.13" test_api: dependency: transitive description: diff --git a/packages/dart/sshnoports/pubspec.yaml b/packages/dart/sshnoports/pubspec.yaml index c9e0b988d..4c23e46df 100644 --- a/packages/dart/sshnoports/pubspec.yaml +++ b/packages/dart/sshnoports/pubspec.yaml @@ -10,7 +10,7 @@ dependencies: noports_core: path: "../noports_core" version: 6.2.1 - at_onboarding_cli: 1.8.0 + at_onboarding_cli: 1.8.1 at_cli_commons: 1.3.0 at_client: 3.3.0 args: 2.6.0 @@ -33,8 +33,8 @@ dependency_overrides: url: https://github.com/gkc/args dev_dependencies: - lints: ^5.0.0 - test: ^1.25.12 + lints: ^5.1.0 + test: ^1.25.13 mocktail: ^1.0.4 - build_runner: ^2.4.13 + build_runner: ^2.4.14 build_version: ^2.1.1 diff --git a/packages/dart/sshnoports/tools/Dockerfile b/packages/dart/sshnoports/tools/Dockerfile index 04b89ecdc..dce171114 100644 --- a/packages/dart/sshnoports/tools/Dockerfile +++ b/packages/dart/sshnoports/tools/Dockerfile @@ -1,7 +1,7 @@ # Dockerfile # Build image for a containerized instance of sshnpd -FROM dart:3.5.4@sha256:3f3877b9a75a1695dd284151d2dab5787bc6cefd04313b6a8e0bee98230d347b AS buildimage +FROM dart:3.6.0@sha256:b677df29e01bca3a2d5bdaffe204498834d56a4ed38ff56c0ac6a42d6541b58e AS buildimage ENV PACKAGEDIR=packages/dart/sshnoports ENV BINARYDIR=/usr/local/at SHELL ["/bin/bash", "-c"] diff --git a/packages/dart/sshnoports/tools/Dockerfile.activate b/packages/dart/sshnoports/tools/Dockerfile.activate index 976592e1b..df0570a3d 100644 --- a/packages/dart/sshnoports/tools/Dockerfile.activate +++ b/packages/dart/sshnoports/tools/Dockerfile.activate @@ -1,6 +1,6 @@ # Dockerfile.activate # Build image for a containerized call of the at_activate binary -FROM dart:3.5.4@sha256:3f3877b9a75a1695dd284151d2dab5787bc6cefd04313b6a8e0bee98230d347b AS buildimage +FROM dart:3.6.0@sha256:b677df29e01bca3a2d5bdaffe204498834d56a4ed38ff56c0ac6a42d6541b58e AS buildimage ENV PACKAGEDIR=packages/dart/sshnoports ENV BINARYDIR=/usr/local/at SHELL ["/bin/bash", "-c"] diff --git a/packages/dart/sshnoports/tools/Dockerfile.sshnpd-slim b/packages/dart/sshnoports/tools/Dockerfile.sshnpd-slim index 5fcc4e5c2..364f5c88e 100644 --- a/packages/dart/sshnoports/tools/Dockerfile.sshnpd-slim +++ b/packages/dart/sshnoports/tools/Dockerfile.sshnpd-slim @@ -9,7 +9,7 @@ # as of 5th Feb 2024 - Will check state as 3.3 Stable is released #FROM dart:beta-sdk AS buildimage -FROM dart:3.5.4@sha256:3f3877b9a75a1695dd284151d2dab5787bc6cefd04313b6a8e0bee98230d347b AS buildimage +FROM dart:3.6.0@sha256:b677df29e01bca3a2d5bdaffe204498834d56a4ed38ff56c0ac6a42d6541b58e AS buildimage ENV PACKAGEDIR=packages/dart/sshnoports ENV OPENSSH=tools/static-openssh ENV BINARYDIR=/usr/local/at diff --git a/tests/end2end_tests/image/Dockerfile b/tests/end2end_tests/image/Dockerfile index 83e6f76b5..7b8e2149f 100644 --- a/tests/end2end_tests/image/Dockerfile +++ b/tests/end2end_tests/image/Dockerfile @@ -26,7 +26,7 @@ RUN set -eux ; \ # BRANCH # BUILD BRANCH -FROM dart:3.5.4@sha256:3f3877b9a75a1695dd284151d2dab5787bc6cefd04313b6a8e0bee98230d347b AS build-branch +FROM dart:3.6.0@sha256:b677df29e01bca3a2d5bdaffe204498834d56a4ed38ff56c0ac6a42d6541b58e AS build-branch ENV URL=https://github.com/atsign-foundation/noports.git ENV REPO_DIR=/app/repo @@ -65,7 +65,7 @@ ENTRYPOINT cp -r /mount/. ${HOMEDIR} && sudo service ssh start && sh ${HOMEDIR}/ # LOCAL # BUILD LOCAL -FROM dart:3.5.4@sha256:3f3877b9a75a1695dd284151d2dab5787bc6cefd04313b6a8e0bee98230d347b AS build-local +FROM dart:3.6.0@sha256:b677df29e01bca3a2d5bdaffe204498834d56a4ed38ff56c0ac6a42d6541b58e AS build-local ENV REPO_DIR=/app/repo ENV PACKAGE_DIR=${REPO_DIR}/packages/dart/sshnoports diff --git a/tools/.gitignore b/tools/.gitignore index 50b5af78f..b940e2b1b 100644 --- a/tools/.gitignore +++ b/tools/.gitignore @@ -1 +1,10 @@ macos-signing.env + +#.NET Related +.vs +bin +obj +*.user +launchSettings.json +windows-installer/NoPortsInstaller/Resources/* +windows-installer/NoPortsHealthCheck/publish \ No newline at end of file diff --git a/tools/multibuild/Dockerfile.package b/tools/multibuild/Dockerfile.package index 6ac3fce1d..bc9c2aa0f 100644 --- a/tools/multibuild/Dockerfile.package +++ b/tools/multibuild/Dockerfile.package @@ -1,7 +1,7 @@ # Dockerfile.package # A dockerfile for packaging SSH No Ports releases using docker buildx -FROM atsigncompany/buildimage:3.5.4@sha256:0d21c9f6dc856f1e3df933b99dd88a4833057ffee9665857d31f32d32a9d1a74 AS build +FROM atsigncompany/buildimage:3.6.0@sha256:bbaa6d8f5cf485988d2974cbd206645a096fb215cd220fd577652951f52eccd5 AS build # Using atsigncompany/buildimage until official dart image has RISC-V support # See https://github.com/atsign-company/at_dockerfiles for source and automated builds WORKDIR /noports diff --git a/tools/windows-installer/NoPortsHealthCheck/App.config b/tools/windows-installer/NoPortsHealthCheck/App.config new file mode 100644 index 000000000..3916e0e4b --- /dev/null +++ b/tools/windows-installer/NoPortsHealthCheck/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/tools/windows-installer/NoPortsHealthCheck/Form1.Designer.cs b/tools/windows-installer/NoPortsHealthCheck/Form1.Designer.cs new file mode 100644 index 000000000..bccf76db7 --- /dev/null +++ b/tools/windows-installer/NoPortsHealthCheck/Form1.Designer.cs @@ -0,0 +1,239 @@ +namespace NoPortsHealthCheck +{ + partial class Form1 + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.label1 = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.label3 = new System.Windows.Forms.Label(); + this.label4 = new System.Windows.Forms.Label(); + this.label5 = new System.Windows.Forms.Label(); + this.label6 = new System.Windows.Forms.Label(); + this.label7 = new System.Windows.Forms.Label(); + this.checkBox1 = new System.Windows.Forms.CheckBox(); + this.checkBox2 = new System.Windows.Forms.CheckBox(); + this.checkBox3 = new System.Windows.Forms.CheckBox(); + this.checkBox4 = new System.Windows.Forms.CheckBox(); + this.checkBox5 = new System.Windows.Forms.CheckBox(); + this.checkBox6 = new System.Windows.Forms.CheckBox(); + this.checkBox7 = new System.Windows.Forms.CheckBox(); + this.label8 = new System.Windows.Forms.Label(); + this.SuspendLayout(); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(25, 61); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(100, 13); + this.label1.TabIndex = 0; + this.label1.Text = "Admin Permissions?"; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(25, 96); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(121, 13); + this.label2.TabIndex = 1; + this.label2.Text = "Can write to user home?"; + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(25, 127); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(143, 13); + this.label3.TabIndex = 2; + this.label3.Text = "Can write to Local app data?"; + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(25, 157); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(137, 13); + this.label4.TabIndex = 3; + this.label4.Text = "Can write to Local Service?"; + // + // label5 + // + this.label5.AutoSize = true; + this.label5.Location = new System.Drawing.Point(25, 187); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(110, 13); + this.label5.TabIndex = 4; + this.label5.Text = "Can write to Registry?"; + // + // label6 + // + this.label6.AutoSize = true; + this.label6.Font = new System.Drawing.Font("Arial", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.label6.Location = new System.Drawing.Point(14, 9); + this.label6.Name = "label6"; + this.label6.Size = new System.Drawing.Size(196, 22); + this.label6.TabIndex = 5; + this.label6.Text = "NoPorts Health Check"; + // + // label7 + // + this.label7.AutoSize = true; + this.label7.Location = new System.Drawing.Point(25, 221); + this.label7.Name = "label7"; + this.label7.Size = new System.Drawing.Size(173, 13); + this.label7.TabIndex = 6; + this.label7.Text = "Can modify Environment Variables?"; + // + // checkBox1 + // + this.checkBox1.AutoCheck = false; + this.checkBox1.AutoSize = true; + this.checkBox1.Location = new System.Drawing.Point(229, 61); + this.checkBox1.Name = "checkBox1"; + this.checkBox1.Size = new System.Drawing.Size(15, 14); + this.checkBox1.TabIndex = 7; + this.checkBox1.UseVisualStyleBackColor = true; + // + // checkBox2 + // + this.checkBox2.AutoCheck = false; + this.checkBox2.AutoSize = true; + this.checkBox2.Location = new System.Drawing.Point(229, 96); + this.checkBox2.Name = "checkBox2"; + this.checkBox2.Size = new System.Drawing.Size(15, 14); + this.checkBox2.TabIndex = 8; + this.checkBox2.UseVisualStyleBackColor = true; + // + // checkBox3 + // + this.checkBox3.AutoCheck = false; + this.checkBox3.AutoSize = true; + this.checkBox3.Location = new System.Drawing.Point(229, 127); + this.checkBox3.Name = "checkBox3"; + this.checkBox3.Size = new System.Drawing.Size(15, 14); + this.checkBox3.TabIndex = 9; + this.checkBox3.UseVisualStyleBackColor = true; + // + // checkBox4 + // + this.checkBox4.AutoCheck = false; + this.checkBox4.AutoSize = true; + this.checkBox4.Location = new System.Drawing.Point(229, 157); + this.checkBox4.Name = "checkBox4"; + this.checkBox4.Size = new System.Drawing.Size(15, 14); + this.checkBox4.TabIndex = 10; + this.checkBox4.UseVisualStyleBackColor = true; + // + // checkBox5 + // + this.checkBox5.AutoCheck = false; + this.checkBox5.AutoSize = true; + this.checkBox5.Location = new System.Drawing.Point(229, 186); + this.checkBox5.Name = "checkBox5"; + this.checkBox5.Size = new System.Drawing.Size(15, 14); + this.checkBox5.TabIndex = 11; + this.checkBox5.UseVisualStyleBackColor = true; + // + // checkBox6 + // + this.checkBox6.AutoCheck = false; + this.checkBox6.AutoSize = true; + this.checkBox6.Location = new System.Drawing.Point(229, 221); + this.checkBox6.Name = "checkBox6"; + this.checkBox6.Size = new System.Drawing.Size(15, 14); + this.checkBox6.TabIndex = 12; + this.checkBox6.UseVisualStyleBackColor = true; + // + // checkBox7 + // + this.checkBox7.AutoCheck = false; + this.checkBox7.AutoSize = true; + this.checkBox7.Location = new System.Drawing.Point(229, 251); + this.checkBox7.Name = "checkBox7"; + this.checkBox7.Size = new System.Drawing.Size(15, 14); + this.checkBox7.TabIndex = 14; + this.checkBox7.UseVisualStyleBackColor = true; + this.checkBox7.CheckedChanged += new System.EventHandler(this.checkBox7_CheckedChanged); + // + // label8 + // + this.label8.AutoSize = true; + this.label8.Location = new System.Drawing.Point(25, 251); + this.label8.Name = "label8"; + this.label8.Size = new System.Drawing.Size(107, 13); + this.label8.TabIndex = 13; + this.label8.Text = "Can create services?"; + this.label8.Click += new System.EventHandler(this.label8_Click); + // + // Form1 + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(256, 292); + this.Controls.Add(this.checkBox7); + this.Controls.Add(this.label8); + this.Controls.Add(this.checkBox6); + this.Controls.Add(this.checkBox5); + this.Controls.Add(this.checkBox4); + this.Controls.Add(this.checkBox3); + this.Controls.Add(this.checkBox2); + this.Controls.Add(this.checkBox1); + this.Controls.Add(this.label7); + this.Controls.Add(this.label6); + this.Controls.Add(this.label5); + this.Controls.Add(this.label4); + this.Controls.Add(this.label3); + this.Controls.Add(this.label2); + this.Controls.Add(this.label1); + this.Name = "Form1"; + this.Text = "Form1"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.Label label5; + private System.Windows.Forms.Label label6; + private System.Windows.Forms.Label label7; + private System.Windows.Forms.CheckBox checkBox1; + private System.Windows.Forms.CheckBox checkBox2; + private System.Windows.Forms.CheckBox checkBox3; + private System.Windows.Forms.CheckBox checkBox4; + private System.Windows.Forms.CheckBox checkBox5; + private System.Windows.Forms.CheckBox checkBox6; + private System.Windows.Forms.CheckBox checkBox7; + private System.Windows.Forms.Label label8; + } +} + diff --git a/tools/windows-installer/NoPortsHealthCheck/Form1.cs b/tools/windows-installer/NoPortsHealthCheck/Form1.cs new file mode 100644 index 000000000..f9188024a --- /dev/null +++ b/tools/windows-installer/NoPortsHealthCheck/Form1.cs @@ -0,0 +1,58 @@ +using Microsoft.Win32; +using System; +using System.Windows.Forms; + +namespace NoPortsHealthCheck +{ + public partial class Form1 : Form + { + static readonly string userHome = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + static readonly string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + readonly bool isUserAdmin = Program.IsUserAdmin(); + readonly bool canWriteUserHome = Program.CanWriteToDirectory(userHome); + readonly bool canWriteLocalAppData = Program.CanWriteToDirectory(localAppData); + readonly bool canWriteNetworkService = Program.CanWriteToDirectory(Environment.ExpandEnvironmentVariables("%systemroot%") + + @"\ServiceProfiles\LocalService\"); + readonly bool canWriteRegistry = Program.CanWriteToRegistry(Registry.LocalMachine, "SOFTWARE\\TestKey"); + readonly bool canModifyPath = Program.CanModifyEnvironmentVariable(); + readonly bool canCreateServices = Program.CanCreateServices(); + public Form1() + { + InitializeComponent(); + this.Text = "NoPorts Health Check"; + checkBox1.Checked = isUserAdmin; + if (isUserAdmin) + { + Program.Log("User is Admin"); + } + else + { + Program.Log("User is not Admin, try running as admin."); + } + checkBox2.Checked = canWriteUserHome; + checkBox3.Checked = canWriteLocalAppData; + checkBox4.Checked = canWriteNetworkService; + checkBox5.Checked = canWriteRegistry; + checkBox6.Checked = canModifyPath; + checkBox7.Checked = canCreateServices; + if (isUserAdmin && canWriteUserHome && canWriteLocalAppData && canWriteNetworkService && canWriteRegistry && canModifyPath && canCreateServices) + { + Program.ShowSuccessLogs(); + } + if (!isUserAdmin) + { + Program.ShowErrorLogs(); + } + } + + private void label8_Click(object sender, EventArgs e) + { + + } + + private void checkBox7_CheckedChanged(object sender, EventArgs e) + { + + } + } +} diff --git a/tools/windows-installer/NoPortsHealthCheck/Form1.resx b/tools/windows-installer/NoPortsHealthCheck/Form1.resx new file mode 100644 index 000000000..29dcb1b3a --- /dev/null +++ b/tools/windows-installer/NoPortsHealthCheck/Form1.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/tools/windows-installer/NoPortsHealthCheck/NoPortsHealthCheck.csproj b/tools/windows-installer/NoPortsHealthCheck/NoPortsHealthCheck.csproj new file mode 100644 index 000000000..4ea18028e --- /dev/null +++ b/tools/windows-installer/NoPortsHealthCheck/NoPortsHealthCheck.csproj @@ -0,0 +1,128 @@ + + + + + Debug + AnyCPU + {CA561C30-B24B-4343-864F-B5115BF93358} + WinExe + NoPortsHealthCheck + NoPortsHealthCheck + v4.8 + 512 + true + true + true + publish\ + true + Web + true + Foreground + 7 + Days + false + false + true + https://localhost:8080/as/ + true + publish.htm + 2 + 1.0.0.%2a + false + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + 5DDC12A5906EFD65E20A1FC8E8DD9BBEF891A664 + + + NoPortsHealthCheck_TemporaryKey.pfx + + + true + + + true + + + + + + + + + + + + + + + + + Form + + + Form1.cs + + + + + + Form1.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + False + Microsoft .NET Framework 4.8 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 + false + + + + \ No newline at end of file diff --git a/tools/windows-installer/NoPortsHealthCheck/Program.cs b/tools/windows-installer/NoPortsHealthCheck/Program.cs new file mode 100644 index 000000000..0d5712f73 --- /dev/null +++ b/tools/windows-installer/NoPortsHealthCheck/Program.cs @@ -0,0 +1,153 @@ +using Microsoft.Win32; +using NoPortsInstaller; +using System; +using System.Collections.Generic; +using System.IO; +using System.Security.Principal; +using System.Windows.Forms; + +namespace NoPortsHealthCheck +{ + internal static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new Form1()); + } + + + private static readonly List logs = new List(); + + + public static bool CanWriteToDirectory(string path) + { + try + { + string testFilePath = Path.Combine(path, "test.txt"); + File.WriteAllText(testFilePath, "test"); + Log($"Wrote to directory: {path}"); + File.Delete(testFilePath); + Log($"Deleted file: {testFilePath}"); + return true; + } + catch (Exception ex) + { + LogErr($"Failed {path}", ex); + ShowErrorLogs(); + return false; + } + } + + public static bool IsUserAdmin() + { + using (WindowsIdentity identity = WindowsIdentity.GetCurrent()) + { + WindowsPrincipal principal = new WindowsPrincipal(identity); + try + { + Log($"Account: {principal.Identity}"); + return principal.IsInRole(WindowsBuiltInRole.Administrator); + } + catch (Exception ex) + { + LogErr("Failed to check if user is admin", ex); + ShowErrorLogs(); + return false; + } + } + } + + public static bool CanWriteToRegistry(RegistryKey baseKey, string subKey) + { + try + { + Log("Creating registry key"); + baseKey.CreateSubKey(subKey); + } + catch (Exception ex) + { + LogErr($"Failed to create registry key {baseKey} {subKey}", ex); + ShowErrorLogs(); + return false; + } + + try + { + using (var key = baseKey.OpenSubKey(subKey, true)) + { + key.SetValue("test", "test"); + Log("Wrote to registry key"); + key.DeleteValue("test"); + Log("Deleted registry key"); + return true; + } + } + catch (Exception ex) + { + LogErr($"Failed to write to registry {baseKey} {subKey}", ex); + ShowErrorLogs(); + return false; + } + } + + public static bool CanModifyEnvironmentVariable() + { + try + { + Environment.SetEnvironmentVariable("TEST_ENV_VAR", "test_value", EnvironmentVariableTarget.User); + Log("Modified environment variable"); + Environment.SetEnvironmentVariable("TEST_ENV_VAR", null, EnvironmentVariableTarget.User); // Clean up + Log("Deleted environment variable"); + return true; + } + catch (Exception ex) + { + LogErr("Failed to modify environment variable", ex); + ShowErrorLogs(); + return false; + } + } + + public static bool CanCreateServices() + { + try + { + ServiceController.Install("TestService", "Test Service", "C:\\Windows\\System32\\svchost.exe"); + ServiceController.TryUninstall("TestService"); + return true; + } + catch (Exception ex) + { + LogErr("Failed to create service", ex); + ShowErrorLogs(); + return false; + } + } + + public static void Log(string what) + { + logs.Add(what); + } + + public static void LogErr(string what, Exception ex) + { + logs.Add($"{what} - {ex.Message} Stack Trace {ex.StackTrace}"); + } + + public static void ShowSuccessLogs() + { + MessageBox.Show($"All checks passed \n \n {string.Join(Environment.NewLine, logs)}", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information); + } + + public static void ShowErrorLogs() + { + MessageBox.Show(string.Join(Environment.NewLine, logs), "Health Check Failed", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } +} diff --git a/tools/windows-installer/NoPortsHealthCheck/Properties/AssemblyInfo.cs b/tools/windows-installer/NoPortsHealthCheck/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..e698b7cf1 --- /dev/null +++ b/tools/windows-installer/NoPortsHealthCheck/Properties/AssemblyInfo.cs @@ -0,0 +1,33 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("NoPortsHealthCheck")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("NoPortsHealthCheck")] +[assembly: AssemblyCopyright("Copyright © 2024")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("ca561c30-b24b-4343-864f-b5115bf93358")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/tools/windows-installer/NoPortsHealthCheck/Properties/Resources.resx b/tools/windows-installer/NoPortsHealthCheck/Properties/Resources.resx new file mode 100644 index 000000000..ffecec851 --- /dev/null +++ b/tools/windows-installer/NoPortsHealthCheck/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/tools/windows-installer/NoPortsHealthCheck/ServiceController.cs b/tools/windows-installer/NoPortsHealthCheck/ServiceController.cs new file mode 100644 index 000000000..8f408ba97 --- /dev/null +++ b/tools/windows-installer/NoPortsHealthCheck/ServiceController.cs @@ -0,0 +1,465 @@ +using System; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; + +namespace NoPortsInstaller +{ + public static class ServiceController + { + private const int STANDARD_RIGHTS_REQUIRED = 0xF0000; + private const int SERVICE_WIN32_OWN_PROCESS = 0x00000010; + + [StructLayout(LayoutKind.Sequential)] + private class SERVICE_STATUS + { + public int dwServiceType = 0; + public ServiceState dwCurrentState = 0; + public int dwControlsAccepted = 0; + public int dwWin32ExitCode = 0; + public int dwServiceSpecificExitCode = 0; + public int dwCheckPoint = 0; + public int dwWaitHint = 0; + } + + [StructLayout(LayoutKind.Sequential)] + private class SERVICE_FAILURE_ACTIONS + { + public int dwResetPeriod = 0; + [MarshalAs(UnmanagedType.LPWStr)] + public string lpRebootMsg = null; + [MarshalAs(UnmanagedType.LPWStr)] + public string lpCommand = null; + public int cActions = 0; + public SC_ACTION[] lpsaActions = new SC_ACTION[10]; + } + + [StructLayout(LayoutKind.Sequential)] + private class SC_ACTION + { + public int type; + public int delay; + } + + #region OpenSCManager + [DllImport("advapi32.dll", EntryPoint = "OpenSCManagerW", ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)] + static extern IntPtr OpenSCManager(string machineName, string databaseName, ScmAccessRights dwDesiredAccess); + #endregion + + #region OpenService + [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)] + static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, ServiceAccessRights dwDesiredAccess); + #endregion + + #region CreateService + [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)] + private static extern IntPtr CreateService(IntPtr hSCManager, string lpServiceName, string lpDisplayName, ServiceAccessRights dwDesiredAccess, int dwServiceType, ServiceBootFlag dwStartType, ServiceError dwErrorControl, string lpBinaryPathName, string lpLoadOrderGroup, IntPtr lpdwTagId, string lpDependencies, string lp, string lpPassword); + #endregion + + #region CloseServiceHandle + [DllImport("advapi32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + static extern bool CloseServiceHandle(IntPtr hSCObject); + #endregion + + #region QueryServiceStatus + [DllImport("advapi32.dll")] + private static extern int QueryServiceStatus(IntPtr hService, SERVICE_STATUS lpServiceStatus); + #endregion + + #region QueryServiceConfig + [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)] + private static extern int ChangeServiceConfig(IntPtr hService, int dwInfoLevel, IntPtr lpInfo); + #endregion + + #region DeleteService + [DllImport("advapi32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool DeleteService(IntPtr hService); + #endregion + + #region ControlService + [DllImport("advapi32.dll")] + private static extern int ControlService(IntPtr hService, ServiceControl dwControl, SERVICE_STATUS lpServiceStatus); + #endregion + + #region StartService + [DllImport("advapi32.dll", SetLastError = true)] + private static extern int StartService(IntPtr hService, int dwNumServiceArgs, int lpServiceArgVectors); + #endregion + + public static void Uninstall(string serviceName) + { + IntPtr scm = OpenSCManager(ScmAccessRights.AllAccess); + + try + { + IntPtr service = OpenService(scm, serviceName, ServiceAccessRights.AllAccess); + if (service == IntPtr.Zero) + { + throw new ApplicationException("Service not installed."); + } + + try + { + StopService(service); + if (!DeleteService(service)) + { + throw new ApplicationException("Could not delete service " + Marshal.GetLastWin32Error()); + } + } + finally + { + CloseServiceHandle(service); + } + } + finally + { + CloseServiceHandle(scm); + } + } + + public static bool ServiceIsInstalled(string serviceName) + { + IntPtr scm = OpenSCManager(ScmAccessRights.Connect); + + try + { + IntPtr service = OpenService(scm, serviceName, ServiceAccessRights.QueryStatus); + + if (service == IntPtr.Zero) + { + return false; + } + + CloseServiceHandle(service); + return true; + } + finally + { + CloseServiceHandle(scm); + } + } + + public static void Install(string serviceName, string displayName, string fileName) + { + IntPtr scm = OpenSCManager(ScmAccessRights.AllAccess); + + try + { + IntPtr service = OpenService(scm, serviceName, ServiceAccessRights.AllAccess); + + if (service == IntPtr.Zero) + { + + service = CreateService(scm, serviceName, displayName, ServiceAccessRights.AllAccess, SERVICE_WIN32_OWN_PROCESS, ServiceBootFlag.AutoStart, ServiceError.Normal, fileName, null, IntPtr.Zero, null, @"NT AUTHORITY\LocalService", null); + } + + if (service == IntPtr.Zero) + { + throw new ApplicationException("Failed to install service."); + } + } + finally + { + CloseServiceHandle(scm); + } + } + + public static void StartService(string serviceName) + { + IntPtr scm = OpenSCManager(ScmAccessRights.Connect); + + try + { + IntPtr service = OpenService(scm, serviceName, ServiceAccessRights.QueryStatus | ServiceAccessRights.Start); + if (service == IntPtr.Zero) + { + throw new ApplicationException("Could not open service."); + } + + try + { + StartService(service); + } + finally + { + CloseServiceHandle(service); + } + } + finally + { + CloseServiceHandle(scm); + } + } + + public static void StopService(string serviceName) + { + IntPtr scm = OpenSCManager(ScmAccessRights.Connect); + + try + { + IntPtr service = OpenService(scm, serviceName, ServiceAccessRights.QueryStatus | ServiceAccessRights.Stop); + if (service == IntPtr.Zero) + { + throw new ApplicationException("Could not open service."); + } + + try + { + StopService(service); + } + finally + { + CloseServiceHandle(service); + } + } + finally + { + CloseServiceHandle(scm); + } + } + + private static void StartService(IntPtr service) + { + SERVICE_STATUS status = new SERVICE_STATUS(); + StartService(service, 0, 0); + var changedStatus = WaitForServiceStatus(service, ServiceState.StartPending, ServiceState.Running); + if (!changedStatus) + { + throw new ApplicationException("Unable to start service"); + } + } + + private static void StopService(IntPtr service) + { + SERVICE_STATUS status = new SERVICE_STATUS(); + ControlService(service, ServiceControl.Stop, status); + var changedStatus = WaitForServiceStatus(service, ServiceState.StopPending, ServiceState.Stopped); + if (!changedStatus) + { + throw new ApplicationException("Unable to stop service"); + } + } + + public static ServiceState GetServiceStatus(string serviceName) + { + IntPtr scm = OpenSCManager(ScmAccessRights.Connect); + + try + { + IntPtr service = OpenService(scm, serviceName, ServiceAccessRights.QueryStatus); + if (service == IntPtr.Zero) + { + return ServiceState.NotFound; + } + + try + { + return GetServiceStatus(service); + } + finally + { + CloseServiceHandle(service); + } + } + finally + { + CloseServiceHandle(scm); + } + } + + private static ServiceState GetServiceStatus(IntPtr service) + { + SERVICE_STATUS status = new SERVICE_STATUS(); + + if (QueryServiceStatus(service, status) == 0) + { + throw new ApplicationException("Failed to query service status."); + } + + return status.dwCurrentState; + } + + + private static bool WaitForServiceStatus(IntPtr service, ServiceState waitStatus, ServiceState desiredStatus) + { + SERVICE_STATUS status = new SERVICE_STATUS(); + + QueryServiceStatus(service, status); + if (status.dwCurrentState == desiredStatus) + { + return true; + } + + int dwStartTickCount = Environment.TickCount; + int dwOldCheckPoint = status.dwCheckPoint; + + while (status.dwCurrentState == waitStatus) + { + // Do not wait longer than the wait hint. A good interval is + // one tenth the wait hint, but no less than 1 second and no + // more than 10 seconds. + + int dwWaitTime = status.dwWaitHint / 10; + + if (dwWaitTime < 1000) + { + dwWaitTime = 1000; + } + else if (dwWaitTime > 10000) + { + dwWaitTime = 10000; + } + + Thread.Sleep(dwWaitTime); + + // Check the status again. + + if (QueryServiceStatus(service, status) == 0) + { + break; + } + + if (status.dwCheckPoint > dwOldCheckPoint) + { + // The service is making progress. + dwStartTickCount = Environment.TickCount; + dwOldCheckPoint = status.dwCheckPoint; + } + else + { + if (Environment.TickCount - dwStartTickCount > status.dwWaitHint) + { + // No progress made within the wait hint + break; + } + } + } + return (status.dwCurrentState == desiredStatus); + } + + private static IntPtr OpenSCManager(ScmAccessRights rights) + { + IntPtr scm = OpenSCManager(null, null, rights); + if (scm == IntPtr.Zero) + { + throw new ApplicationException("Could not connect to service control manager."); + } + + return scm; + } + + + + public static async Task TryUninstall(string service) + { + if (ServiceController.ServiceIsInstalled(service)) + { + int maxAttempts = 3; + for (int i = 0; i < maxAttempts; i++) + { + try + { + StopService(service); + Uninstall(service); + return; + } + catch (Exception ex) + { + if (i == maxAttempts - 1) + { + throw new Exception("Failed to uninstall service", ex); + } + await Task.Delay(500); + } + } + } + } + } + + public enum ServiceState + { + Unknown = -1, // The state cannot be (has not been) retrieved. + NotFound = 0, // The service is not known on the host server. + Stopped = 1, + StartPending = 2, + StopPending = 3, + Running = 4, + ContinuePending = 5, + PausePending = 6, + Paused = 7 + } + + [Flags] + public enum ScmAccessRights + { + Connect = 0x0001, + CreateService = 0x0002, + EnumerateService = 0x0004, + Lock = 0x0008, + QueryLockStatus = 0x0010, + ModifyBootConfig = 0x0020, + StandardRightsRequired = 0xF0000, + AllAccess = (StandardRightsRequired | Connect | CreateService | + EnumerateService | Lock | QueryLockStatus | ModifyBootConfig) + } + + [Flags] + public enum ServiceAccessRights + { + QueryConfig = 0x1, + ChangeConfig = 0x2, + QueryStatus = 0x4, + EnumerateDependants = 0x8, + Start = 0x10, + Stop = 0x20, + PauseContinue = 0x40, + Interrogate = 0x80, + UserDefinedControl = 0x100, + Delete = 0x00010000, + StandardRightsRequired = 0xF0000, + AllAccess = (StandardRightsRequired | QueryConfig | ChangeConfig | + QueryStatus | EnumerateDependants | Start | Stop | PauseContinue | + Interrogate | UserDefinedControl) + } + + public enum ServiceBootFlag + { + Start = 0x00000000, + SystemStart = 0x00000001, + AutoStart = 0x00000002, + DemandStart = 0x00000003, + Disabled = 0x00000004 + } + + public enum ServiceControl + { + Stop = 0x00000001, + Pause = 0x00000002, + Continue = 0x00000003, + Interrogate = 0x00000004, + Shutdown = 0x00000005, + ParamChange = 0x00000006, + NetBindAdd = 0x00000007, + NetBindRemove = 0x00000008, + NetBindEnable = 0x00000009, + NetBindDisable = 0x0000000A + } + + public enum ServiceError + { + Ignore = 0x00000000, + Normal = 0x00000001, + Severe = 0x00000002, + Critical = 0x00000003 + } + + public enum ServiceAction + { + None = 0, + Restart = 1, + Reboot = 2, + RunCommand = 3 + } +} diff --git a/tools/windows-installer/NoPortsInstaller/AccessRules.cs b/tools/windows-installer/NoPortsInstaller/AccessRules.cs new file mode 100644 index 000000000..56e53ff8d --- /dev/null +++ b/tools/windows-installer/NoPortsInstaller/AccessRules.cs @@ -0,0 +1,61 @@ +namespace NoPortsInstaller +{ + public class AccessEntry : IAccessEntry + { + public string atSign { get; set; } + public AccessType type { get; set; } + + AccessEntry() + { + atSign = ""; + type = AccessType.Manager; + } + } + + public class AccessRules : IAccessRules + { + public List Entries { get; set; } + + public List Managers + { + get { return Entries.FindAll(entry => IsManager((AccessEntry)entry)); } + } + + public IAccessEntry? Policy + { + get { return Entries.Find(entry => IsPolicy((AccessEntry)entry)); } + } + + public bool IsValid + { + get { return Entries.Count > 0; } + } + + AccessRules() + { + Entries = new(); + } + + public void SetEntryType(string atSign, AccessType type) + { + // Unset the current policy atSign if it exists (there can only be one) + if (type == AccessType.Policy && Policy != null) + { + InstallLogger.Log($"Unsetting current policy atSign: {Policy.atSign}"); + Policy.type = AccessType.Manager; + } + InstallLogger.Log($"Setting policy atSign: {atSign}"); + Entries.Find(entry => entry.atSign == atSign)!.type = type; + } + + private static bool IsManager(AccessEntry entry) + { + return entry.type == AccessType.Manager; + } + + private static bool IsPolicy(AccessEntry entry) + { + return entry.type == AccessType.Policy; + } + } +} diff --git a/tools/windows-installer/NoPortsInstaller/ActivateController.cs b/tools/windows-installer/NoPortsInstaller/ActivateController.cs new file mode 100644 index 000000000..332fa7171 --- /dev/null +++ b/tools/windows-installer/NoPortsInstaller/ActivateController.cs @@ -0,0 +1,248 @@ +using System.Diagnostics; +using System.IO; +using System.Text.Json.Nodes; + +namespace NoPortsInstaller +{ + class ActivateController + { + private static readonly Controller _controller = App.ControllerInstance; + + /// + /// Run a command with the at_activate.exe, if you want the return codes that comes in stdErr, use returnStdErr = true + /// + /// + /// + /// returns the values that come in stdErr if the errorCode is not 0 (an error exit) + /// + /// stdOut of the command + /// + private static string RunCommand(string args, bool returnStdErr = false) + { + string stdout = ""; + string stderr = ""; + int exitCode; + using (var process = new Process()) + { + var startInfo = process.StartInfo; + + startInfo.FileName = Path.Combine(_controller.InstallDirectory, "at_activate.exe"); + startInfo.UseShellExecute = false; + startInfo.RedirectStandardOutput = true; + startInfo.RedirectStandardInput = true; + startInfo.RedirectStandardError = true; + startInfo.CreateNoWindow = true; + startInfo.Arguments = args; + + try + { + process.Start(); + process.WaitForExit(); + stdout = process.StandardOutput.ReadToEnd(); + stderr = process.StandardError.ReadToEnd(); + exitCode = process.ExitCode; + } + catch(Exception ex) + { + exitCode = 1; + _controller.LoadError(ex); + } + } + + if (exitCode != 0 && !returnStdErr) + { + throw new System.Exception(string.Join("\n", stderr)); + } + if (!string.IsNullOrEmpty(stderr) && returnStdErr) + { + return stderr; + } + return stdout; + + } + + public static void Approve(string enrollmentId) + { + var args = $"approve -a \"{_controller.DeviceAtsign}\" -i {enrollmentId}"; + try + { + RunCommand(args); + } + catch(Exception ex) + { + _controller.LoadError(ex); + } + } + + public static void Deny(string enrollmentId) + { + var args = $"deny -a \"{_controller.DeviceAtsign}\" -i {enrollmentId}"; + try + { + RunCommand(args); + } + catch(Exception ex) + { + _controller.LoadError(ex); + } + } + + public static string GenerateOTP() + { + var args = $"otp -a \"{_controller.DeviceAtsign}\""; + try + { + return RunCommand(args); + } + catch(Exception ex) + { + _controller.LoadError(ex); + return ""; + } + } + + public static AtsignStatus Status(string atsign) + { + var args = $"status -a \"{atsign}\""; + string response = ""; + try + { + response = RunCommand(args, true); + } + catch (Exception ex) + { + _controller.LoadError(ex); + } + var returnString = response.Split(":").ToList(); + if(returnString.Count < 1) + { + // Most likely at_activate binary is missing if the output was empty + _controller.LoadError(new Exception("No output from at_activate status")); + } + var exitCode = returnString[0].Split(" ")[1]; + + if (exitCode == "0") + { + return AtsignStatus.Activated; + } + if (exitCode == "1") + { + return AtsignStatus.NotActivated; + } + return AtsignStatus.DNE; + + } + + private static readonly string AppName = "noports_win"; + private static readonly List Namespaces = ["sshnp: rw, sshrvd: rw", "sshrvd: rw, sshnp: rw"]; + + public static bool Enroll(string otp) + { + string devName = _controller.DeviceName; + var args = + $"enroll -a \"{_controller.DeviceAtsign}\" -s \"{otp}\" -p \"{AppName}\" -d \"{devName}\" -n \"sshnp:rw,sshrvd:rw\" -k \"{Path.Combine(_controller.AtsignKeysDirectory, _controller.DeviceAtsign + "_key.atKeys")}\""; + string response = ""; + try + { + response = RunCommand(args); + } + catch(Exception ex) + { + _controller.LoadError(ex); + } + + if (response.Contains("[Success]")) + { + return true; + } + + return false; + } + + public static List ListEnrollments() + { + var args = $"list -a \"{_controller.DeviceAtsign}\" -s pending"; + var response = ""; + try + { + response = RunCommand(args); + } + catch(Exception ex) + { + _controller.LoadError(ex); + } + List strings = response.Split("\n").ToList(); + List lines = []; + for (int i = 2; i < strings.Count; i++) + { + var parts = strings[i].Trim().Split().ToList(); + if (parts.Count < 4) continue; + parts.RemoveAll(x => x == ""); + + EnrollmentRecord record = new(parts[0], parts[3]); + lines.Add(record); + } + return lines; + } + + public static bool CheckIfMPKAM(string atsign) + { + var dir = Path.Combine(_controller.AtsignKeysDirectory, atsign + "_key.atKeys"); + if (File.Exists(dir)) + { + string enrollmentId = ""; + var fileContent = File.ReadAllText(dir); + if (fileContent.Contains("enrollmentId")) + { + try + { + var json = JsonObject.Parse(fileContent); + if (json != null) enrollmentId = (String?)json["enrollmentId"] ?? ""; + } + catch + { + throw new Exception("Failed to parse the atKeys file json"); + } + var args = $"list -a \"{atsign}\" -s \"approved\""; + var response = RunCommand(args); + + List strings = response.Split("\n").ToList(); + int count = strings.Count; + for (int i = 2; i <= count; i++) + { + if (strings[i].Contains(enrollmentId)) + { + var braceStart = strings[i].IndexOf("{"); + var braceEnd = strings[i].IndexOf("}"); + if (braceStart != -1 && braceEnd != -1) + { + var permissions = strings[i].Substring(braceStart, braceEnd - braceStart); + return permissions.Contains("__manage: rw") && permissions.Contains("*: rw"); + } + } + } + + return false; + } + else + { + return true; + } + } + throw new Exception("Failed to locate atKeys file"); + } + } + + public class EnrollmentRecord(string Id, string DeviceName) + { + public string Id { get; set; } = Id; + public string DeviceName { get; set; } = DeviceName; + } + + public enum AtsignStatus + { + DNE, + NotActivated, + Activated + } +} diff --git a/tools/windows-installer/NoPortsInstaller/App.xaml b/tools/windows-installer/NoPortsInstaller/App.xaml new file mode 100644 index 000000000..29eeceeb0 --- /dev/null +++ b/tools/windows-installer/NoPortsInstaller/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/tools/windows-installer/NoPortsInstaller/App.xaml.cs b/tools/windows-installer/NoPortsInstaller/App.xaml.cs new file mode 100644 index 000000000..d50bbfd07 --- /dev/null +++ b/tools/windows-installer/NoPortsInstaller/App.xaml.cs @@ -0,0 +1,27 @@ +using NoPortsInstaller.Pages; +using System.Windows; + +namespace NoPortsInstaller; + +/// +/// Interaction logic for App.xaml +/// +public partial class App : Application +{ + public static Controller ControllerInstance { get; set; } + public App() + { + ControllerInstance = new Controller(); + } + + public void OnStartup(object sender, StartupEventArgs e) + { + MainWindow mainWindow = new(); + mainWindow.Show(); + if (e.Args.Length == 1 && e.Args[0] == "u") + { + ControllerInstance.LoadPages(InstallType.Uninstall); + } + } +} + diff --git a/tools/windows-installer/NoPortsInstaller/AssemblyInfo.cs b/tools/windows-installer/NoPortsInstaller/AssemblyInfo.cs new file mode 100644 index 000000000..fbfbb0bc9 --- /dev/null +++ b/tools/windows-installer/NoPortsInstaller/AssemblyInfo.cs @@ -0,0 +1,11 @@ +using System.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + diff --git a/tools/windows-installer/NoPortsInstaller/Assets/NoPortsDark.png b/tools/windows-installer/NoPortsInstaller/Assets/NoPortsDark.png new file mode 100644 index 000000000..2a964eb99 Binary files /dev/null and b/tools/windows-installer/NoPortsInstaller/Assets/NoPortsDark.png differ diff --git a/tools/windows-installer/NoPortsInstaller/Assets/noports-stacked-dark.ico b/tools/windows-installer/NoPortsInstaller/Assets/noports-stacked-dark.ico new file mode 100644 index 000000000..2aeba216a Binary files /dev/null and b/tools/windows-installer/NoPortsInstaller/Assets/noports-stacked-dark.ico differ diff --git a/tools/windows-installer/NoPortsInstaller/Controller.cs b/tools/windows-installer/NoPortsInstaller/Controller.cs new file mode 100644 index 000000000..fa0597bc2 --- /dev/null +++ b/tools/windows-installer/NoPortsInstaller/Controller.cs @@ -0,0 +1,766 @@ +using Microsoft.Win32; +using NoPortsInstaller.Pages; +using NoPortsInstaller.Pages.Activate; +using NoPortsInstaller.Pages.Install; +using NoPortsInstaller.Pages.Update; +using System.Diagnostics; +using System.IO; +using System.Security.AccessControl; +using System.Windows; +using System.Windows.Controls; +using static NoPortsInstaller.ActivateController; + +namespace NoPortsInstaller +{ + public class Controller + { + public string InstallDirectory { get; set; } + public InstallType InstallType { get; set; } + public string ClientAtsign { get; set; } + public string DeviceAtsign { get; set; } + public string DeviceName { get; set; } + public string RegionAtsign { get; set; } + public string AdditionalArgs { get; set; } + public string AccessGroup { get; set; } + public bool IsInstalled + { + get { return Directory.Exists(InstallDirectory); } + set { } + } + public bool IsActivateInstalled + { + get { return Directory.Exists(InstallDirectory) && File.Exists(InstallDirectory + "/at_activate.exe"); } + set { } + } + public List Pages { get; set; } + private int index = 0; + private readonly string serviceDirectory = + Environment.ExpandEnvironmentVariables("%systemroot%") + + @"\ServiceProfiles\LocalService\"; + public Window? Window { get; set; } + public AccessRules AccessRules { get; set; } + + public string AtsignKeysDirectory + { + get + { + return Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + @".atsign\keys" + ); + } + } + + public Controller() + { + InstallDirectory = "C:\\Program Files\\NoPorts"; + InstallType = InstallType.Home; + ClientAtsign = ""; + DeviceAtsign = ""; + DeviceName = ""; + RegionAtsign = ""; + AdditionalArgs = ""; + Pages = []; + IsInstalled = false; + AccessGroup = "Users"; + LogEnvironment(); + } + + /// + /// Installs NoPorts depending on the InstallType. + /// + /// + /// + public async Task Install(ProgressBar progress, Label status ) + { + try + { + status.Content = "Creating directories..."; + CreateDirectories(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)); + await UpdateProgressBar(progress, 25); + status.Content = "Installing NoPorts..."; + await MoveResources(); + VerifyInstall(); + status.Content = "Updating Trusted Certificates..."; + await UpdateProgressBar(progress, 50); + UpdateTrustedCerts(); + await UpdateProgressBar(progress, 75); + status.Content = "Creating Registries NoPorts..."; + CreateRegistryKeys(); + await UpdateProgressBar(progress, 100); + InstallLogger.Log($"Installation Complete"); + if (InstallType.Equals(InstallType.Client)) + { + Pages.Add(new FinishInstall()); + } + if (InstallType.Equals(InstallType.Device)) + { + EnrollDevice(); + } + + } + catch (Exception ex) + { + await Cleanup(); + LoadError(ex); + } + } + + public async Task InstallService(ProgressBar progress, Label status) + { + try + { + await UpdateProgressBar(progress, 25); + status.Content = "Setting up NoPorts Service..."; + CopyIntoServiceAccount(); + await SetupService(status); + await UpdateProgressBar(progress, 100); + NextPage(); + } + catch (Exception ex) + { + await Cleanup(); + LoadError(ex); + } + } + + /// + /// Uninstalls NoPorts, including the service. + /// + /// + public async Task Uninstall(ProgressBar progress) + { + try + { + RegistryKey? registryKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\NoPorts"); + object? binPath = null; + if (registryKey != null) + { + binPath = registryKey.GetValue("BinPath"); + Registry.LocalMachine.DeleteSubKey(@"SOFTWARE\NoPorts"); + Registry.LocalMachine.DeleteSubKey( + @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\NoPorts" + ); + } + + await ServiceController.TryUninstall("sshnpd"); + + if (binPath != null) + { + DirectoryInfo di = new(binPath.ToString()!); + foreach (FileInfo file in di.GetFiles()) + { + file.Delete(); + } + foreach (DirectoryInfo dir in di.GetDirectories()) + { + dir.Delete(true); + } + di.Delete(); + } + await UpdateProgressBar(progress, 100); + } + catch (Exception ex) + { + LoadError(ex); + } + } + + /// + /// will change + /// + public void EnrollDevice() + { + if (!KeysInstalled()) + { + if (ActivateController.Status(DeviceAtsign) != AtsignStatus.Activated) + { + throw new Exception("Keys not found locally and on registrar. Please onboard first."); + } + + InstallLogger.Log("Starting enrollment process before continuing to install..."); + Pages.Add(new Enroll()); + } + else + { + InstallLogger.Log("Keys found. Continuing to service install..."); + Pages.Add(new InstallService()); + Pages.Add(new FinishInstall()); + } + } + + public void UpdateConfigRegistry() + { + try + { + RegistryKey? registryKey = Registry.LocalMachine.OpenSubKey(@"Software\NoPorts"); + var args = $"-a {DeviceAtsign} -m {ClientAtsign} -d {DeviceName} -sv"; + if (registryKey != null) + { + if (AdditionalArgs != "") + { + args += AdditionalArgs; + } + registryKey.SetValue("DeviceArgs", args); + registryKey.Close(); + } + } + catch (Exception ex) + { + LoadError(ex); + } + } + + private void CreateDirectories(string userHome) + { + DirectorySecurity securityRules = new(); + DirectoryInfo di; + securityRules.AddAccessRule( + new FileSystemAccessRule(AccessGroup, FileSystemRights.ExecuteFile, AccessControlType.Allow) + ); + List directories = + [ + Path.Combine(userHome, ".sshnp"), + Path.Combine(userHome, ".atsign"), + Path.Combine(userHome, ".atsign", "keys"), + InstallDirectory, + ]; + + if (InstallType.Equals(InstallType.Device)) + { + directories.Add(Path.Combine(userHome, ".ssh")); + } + + foreach (string dir in directories) + { + if (!Directory.Exists(dir)) + { + InstallLogger.Log($"Creating directory: {dir}"); + Directory.CreateDirectory(dir); + di = new DirectoryInfo(dir); + di.SetAccessControl(securityRules); + } + else + { + InstallLogger.Log($"Directory already exists, skipping: {dir}"); + } + } + if (InstallType.Equals(InstallType.Device)) + { + var authkeys = Path.Combine(userHome, ".ssh", "authorized_keys"); + InstallLogger.Log($"Creating authorized_keys file: {authkeys}"); + if (!File.Exists(authkeys)) + { + FileInfo fi = new(authkeys); + fi.Create().Close(); + } + } + } + + private void CopyIntoServiceAccount() + { + string sourceFile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + string[] sources = + [ + Path.Combine(sourceFile, ".ssh", "authorized_keys"), + Path.Combine(sourceFile, ".atsign", "keys", DeviceAtsign + "_key.atKeys"), + ]; + InstallLogger.Log($"Creating Directories in LocalService Account: {serviceDirectory}"); + CreateDirectories(serviceDirectory); + string[] destinations = + [ + Path.Combine(serviceDirectory, ".ssh", "authorized_keys"), + Path.Combine(serviceDirectory, ".atsign", "keys", DeviceAtsign + "_key.atKeys"), + ]; + for (int i = 0; i < sources.Length; i++) + { + try + { + if (File.Exists(sources[i])) + { + InstallLogger.Log($"Copying {sources[i]} to {destinations[i]}"); + File.Copy(sources[i], destinations[i], true); + } + } + catch + { + throw new FileNotFoundException( + "No keys found. Use at_activate to onboard keys or enroll/approve this device." + ); + } + } + } + + private async Task MoveResources() + { + Dictionary resources = + new() + { + { Properties.Resources.at_activate, "at_activate.exe" }, + }; + + if (InstallType.Equals(InstallType.Device) || InstallType.Equals(InstallType.Client)) + { + resources.Add(Properties.Resources.srv, "srv.exe"); + } + if (InstallType.Equals(InstallType.Device)) + { + await ServiceController.TryUninstall("sshnpd"); + resources.Add(Properties.Resources.sshnpd, "sshnpd.exe"); + resources.Add(Properties.Resources.SshnpdService, "SshnpdService.exe"); + } + else if (InstallType.Equals(InstallType.Client)) + { + resources.Add(Properties.Resources.sshnp, "sshnp.exe"); + resources.Add(Properties.Resources.npt, "npt.exe"); + } + + foreach (var resource in resources) + { + string path = Path.Combine(InstallDirectory, resource.Value); + if (Directory.Exists(InstallDirectory)) + { + InstallLogger.Log($"placing resource: {resource.Value} into {path}"); + await Task.Run(() => File.WriteAllBytes(path, resource.Key)); + } + else + { + throw new Exception("Install directory does not exist."); + } + } + InstallLogger.Log("Adding NoPorts to PATH..."); + var newValue = + Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Machine) + + ";" + + InstallDirectory; + Environment.SetEnvironmentVariable("PATH", newValue, EnvironmentVariableTarget.Machine); + } + + private async Task SetupService(Label status) + { + if (!EventLog.SourceExists("NoPorts")) + { + InstallLogger.Log("Makign Event Logger for NoPorts"); + EventLog.CreateEventSource("NoPorts", "NoPorts"); + } + + try + { + InstallLogger.Log("Installing sshnpd service..."); + status.Content = "Installing sshnpd service..."; + await Task.Run( + () => + ServiceController.InstallAndStart( + "sshnpd", + "sshnpd", + InstallDirectory + @"\SshnpdService.exe" + ) + ); + InstallLogger.Log($"sshnpd for {DeviceAtsign} at {DeviceName} is now running."); + InstallLogger.Log("Setting service options for sshnpd..."); + status.Content = "Configuring the Windows Service..."; + await Task.Run(() => ServiceController.SetRecoveryOptions("sshnpd")); + Process.Start("sc", "description sshnpd NoPorts-SSH-Daemon"); + await Task.Run( + () => + ServiceController.CreateUninstaller( + Path.Combine( + Path.GetDirectoryName(Environment.ProcessPath)!, + "NoPortsInstaller.exe u" + ) + ) + ); + } + catch + { + throw; + } + return; + } + + private static void UpdateTrustedCerts() + { + var tempPath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + @"Temp\trusted-certs" + ); + if (!Directory.Exists(tempPath)) + { + Directory.CreateDirectory(tempPath); + } + tempPath += @"\roots.sst"; + InstallLogger.Log("Generating certificates..."); + ProcessStartInfo startInfo = + new() + { + FileName = "Certutil.exe", + Arguments = $"-generateSSTFromWU {tempPath}", + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true, + }; + + using (Process? process = Process.Start(startInfo)) + { + process?.WaitForExit(); + } + InstallLogger.Log("Importing certificates..."); + startInfo = new ProcessStartInfo + { + FileName = "powershell.exe", + Verb = "runas", + Arguments = + $"-WindowStyle Hidden -Command \"(Get-ChildItem '{tempPath}') | Import-Certificate -CertStoreLocation Cert:\\LocalMachine\\Root\"", + UseShellExecute = true, + CreateNoWindow = true, + }; + + using (Process? process = Process.Start(startInfo)) + { + process?.WaitForExit(); + } + } + + private void CreateRegistryKeys() + { + InstallLogger.Log("Creating registry keys..."); + RegistryKey registryKey = Registry.LocalMachine.CreateSubKey(@"Software\NoPorts"); + registryKey.SetValue("BinPath", InstallDirectory); + if (InstallType.Equals(InstallType.Device)) + { + var args = $"-a {DeviceAtsign} -m {ClientAtsign} -d {DeviceName}"; + if (AdditionalArgs != "") + { + args += AdditionalArgs; + } + + args += " -v"; + registryKey.SetValue("DeviceArgs", args); + } + registryKey.Close(); + } + + private static Task Cleanup() + { + return Task.Run(() => + { + try + { + Directory.Delete( + Path.Combine( + Environment.GetFolderPath( + Environment.SpecialFolder.LocalApplicationData + ), + @"Temp\NoPorts" + ), + true + ); + RegistryKey? registryKey = Registry.LocalMachine.OpenSubKey( + @"SOFTWARE\NoPorts" + ); + if (registryKey != null) + { + registryKey.DeleteValue("BinPath"); + registryKey.DeleteValue("DeviceArgs"); + registryKey.Close(); + } + } + catch { } + }); + } + + /// + /// Loads the appropriate pages based on the InstallType. + /// + public void LoadPages(InstallType type) + { + InstallType = type; + index = 0; + Pages.Clear(); + switch (InstallType) + { + case InstallType.Home: + InstallLogger.DumpLog(); + Pages.Add(new Setup()); + break; + case InstallType.Uninstall: + Pages.Add(new UninstallPage()); + break; + case InstallType.Client: + Pages.Add(new Setup()); + Pages.Add(new ClientConfig()); + break; + case InstallType.Device: + Pages.Add(new Setup()); + Pages.Add(new DeviceConfig1()); + Pages.Add(new DeviceConfig2()); + break; + case InstallType.Enroll: + Pages.Add(new Setup()); + if(!IsActivateInstalled) + { + Pages.Add(new Download()); + } + Pages.Add(new PreEnroll()); + break; + case InstallType.Onboard: + Pages.Add(new Setup()); + if(!IsActivateInstalled) + { + Pages.Add(new Download()); + } + Pages.Add(new Onboard()); + Pages.Add(new FinishGeneratingKeys()); + break; + case InstallType.Approve: + Pages.Add(new Setup()); + if (!IsActivateInstalled) + { + Pages.Add(new Download()); + } + Pages.Add(new AtsignApprove()); + break; + } + if (Window != null) + { + Window.Content = Pages[index]; + } + else + { + Pages.Add(new ServiceErrorPage("Window is null")); + } + } + + public void LoadError(Exception ex) + { + index = 0; + Pages.Clear(); + InstallLogger.Log($"Error message: {ex.Message}"); + InstallLogger.Log($"Error trace: {ex.StackTrace}"); + InstallLogger.DumpLog(); + Pages.Add(new ServiceErrorPage(ex.Message)); + NextPage(); + } + + /// + /// Moves to the next page in the Pages list. + /// + public void NextPage() + { + if (index < Pages.Count - 1) + { + index++; + } + Window!.Content = Pages[index]; + } + + /// + /// Moves to the previous page in the Pages list. + /// + public void PreviousPage() + { + if (index > 0) + { + index--; + } + Window!.Content = Pages[index]; + } + + /// + /// Updates the progress bar value asynchronously. + /// + /// The progress bar control. + /// The target value for the progress bar. + /// A task representing the asynchronous operation. + private async static Task UpdateProgressBar(ProgressBar pb, int value) + { + double start = pb.Value; + await Task.Run(() => + { + for (double i = start; i <= value; i++) + { + pb.Dispatcher.Invoke(() => pb.Value = i); + Thread.Sleep(20); + } + }); + } + + /// + /// Populates the given ComboBox with available Atsigns. + /// + /// The ComboBox control to populate. + public void PopulateAtsigns(ComboBox box) + { + List files = []; + if ( + Directory.Exists( + Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + @".atsign\keys" + ) + ) + ) + { + files = + [ + .. Directory.GetFiles( + Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + @".atsign\keys" + ), + "*.atKeys", + SearchOption.AllDirectories + ), + ]; + } + foreach (var key in files) + { + ComboBoxItem item = + new() { Content = Path.GetFileNameWithoutExtension(key).Replace("_key", "") }; + box.Items.Add(item); + } + } + + /// + /// Normalizes the given Atsign by adding the '@' symbol if it is missing. + /// + /// The Atsign to normalize. + /// The normalized Atsign. + public string NormalizeAtsign(string atsign) + { + if (atsign.StartsWith('@')) + { + return atsign; + } + else + { + return "@" + atsign; + } + } + + public string NormalizeArgs(string args) + { + string[] argArray = args.Split(args, ' '); + for (int i = 0; i < argArray.Length; i++) + { + if (argArray[i].Equals("--managers")) + { + argArray[i + 1] = NormalizeMultipleManagers(argArray[i + 1]); + } + if (argArray[i].Equals("--po")) + { + argArray[i + 1] = NormalizePermittedPorts(argArray[i + 1]); + } + } + return string.Join(' ', argArray); + } + + private string NormalizeMultipleManagers(string atsigns) + { + string[] atsignArray = atsigns.Split(','); + for (int i = 0; i < atsignArray.Length; i++) + { + atsignArray[i] = NormalizeAtsign(atsignArray[i]); + } + return string.Join(',', atsignArray); + } + + private static string NormalizePermittedPorts(string ports) + { + string[] portArray = ports.Split(','); + for (int i = 0; i < portArray.Length; i++) + { + if (!portArray[i].Contains(':')) + { + portArray[i] = "localhost:" + portArray[i]; + } + } + return string.Join(',', portArray); + } + + public string NormalizeDeviceName(string device) + { + if (device.Contains(' ') || device.Contains('-')) + { + return device.ToLower().Replace(" ", "_"); + } + return device.ToLower(); + } + + public void VerifyInstall() + { + List dirs = ["at_activate.exe"]; + if(InstallType == InstallType.Client || InstallType == InstallType.Device ) + { + dirs.Add("srv.exe"); + } + if (InstallType == InstallType.Client) + { + dirs.Add("sshnp.exe"); + dirs.Add("npt.exe"); + } + if (InstallType == InstallType.Device) + { + dirs.Add("sshnpd.exe"); + dirs.Add("Sshnpd.exe"); + } + foreach (var dir in dirs) + { + string sourceFilePath = Path.Combine(InstallDirectory, dir); + if (!File.Exists(sourceFilePath)) + { + throw new Exception("File not found: " + sourceFilePath); + } + } + } + + private bool KeysInstalled() + { + return File.Exists(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), @".atsign\keys", DeviceAtsign + "_key.atKeys")); + } + + private static void LogEnvironment() + { + InstallLogger.Log("Environment Variables:"); + InstallLogger.Log( + $"User Home: {Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}" + ); + InstallLogger.Log( + $"Program Files: {Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles)}" + ); + InstallLogger.Log( + $"Local App Data: {Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}" + ); + InstallLogger.Log( + $"LocalService: {Environment.ExpandEnvironmentVariables("%systemroot%") + @"\ServiceProfiles\LocalService\"}" + ); + try + { + Directory.CreateDirectory( + Environment.ExpandEnvironmentVariables("%systemroot%") + @"\ServiceProfiles\LocalService\" + @"\.atsign" + ); + InstallLogger.Log("Created .atsign directory in LocalService."); + } + catch + { + InstallLogger.Log("Failed to create .atsign directory in LocalService Account."); + } + + InstallLogger.Log("User Information:"); + InstallLogger.Log($"Username: {Environment.UserName}"); + InstallLogger.Log($"Domain: {Environment.UserDomainName}"); + + InstallLogger.Log("Operating System Information:"); + InstallLogger.Log($"OS Version: {Environment.OSVersion}"); + InstallLogger.Log($"Machine Name: {Environment.MachineName}"); + InstallLogger.Log($"System Directory: {Environment.SystemDirectory}"); + InstallLogger.Log($"Is 64-bit Operating System: {Environment.Is64BitOperatingSystem}"); + InstallLogger.Log($"Is 64-bit Process: {Environment.Is64BitProcess}"); + + InstallLogger.Log("Process Information:"); + InstallLogger.Log($"Process Path: {Environment.ProcessPath}"); + InstallLogger.Log($"Process is run as Admin: {Environment.IsPrivilegedProcess}"); + InstallLogger.Log($"Runtime Version:{Environment.Version}"); + } + } +} diff --git a/tools/windows-installer/NoPortsInstaller/IAccessRules.cs b/tools/windows-installer/NoPortsInstaller/IAccessRules.cs new file mode 100644 index 000000000..a8488c2df --- /dev/null +++ b/tools/windows-installer/NoPortsInstaller/IAccessRules.cs @@ -0,0 +1,25 @@ +namespace NoPortsInstaller +{ + public enum AccessType + { + Manager, + Policy, + } + + public interface IAccessEntry + { + string atSign { get; set; } + AccessType type { get; set; } + } + + public interface IAccessRules + { + List Entries { get; set; } + List Managers { get; } + IAccessEntry? Policy { get; } + + bool IsValid { get; } + + void SetEntryType(string atSign, AccessType type); + } +} diff --git a/tools/windows-installer/NoPortsInstaller/InstallLogger.cs b/tools/windows-installer/NoPortsInstaller/InstallLogger.cs new file mode 100644 index 000000000..6d61344f0 --- /dev/null +++ b/tools/windows-installer/NoPortsInstaller/InstallLogger.cs @@ -0,0 +1,33 @@ +using System.IO; + +namespace NoPortsInstaller +{ + internal class InstallLogger + { + private static readonly string LogDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "NoPortsInstaller"); + private static readonly string LogFile = Path.Combine(LogDirectory, "install.log"); + private static readonly List LogMessages = []; + + public static void Log(string message) + { + LogMessages.Add($"{DateTime.Now} - {message}"); + } + + public static void DumpLog() + { + if (!Directory.Exists(LogDirectory)) + { + Directory.CreateDirectory(Path.GetDirectoryName(LogFile)!); + } + if (File.Exists(LogFile)) + { + File.Delete(LogFile); + } + using StreamWriter stream = new(File.Create(LogFile)); + foreach (string message in LogMessages) + { + stream.WriteLine(message); + } + } + } +} \ No newline at end of file diff --git a/tools/windows-installer/NoPortsInstaller/InstallType.cs b/tools/windows-installer/NoPortsInstaller/InstallType.cs new file mode 100644 index 000000000..5a6ebe0fe --- /dev/null +++ b/tools/windows-installer/NoPortsInstaller/InstallType.cs @@ -0,0 +1,11 @@ +public enum InstallType +{ + Home, + Device, + Client, + Onboard, + Enroll, + Approve, + Uninstall +} + diff --git a/tools/windows-installer/NoPortsInstaller/NoPortsInstaller.csproj b/tools/windows-installer/NoPortsInstaller/NoPortsInstaller.csproj new file mode 100644 index 000000000..ab64d8b62 --- /dev/null +++ b/tools/windows-installer/NoPortsInstaller/NoPortsInstaller.csproj @@ -0,0 +1,83 @@ + + + + WinExe + {FCF07AC8-119A-4DD0-A45D-3D765D138A85} + 0.1.0.0 + net8.0-windows + enable + enable + true + embedded + win-x64 + app.manifest + true + true + true + true + copyused + Assets\noports-stacked-dark.ico + + + + + + + + + + + + + + PreserveNewest + + + + + + PreserveNewest + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + + + + + + + + Code + + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + diff --git a/tools/windows-installer/NoPortsInstaller/Pages/Activate/Approve.xaml b/tools/windows-installer/NoPortsInstaller/Pages/Activate/Approve.xaml new file mode 100644 index 000000000..3ba08a796 --- /dev/null +++ b/tools/windows-installer/NoPortsInstaller/Pages/Activate/Approve.xaml @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + +