%PDF- %PDF-
| Direktori : /backups/router/usr/local/etc/inc/ |
| Current File : //backups/router/usr/local/etc/inc/auth.inc |
<?php
/*
* Copyright (C) 2014-2023 Deciso B.V.
* Copyright (C) 2010 Ermal Luçi
* Copyright (C) 2007-2008 Scott Ullrich <sullrich@gmail.com>
* Copyright (C) 2005-2006 Bill Marquette <bill.marquette@gmail.com>
* Copyright (C) 2006 Paul Taylor <paultaylor@winn-dixie.com>
* Copyright (C) 2003-2006 Manuel Kasper <mk@neon1.net>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
require_once("interfaces.inc");
require_once("util.inc");
function auth_log($message, $prio = LOG_ERR)
{
openlog('audit', LOG_ODELAY, LOG_AUTH);
log_msg($message, $prio);
reopenlog();
}
$groupindex = index_groups();
$userindex = index_users();
/**
* check if $http_host is a local configured ip address
*/
function isAuthLocalIP($http_host)
{
global $config;
if (isset($config['virtualip']['vip'])) {
foreach ($config['virtualip']['vip'] as $vip) {
if ($vip['subnet'] == $http_host) {
return true;
}
}
}
$address_in_list = function ($interface_list_ips, $http_host) {
foreach ($interface_list_ips as $ilips => $ifname) {
// remove scope from link-local IPv6 addresses
$ilips = preg_replace('/%.*/', '', $ilips);
if (strcasecmp($http_host, $ilips) == 0) {
return true;
}
}
};
// try using cached addresses
$interface_list_ips = get_cached_json_content("/tmp/isAuthLocalIP.cache.json");
if (!empty($interface_list_ips) && $address_in_list($interface_list_ips, $http_host)) {
return true;
}
// fetch addresses and store in cache
$interface_list_ips = get_configured_ip_addresses();
file_put_contents("/tmp/isAuthLocalIP.cache.json", json_encode($interface_list_ips));
return $address_in_list($interface_list_ips, $http_host);
}
function index_groups()
{
global $config, $groupindex;
$groupindex = array();
if (isset($config['system']['group'])) {
$i = 0;
foreach ($config['system']['group'] as $groupent) {
if (isset($groupent['name'])) {
$groupindex[$groupent['name']] = $i;
$i++;
}
}
}
return ($groupindex);
}
function index_users()
{
global $config;
$userindex = [];
if (!empty($config['system']['user'])) {
$i = 0;
foreach ($config['system']['user'] as $userent) {
if (!empty($userent) && !empty($userent['name'])) {
$userindex[$userent['name']] = $i;
}
$i++;
}
}
return $userindex;
}
function getUserGroups($username)
{
global $config;
$member_groups = array();
$user = getUserEntry($username);
if ($user !== false) {
$allowed_groups = local_user_get_groups($user);
if (isset($config['system']['group'])) {
foreach ($config['system']['group'] as $group) {
if (in_array($group['name'], $allowed_groups)) {
$member_groups[] = $group['name'];
}
}
}
}
return $member_groups;
}
function &getUserEntry($name)
{
global $config, $userindex;
$false = false;
if (isset($userindex[$name])) {
return $config['system']['user'][$userindex[$name]];
} else {
return $false;
}
}
function &getUserEntryByUID($uid)
{
global $config;
if (is_array($config['system']['user'])) {
foreach ($config['system']['user'] as & $user) {
if ($user['uid'] == $uid) {
return $user;
}
}
}
return false;
}
function &getGroupEntry($name)
{
global $config, $groupindex;
if (isset($groupindex[$name])) {
return $config['system']['group'][$groupindex[$name]];
}
return array();
}
function get_user_privileges(&$user)
{
$privs = [];
/* legacy listtags() ensures 'priv' will be an array when offered, also when there's only one */
foreach (!empty($user['priv']) ? (array)$user['priv'] : [] as $item) {
foreach (array_filter(explode(',', $item)) as $priv) {
$privs[] = $priv;
}
}
foreach (local_user_get_groups($user) as $name) {
$group = getGroupEntry($name);
foreach (!empty($group['priv']) ? (array)$group['priv'] : [] as $item) {
foreach (array_filter(explode(',', $item)) as $priv) {
$privs[] = $priv;
}
}
}
return $privs;
}
function userHasPrivilege($userent, $privid = false)
{
if (!$privid || !is_array($userent)) {
return false;
}
$privs = get_user_privileges($userent);
if (!is_array($privs)) {
return false;
}
if (!in_array($privid, $privs)) {
return false;
}
return true;
}
function userIsAdmin($username)
{
$user = getUserEntry($username);
return userHasPrivilege($user, 'page-all');
}
function local_sync_accounts()
{
global $config;
/* we need to know the current state of users and groups, both configured and actual */
$current_data = [];
foreach (['usershow', 'groupshow'] as $command) {
$section = $command == 'usershow' ? 'user' : 'group';
$current_data[$section] = [];
$data = "";
exec("/usr/sbin/pw {$command} -a", $data, $ret);
if (!$ret) {
foreach ($data as $record) {
$line = explode(':', $record);
// filter system managed users and groups
if (count($line) < 3 || !strncmp($line[0], '_', 1) || ($line[2] < 2000 && $line[0] != 'root') || $line[2] > 65000) {
continue;
}
$current_data[$section][$line[2]] = $line;
}
}
}
$config_map = [];
foreach (['user', 'group'] as $section) {
$config_map[$section] = [];
if (is_array($config['system'][$section])) {
foreach ($config['system'][$section] as $item) {
if ($section == 'user' && (empty($item['shell']) && $item['uid'] != 0)) {
/* no shell, no local user */
continue;
}
$this_id = $section == 'user' ? $item['uid'] : $item['gid'];
$config_map[$section][(string)$this_id] = $item['name'];
}
}
}
/* remove conflicting users and groups (uid/gid or name mismatch) */
foreach ($current_data as $section => $data) {
foreach ($data as $oid => $object) {
if (empty($config_map[$section][$oid]) || $config_map[$section][$oid] !== $object[0]) {
if ($section == 'user') {
/*
* If a crontab was created to user, pw userdel will be interactive and
* can cause issues. Just remove crontab before run it when necessary
*/
@unlink("/var/cron/tabs/{$object[0]}");
mwexecf('/usr/sbin/pw userdel -n %s', $object[0]);
} else {
mwexecf('/usr/sbin/pw groupdel -g %s', $object[2]);
}
}
}
}
/* sync all local users with a shell account configured (filtered in local_user_set()) */
if (is_array($config['system']['user'])) {
foreach ($config['system']['user'] as $user) {
$userattrs = !empty($current_data['user'][$user['uid']]) ? $current_data['user'][$user['uid']] : [];
local_user_set($user, false, $userattrs);
}
}
/* sync all local groups */
if (is_array($config['system']['group'])) {
foreach ($config['system']['group'] as $group) {
local_group_set($group);
}
}
}
function local_user_set(&$user, $force_password = false, $userattrs = null)
{
global $config;
if (empty($user['password'])) {
auth_log("Cannot set user {$user['name']}: password is missing");
return;
} elseif (empty($user['shell']) && $user['uid'] != 0) {
/* no shell, no local user */
return;
}
@mkdir('/home', 0755);
/* integrated authentication handles passwords unless 'installer' user needs it locally */
$user_pass = $force_password ? $user['password'] : '*';
$user_name = $user['name'];
$user_uid = $user['uid'];
$comment = str_replace(array(':', '!', '@'), ' ', $user['descr']);
$lock_account = 'lock';
$is_expired = !empty($user['expires']) &&
strtotime('-1 day') > strtotime(date('m/d/Y', strtotime($user['expires'])));
$is_disabled = !empty($user['disabled']);
$is_unlocked = !$is_disabled && !$is_expired;
if ($is_unlocked || $user_uid == 0) {
/*
* The root account shall not be locked as this will have
* side-effects such as cron not working correctly. Our
* auth framework will make sure not to allow login to a
* disabled root user at the same time.
*/
$lock_account = 'unlock';
}
if ($user_uid == 0) {
$user_shell = !empty($user['shell']) ? $user['shell'] : '/usr/local/sbin/opnsense-shell';
$user_group = 'wheel';
$user_home = '/root';
} else {
$is_admin = userIsAdmin($user['name']);
$user_shell = $is_admin && !empty($user['shell']) ? $user['shell'] : '/usr/sbin/nologin';
$user_group = $is_admin ? 'wheel' : 'nobody';
$user_home = "/home/{$user_name}";
}
// XXX: primary group id can only be wheel or nobody, otherwise we should map the correct numbers for comparison
$user_gid = $user_group == 'wheel' ? 0 : 65534;
if (!$force_password) {
/* read from pw db if not provided (batch mode) */
if ($userattrs === null) {
$fd = popen("/usr/sbin/pw usershow -n {$user_name}", 'r');
$pwread = fgets($fd);
pclose($fd);
if (substr_count($pwread, ':')) {
$userattrs = explode(':', trim($pwread));
}
}
}
/* determine add or mod */
if ($userattrs === null || $userattrs[0] != $user['name']) {
$user_op = 'useradd -m -k /usr/share/skel -o';
} elseif (
$userattrs[0] == $user_name &&
$userattrs[1] == '*' &&
$userattrs[2] == $user_uid &&
$userattrs[3] == $user_gid &&
$userattrs[7] == $comment &&
$userattrs[8] == $user_home &&
$userattrs[9] == $user_shell
) {
$user_op = null;
} else {
$user_op = 'usermod';
}
/* add or mod pw db */
if ($user_op != null) {
$cmd = "/usr/sbin/pw {$user_op} -q -u {$user_uid} -n {$user_name}" .
" -g {$user_group} -s {$user_shell} -d {$user_home}" .
" -c " . escapeshellarg($comment) . " -H 0 2>&1";
$fd = popen($cmd, 'w');
fwrite($fd, $user_pass);
pclose($fd);
}
/* create user directory if required */
@mkdir($user_home, 0700);
@chown($user_home, $user_name);
@chgrp($user_home, $user_group);
/* write out ssh authorized key file */
if ($is_unlocked && !empty($user['authorizedkeys'])) {
@mkdir("{$user_home}/.ssh", 0700);
@chown("{$user_home}/.ssh", $user_name);
$keys = base64_decode($user['authorizedkeys']);
$keys = preg_split('/[\n\r]+/', $keys);
$keys[] = '';
$keys = implode("\n", $keys);
@file_put_contents("{$user_home}/.ssh/authorized_keys", $keys);
@chown("{$user_home}/.ssh/authorized_keys", $user_name);
} else {
@unlink("{$user_home}/.ssh/authorized_keys");
}
mwexecf('/usr/sbin/pw %s %s', array($lock_account, $user_name), true);
}
function local_user_set_password(&$user, $password = null)
{
$local = config_read_array('system', 'webgui');
$hash = false;
if ($password == null) {
/* generate a random password */
$password = random_bytes(50);
/* XXX since PHP 8.2.18 we need to avoid NUL char */
while (($i = strpos($password, "\0")) !== false) {
$password[$i] = random_bytes(1);
}
}
if (
!empty($local['enable_password_policy_constraints']) &&
!empty($local['password_policy_compliance'])
) {
$process = proc_open(
'/usr/local/bin/openssl passwd -6 -stdin',
[['pipe', 'r'], ['pipe', 'w']],
$pipes
);
if (is_resource($process)) {
fwrite($pipes[0], $password);
fclose($pipes[0]);
$hash = trim(stream_get_contents($pipes[1]));
fclose($pipes[1]);
proc_close($process);
}
} else {
$hash = password_hash($password, PASSWORD_BCRYPT, [ 'cost' => 11 ]);
}
if ($hash !== false && strpos($hash, '$') === 0) {
$user['password'] = $hash;
} else {
auth_log("Failed to hash password for user {$user['name']}");
}
}
function local_user_get_groups($user)
{
global $config;
$groups = [];
if (!isset($config['system']['group'])) {
return $groups;
}
foreach ($config['system']['group'] as $group) {
if (isset($group['member'])) {
foreach ($group['member'] as $member) {
if (in_array($user['uid'], explode(',', $member))) {
$groups[] = $group['name'];
break;
}
}
}
}
sort($groups);
return $groups;
}
function local_group_set($group)
{
if (!isset($group['name']) || !isset($group['gid'])) {
return;
}
$group_name = $group['name'];
$group_gid = $group['gid'];
$group_members = '';
if (!empty($group['member']) && count($group['member']) > 0) {
$members = [];
foreach ($group['member'] as $member) {
$members = array_merge($members, explode(',', $member));
}
$group_members = implode(',', $members);
}
$ret = mwexecf('/usr/sbin/pw groupshow %s', $group_name, true);
if ($ret) {
$group_op = 'groupadd';
} else {
$group_op = 'groupmod';
}
mwexecf('/usr/sbin/pw %s %s -g %s -M %s', array($group_op, $group_name, $group_gid, $group_members));
}
/**
* @param $name string name of the authentication system configured on the authentication server page or 'Local Database' for local authentication
* @return array|bool false if the authentication server was not found, otherwise the configuration of the authentication server
*/
function auth_get_authserver($name)
{
global $config;
if ($name == "Local Database") {
return array(
"name" => gettext("Local Database"),
"type" => "local",
"host" => $config['system']['hostname']
);
}
if (!empty($config['system']['authserver'])) {
foreach ($config['system']['authserver'] as $authcfg) {
if ($authcfg['name'] == $name) {
if ($authcfg['type'] == 'ldap' || $authcfg['type'] == 'ldap-totp') {
// make sure a user and password entry exists and are null for anonymous usage
if (empty($authcfg['ldap_binddn'])) {
$authcfg['ldap_binddn'] = null;
}
if (empty($authcfg['ldap_bindpw'])) {
$authcfg['ldap_bindpw'] = null;
}
}
return $authcfg;
}
}
}
return false;
}
function auth_get_authserver_list()
{
global $config;
$list = array();
if (!empty($config['system']['authserver'])) {
foreach ($config['system']['authserver'] as $authcfg) {
/* Add support for disabled entries? */
$list[$authcfg['name']] = $authcfg;
}
}
$list["Local Database"] = array( "name" => gettext("Local Database"), "type" => "local", "host" => $config['system']['hostname']);
return $list;
}
/**
* return authenticator object
* @param array|null $authcfg configuration
* @return Auth\Base type object
*/
function get_authenticator($authcfg = null)
{
if (empty($authcfg)) {
$authName = 'Local Database';
} else {
$authName = $authcfg['name'];
if ($authcfg['type'] == 'local') {
// avoid gettext type issues on Local Database, authenticator should always be named "Local Database"
$authName = 'Local Database';
}
}
$authFactory = new OPNsense\Auth\AuthenticationFactory();
return $authFactory->get($authName);
}