Skip to content

Commit 88feed1

Browse files
Added endpoints for /services/sshd/ and /services/ssdh/modify to read and modify existing sshd configurations, added endpoints for /system/hostname/ and /system/hostname/modify/ to read and modify the existing system hostname, added pre-run check to ensure a supported version of pfSense is running before making any changes, updated Makefile and pkg-plist accordingly
1 parent dda17c2 commit 88feed1

9 files changed

Lines changed: 357 additions & 2 deletions

File tree

pfSense-pkg-API/Makefile

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,14 @@ do-install:
9090
${MKDIR} ${STAGEDIR}${PREFIX}/www/api/v1/system/config
9191
${INSTALL_DATA} ${FILESDIR}${PREFIX}/www/api/v1/system/config/index.php \
9292
${STAGEDIR}${PREFIX}/www/api/v1/system/config
93-
93+
# Hostname base
94+
${MKDIR} ${STAGEDIR}${PREFIX}/www/api/v1/system/hostname
95+
${INSTALL_DATA} ${FILESDIR}${PREFIX}/www/api/v1/system/hostname/index.php \
96+
${STAGEDIR}${PREFIX}/www/api/v1/system/hostname
97+
# Hostname modify
98+
${MKDIR} ${STAGEDIR}${PREFIX}/www/api/v1/system/hostname/modify
99+
${INSTALL_DATA} ${FILESDIR}${PREFIX}/www/api/v1/system/hostname/modify/index.php \
100+
${STAGEDIR}${PREFIX}/www/api/v1/system/hostname/modify
94101
# STATUS API ENDPOINTS----------------------------------------
95102
${MKDIR} ${STAGEDIR}${PREFIX}/www/api/v1/status/
96103
# CARP base
@@ -263,6 +270,12 @@ do-install:
263270
${STAGEDIR}${PREFIX}/www/api/v1/services/ntpd/restart
264271
# SSHD base
265272
${MKDIR} ${STAGEDIR}${PREFIX}/www/api/v1/services/sshd
273+
${INSTALL_DATA} ${FILESDIR}${PREFIX}/www/api/v1/services/sshd/index.php \
274+
${STAGEDIR}${PREFIX}/www/api/v1/services/sshd
275+
# SSHD modify
276+
${MKDIR} ${STAGEDIR}${PREFIX}/www/api/v1/services/sshd/modify
277+
${INSTALL_DATA} ${FILESDIR}${PREFIX}/www/api/v1/services/sshd/modify/index.php \
278+
${STAGEDIR}${PREFIX}/www/api/v1/services/sshd/modify
266279
# SSHD start
267280
${MKDIR} ${STAGEDIR}${PREFIX}/www/api/v1/services/sshd/start
268281
${INSTALL_DATA} ${FILESDIR}${PREFIX}/www/api/v1/services/sshd/start/index.php \

pfSense-pkg-API/files/etc/inc/api.inc

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ function api_authorized($req_privs, $read_only=false) {
204204

205205
// Run our pre-runtime checks
206206
function api_runtime_allowed() {
207+
api_version_check(); // Die if incompatible pfSense version
207208
api_enabled(); // Die if API is disabled in configuration
208209
api_whitelist_check(); // Die if server address is not whitelisted
209210
}
@@ -245,6 +246,21 @@ function is_api_read_only() {
245246
}
246247
}
247248

