authgui.inc 14.5 KB
Newer Older
Ad Schellevis's avatar
Ad Schellevis committed
1
<?php
2

Ad Schellevis's avatar
Ad Schellevis committed
3
/*
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
	Copyright (C) 2008 Shrew Soft Inc
	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.
Ad Schellevis's avatar
Ad Schellevis committed
31
*/
32

33 34
require_once("auth.inc");

35
// provided via legacy_bindings.inc
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
global $priv_list;
$acl = new OPNsense\Core\ACL();
$priv_list = $acl->getLegacyPrivList();


function cmp_page_matches($page, & $matches, $fullwc = true) {
	if (!is_array($matches))
		return false;

	/* skip any leading fwdslash */
	$test = strpos($page, "/");
	if ($test !== false && $test == 0)
		$page = substr($page, 1);

	/* look for a match */
	foreach ($matches as $match) {

		/* possibly ignore full wildcard match */
		if (!$fullwc && !strcmp($match ,"*"))
			continue;

		/* compare exact or wildcard match */
		$match =  str_replace(array(".", "*","?"), array("\.", ".*","\?"), $match);
		$result = preg_match("@^/{$match}$@", "/{$page}");

		if ($result)
			return true;
	}

	return false;
}

function isAllowedPage($page)
{
70 71 72
	if (session_status() == PHP_SESSION_NONE) {
		session_start();
	}
73
	if (!isset($_SESSION['Username'])) {
74
		session_write_close();
75 76 77 78 79
		return false;
	}

	/* root access check */
	$user = getUserEntry($_SESSION['Username']);
80
	session_write_close();
81 82 83 84 85 86 87 88 89
	if (isset($user)) {
		if (isset($user['uid'])) {
			if ($user['uid'] == 0) {
				return true;
			}
		}
	}

	/* user privelege access check */
90 91
	$allowedpages = getAllowedPages();
	return cmp_page_matches($page, $allowedpages);
92 93
}

Ad Schellevis's avatar
Ad Schellevis committed
94

95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
function getPrivPages(& $entry, & $allowed_pages) {
	global $priv_list;

	if (!isset($entry['priv']) || !is_array($entry['priv']))
		return;

	foreach ($entry['priv'] as $pname) {
		if (strncmp($pname, "page-", 5))
			continue;
		$priv = &$priv_list[$pname];
		if (!is_array($priv))
			continue;
		$matches = &$priv['match'];
		if (!is_array($matches))
			continue;
		foreach ($matches as $match)
			$allowed_pages[] = $match;
	}
}



function getAllowedPages($username) {
118
	global $config;
119 120 121 122

	$allowed_pages = array();
	$allowed_groups = array();

123 124 125 126 127 128
	// search for a local user by name
	$local_user = getUserEntry($username);
	getPrivPages($local_user, $allowed_pages);

	// obtain local groups if we have a local user
	$allowed_groups = local_user_get_groups($local_user);
129 130 131 132

	// build a list of allowed pages
	if (is_array($config['system']['group']) && is_array($allowed_groups)) {
		foreach ($config['system']['group'] as $group) {
133 134 135
			// a bit odd, we have seem some cases in the wild where $group doesn't contain a name attribute.
			// this shouldn't happen, but to avoid warnings we will check over here.
			if (isset($group['name']) && in_array($group['name'], $allowed_groups)) {
136 137 138 139 140 141 142 143 144
				getPrivPages($group, $allowed_pages);
			}
		}
	}

	return $allowed_pages;
}


