Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php
// Copyright 2022 Jared Hendrickson
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

require_once("api/framework/APIEndpoint.inc");

class APIInterfaceAvailable extends APIEndpoint {
public function __construct() {
$this->url = "/api/v1/interface/available";
}

protected function get() {
return (new APIInterfaceAvailableRead())->call();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php
// Copyright 2022 Jared Hendrickson
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

require_once("api/framework/APIEndpoint.inc");

class APIServicesUnboundHostOverrideFlush extends APIEndpoint {
public function __construct() {
$this->url = "/api/v1/services/unbound/host_override/flush";
}

protected function delete() {
return (new APIServicesUnboundHostOverrideFlushDelete())->call();
}
}
16 changes: 2 additions & 14 deletions pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc
Original file line number Diff line number Diff line change
Expand Up @@ -569,21 +569,9 @@ function get($id, $data=[], $all=false) {
],
1077 => [
"status" => "bad request",
"code" => 400,
"return" => $id,
"message" => "Remote URL does not contain a pfSense package"
],
1078 => [
"status" => "bad request",
"code" => 504,
"return" => $id,
"message" => "System package installation exceeded timeout"
],
1079 => [
"status" => "bad request",
"code" => 400,
"code" => 500,
"return" => $id,
"message" => "System package installation timeout cannot be greater than 120 seconds"
"message" => "System package failed to install"
],
1080 => [
"status" => "bad request",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php
// Copyright 2022 Jared Hendrickson
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

require_once("api/framework/APIModel.inc");
require_once("api/framework/APIResponse.inc");


class APIInterfaceAvailableRead extends APIModel {
# Create our method constructor
public function __construct() {
parent::__construct();
$this->privileges = ["page-all", "page-interfaces-assignnetworkports"];

}

public function action() {
return APIResponse\get(0, APITools\get_all_avail_interfaces());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php
// Copyright 2022 Jared Hendrickson
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

require_once("api/framework/APIModel.inc");
require_once("api/framework/APIResponse.inc");

class APIServicesUnboundHostOverrideFlushDelete extends APIModel {
# Create our method constructor
public function __construct() {
parent::__construct();
$this->privileges = ["page-all", "page-services-dnsresolver-edithost"];
$this->change_note = "Flushed DNS Resolver host overrides via API";
}

public function action() {
# Capture the host overrides being deleted, if empty, default to empty array
$this->validated_data = ($this->config["unbound"]["hosts"]) ?: [];

# Remove the host overrides from the configuration
unset($this->config["unbound"]["hosts"]);
$this->write_config();

# Mark the Unbound subsystem as changed, but do not allow clients to immediately apply the change.
mark_subsystem_dirty("unbound");

return APIResponse\get(0, $this->validated_data);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ class APISystemCertificateUpdate extends APIModel {
break;
}
}
# If we did not find an ID in the loop, return a not found error
if (is_null($this->id)) {
$this->errors[] = APIResponse\get(1009);
}
}
}
# If we did not find an ID in the loop, return a not found error
if (is_null($this->id)) {
$this->errors[] = APIResponse\get(1009);
}
}

private function __validate_crt() {
Expand Down
154 changes: 22 additions & 132 deletions pfSense-pkg-API/files/etc/inc/api/models/APISystemPackageCreate.inc
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ require_once("api/framework/APIResponse.inc");


class APISystemPackageCreate extends APIModel {
public $pkg_mode;

# Create our method constructor
public function __construct() {
parent::__construct();
Expand All @@ -28,32 +26,35 @@ class APISystemPackageCreate extends APIModel {
}

public function action() {
return APIResponse\get($this->install_pkg());
# Force the action to be logged in the configuration history if our API response code is success
if (pkg_install($this->validated_data["name"])) {
return APIResponse\get(0);
} else {
return APIResponse\get(1077);
}
}

public function validate_payload(){
$this->__validate_force(); // Must run before __validate_name
$this->__validate_name();
$this->__validate_timeout();
}

# Attempts to install the package specified in $this->validated_data["name"]
public function install_pkg() {
# Format and execute our pkg command. Enforce a timeout to prevent gateway timeouts.
$pkg_force = ($this->validated_data["force"]) ? " -f" : "";
$pkg_y = ($this->pkg_mode === "install") ? " -y" : "";
$pkg_cmd = "pkg ".$this->pkg_mode.$pkg_y.$pkg_force." ".$this->validated_data["name"]." 2>&1";
exec("timeout ".$this->validated_data["timeout"]." ".$pkg_cmd, $pkg_out, $pkg_rc);

# Check for known errors
$api_rc = $this->__check_pkg_install($pkg_out, $pkg_rc);

# Force the action to be logged in the configuration history if our API response code is success
if ($api_rc === 0) {
$this->write_config();
private function __validate_name() {
# Check for our required name input
if (isset($this->initial_data["name"])) {
# Ensure this package exists in pfSense's repos
if ($this->is_pkg_in_repo($this->initial_data["name"])) {
# Ensure package is not already installed
if (!is_pkg_installed($this->initial_data["name"])) {
$this->validated_data["name"] = $this->initial_data["name"];
} else {
$this->errors[] = APIResponse\get(1076);
}
} else {
$this->errors[] = APIResponse\get(1075);
}
} else {
$this->errors[] = APIResponse\get(1073);
}

return $api_rc;
}

# Checks pfSense's upstream package repo for a package by name. Requires upstream internet connection.
Expand All @@ -74,115 +75,4 @@ class APISystemPackageCreate extends APIModel {

return false;
}

private function __validate_name() {
# Check for our required name input
if (isset($this->initial_data["name"])) {
# Check if this is a remote/third-party package to be installed by URL
if (filter_var($this->initial_data["name"], FILTER_VALIDATE_URL)) {
# Set pkg add to install package from URL
$this->pkg_mode = "add";
$this->validated_data["name"] = filter_var($this->initial_data["name"], FILTER_VALIDATE_URL);
}
# Otherwise, we will assume this package exists in pfSense's package repos
else {
# Set pkg install to install the package from pfSense's repos
$this->pkg_mode = "install";

# Ensure this package exists in pfSense's repos
if ($this->is_pkg_in_repo($this->initial_data["name"])) {
# Ensure package is not already installed
if (!is_pkg_installed($this->initial_data["name"]) or $this->validated_data["force"]) {
$this->validated_data["name"] = $this->initial_data["name"];
} else {
$this->errors[] = APIResponse\get(1076);
}
} else {
$this->errors[] = APIResponse\get(1075);
}

}
} else {
$this->errors[] = APIResponse\get(1073);
}
}

private function __validate_force() {
# Check for our optional force input
if ($this->initial_data["force"] === true) {
$this->validated_data["force"] = true;
}
}

private function __validate_timeout() {
# Check for our optional timeout input
if (isset($this->initial_data["timeout"])) {
# Require timeout value to be 120 seconds or less
if (is_numeric($this->initial_data["timeout"]) and intval($this->initial_data["timeout"]) <= 120) {
# Force timeouts less than 5 to minimum of 5 seconds
if (intval($this->initial_data["timeout"]) < 5) {
$this->initial_data["timeout"] = 5;
}

$this->validated_data["timeout"] = intval($this->initial_data["timeout"]);
} else {
$this->errors[] = APIResponse\get(1079);
}
} else {
$this->validated_data["timeout"] = 90;
}
}

# This function is intended to take the output of our pkg add or install command and check for failures.
# Returns the corresponding API response ID.
# TODO: matching text based error messages is prone to breaking as the pkg cli tool changes. As of now, pkg does
# TODO: not return unique return codes for specific errors. Re-evaluate as time goes and refactor when a better way
# TODO: is made available.
private function __check_pkg_install($pkg_out, $pkg_rc) {
# Check if our package installation timed out
if ($pkg_rc === 124) {
return 1078;
}

# Loop through each line of the pkg output and check for known error messages
foreach ($pkg_out as $pkg_line) {
# Check for 'pkg install' no matching package in repository error
if (APITools\str_starts_with("pkg: No packages available to install matching", $pkg_line)) {
return 1075;
}
# Check for 'pkg install' most recent version is already installed error
elseif (APITools\str_starts_with("The most recent versions of packages are already installed", $pkg_line)) {
return 1076;
}
# Check for 'pkg add' most recent version is already installed error
elseif (APITools\str_starts_with("the most recent version of", $pkg_line)) {
return 1076;
}
# Check for 'pkg add' no package file found at URL error
elseif (APITools\str_ends_with(": Not Found", $pkg_line)) {
return 1075;
}
# Check for 'pkg add' URL does not contain package file error
elseif (APITools\str_ends_with(": Unrecognized archive format", $pkg_line)) {
return 1077;
}
# Check for 'pkg add' DNS resolution error
elseif (APITools\str_ends_with(": No address record", $pkg_line)) {
return 13;
}
# Check for 'pkg add' URL unreachable error
elseif (APITools\str_ends_with(": Network is unreachable", $pkg_line)) {
return 13;
}
}

# When no known error messages were matched, but the pkg command still failed, return unexpected error
if ($pkg_rc !== 0) {
return 1;
}
# Otherwise, our package installation appears to be successful
else {
return 0;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,14 @@ class APISystemPackageDelete extends APIModel {
}

public function action() {
$this->delete_pkg();
pkg_delete($this->validated_data["name"]);
return APIResponse\get(0);
}

public function validate_payload(){
$this->__validate_name();
}

# Deletes the package specified in $this->validated_data["name"]
public function delete_pkg() {
# Remove the requested package and clean up dependencies afterwards
pkg_call("delete -y " . $this->validated_data["name"]);
pkg_call("autoremove -y");

# Force the action to be logged in the configuration history
$this->write_config();
}

private function __validate_name() {
# Check for our required name input
if (isset($this->initial_data["name"])) {
Expand Down
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,13 @@
<title>pfSense REST API Documentation</title>
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" />
<link rel="stylesheet" type="text/css" href="index.css" />
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
<link rel="apple-touch-icon-precomposed" href="/apple-touch/apple-touch-icon-iphone-60x60-precomposed.png">
<link rel="apple-touch-icon-precomposed" sizes="60x60" href="/apple-touch/apple-touch-icon-ipad-76x76-precomposed.png">
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="/apple-touch/apple-touch-icon-iphone-retina-120x120-precomposed.png">
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="/apple-touch/apple-touch-icon-ipad-retina-152x152-precomposed.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
</head>

<body>
Expand Down
Loading