Skip to content

Commit 8b4c85c

Browse files
committed
Initial commit.
1 parent 04fb5a5 commit 8b4c85c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+11176
-1
lines changed

.gitignore

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
*.icns
2+
*.tar.gz
3+
/www/*
4+
!/www/README.md
5+
/installers/win-innosetup/Output/
6+
/installers/win-innosetup/php-win-32/*
7+
!/installers/win-innosetup/php-win-32/README.md
8+
/installers/win-innosetup/php-win-64/*
9+
!/installers/win-innosetup/php-win-64/README.md
10+
/installers/win-wix/*.msi
11+
/installers/win-wix/*.wxs
12+
!/installers/win-wix/yourapp.wxs

README.md

Lines changed: 168 additions & 1 deletion
Large diffs are not rendered by default.

extensions/1_security_token.php

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
<?php
2+
// Uses a one-time use security token to handle the rare case of crossing local user account boundaries.
3+
// (C) 2018 CubicleSoft. All Rights Reserved.
4+
5+
// The '1' prefix helps this extension to run before other extensions.
6+
class PAS_Extension_1_security_token
7+
{
8+
private $inittoken, $realtoken;
9+
10+
public function InitServer()
11+
{
12+
$this->inittoken = false;
13+
$this->realtoken = false;
14+
}
15+
16+
public function ServerReady()
17+
{
18+
global $args, $initresult;
19+
20+
// Only activate this extension when using the startup JSON file option (i.e. production mode).
21+
if (isset($args["opts"]["sfile"]))
22+
{
23+
$rng = new CSPRNG();
24+
$this->inittoken = $rng->GenerateToken();
25+
$this->realtoken = $rng->GenerateToken();
26+
27+
$initresult["url"] .= "?pas_sec_token=" . urlencode($this->inittoken);
28+
}
29+
}
30+
31+
public function UpdateStreamsAndTimeout($prefix, $timeout, &$readfps, &$writefps)
32+
{
33+
}
34+
35+
public function CanHandleRequest($method, $url, $path, $client)
36+
{
37+
global $docroot;
38+
39+
// Make one minor concession to allow retrieval of a favicon as it tends to happen out-of-band at the same time as the first request to the host.
40+
// There's no way for the web browser to know that the first request unlocks the host.
41+
if ($path === "/favicon.ico" && is_file($docroot . "/favicon.ico")) return false;
42+
43+
// Route all initial token requests and requests without a valid real token (via cookies) to the request handler.
44+
if ($this->inittoken !== false || ($this->realtoken !== false && (!isset($client->cookievars["pas_rst"]) || Str::CTstrcmp($this->realtoken, $client->cookievars["pas_rst"]) != 0)))
45+
{
46+
// Attempt to hide WebSocket upgrade requests from the server.
47+
unset($client->headers["Upgrade"]);
48+
49+
return true;
50+
}
51+
52+
return false;
53+
}
54+
55+
public function ProcessRequest($method, $path, $client, &$data)
56+
{
57+
// Remove WebSocket connections.
58+
if ($method === false) return false;
59+
60+
if ($this->inittoken !== false)
61+
{
62+
if (isset($data["pas_sec_token"]) && is_string($data["pas_sec_token"]) && Str::CTstrcmp($this->inittoken, $data["pas_sec_token"]) == 0)
63+
{
64+
// This is the first request with a valid token.
65+
$this->origtoken = $this->inittoken;
66+
$this->inittoken = false;
67+
68+
$client->appdata["respcode"] = 301;
69+
$client->appdata["respmsg"] = "Moved Permanently";
70+
71+
$client->SetResponseCode($client->appdata["respcode"]);
72+
73+
// Prevent browsers and proxies from doing bad things.
74+
$client->SetResponseNoCache();
75+
76+
$client->SetResponseCookie("pas_rst", $this->realtoken, 0, "", "", false, true);
77+
78+
// Redirect the browser.
79+
unset($client->appdata["url"]["query"]);
80+
unset($client->appdata["url"]["queryvars"]["pas_sec_token"]);
81+
$client->AddResponseHeader("Location", HTTP::CondenseURL($client->appdata["url"]), true);
82+
83+
$client->FinalizeResponse();
84+
}
85+
else
86+
{
87+
// Emit a generic 403 Forbidden error.
88+
$client->appdata["respcode"] = 403;
89+
$client->appdata["respmsg"] = "Forbidden<br><br>See log file for details.";
90+
91+
if (!isset($data["pas_sec_token"])) WriteErrorLog("403 Forbidden - Missing token", $client->ipaddr, $client->request, array("success" => false, "error" => "A PAS security token was not used.", "errorcode" => "missing_pas_sec_token", "server_ext" => "security_token"));
92+
else WriteErrorLog("403 Forbidden - Invalid token", $client->ipaddr, $client->request, array("success" => false, "error" => "An invalid PAS security token was used.", "errorcode" => "invalid_pas_sec_token", "server_ext" => "security_token"));
93+
94+
SendHTTPErrorResponse($client);
95+
}
96+
}
97+
else if (isset($data["pas_sec_token"]) && is_string($data["pas_sec_token"]) && Str::CTstrcmp($this->origtoken, $data["pas_sec_token"]) == 0)
98+
{
99+
// The user has a valid token BUT someone else beat them to using it.
100+
$client->appdata["respcode"] = 403;
101+
$client->appdata["respmsg"] = "Forbidden - Token Reused<br><br>See error log for details.";
102+
103+
WriteErrorLog("403 Forbidden - Token reused", $client->ipaddr, $client->request, array("success" => false, "error" => "A valid PAS security token was reused. A PAS security token may only be used precisely one time per server instance. Seeing this message in this log file may be an indicator of a serious security problem.", "errorcode" => "pas_sec_token_reused", "server_ext" => "security_token"));
104+
105+
$client->SetResponseCode($client->appdata["respcode"]);
106+
107+
// Prevent browsers and proxies from doing bad things.
108+
$client->SetResponseNoCache();
109+
110+
ob_start();
111+
?>
112+
<!DOCTYPE html>
113+
<html>
114+
<head><title>403 Forbidden</title></head>
115+
<body>
116+
<h2>403 Forbidden - PAS Security Token Reused</h2>
117+
118+
<p>Your PAS security token is valid (i.e. the 'pas_sec_token' part of the URL). However, a PAS security token may only be used precisely one time.</p>
119+
120+
<p><span style="color: #A94442; font-size: 1.1em;">This message can appear if another user is on your system and stole your PAS security token before your web browser got a chance to use it.</span></p>
121+
122+
<p><span style="color: #A94442; font-size: 1.1em; font-weight: bold;">It is highly recommended that you reboot your computer immediately to prevent any significant damage to your user account on this system.</span></p>
123+
124+
<p>This message can also appear when attempting to reuse a PAS security token across multiple web browsers. To use another web browser with this application, change your default web browser and start the application again. This is a much rarer reason than the one above.</p>
125+
126+
</body>
127+
</html>
128+
<?php
129+
$content = ob_get_contents();
130+
ob_end_clean();
131+
132+
$client->AddResponseContent($content);
133+
$client->FinalizeResponse();
134+
}
135+
else
136+
{
137+
// The user has probably just attempted to switch browsers with a plain URL (or an attacker).
138+
$client->appdata["respcode"] = 403;
139+
$client->appdata["respmsg"] = "Forbidden<br><br>See error log for details.";
140+
141+
WriteErrorLog("403 Forbidden - Missing cookie", $client->ipaddr, $client->request, array("success" => false, "error" => "The expected PAS security cookie is missing. A PAS security cookie is required for all requests.", "errorcode" => "pas_rst_missing", "server_ext" => "security_token"));
142+
143+
$client->SetResponseCode($client->appdata["respcode"]);
144+
145+
// Prevent browsers and proxies from doing bad things.
146+
$client->SetResponseNoCache();
147+
148+
ob_start();
149+
?>
150+
<html>
151+
<head><title>403 Forbidden</title></head>
152+
<body>
153+
<h2>403 Forbidden - Security Cookie Missing</h2>
154+
155+
<p>An expected web browser cookie is missing or is invalid.</p>
156+
157+
<p>This message can appear for a number of reasons:</p>
158+
159+
<ul>
160+
<li>Switching web browsers.</li>
161+
<li>Switching to Incognito/Private Web Browsing mode.</li>
162+
<li>Disabling web browser cookies.</li>
163+
<li>Blocking cookies using a third-party browser plugin.</li>
164+
<li>Loading the wrong cookies from another application instance due to malware or a bad third-party browser plugin.</li>
165+
</ul>
166+
167+
</body>
168+
</html>
169+
<?php
170+
$content = ob_get_contents();
171+
ob_end_clean();
172+
173+
$client->AddResponseContent($content);
174+
$client->FinalizeResponse();
175+
}
176+
177+
return true;
178+
}
179+
180+
public function ServerDone()
181+
{
182+
}
183+
}
184+
?>

installers/nix-tar-gz/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
Instructions
2+
============
3+
4+
This directory contains a packaging program (package.php) and a configuration file (package.json) for preparing tar-gzipped (.tar.gz) installable packages for Linux.
5+
6+
Open `install-support/php-nix-install.sh` in a text editor and adjust the system dependencies as needed (e.g. add any extra PHP extensions that are required by the application).
7+
8+
Open `package.json` in a text editor and fill out the various values. Most of the fields should be obvious as to what they are for. However, the following keys are less obvious:
9+
10+
* vendor - May only contain A-Z, a-z, and hyphens. Required by the `xdg-utils` package to avoid name conflicts.
11+
* app_categories - A semicolon separated list from the standard categories in the [freedesktop.org category registry](https://specifications.freedesktop.org/menu-spec/menu-spec-latest.html#category-registry).
12+
* app_keywords - A semicolon separated list of additional keywords that a user might use to search for the application (e.g. acronyms).
13+
* user_desktop_icon - A boolean indicating whether or not an icon should also be placed on the user's desktop. Users tend to prefer clean desktops, so setting this to true is generally inadvisable.
14+
15+
Once the package information file has been filled out, save it and run (from Linux or Mac OSX is recommended):
16+
17+
```
18+
php package.php
19+
```
20+
21+
Assuming all the required pieces exist, the `.tar.gz` package will be generated and made ready for deployment. The recommended icon size for the PNG icon is 512x512. All additional sizes are generated during installation.
22+
23+
Don't forget to test the installation to verify that it works as expected. The installer is run via the command `./install.sh` and the uninstaller via `./uninstall.sh`. Use `sudo ./install.sh` to install for all users.
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php
2+
// Directory helper functions.
3+
// (C) 2018 CubicleSoft. All Rights Reserved.
4+
5+
class DirHelper
6+
{
7+
static function Delete($path, $recursive = true, $exclude = array())
8+
{
9+
$path = rtrim(str_replace("\\", "/", $path), "/");
10+
11+
$dir = @opendir($path);
12+
if ($dir !== false)
13+
{
14+
while (($file = readdir($dir)) !== false)
15+
{
16+
if ($file != "." && $file != ".." && !isset($exclude[$path . "/" . $file]))
17+
{
18+
if (is_file($path . "/" . $file) || is_link($path . "/" . $file)) @unlink($path . "/" . $file);
19+
else if ($recursive && is_dir($path . "/" . $file)) self::Delete($path . "/" . $file, true, $exclude);
20+
}
21+
}
22+
23+
closedir($dir);
24+
25+
@rmdir($path);
26+
}
27+
}
28+
29+
static function Copy($srcdir, $destdir, $recurse = true, $exclude = array())
30+
{
31+
@mkdir($destdir, 0777, true);
32+
33+
$dir = @opendir($srcdir);
34+
if ($dir)
35+
{
36+
while (($file = readdir($dir)) !== false)
37+
{
38+
if ($file != "." && $file != ".." && !isset($exclude[$srcdir . "/" . $file]))
39+
{
40+
if (is_dir($srcdir . "/" . $file))
41+
{
42+
if ($recurse) self::Copy($srcdir . "/" . $file, $destdir . "/" . $file, true, true);
43+
}
44+
else
45+
{
46+
$fp = @fopen($srcdir . "/" . $file, "rb");
47+
$fp2 = @fopen($destdir . "/" . $file, "wb");
48+
49+
if ($fp !== false && $fp2 !== false)
50+
{
51+
while (($data = fread($fp, 1048576)) !== false && $data !== "") fwrite($fp2, $data);
52+
}
53+
54+
if ($fp2 !== false) fclose($fp2);
55+
if ($fp !== false) fclose($fp);
56+
}
57+
}
58+
}
59+
60+
closedir($dir);
61+
}
62+
}
63+
64+
static function SetPermissions($path, $dirowner, $dirgroup, $dirperms, $fileowner, $filegroup, $fileperms, $recurse = true, $exclude = array())
65+
{
66+
$path = rtrim(str_replace("\\", "/", $path), "/");
67+
68+
if (!isset($exclude[$path]))
69+
{
70+
if ($dirowner !== false) @chown($path, $dirowner);
71+
if ($dirgroup !== false) @chgrp($path, $dirgroup);
72+
if ($dirperms !== false) @chmod($path, $dirperms);
73+
}
74+
75+
$dir = @opendir($path);
76+
if ($dir)
77+
{
78+
while (($file = readdir($dir)) !== false)
79+
{
80+
if ($file != "." && $file != ".." && !isset($exclude[$path . "/" . $file]))
81+
{
82+
if (is_dir($path . "/" . $file))
83+
{
84+
if ($recurse) self::SetPermissions($path . "/" . $file, $dirowner, $dirgroup, $dirperms, $fileowner, $filegroup, $fileperms, true, $exclude);
85+
}
86+
else
87+
{
88+
if ($fileowner !== false) @chown($path . "/" . $file, $fileowner);
89+
if ($filegroup !== false) @chgrp($path . "/" . $file, $filegroup);
90+
if ($fileperms !== false) @chmod($path . "/" . $file, $fileperms);
91+
}
92+
}
93+
}
94+
95+
@closedir($dir);
96+
}
97+
}
98+
}
99+
?>

0 commit comments

Comments
 (0)