1<?php 2/** 3 * DokuWiki Plugin letsencrypt (Helper Component) 4 * 5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 6 * @author Andreas Gohr <andi@splitbrain.org> 7 */ 8 9// must be run within Dokuwiki 10use dokuwiki\plugin\letsencrypt\classes\CliLogger; 11use dokuwiki\plugin\letsencrypt\classes\HTMLLogger; 12use dokuwiki\plugin\letsencrypt\classes\Lescript; 13use dokuwiki\plugin\letsencrypt\classes\NullLogger; 14 15if(!defined('DOKU_INC')) die(); 16 17require_once __DIR__ . '/Lescript.php'; 18 19class helper_plugin_letsencrypt extends DokuWiki_Plugin { 20 21 protected $logger; 22 23 /** 24 * helper_plugin_letsencrypt constructor. 25 */ 26 public function __construct() { 27 $this->logger = new NullLogger(); 28 } 29 30 /** 31 * switch to HTML logging 32 */ 33 public function setHTMLLogger() { 34 $this->logger = new HTMLLogger(); 35 } 36 37 /** 38 * switch to Console logging 39 * @param DokuCLI $cli 40 */ 41 public function setCliLogger($cli) { 42 $this->logger = new CliLogger($cli); 43 } 44 45 /** 46 * Get all know domains of this wiki (includes all animals) 47 * 48 * @return array 49 */ 50 protected function getOwnDomains() { 51 $domains = array(); 52 53 // get all domains from farming 54 /** @var helper_plugin_farmer $farmer */ 55 $farmer = plugin_load('helper', 'farmer'); 56 if($farmer) { 57 foreach($farmer->getAllAnimals() as $animal) { 58 $url = $farmer->getAnimalURL($animal); 59 $domains[] = parse_url($url, PHP_URL_HOST); 60 } 61 $farmconf = $farmer->getConfig(); 62 if(isset($farmconf['base']['farmhost'])) { 63 $domains[] = $farmconf['base']['farmhost']; 64 } 65 } 66 $domains[] = parse_url(DOKU_URL, PHP_URL_HOST); 67 return $domains; 68 } 69 70 /** 71 * Get all domains configured in config file 72 * 73 * @return array $domains, $excludes 74 */ 75 protected function getDomainConfig() { 76 $file = DOKU_CONF . '/letsencrypt-domains.conf'; 77 if(!file_exists($file)) return array(array(), array()); 78 79 $data = file($file); 80 $domains = array(); 81 $excludes = array(); 82 83 foreach($data as $line) { 84 $line = preg_replace('/(?<![&\\\\])#.*$/', '', $line); 85 $line = str_replace('\\#', '#', $line); 86 $line = trim($line); 87 if(empty($line)) continue; 88 if($line[0] == '!') { 89 $excludes[] = substr($line, 1); 90 } else { 91 $domains[] = $line; 92 } 93 } 94 return array($domains, $excludes); 95 } 96 97 /** 98 * Get all domains 99 * 100 * @return array 101 */ 102 public function getAllDomains() { 103 list($domains, $excludes) = $this->getDomainConfig(); 104 $domains = array_merge($this->getOwnDomains(), $domains); 105 $domains = array_unique($domains); 106 $domains = array_filter($domains, array($this, 'domainFilter')); 107 $excludes = array_unique($excludes); 108 $excludes = array_filter($excludes, array($this, 'domainFilter')); 109 $domains = array_diff($domains, $excludes); 110 111 $info = $this->getCertInfo(); 112 $result = array(); 113 foreach($domains as $domain) { 114 if($info['expires_in_days'] && in_array($domain, $info['domains'])) { 115 $expire = $info['expires_in_days']; 116 } else { 117 $expire = 0; 118 } 119 120 $result[$domain] = $expire; 121 } 122 123 return $result; 124 } 125 126 /** 127 * Filter callback to remove non-valid domains 128 * 129 * @param string $domain 130 * @return bool 131 */ 132 public function domainFilter($domain) { 133 if(empty($domain)) return false; 134 if(strpos($domain, '.') === false) return false; // need at least one dot 135 if($domain == 'localhost.localdomain') return false; 136 if(preg_match('/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/', $domain)) return false; 137 return true; 138 } 139 140 /** 141 * @return bool is the account set up already? 142 */ 143 public function hasAccount() { 144 return file_exists($this->getCertDir() . '/_account/private.pem'); 145 } 146 147 public function checkDir() { 148 $root = $this->getRoot(); 149 if($root) { 150 $wellknown = "$root/.well-known/acme-challenge"; 151 io_makeFileDir("$wellknown/test.txt"); 152 $ok = io_saveFile("$wellknown/test.txt", 'works'); 153 } else { 154 $ok = false; 155 } 156 157 $certdir = $this->getConf('certificatedir'); 158 io_makeFileDir("$certdir/test.txt"); 159 $ok &= io_saveFile("$certdir/test.txt", 'works'); 160 161 return $ok; 162 } 163 164 /** 165 * Register a new LE account 166 * 167 * @param string $code 168 * @param string $country 169 * @param string $email 170 */ 171 public function register($code, $country, $email) { 172 set_time_limit(0); 173 $lescript = new Lescript($this->getCertDir(), $this->getRoot(), $this->logger); 174 $lescript->countryCode = $code; 175 $lescript->state = $country; 176 $email = trim($email); 177 if($email) { 178 $lescript->contact = array('mailto:' . $email); 179 } 180 181 $lescript->initAccount(); 182 } 183 184 /** 185 * Creates or updates the certificates 186 */ 187 public function updateCerts() { 188 set_time_limit(0); 189 $lescript = new Lescript($this->getCertDir(), $this->getRoot(), $this->logger); 190 $lescript->signDomains(array_keys($this->getAllDomains())); 191 } 192 193 /** 194 * The directory to store certificates 195 * 196 * @return string|null null if none is set or can be found 197 */ 198 public function getCertDir() { 199 $certdir = $this->getConf('certificatedir'); 200 if($certdir) return $certdir; 201 202 $root = $this->getRoot(); 203 if($root) $certdir = fullpath("$root/../certs"); 204 if($certdir) return $certdir; 205 206 return null; 207 } 208 209 /** 210 * Get info on our certificate 211 * 212 * @return array|null 213 */ 214 protected function getCertInfo() { 215 $lescript = new Lescript($this->getCertDir(), $this->getRoot(), $this->logger); 216 $data = $lescript->getCertInfo('wiki'); 217 return $data; 218 } 219 220 /** 221 * The webserver root directory 222 * 223 * @return string|null null if none is set or detected 224 */ 225 public function getRoot() { 226 // did the user tell us? 227 $root = $this->getConf('documentroot'); 228 if($root) return $root; 229 230 // does the webserver tell us? 231 $root = $_SERVER['DOCUMENT_ROOT']; 232 if($root) return $root; 233 234 // can we figure it out? 235 $len = -1 * strlen(DOKU_BASE); 236 if(substr(DOKU_INC, $len) == DOKU_BASE) { 237 return substr(DOKU_INC, 0, $len); 238 } 239 240 // we're lost 241 return null; 242 } 243} 244 245// vim:ts=4:sw=4:et: 246