249+
// Check if our pfSense version is allowed to run this version of API
250+
function api_version_check() {
251+
# Local variables
252+
$curr_ver = str_replace(".", "", explode("-", get_pfsense_version()["version"])[0]);
253+
$min_ver = 244;
254+
$curr_ver = is_numeric($curr_ver) ? intval($curr_ver) : 0;
255+
$valid_ver = false;
256+
if ($curr_ver < $min_ver) {
257+
http_response_code(501);
258+
$api_resp = array("status" => "not implemented", "code" => 501, "return" => 17, "message" => "incompatible pfsense version");
259+
echo json_encode($api_resp) . PHP_EOL;
260+
die();
261+
}
262+
}
263+
248264
// Check if server IP is allowed to answer API calls. Redirects to login if not
249265
function api_whitelist_check() {
250266
global $config;
@@ -342,6 +358,39 @@ function api_extended_search($base_data, $search_data) {
342358
return $base_data;
343359
}
344360

361+
// Check our local pfSense version
362+
function get_pfsense_version() {
363+
# VARIABLES
364+
$ver_path = "/etc/version"; // Assign the path to our version file
365+
$ver_patch_path = "/etc/version.patch"; // Assign the path to our version patch file
366+
$ver_bt_path = "/etc/version.buildtime"; // Assign the path to our version build time file
367+
$ver_lc_path = "/etc/version.lastcommit"; // Assign the path to our version last commit file
368+
$ver_data = array(); // Init an empty array for our version data
369+
# RUN TIME
370+
// Check that our files exist, if so read the files. Otherwise return error
371+
if (file_exists($ver_path)) {
372+
$ver_file = fopen($ver_path, "r"); // Open our file
373+
$ver = str_replace(PHP_EOL, "", fread($ver_file, filesize($ver_path))); // Save our version data
374+
$ver_data["version"] = $ver; // Save to array
375+
}
376+
if (file_exists($ver_patch_path)) {
377+
$ver_patch_file = fopen($ver_patch_path, "r"); // Open our file
378+
$ver_patch = str_replace(PHP_EOL, "", fread($ver_patch_file, filesize($ver_patch_path))); // Save patch
379+
$ver_data["patch"] = $ver_patch; // Save to array
380+
}
381+
if (file_exists($ver_bt_path)) {
382+
$ver_bt_file = fopen($ver_bt_path, "r"); // Open our file
383+
$ver_bt = str_replace(PHP_EOL, "", fread($ver_bt_file, filesize($ver_bt_path))); // Save bt data
384+
$ver_data["buildtime"] = $ver_bt; // Save to array
385+
}
386+
if (file_exists($ver_lc_path)) {
387+
$ver_lc_file = fopen($ver_lc_path, "r"); // Open our file
388+
$ver_lc = str_replace(PHP_EOL, "", fread($ver_lc_file, filesize($ver_lc_path))); // Save bt data
389+
$ver_data["lastcommit"] = $ver_lc; // Save to array
390+
}
391+
return $ver_data;
392+
}
393+
345394
// Check if a DNS Resolver (Unbound) host override already exists
346395
function unbound_host_override_exists($hostname, $domain) {
347396
// Local variables

pfSense-pkg-API/files/etc/inc/apicalls.inc

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1966,6 +1966,106 @@ function api_system_version() {
19661966
}
19671967
}
19681968

1969+
function api_system_hostname() {
1970+
# VARIABLES
1971+
global $config, $api_resp, $client_params;
1972+
$read_only_action = true; // Set whether this action requires read only access
1973+
$req_privs = array("page-all", "page-system"); // Allowed privs
1974+
$http_method = $_SERVER['REQUEST_METHOD']; // Save our HTTP method
1975+
$host_array = array("hostname" => "", "domain" => ""); // Init our return array
1976+
# RUN TIME
1977+
// Check that client is authenticated and authorized
1978+
if (api_authorized($req_privs, $read_only_action)) {
1979+
// Check that our HTTP method is GET (READ)
1980+
if ($http_method === 'GET') {
1981+
// Check that we have a hostname configuration
1982+
if (!empty($config["system"]["hostname"])) {
1983+
$host_array["hostname"] = $config["system"]["hostname"];
1984+
}
1985+
// Check that we have a domain configuration
1986+
if (!empty($config["system"]["domain"])) {
1987+
$host_array["domain"] = $config["system"]["domain"];
1988+
}
1989+
if (isset($client_params['search'])) {
1990+
$search = $client_params['search'];
1991+
$host_array = api_extended_search($host_array, $search);
1992+
}
1993+
// Print our JSON response
1994+
$api_resp = array("status" => "ok", "code" => 200, "return" => 0, "message" => "", "data" => $host_array);
1995+
return $api_resp;
1996+
} else {
1997+
$api_resp = array("status" => "bad request", "code" => 400, "return" => 2, "message" => "invalid http method");
1998+
return $api_resp;
1999+
}
2000+
} else {
2001+
return $api_resp;
2002+
}
2003+
}
2004+
2005+
function api_system_hostname_modify() {
2006+
# VARIABLES
2007+
global $config, $api_resp, $client_params, $client_id;
2008+
$read_only_action = false; // Set whether this action requires read only access
2009+
$req_privs = array("page-all", "page-system"); // Array of privs allowed
2010+
$http_method = $_SERVER['REQUEST_METHOD']; // Save our HTTP method
2011+
# RUN TIME
2012+
// Check that client is authenticated and authorized
2013+
if (api_authorized($req_privs, $read_only_action)) {
2014+
// Check that our HTTP method is POST (DELETE)
2015+
if ($http_method === 'POST') {
2016+
if (isset($client_params['hostname'])) {
2017+
$hostname = $client_params['hostname'];
2018+
$hostname = trim($hostname);
2019+
// Check if our hostname is valid
2020+
if (!is_hostname($hostname) or !is_unqualified_hostname($hostname)) {
2021+
$api_resp = array("status" => "bad request", "code" => 400, "return" => 170);
2022+
$api_resp["message"] = "invalid hostname";
2023+
return $api_resp;
2024+
} else {
2025+
$config["system"]["hostname"] = $hostname;
2026+
}
2027+
}
2028+
if (isset($client_params['domain'])) {
2029+
$domain = $client_params['domain'];
2030+
$domain = trim($domain);
2031+
// Check if our hostname is valid
2032+
if (!is_domain($domain)) {
2033+
$api_resp = array("status" => "bad request", "code" => 400, "return" => 171);
2034+
$api_resp["message"] = "invalid domain";
2035+
return $api_resp;
2036+
} else {
2037+
$config["system"]["domain"] = $domain;
2038+
}
2039+
}
2040+
// Write our new hostname
2041+
$_SESSION["Username"] = $client_id; // Save our CLIENT ID to session data for logging
2042+
$change_note = " Modified system hostname via API"; // Add a change note
2043+
write_config(sprintf(gettext($change_note))); // Apply our configuration change
2044+
// Update a slew of backend services
2045+
if (isset($hostname) or isset($domain)) {
2046+
system_hostname_configure();
2047+
system_hosts_generate();
2048+
system_resolvconf_generate();
2049+
if (isset($config['dnsmasq']['enable'])) {
2050+
services_dnsmasq_configure();
2051+
} elseif (isset($config['unbound']['enable'])) {
2052+
services_unbound_configure();
2053+
}
2054+
filter_configure();
2055+
}
2056+
// Print our JSON response
2057+
$api_resp = array("status" => "ok", "code" => 200, "return" => 0, "message" => "system hostname modified");
2058+
$api_resp["data"] = array("hostname" => $config["system"]["hostname"], "domain" => $config["system"]["domain"]);
2059+
return $api_resp;
2060+
} else {
2061+
$api_resp = array("status" => "bad request", "code" => 400, "return" => 2, "message" => "invalid http method");
2062+
return $api_resp;
2063+
}
2064+
} else {
2065+
return $api_resp;
2066+
}
2067+
}
2068+
19692069
function api_system_arp() {
19702070
# VARIABLES
19712071
global $g, $config, $argv, $userindex, $api_resp, $client_params;
@@ -3008,6 +3108,145 @@ function api_services() {
30083108
}
30093109
}
30103110

3111+
function api_services_sshd() {
3112+
# VARIABLES
3113+
global $config, $api_resp, $client_params;
3114+
$read_only_action = true; // Set whether this action requires read only access
3115+
$req_privs = array("page-all", "page-system-advanced-admin"); // Array of privs allowed
3116+
$http_method = $_SERVER['REQUEST_METHOD']; // Save our HTTP method
3117+
$ssh_array = array(); // Init our return array
3118+
# RUN TIME
3119+
// Check that client is authenticated and authorized
3120+
if (api_authorized($req_privs, $read_only_action)) {
3121+
// Check that our HTTP method is GET (READ)
3122+
if ($http_method === 'GET') {
3123+
// Check that we have a configuration
3124+
if (!empty($config["system"]["ssh"])) {
3125+
$ssh_array = $config["system"]["ssh"];
3126+
}
3127+
if (isset($client_params['search'])) {
3128+
$search = $client_params['search'];
3129+
$ssh_array = api_extended_search($ssh_array, $search);
3130+
}
3131+
// Print our JSON response
3132+
$api_resp = array("status" => "ok", "code" => 200, "return" => 0, "message" => "", "data" => $ssh_array);
3133+
return $api_resp;
3134+
} else {
3135+
$api_resp = array("status" => "bad request", "code" => 400, "return" => 2, "message" => "invalid http method");
3136+
return $api_resp;
3137+
}
3138+
} else {
3139+
return $api_resp;
3140+
}
3141+
}
3142+
3143+
function api_services_sshd_modify() {
3144+
# VARIABLES
3145+
global $config, $api_resp, $client_id, $client_params;
3146+
$read_only_action = false; // Set whether this action requires read only access
3147+
$req_privs = array("page-all", "page-system-advanced-admin"); // Array of privileges allowing this action
3148+
$http_method = $_SERVER['REQUEST_METHOD']; // Save our HTTP method
3149+
$allowed_auth_types = array("disabled", "enabled", "both"); // Array of allowed auth types
3150+
$err_found = false; // Track errors
3151+
# RUN TIME
3152+
// Check that client is authenticated and authorized
3153+
if (api_authorized($req_privs, $read_only_action)) {
3154+
$api_resp = array("status" => "bad request", "code" => 400);
3155+
// Check that our HTTP method is POST (UPDATE)
3156+
if ($http_method === 'POST') {
3157+
if (isset($client_params['enable'])) {
3158+
$enable = $client_params['enable'];
3159+
if ($enable === true) {
3160+
$config["system"]["ssh"]["enable"] = "enabled";
3161+
} elseif ($enable === false) {
3162+
unset($config["system"]["ssh"]["enable"]);
3163+
} else {
3164+
$api_resp["message"] = "invalid sshd enable value";
3165+
$api_resp["return"] = 165;
3166+
}
3167+
}
3168+
if (isset($client_params["sshdkeyonly"])) {
3169+
$sshdkeyonly = $client_params["sshdkeyonly"];
3170+
// Check if our auth type is valid
3171+
if (in_array($sshdkeyonly, $allowed_auth_types)) {
3172+
if ($sshdkeyonly === "disabled") {
3173+
unset($config["system"]["ssh"]["sshdkeyonly"]);
3174+
} else {
3175+
$config["system"]["ssh"]["sshdkeyonly"] = $sshdkeyonly;
3176+
}
3177+
} else {
3178+
$err_found = true;
3179+
$api_resp["message"] = "invalid sshd key only type";
3180+
$api_resp["return"] = 166;
3181+
}
3182+
}
3183+
if (isset($client_params['sshdagentforwarding'])) {
3184+
$sshdagentforwarding = $client_params['sshdagentforwarding'];
3185+
if ($sshdagentforwarding === true) {
3186+
$config["system"]["ssh"]["sshdagentforwarding"] = "enabled";
3187+
} elseif ($sshdagentforwarding === false) {
3188+
unset($config["system"]["ssh"]["sshdagentforwarding"]);
3189+
} else {
3190+
$err_found = true;
3191+
$api_resp["message"] = "invalid sshd agent forwarding value";
3192+
$api_resp["return"] = 167;
3193+
}
3194+
}
3195+
if (isset($client_params['port'])) {
3196+
$port = strval($client_params['port']);
3197+
// Convert string to array
3198+
if (is_port($port)) {
3199+
$config["system"]["ssh"]["port"] = $port;
3200+
} else {
3201+
$err_found = true;
3202+
$api_resp["message"] = "invalid sshd port value";
3203+
$api_resp["return"] = 168;
3204+
}
3205+
}
3206+
// Add debug data if requested
3207+
if (array_key_exists("debug", $client_params)) {
3208+
echo "ENABLE SSHD:" . PHP_EOL;
3209+
echo var_dump($enable) . PHP_EOL;
3210+
echo "SSHD KEY ONLY:" . PHP_EOL;
3211+
echo var_dump($sshdkeyonly) . PHP_EOL;
3212+
echo "SSHD AGENT FORWARDING:" . PHP_EOL;
3213+
echo var_dump($sshdagentforwarding) . PHP_EOL;
3214+
echo "SSHD PORT:" . PHP_EOL;
3215+
echo var_dump($port) . PHP_EOL;
3216+
3217+
}
3218+
// Check if we found an error, if so, quit
3219+
if ($err_found) {
3220+
http_response_code(400);
3221+
echo json_encode($api_resp);
3222+
die();
3223+
}
3224+
$_SESSION["Username"] = $client_id; // Save our CLIENT ID to session data for logging
3225+
$change_note = " Modified SSHD configuration via API"; // Add a change note
3226+
write_config(sprintf(gettext($change_note))); // Apply our configuration change
3227+
// Check that something was changed before altering service
3228+
if (isset($enable) or isset($port) or isset($sshdagentforwarding) or isset($sshdkeyonly)) {
3229+
killbyname("sshd"); // Kill SSHD
3230+
log_error(gettext("secure shell configuration has changed. Stopping sshd."));
3231+
if ($config['system']['ssh']['enable']) {
3232+
log_error(gettext("secure shell configuration has changed. Restarting sshd."));
3233+
send_event("service restart sshd");
3234+
}
3235+
}
3236+
// Loop through each alias and see if our alias was added successfully
3237+
$api_resp = array("status" => "ok", "code" => 200, "return" => 0);
3238+
$api_resp["message"] = "sshd configuration modified";
3239+
$api_resp["data"] = $config["system"]["ssh"];
3240+
return $api_resp;
3241+
} else {
3242+
$api_resp = array("status" => "bad request", "code" => 400, "return" => 2, "message" => "invalid http method");
3243+
return $api_resp;
3244+
}
3245+
} else {
3246+
return $api_resp;
3247+
}
3248+
}
3249+
30113250
function api_services_sshd_start() {
30123251
# VARIABLES
30133252
global $config, $api_resp, $client_params;

pfSense-pkg-API/files/usr/local/www/api/v1/api_return_codes.txt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ API RETURN CODES
1616
14: ip or subnet in use
1717
15: forbidden
1818
16: invalid mac address
19-
19+
17: incompatible pfsense version
2020
20: Username required
2121
21: Password required
2222
22: User already exists
@@ -106,6 +106,12 @@ API RETURN CODES
106106
163: invalid redirect ip address
107107
164: invalid redirect port
108108
164: invalid rule association value
109+
165: invalid sshd enable value
110+
166: invalid sshd key only type
111+
167: invalid sshd agent forwarding value
112+
168: invalid sshd port value
113+
170: invalid hostname
114+
171: invalid domain
109115

110116

111117

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
# Copyright 2020 - Jared Hendrickson
3+
# IMPORTS
4+
require_once("apicalls.inc");
5+
6+
# RUN API CALL
7+
$resp = api_services_sshd();
8+
http_response_code($resp["code"]);
9+
echo json_encode($resp) . PHP_EOL;
10+
exit();
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
# Copyright 2020 - Jared Hendrickson
3+
# IMPORTS
4+
require_once("apicalls.inc");
5+
6+
# RUN API CALL
7+
$resp = api_services_sshd_modify();
8+
http_response_code($resp["code"]);
9+
echo json_encode($resp) . PHP_EOL;
10+
exit();

0 commit comments

Comments
 (0)