145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
function session_auth() {
	global $config, $_SESSION;

	// Handle HTTPS httponly and secure flags
	$currentCookieParams = session_get_cookie_params();
	session_set_cookie_params(
		$currentCookieParams["lifetime"],
		$currentCookieParams["path"],
		NULL,
		($config['system']['webgui']['protocol'] == "https"),
		true
	);

	if (session_status() == PHP_SESSION_NONE) {
		session_start();
	}

	// Detect protocol change
163 164
	if (!isset($_POST['login']) && !empty($_SESSION['Logged_In']) && $_SESSION['protocol'] != $config['system']['webgui']['protocol']) {
		session_write_close();
165
		return false;
166
	}
167 168 169 170 171 172 173 174

	/* Validate incoming login request */
	if (isset($_POST['login']) && !empty($_POST['usernamefld']) && !empty($_POST['passwordfld'])) {
		if (isset($config['system']['webgui']['authmode'])) {
			$authcfg = auth_get_authserver($config['system']['webgui']['authmode']);
		} else {
			$authcfg = null;
		}
175
		// authenticate using config settings, or local if failed
176 177 178 179 180 181 182 183 184 185 186
		if (authenticate_user($_POST['usernamefld'], $_POST['passwordfld'], $authcfg) ||
		    authenticate_user($_POST['usernamefld'], $_POST['passwordfld'])) {
			// Generate a new id to avoid session fixation
			session_regenerate_id();
			$_SESSION['Logged_In'] = "True";
			$_SESSION['Username'] = $_POST['usernamefld'];
			$_SESSION['last_access'] = time();
			$_SESSION['protocol'] = $config['system']['webgui']['protocol'];
			if (!isset($config['system']['webgui']['quietlogin'])) {
				log_error(sprintf(gettext("Successful login for user '%1\$s' from: %2\$s"), $_POST['usernamefld'], $_SERVER['REMOTE_ADDR']));
			}
187
			header("Location: {$_SERVER['REQUEST_URI']}");
188 189 190
			exit;
		} else {
			/* give the user an error message */
191
			$_SESSION['Login_Error'] = gettext('Wrong username or password.');
192 193 194 195 196
			log_error("webConfigurator authentication error for '{$_POST['usernamefld']}' from {$_SERVER['REMOTE_ADDR']}");
		}
	}

	/* Show login page if they aren't logged in */
197 198 199 200
	if (empty($_SESSION['Logged_In'])) {
			session_write_close();
			return false;
	}
201 202 203 204 205 206 207

	/* If session timeout isn't set, we don't mark sessions stale */
	if (!isset($config['system']['webgui']['session_timeout'])) {
		/* Default to 4 hour timeout if one is not set */
		if ($_SESSION['last_access'] < (time() - 14400)) {
			$_GET['logout'] = true;
			$_SESSION['Logout'] = true;
208
		} else {
209
			$_SESSION['last_access'] = time();
210
		}
211 212 213 214 215 216 217 218
	} else if (intval($config['system']['webgui']['session_timeout']) == 0) {
			$_SESSION['last_access'] = time();
	} else {
		/* Check for stale session */
		if ($_SESSION['last_access'] < (time() - ($config['system']['webgui']['session_timeout'] * 60))) {
			$_GET['logout'] = true;
			$_SESSION['Logout'] = true;
		} else {
219
			$_SESSION['last_access'] = time();
220 221 222 223 224
		}
	}

	/* user hit the logout button */
	if (isset($_GET['logout'])) {
225 226 227 228 229
		if (isset($_SESSION['Logout'])) {
				log_error(sprintf(gettext("Session timed out for user '%1\$s' from: %2\$s"), $_SESSION['Username'], $_SERVER['REMOTE_ADDR']));
		} else {
				log_error(sprintf(gettext("User logged out for user '%1\$s' from: %2\$s"), $_SESSION['Username'], $_SERVER['REMOTE_ADDR']));
		}
230 231 232 233

		/* wipe out $_SESSION */
		$_SESSION = array();

234 235 236
		if (isset($_COOKIE[session_name()])) {
				setcookie(session_name(), '', time()-42000, '/');
		}
237 238 239 240 241 242 243 244 245 246

		/* and destroy it */
		session_destroy();

		$scriptName = explode("/", $_SERVER["SCRIPT_FILENAME"]);
		$scriptElms = count($scriptName);
		$scriptName = $scriptName[$scriptElms-1];

		/* redirect to page the user is on, it'll prompt them to login again */
		header("Location: {$scriptName}");
247
		exit;
248 249
	}

250
	session_write_close();
251 252 253 254
	return true;
}


Ad Schellevis's avatar
Ad Schellevis committed
255 256
/* Authenticate user - exit if failed */
if (!session_auth()) {
257 258
    display_login_form();
    exit;
Ad Schellevis's avatar
Ad Schellevis committed
259 260 261 262 263 264
}

