function ProcessVirtualHostConf($VH, $VHPort, &$ReturnArray) { //Find the ServerName $VHInfo = array('Port' => $VHPort); //Save for messages if (!preg_match('/^\\h*ServerName\\h+([-\\w\\.]+)\\h*$/iumD', $VH, $Matches)) { //Only allow the domain name on this line to guarantee we found a proper domain return OL('ServerName match not found for VirtualHost. The ServerName line may only contain the domain name' . "\n" . str_repeat('-', 80) . "\n{$VH}\n" . str_repeat('-', 80)); } $VHInfo['AllDomains'] = array($ServerName = $VHInfo['ServerName'] = mb_strtolower($Matches[1])); //Set AllDomains and ServerName //Find the ServerAlias if (!preg_match('/^\\h*ServerAlias\\b/im', $VH)) { OL("Warning: ServerAlias not given for {$ServerName}:{$VHPort}", 'Warning'); } else { if (!preg_match('/^\\h*ServerAlias\\h+((?:[-\\w\\.]+\\h+)*[-\\w\\.]+)\\h*$/iumD', $VH, $Matches)) { //Only allow domains on this apache config line return OL("ServerAlias is invalid for VirtualHost {$ServerName}:{$VHPort}. The ServerAlias line may only contain a whitespace separated list of domain names"); } else { //Update AllDomains and set ServerAliases $VHInfo['AllDomains'] = array_merge($VHInfo['AllDomains'], $VHInfo['ServerAliases'] = preg_split('/\\h+/u', mb_strtolower($Matches[1]))); } } //Find the Document Root if (!preg_match('/^\\h*DocumentRoot\\h+(\\S.*?)\\h*$/iumD', $VH, $Matches)) { return OL("DocumentRoot not found for VirtualHost {$ServerName}:{$VHPort}"); } $VHInfo['DocumentRoot'] = $Matches[1]; //Output the matches OL("Found {$ServerName}:{$VHPort} at {$VHInfo['DocumentRoot']}" . (!isset($VHInfo['ServerAliases']) ? '' : ' with aliases ' . implode(',', $VHInfo['ServerAliases'])), 'Success'); //Add to the list of virtual host’s infos $ReturnArray[] = $VHInfo; return 0; }
EndText; //Get the last PHP error for user output //This is required b/c PHP ~<5.3 does not allow direct array access from a function return function GetLastError() { $E = error_get_last(); return $E['message']; } //Confirm PHP version if (version_compare(PHP_VERSION, '5.3.0', '<')) { return OL('Requires PHP>=5.2.4 for the \\h PREG character set; php>=5.3.0 for anonymous functions'); } //Output the help if requested or required global $IsCLI; if ($IsCLI) { global $argc; require_once __DIR__ . '/CustomGetOpt.php'; } if ($IsCLI && ($argc < 2 || count(cgetopt('h', array('help')))) || !$IsCLI && (isset($_REQUEST['Help']) || !count(array_merge($_GET, $_POST)))) { //Output the program description global $ConfigVars; OL(str_ireplace('?AllowConfigOverride?', $ConfigVars['AllowConfigOverride'] ? 'On' : 'Off', $ProgramDescription), 'Normal', false); //Output the parameters require_once __DIR__ . '/Parameters.php'; foreach (GetParametersInfo($ConfigVars) as $PD) { OL('* ' . ($IsCLI ? "-{$PD['0']} --{$PD['1']}" : $PD[2]) . "{$PD['3']}:" . implode("\n ", array_merge(array(''), !is_array($PD[4]) ? array($PD[4]) : $PD[4])), 'Normal', false); } //Nothing left to do after the help prompt return 1; } return 0;
function GetCertLoc($DocRoot, $LEReturn, &$CertDataPerDocRoot, $CertPathOverride, $Only1Cert) { //Get the path to the “fullchain” file, which shares the path with the other certificates $FullChainPath = null; $ForDocRootStr = $Only1Cert ? '' : ' for ' . $DocRoot; //Add the document root to error messages $LEReturn = implode("\n", $LEReturn); if (!preg_match('/Congratulations.*?saved\\s+at\\s+(.*?)\\w+\\.pem\\s*\\.\\s+Your\\s+cert/ius', $LEReturn, $Matches)) { //Extract via a regex return OL("Cannot find path to certificate in return from letsencrypt{$ForDocRootStr} (They changed their return string):\n{$LEReturn}\n"); } //If not found, throw error $FullChainPath = $Matches[1]; //Transform the certificate path if parameter is given if (isset($CertPathOverride) && !($FullChainPath = @preg_replace($CertPathOverride[0], $CertPathOverride[1], $FullChainPath))) { //Transform occurs here return OL("Error while transforming certiciate path{$ForDocRootStr}: " . ($FullChainPath === NULL ? 'PREG Error: ' . GetLastError() : 'Result string is empty')); } //Load the certificates’ data $CertData = array(); foreach (array('cert', 'privkey', 'chain') as $CertType) { if (!($CertData[$CertType] = cfile_get_contents("{$FullChainPath}{$CertType}.pem"))) { return OL("Cannot read {$CertType} certificate{$ForDocRootStr} at: {$FullChainPath}{$CertType}.pem : " . ($CertData[$CertType] === FALSE ? GetLastError() : 'File is empty')); } } $CertDataPerDocRoot[$DocRoot] = $CertData; return 0; }
function CreateCerts(&$Config, $Params, $VirtualHostInfos) { //Get the list of domains to ignore (Its keys are the domains) $IgnoreList = array(); if (isset($Params['Ignore']) && strlen(trim($Params['Ignore']))) { $IgnoreList = preg_split('/\\h+/u', mb_strtolower(trim($Params['Ignore']))); $IgnoreList = array_combine($IgnoreList, array_fill(0, count($IgnoreList), 1)); } //Combine domains into a list by the document root $PrimaryDomain = mb_strtolower($Params['Domain']); $PrimaryDomainDocumentRoot = null; //The primary domain (lowercase) and its document root $DocRoots = array(); //Array(DOCROOT=>Array(DOMAIN1=>, DOMAIN2=>, ...)) $IgnoreListCheck = $IgnoreList; //Domains are removed from this list as they are found. If any domains are not found, issue a warning foreach ($VirtualHostInfos as $VHI) { foreach ($VHI['AllDomains'] as $Domain) { if ($Domain == $PrimaryDomain) { //If primary domain, remember document root $PrimaryDomainDocumentRoot = $VHI['DocumentRoot']; } if (!isset($IgnoreList[$Domain])) { //If not on the ignore list, add to the DocRoots list to process $DocRoots[$VHI['DocumentRoot']][$Domain] = 1; } else { //If on the ignore list, mark as found unset($IgnoreListCheck[$Domain]); } } } if (count($IgnoreListCheck)) { //If any requested ignored domains are found, issue a warning OL('Warning: The following domains on the domain ignore list were not used: ' . implode(', ', array_keys($IgnoreListCheck)), 'Warning'); } //Get the letsencrypt executable command and parameters $AllowConfigOverride = $Config['AllowConfigOverride']; $LetsEncryptCommand =& $Config['LetsEncryptCommand']; $LetsEncryptParams =& $Config['LetsEncryptParams']; if ($AllowConfigOverride && isset($Params['LECmd'])) { $LetsEncryptCommand = $Params['LECmd']; } if (!strlen(trim($LetsEncryptCommand))) { return OL('LetsEncryptCommand is empty'); } if ($AllowConfigOverride && isset($Params['LEParams'])) { $LetsEncryptParams = $Params['LEParams']; } //Get the DistributionType $DistributionType =& $Config['DistributionType']; if (isset($Params['Distribution'])) { $DistributionType = $Params['Distribution']; } if (!preg_match('/^(?:GivenDomainOnly|AllInOne|SeparateVHosts)$/iD', $DistributionType)) { return OL('Invalid Distribution parameter: ' . $DistributionType); } $DistributionType = mb_strtolower($DistributionType); if (strcasecmp($DistributionType, 'GivenDomainOnly') == 0) { if (!isset($DocRoots[$PrimaryDomainDocumentRoot])) { return OL('Document root not found for primary vhost'); } $DocRoots = array($PrimaryDomainDocumentRoot => $DocRoots[$PrimaryDomainDocumentRoot]); OL('Removed all doc roots except: ' . $PrimaryDomainDocumentRoot, 'Success'); } //Gather the per-document-root params for the letsencrypt call $LEDocRootParams = array(); if (!stripos($LetsEncryptCommand, '?Params?')) { return OL('The letsencrypt command (LECmd) requires “?Params?” to be somewhere within it'); } foreach ($DocRoots as $DocRoot => $Domains) { //Confirm this document-root has domains if (!count($Domains)) { OL('Warning: No domains found for vhost-document-root-path: ' . $DocRoot, 'Warning'); continue; } //Store the parameters for the docroot $CurDRParams = '-w ' . escapeshellarg($DocRoot); foreach ($Domains as $Domain => $Dummy) { $CurDRParams .= ' -d ' . escapeshellarg($Domain); } $LEDocRootParams[$DocRoot] = $CurDRParams; } if (!count($LEDocRootParams)) { return OL('No valid vhosts found! This probably mean that all found domains were set in the “ignore” parameter'); } //Add the email parameter to the letsencrypt parameters $LetsEncryptParams .= ' --email ' . escapeshellarg($Params['Email']); //If DistributionType=AllInOne, combine all LEDocRootParams into 1 item if (strcasecmp($DistributionType, 'AllInOne') == 0) { $RemLEDocRootParams = $LEDocRootParams; $LEDocRootParams = array('AllDomains' => implode(' ', $LEDocRootParams)); OL('Combined all doc roots into one', 'Success'); } $Only1Cert = count($LEDocRootParams) == 1; //Execute each command and check their returns $LEResponses = array(); foreach ($LEDocRootParams as $DocRoot => $DRParams) { //Execute the letsencrypt call $LECall = str_ireplace('?Params?', "{$LetsEncryptParams} {$DRParams}", $LetsEncryptCommand) . ' 2>&1'; OL('Executing: ' . $LECall, 'Success'); exec($LECall, $LEReturn, $LEStatusCode); //Check/store the response if ($LEStatusCode) { OL('Letsencrypt call failed' . ($Only1Cert ? '' : ' for ' . $DocRoot) . ':' . "\n" . implode("\n", $LEReturn)); } else { $LEResponses[$DocRoot] = $LEReturn; } unset($LEReturn); } //If there was an error, exit here (if requested) if (count($LEResponses) != count($LEDocRootParams)) { //Can only continue if distribution type is SeparateVHosts if (strcasecmp($DistributionType, 'SeparateVHosts') != 0) { return OL('Required single domain not found'); } //Determine if the user wants to exit early on an error $InstallAnyways =& $Config['InstallAnyways']; if (isset($Params['InstallAnyways'])) { $InstallAnyways = $Params['InstallAnyways']; } $InstallAnyways = preg_match('/^\\s*[ty1]/iD', $InstallAnyways); if (!$InstallAnyways) { return OL('Not all domains got a valid return (turn on InstallAnyways to bypass this error)'); } } //If AllInOne, redistribute the certificate to all document roots if (strcasecmp($DistributionType, 'AllInOne') == 0) { $LEDocRootParams = $RemLEDocRootParams; foreach ($LEDocRootParams as $DocRoot => $_) { $LEResponses[$DocRoot] = $LEResponses['AllDomains']; } unset($LEResponses['AllDomains']); } //Output success OL('Certificate(s) successfully created', 'Success'); return compact('LEResponses', 'LEDocRootParams'); }
return OL('Domain not given. ' . ($IsCLI ? 'Add --help to see the help screen' : 'Add &help to see the help screen')); } if (!isset($Params['Email'])) { //LEEmailHelp (help information for the user’s email) is directly from the letsencrypt cli.py source $LEEmailHelp = 'Not using an email is strongly discouraged, because in the event of key loss or account compromise you will irrevocably lose access to your account. You will also be unable to receive notice about impending expiration or revocation of your certificates'; return OL('Email is required. ' . $LEEmailHelp); } //Read the apache conf require_once __DIR__ . '/ReadApacheConf.php'; $VirtualHostInfos = ReadApacheConf($ConfigVars, $Params); if (!is_array($VirtualHostInfos)) { //If there was an error, exit here return $VirtualHostInfos; } //Create the certs require_once __DIR__ . '/CreateCerts.php'; $FinalCertsInfo = CreateCerts($ConfigVars, $Params, $VirtualHostInfos); if (!is_array($FinalCertsInfo)) { //If there was an error, exit here return $FinalCertsInfo; } //Install the certs require_once __DIR__ . '/InstallCerts.php'; $Ret = InstallCerts($ConfigVars, $Params, $VirtualHostInfos, $FinalCertsInfo['LEResponses'], $FinalCertsInfo['LEDocRootParams']); if ($Ret) { //If there was an error, exit here return $Ret; } //Script has completed OL('Script has finished successfully', 'Complete'); return 0;