%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); }