/*
 * redirect to first allowed page if requesting a wrong url
 */
if (!isAllowedPage($_SERVER['REQUEST_URI'])) {
265 266 267
    if (session_status() == PHP_SESSION_NONE) {
        session_start();
    }
268
    $allowedpages = getAllowedPages($_SESSION['Username']);
269 270 271 272 273 274 275 276
    if (count($allowedpages) > 0) {
        $page = str_replace('*', '', $allowedpages[0]);
        $username = empty($_SESSION["Username"]) ? "(system)" : $_SESSION['Username'];
        if (!empty($_SERVER['REMOTE_ADDR'])) {
            $username .= '@' . $_SERVER['REMOTE_ADDR'];
        }
        log_error("{$username} attempted to access {$_SERVER['REQUEST_URI']} but does not have access to that page. Redirecting to {$page}.");

277
        header("Location: /{$page}");
278 279 280 281 282
        exit;
    } else {
        display_error_form("201", gettext("No page assigned to this user! Click here to logout."));
        exit;
    }
283
}
Ad Schellevis's avatar
Ad Schellevis committed
284

285

286 287 288 289 290
/*
 * determine if the user is allowed access to the requested page
 */
function display_error_form($http_code, $desc)
{
291 292
    global $config, $g;
    $g['theme'] = get_current_theme();
Ad Schellevis's avatar
Ad Schellevis committed
293

294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310
?><!doctype html>
<!--[if IE 8 ]><html lang="en" class="ie ie8 lte9 lte8 no-js"><![endif]-->
<!--[if IE 9 ]><html lang="en" class="ie ie9 lte9 no-js"><![endif]-->
<!--[if (gt IE 9)|!(IE)]><!--><html lang="en" class="no-js"><!--<![endif]-->
	<head>

		<meta charset="UTF-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">

		<meta name="robots" content="index, follow, noodp, noydir" />
		<meta name="keywords" content="" />
		<meta name="description" content="" />
		<meta name="copyright" content="" />
		<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />

		<title><?=$http_code?></title>

311 312
		<link href="/ui/themes/<?=$g['theme'];?>/build/css/main.css" media="screen, projection" rel="stylesheet">
		<link href="/ui/themes/<?=$g['theme'];?>/build/images/favicon.png" rel="shortcut icon">
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327

		<!--[if lt IE 9]><script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.2/html5shiv.min.js"></script><![endif]-->
	</head>
	<body class="page-login">
		<div id="errordesc">
			<h1>&nbsp</h1>
			<a href="/index.php?logout">
			<p id="errortext" style="vertical-align: middle; text-align: center;">
				<span style="color: #000000; font-weight: bold;">
					<?=$desc;?>
				</span>
			</p>
		</div>
	</body>
</html><?php
Ad Schellevis's avatar
Ad Schellevis committed
328 329 330

} // end function

