SwitchToSSLMode

Summary: How to force PmWiki to use Transport Layer Security (TLS)
Version: 2007-06-12
Prerequisites: PmWiki 2.x
Status: Beta
Maintainer: HaganFox

Questions answered by this recipe

How can I force PmWiki to use Transport Layer Security (TLS)?

Description

How to secure your PmWiki website using Hypertext Transfer Protocol Secure (HTTPS).

Method 1

One way to do this is with the following in a local configuration file (near the very top of config.php would be a good place):

if (@$_SERVER['HTTPS'] != 'on' && @$_SERVER['SERVER_PORT'] != '443') {
  header( "Location: " . "https://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
  exit('<html><body>
    <a href="https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] . '">Please use TLS.</a>
    </body></html>');
}

$ScriptUrl = "https://".$_SERVER['HTTP_HOST']."/pmwiki/pmwiki.php";
$PubDirUrl = 'https://'.$_SERVER['HTTP_HOST'].'/pmwiki/pub';

How it works: If the server isn't in TLS mode (determined by checking whether or not the global variable $_SERVER['HTTPS'] is set to "on") redirect the browser to a default URL that uses TLS. If the redirection doesn't take effect for some reason, stop the script and produce a "Please use TLS" link that points to the default URL.

The example assumes the URL path to your wiki is pmwiki/pmwiki.php. If you're using Clean URLs, $ScriptUrl might be something else, like https://www.example.com/wiki or even https://www.example.com . If you are not sure what $ScriptUrl should be, put {$ScriptUrl} in a wikipage and see what it is now.

Method 2

Here's another possible solution. This time where only actions where passwords are likely to be passed are sent via TLS:

## Switch to TLS mode when password would be sent in the clear.
$TLSActions = array(
  '1'=>'login', 'edit', 'post', 'postattr', 'attr', 'upload', 'loginadmin');
$TLSActions = array_flip($TLSActions);
if ($TLSActions[$action])
{
  $ScriptUrl = 'https://www.example.com/pmwiki/pmwiki.php';
  $PubDirUrl = 'https://www.example.com/pmwiki/pub';

  if (@$_SERVER['HTTPS'] == 'on'
    || @$_SERVER['SERVER_PORT'] == '443')
  {
    # Copy all GET request parameters and avoid
    # a problem with empty filename on upload page.
    $getparms = array();
    reset($_GET);
    while(list($name,$value) = each($_GET))
      if(!empty($value) && $name != 'n')
        $getparms[$name] = $name."=".urlencode($value);
    Redirect($pagename,'$PageUrl?'.implode("?",$getparms));
  }
} else {
  $ScriptUrl = 'http://www.example.com/pmwiki/pmwiki.php';
  $PubDirUrl = 'http://www.example.com/pmwiki/pub';
}

The example assumes there are not read-protected pages, since any 'read' passwords entered to view a page would be sent via a non-TLS connection.

Method 3

This is the method I am using. --RyanVarick, November 2010.

I want to enable TLS for any admin action, the entire admin session (ie when I am logged in), all read-protected pages (which redirect to the login form), and any page edit (anonymous or otherwise). To do this, I make a list of things I should be in TLS mode for, compare this against PmWiki's $UrlBase variable, and reload the page if there is a mismatch. Finally, I set the base PmWiki URL variables with the appropriate TLS prefix.

Note that this solution was designed for a wiki that uses only uses AuthUser. It was not tested for page- or group-level passwords.

##
## Force TLS mode for certain conditions
##

# Force TLS for all admin actions
$tls_actions = array();
foreach($HandleAuth as $key => $value)
{
  if($value == 'admin') array_push($tls_actions, $key);
}

# Force TLS for a few additional actions
array_push($tls_actions, 'login');
array_push($tls_actions, 'edit');

# Force TLS for any read-protected page and the entire admin session
if(PageVar($pagename, '$PasswdEdit') || $AuthId)
{
  array_push($tls_actions, $action);
}

# Reload the page if the current TLS mode does not match the desired TLS mode
$desired_url_scheme = 'http';
if(in_array($action, $tls_actions))
{
  $desired_url_scheme = 'https';
}
$http = $desired_url_scheme . '://';

if($UrlScheme != $desired_url_scheme)
{
  $url = $http . $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'];
  Redirect($pagename, $urlfmt='$url');
}

# Set PmWiki paths according to the TLS mode
$ScriptUrl    = $http . 'path/to/pmwiki.php';
$PubDirUrl    = $http . 'path/to/pub';
$UploadUrlFmt = $http . 'path/to/uploads';

Notes

If someone has a more reliable way for a PHP script to detect TLS mode, please share it!

For versions of PmWiki after 2.2.0-beta18, one can do the equivalent of

if (@$_SERVER['HTTPS'] != 'on' && @$_SERVER['SERVER_PORT'] != '443') {

with

if ($UrlScheme != 'https') {

or

if ($UrlScheme == 'http') {

With the following lines in config.php the values for $ScriptUrl and $PubDirUrl will adjust automatically, so a wiki will adapt and work properly whether TLS is on or not.

##  $ScriptUrl is the preferred URL for accessing wiki pages
##  $PubDirUrl is the URL for the pub directory.
$ScriptUrl = $UrlScheme.'://www.example.com/pmwiki/pmwiki.php';
$PubDirUrl = $UrlScheme.'://www.example.com/pmwiki/pub';

For versions of PmWiki prior to 2.2.0-beta18 you can use

##  $ScriptUrl is the preferred URL for accessing wiki pages
##  $PubDirUrl is the URL for the pub directory.
if (@$_SERVER["HTTPS"] == 'on' || @$_SERVER['SERVER_PORT'] == '443') {
  $ScriptUrl = 'https://www.example.com/pmwiki/pmwiki.php';
  $PubDirUrl = 'https://www.example.com/pmwiki/pub';
} else {
  $ScriptUrl = 'http://www.example.com/pmwiki/pmwiki.php';
  $PubDirUrl = 'http://www.example.com/pmwiki/pub';
}

This example is merely showing "TLS versus non-TLS" values for $ScriptUrl and $PubDirUrl. Your site may use URLs that look different from these or possibly different from each other, even to the point of changing the domain for TLS. The point is that one URL is HTTP and the other is HTTPS.


I use the config.php code below to automatically enable TLS as needed based on page actions or page name. Tested with PmWiki 2.2.0-beta63 with standard URLs, please see SwitchToSSLMode-Talk for more including limitations. --jtankers

##  Set URL to http or https,
##  Open Source 2007/09/13pm jtankers/PmWiki 2.2.0-beta63
function ChangeUrlScheme($urlscheme) { 
  $url = array();
  $query = '';
  reset($_GET);
  while(list($name,$value) = each($_GET)) {
    if (!empty($value)) {
      $url[$name] = $name."=".urlencode($value);
      $query = '?';
    }
  }
  header("Location: ".$urlscheme."://".$_SERVER['HTTP_HOST']
   .$_SERVER['PHP_SELF'].$query.implode("&",$url));
  exit;
}
##  Enable TLS for security actions and group or page names containing
##  key words
##  Open Source 2007/09/13pm2 jtankers/PmWiki 2.2.0-beta63.  
##  Limitations: TLS is not enabled for automatic login on unauthorized
##  page actions unless page action is included in $my_tls_actions,
##  or group.page name contains $my_secure_keyword
$my_secure_domain = 'www.wiki1.net';
$my_secure_keyword = 'Secure';
$my_secure_pageactions =
  'admin,analyze,attr,crypt,diag,login,loginadmin,postattr,source,webadmin';
if (substr_count($_SERVER['HTTP_HOST'], $my_secure_domain)) { 
  if ( substr_count($my_secure_pageactions, $action) 
    || substr_count($pagename, $my_secure_keyword) 
    || substr_count($pagename, 'SiteAdmin') 
  ) {
    if (@$_SERVER['SERVER_PORT'] != '443') ChangeUrlScheme('https');
  } else {
    if (@$_SERVER['SERVER_PORT'] == '443') ChangeUrlScheme('http');
  }
}

Farms

With the following in farmconfig.php the wikis in a farm will adapt according to whether TLS is on or not.

##  $FarmPubDirUrl is the URL for the farm-wide pub directory.
$FarmPubDirUrl = $UrlScheme.'://www.example.com/path/to/the/farm's/pub';

or, in some farms,

##  $FarmPubDirUrl is the URL for the farm-wide pub directory.
$FarmPubDirUrl =
  $UrlScheme.'://'.$_SERVER['HTTP_HOST'].'/path/to/the/farm's/pub';

Finally, here's an alternative that may work on more servers (and older versions of PmWiki).

##  $FarmPubDirUrl is the URL for the farm-wide pub directory.
if (@$_SERVER["HTTPS"] == 'on' || @$_SERVER['SERVER_PORT'] == '443') {
  $FarmPubDirUrl = 'https://www.example.com/path/to/the/farm's/pub';
} else {
  $FarmPubDirUrl = 'http://www.example.com/path/to/the/farm's/pub';
}

Why not always use TLS?

From a posting by Pm : Note also that serving content through TLS can significantly increase server loads. It's often a good idea to use TLS judiciously--to only use TLS on those pages that really need to be protected in transit. This is also why commercial sites such as Amazon don't use TLS for the entire session, but only for those portions where sensitive personal information such as credit card numbers or addresses are being transmitted over the wires.

Release Notes

  • 2006-11-16 - Initial release
  • 2007-01-09 - Incremental improvement
  • 2007-04-29 - Additions and corrections
  • 2007-07-12 - Incremental improvement

Comments

See Discussion at SwitchToSSLMode-Talk

See Also

Contributors

User notes? : If you use, used or reviewed this recipe, you can add your name. These statistics appear in the Cookbook listings and will help newcomers browsing through the wiki.