331 332
function display_login_form()
{
333 334 335 336 337 338
    global $config, $g;
    $g['theme'] = get_current_theme();

    unset($input_errors);

    /* Check against locally configured IP addresses, which will catch when someone
339 340 341 342
       port forwards WebGUI access from WAN to an internal IP on the router. */
    // fix, local ip check was previously done using "filter_generate_optcfg_array" which basically includes alomst everything here.
    // this should do the trick as well.
    $local_ip = isAuthLocalIP($http_host);
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358

    if (isset($config['openvpn']['openvpn-server'])) {
        foreach ($config['openvpn']['openvpn-server'] as $ovpns) {
            if (is_ipaddrv4($http_host) && !empty($ovpns['tunnel_network']) && ip_in_subnet($http_host, $ovpns['tunnel_network'])) {
                $local_ip = true;
                break;
            }

            if (is_ipaddrv6($http_host) && !empty($ovpns['tunnel_networkv6']) && ip_in_subnet($http_host, $ovpns['tunnel_networkv6'])) {
                $local_ip = true;
                break;
            }
        }
    }
    setcookie("cookie_test", time() + 3600);
    $have_cookies = isset($_COOKIE["cookie_test"]);
359

360 361 362 363 364
?><!doctype html>
<!--[if IE 8 ]><html lang="en" class="ie ie8 lte9 lte8 no-js"><![endif]-->
<!--[if IE 9 ]><html lang="en" class="ie ie9 lte9 no-js"><![endif]-->
<!--[if (gt IE 9)|!(IE)]><!--><html lang="en" class="no-js"><!--<![endif]-->
	<head>
365

366 367
		<meta charset="UTF-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
368

369 370 371 372 373
		<meta name="robots" content="index, follow, noodp, noydir" />
		<meta name="keywords" content="" />
		<meta name="description" content="" />
		<meta name="copyright" content="" />
		<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
374

375
		<title><?=gettext("Login"); ?></title>
376

377 378
		<link href="/ui/themes/<?=$g['theme'];?>/build/css/main.css" media="screen, projection" rel="stylesheet">
		<link href="/ui/themes/<?=$g['theme'];?>/build/images/favicon.png" rel="shortcut icon">
379

380
		<!--[if lt IE 9]><script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.2/html5shiv.min.js"></script><![endif]-->
381

382 383
	</head>
	<body class="page-login">
384

385 386
	<div class="container">
		<?php
387 388 389 390 391
        if (is_ipaddr($http_host) && !$local_ip && !isset($config['system']['webgui']['nohttpreferercheck'])) {
            print_info_box(gettext("You are accessing this router by an IP address not configured locally, which may be forwarded by NAT or other means. <br /><br />If you did not setup this forwarding, you may be the target of a man-in-the-middle attack."));
        }
                $loginautocomplete = isset($config['system']['webgui']['loginautocomplete']) ? '' : 'autocomplete="off"';
            ?>
392 393


394
		<main class="login-modal-container">
395 396
			<header class="login-modal-head" style="height:55px;">
				<div class="navbar-brand">
397
					<img src="/ui/themes/<?=$g['theme'];?>/build/images/default-logo.png" height="30"/>
398 399
				</div>
			</header>
400

Franco Fichtner's avatar
Franco Fichtner committed
401
			<div class="login-modal-content">
402 403
				<?php if (isset($_SESSION['Login_Error'])) {
?>
Franco Fichtner's avatar
Franco Fichtner committed
404
				<div id="inputerrors" class="text-danger"><?=$_SESSION['Login_Error'];?></div><br />
405 406
				<?php unset($_SESSION['Login_Error']);
} // endif ?>
407

408
				    <form class="clearfix" id="iform" name="iform" method="post" <?= $loginautocomplete ?> action="<?=$_SERVER['REQUEST_URI'];?>">
409

410
			  <div class="form-group">
411
			    <label for="usernamefld"><?=gettext("Username:"); ?></label>
cbasolutions's avatar
cbasolutions committed
412
			    <input id="usernamefld" type="text" name="usernamefld" class="form-control user" tabindex="1" autofocus="autofocus" autocapitalize="off" autocorrect="off" />
413
			  </div>
414

415
			  <div class="form-group">
416
			    <label for="passwordfld"><?=gettext("Password:"); ?></label>
417 418
			    <input id="passwordfld" type="password" name="passwordfld" class="form-control pwd" tabindex="2" />
			  </div>
419

420
			  <input type="hidden" name="login" value="1" /><!-- XXX login workaround -->
421

422
			  <button type="submit" name="login" class="btn btn-primary pull-right"><?=gettext("Login"); ?></button>
423

424
			</form>
425

426 427
			<?php if (!$have_cookies && isset($_POST['login'])) :
?>
428 429 430 431
					<br /><br />
					<span class="text-danger">
						<?= gettext("Your browser must support cookies to login."); ?>
					</span>
432 433
					<?php
endif; ?>
434

435
			    </div>
436

437
			</main>
438

439
		</div>
440

441
			<footer class="login-foot container-fluid">
442
			<a target="_blank" href="<?=$g['product_website']?>" class="redlnk"><?=$g['product_name']?></a> is &copy;
443
					<?=$g['product_copyright_years']?> by <a href="<?=$g['product_copyright_url']?>" class="tblnk"><?=$g['product_copyright_owner']?></a>
444
			</footer>
445

446 447
		</body>
	</html><?php
448

Ad Schellevis's avatar
Ad Schellevis committed
449
} // end function