1<?php
2
3/*><div style="width:60%; margin: auto; background-color: #fcc;
4                border: 1px solid #faa; padding: 0.5em 1em;">
5    <h1 style="font-size: 120%">No PHP Support</h1>
6
7    It seems this server has no PHP support enabled. You will need to
8    enable PHP before you can install and run DokuWiki. Contact your hosting
9    provider if you're unsure what this means.
10
11</div>*/
12
13use dokuwiki\PassHash;
14
15if (!defined('DOKU_INC')) define('DOKU_INC', __DIR__ . '/');
16if (!defined('DOKU_CONF')) define('DOKU_CONF', DOKU_INC . 'conf/');
17if (!defined('DOKU_LOCAL')) define('DOKU_LOCAL', DOKU_INC . 'conf/');
18
19// load and initialize the core system
20require_once(DOKU_INC . 'inc/init.php');
21require_once(DOKU_INC . 'inc/pageutils.php');
22
23// check for error reporting override or set error reporting to sane values
24if (!defined('DOKU_E_LEVEL')) {
25    error_reporting(E_ALL ^ E_NOTICE);
26} else {
27    error_reporting(DOKU_E_LEVEL);
28}
29
30// language strings
31require_once(DOKU_INC . 'inc/lang/en/lang.php');
32if (isset($_REQUEST['l']) && !is_array($_REQUEST['l'])) {
33    $LC = preg_replace('/[^a-z\-]+/', '', $_REQUEST['l']);
34}
35if (empty($LC)) $LC = 'en';
36if ($LC && $LC != 'en') {
37    require_once(DOKU_INC . 'inc/lang/' . $LC . '/lang.php');
38}
39
40// initialise variables ...
41$error = [];
42
43// begin output
44header('Content-Type: text/html; charset=utf-8');
45?>
46    <!DOCTYPE html>
47    <html lang="<?php echo $LC ?>" dir="<?php echo $lang['direction'] ?>">
48    <head>
49        <meta charset="utf-8"/>
50        <title><?php echo $lang['i_installer'] ?></title>
51        <style>
52            body {
53                width: 90%;
54                margin: 0 auto;
55                font: 84% Verdana, Helvetica, Arial, sans-serif;
56            }
57
58            img {
59                border: none
60            }
61
62            br.cl {
63                clear: both;
64            }
65
66            code {
67                font-size: 110%;
68                color: #800000;
69            }
70
71            fieldset {
72                border: none
73            }
74
75            label {
76                display: block;
77                margin-top: 0.5em;
78            }
79
80            select.text, input.text {
81                width: 30em;
82                margin: 0 0.5em;
83            }
84
85            a {
86                text-decoration: none
87            }
88        </style>
89        <script>
90            function acltoggle() {
91                var cb = document.getElementById('acl');
92                var fs = document.getElementById('acldep');
93                if (!cb || !fs) return;
94                if (cb.checked) {
95                    fs.style.display = '';
96                } else {
97                    fs.style.display = 'none';
98                }
99            }
100
101            window.onload = function () {
102                acltoggle();
103                var cb = document.getElementById('acl');
104                if (cb) cb.onchange = acltoggle;
105            };
106        </script>
107    </head>
108    <body style="">
109    <h1 style="float:left">
110        <img src="lib/exe/fetch.php?media=wiki:dokuwiki-128.png"
111             style="vertical-align: middle;" alt="" height="64" width="64"/>
112        <?php echo $lang['i_installer'] ?>
113    </h1>
114    <div style="float:right; margin: 1em;">
115        <?php langsel() ?>
116    </div>
117    <br class="cl"/>
118
119    <div style="float: right; width: 34%;">
120        <?php
121        if (file_exists(DOKU_INC . 'inc/lang/' . $LC . '/install.html')) {
122            include(DOKU_INC . 'inc/lang/' . $LC . '/install.html');
123        } else {
124            echo "<div lang=\"en\" dir=\"ltr\">\n";
125            include(DOKU_INC . 'inc/lang/en/install.html');
126            echo "</div>\n";
127        }
128        ?>
129        <a style="
130                background: transparent
131                url(data/dont-panic-if-you-see-this-in-your-logs-it-means-your-directory-permissions-are-correct.png)
132                left top no-repeat;
133                display: block; width:380px; height:73px; border:none; clear:both;"
134           target="_blank"
135           href="https://www.dokuwiki.org/security#web_access_security"></a>
136    </div>
137
138    <div style="float: left; width: 58%;">
139        <?php
140        try {
141            if (!(check_functions() && check_permissions())) {
142                echo '<p>' . $lang['i_problems'] . '</p>';
143                print_errors();
144                print_retry();
145            } elseif (!check_configs()) {
146                echo '<p>' . $lang['i_modified'] . '</p>';
147                print_errors();
148            } elseif (check_data($_REQUEST['d'])) {
149                // check_data has sanitized all input parameters
150                if (!store_data($_REQUEST['d'])) {
151                    echo '<p>' . $lang['i_failure'] . '</p>';
152                    print_errors();
153                } else {
154                    echo '<p>' . $lang['i_success'] . '</p>';
155                }
156            } else {
157                print_errors();
158                print_form($_REQUEST['d']);
159            }
160        } catch (Exception $e) {
161            echo 'Caught exception: ', $e->getMessage(), "\n";
162        }
163        ?>
164    </div>
165
166
167    <div style="clear: both">
168        <a href="https://dokuwiki.org/"><img src="lib/tpl/dokuwiki/images/button-dw.png" alt="driven by DokuWiki"/></a>
169        <a href="https://php.net"><img src="lib/tpl/dokuwiki/images/button-php.gif" alt="powered by PHP"/></a>
170    </div>
171    </body>
172    </html>
173<?php
174
175/**
176 * Print the input form
177 *
178 * @param array $d submitted entry 'd' of request data
179 */
180function print_form($d)
181{
182    global $lang;
183    global $LC;
184
185    include(DOKU_CONF . 'license.php');
186
187    if (!is_array($d)) $d = [];
188    $d = array_map('hsc', $d);
189
190    if (!isset($d['acl'])) $d['acl'] = 1;
191    if (!isset($d['pop'])) $d['pop'] = 1;
192
193    ?>
194    <form action="" method="post">
195        <input type="hidden" name="l" value="<?php echo $LC ?>"/>
196        <fieldset>
197            <label for="title"><?php echo $lang['i_wikiname'] ?>
198                <input type="text" name="d[title]" id="title" value="<?php echo $d['title'] ?>" style="width: 20em;"/>
199            </label>
200
201            <fieldset style="margin-top: 1em;">
202                <label for="acl">
203                    <input type="checkbox" name="d[acl]"
204                           id="acl" <?php echo(($d['acl'] ? ' checked="checked"' : '')); ?> />
205                    <?php echo $lang['i_enableacl'] ?></label>
206
207                <fieldset id="acldep">
208                    <label for="superuser"><?php echo $lang['i_superuser'] ?></label>
209                    <input class="text" type="text" name="d[superuser]" id="superuser"
210                           value="<?php echo $d['superuser'] ?>"/>
211
212                    <label for="fullname"><?php echo $lang['fullname'] ?></label>
213                    <input class="text" type="text" name="d[fullname]" id="fullname"
214                           value="<?php echo $d['fullname'] ?>"/>
215
216                    <label for="email"><?php echo $lang['email'] ?></label>
217                    <input class="text" type="text" name="d[email]" id="email" value="<?php echo $d['email'] ?>"/>
218
219                    <label for="password"><?php echo $lang['pass'] ?></label>
220                    <input class="text" type="password" name="d[password]" id="password"/>
221
222                    <label for="confirm"><?php echo $lang['passchk'] ?></label>
223                    <input class="text" type="password" name="d[confirm]" id="confirm"/>
224
225                    <label for="policy"><?php echo $lang['i_policy'] ?></label>
226                    <select class="text" name="d[policy]" id="policy">
227                        <option value="0" <?php echo ($d['policy'] == 0) ? 'selected="selected"' : '' ?>><?php
228                            echo $lang['i_pol0'] ?></option>
229                        <option value="1" <?php echo ($d['policy'] == 1) ? 'selected="selected"' : '' ?>><?php
230                            echo $lang['i_pol1'] ?></option>
231                        <option value="2" <?php echo ($d['policy'] == 2) ? 'selected="selected"' : '' ?>><?php
232                            echo $lang['i_pol2'] ?></option>
233                    </select>
234
235                    <label for="allowreg">
236                        <input type="checkbox" name="d[allowreg]" id="allowreg" <?php
237                        echo(($d['allowreg'] ? ' checked="checked"' : '')); ?> />
238                        <?php echo $lang['i_allowreg'] ?>
239                    </label>
240                </fieldset>
241            </fieldset>
242
243            <fieldset>
244                <p><?php echo $lang['i_license'] ?></p>
245                <?php
246                $license[] = ['name' => $lang['i_license_none'], 'url' => ''];
247                if (empty($d['license'])) $d['license'] = 'cc-by-sa';
248                foreach ($license as $key => $lic) {
249                    echo '<label for="lic_' . $key . '">';
250                    echo '<input type="radio" name="d[license]" value="' . hsc($key) . '" id="lic_' . $key . '"' .
251                        (($d['license'] === $key) ? ' checked="checked"' : '') . '>';
252                    echo hsc($lic['name']);
253                    if ($lic['url']) echo ' <a href="' . $lic['url'] . '" target="_blank"><sup>[?]</sup></a>';
254                    echo '</label>';
255                }
256                ?>
257            </fieldset>
258
259            <fieldset>
260                <p><?php echo $lang['i_pop_field'] ?></p>
261                <label for="pop">
262                    <input type="checkbox" name="d[pop]" id="pop" <?php
263                    echo(($d['pop'] ? ' checked="checked"' : '')); ?> />
264                    <?php echo $lang['i_pop_label'] ?>
265                    <a href="https://www.dokuwiki.org/popularity" target="_blank"><sup>[?]</sup></a>
266                </label>
267            </fieldset>
268
269        </fieldset>
270        <fieldset id="process">
271            <button type="submit" name="submit"><?php echo $lang['btn_save'] ?></button>
272        </fieldset>
273    </form>
274    <?php
275}
276
277function print_retry()
278{
279    global $lang;
280    global $LC;
281    ?>
282    <form action="" method="get">
283        <fieldset>
284            <input type="hidden" name="l" value="<?php echo $LC ?>"/>
285            <button type="submit"><?php echo $lang['i_retry']; ?></button>
286        </fieldset>
287    </form>
288    <?php
289}
290
291/**
292 * Check validity of data
293 *
294 * @param array $d
295 * @return bool ok?
296 * @author Andreas Gohr
297 *
298 */
299function check_data(&$d)
300{
301    static $form_default = [
302        'title' => '',
303        'acl' => '1',
304        'superuser' => '',
305        'fullname' => '',
306        'email' => '',
307        'password' => '',
308        'confirm' => '',
309        'policy' => '0',
310        'allowreg' => '0',
311        'license' => 'cc-by-sa'
312    ];
313    global $lang;
314    global $error;
315
316    if (!is_array($d)) $d = [];
317    foreach ($d as $k => $v) {
318        if (is_array($v))
319            unset($d[$k]);
320        else $d[$k] = (string)$v;
321    }
322
323    //autolowercase the username
324    $d['superuser'] = isset($d['superuser']) ? strtolower($d['superuser']) : "";
325
326    $ok = false;
327
328    if (isset($_REQUEST['submit'])) {
329        $ok = true;
330
331        // check input
332        if (empty($d['title'])) {
333            $error[] = sprintf($lang['i_badval'], $lang['i_wikiname']);
334            $ok = false;
335        }
336        if (isset($d['acl'])) {
337            if (empty($d['superuser']) || ($d['superuser'] !== cleanID($d['superuser']))) {
338                $error[] = sprintf($lang['i_badval'], $lang['i_superuser']);
339                $ok = false;
340            }
341            if (empty($d['password'])) {
342                $error[] = sprintf($lang['i_badval'], $lang['pass']);
343                $ok = false;
344            } elseif (!isset($d['confirm']) || $d['confirm'] != $d['password']) {
345                $error[] = sprintf($lang['i_badval'], $lang['passchk']);
346                $ok = false;
347            }
348            if (empty($d['fullname']) || strstr($d['fullname'], ':')) {
349                $error[] = sprintf($lang['i_badval'], $lang['fullname']);
350                $ok = false;
351            }
352            if (empty($d['email']) || strstr($d['email'], ':') || !strstr($d['email'], '@')) {
353                $error[] = sprintf($lang['i_badval'], $lang['email']);
354                $ok = false;
355            }
356        } else {
357            // Since default = 1, browser won't send acl=0 when user untick acl
358            $d['acl'] = '0';
359        }
360    }
361    $d = array_merge($form_default, $d);
362    return $ok;
363}
364
365/**
366 * Writes the data to the config files
367 *
368 * @param array $d
369 * @return bool
370 * @throws Exception
371 *
372 * @author  Chris Smith <chris@jalakai.co.uk>
373 */
374function store_data($d)
375{
376    global $LC;
377    $ok = true;
378    $d['policy'] = (int)$d['policy'];
379
380    // create local.php
381    $now = gmdate('r');
382    $output = <<<EOT
383<?php
384/**
385 * Dokuwiki's Main Configuration File - Local Settings
386 * Auto-generated by install script
387 * Date: $now
388 */
389
390EOT;
391    // add any config options set by a previous installer
392    $preset = __DIR__ . '/install.conf';
393    if (file_exists($preset)) {
394        $output .= "# preset config options\n";
395        $output .= file_get_contents($preset);
396        $output .= "\n\n";
397        $output .= "# options selected in installer\n";
398        @unlink($preset);
399    }
400
401    $output .= '$conf[\'title\'] = \'' . addslashes($d['title']) . "';\n";
402    $output .= '$conf[\'lang\'] = \'' . addslashes($LC) . "';\n";
403    $output .= '$conf[\'license\'] = \'' . addslashes($d['license']) . "';\n";
404    if ($d['acl']) {
405        $output .= '$conf[\'useacl\'] = 1' . ";\n";
406        $output .= "\$conf['superuser'] = '@admin';\n";
407    }
408    if (!$d['allowreg']) {
409        $output .= '$conf[\'disableactions\'] = \'register\'' . ";\n";
410    }
411    $ok = $ok && fileWrite(DOKU_LOCAL . 'local.php', $output);
412
413    if ($d['acl']) {
414        // hash the password
415        $phash = new PassHash();
416        $pass = $phash->hash_bcrypt($d['password']);
417
418        // create users.auth.php
419        $output = <<<EOT
420# users.auth.php
421# <?php exit()?>
422# Don't modify the lines above
423#
424# Userfile
425#
426# Auto-generated by install script
427# Date: $now
428#
429# Format:
430# login:passwordhash:Real Name:email:groups,comma,separated
431
432EOT;
433        // --- user:bcryptpasswordhash:Real Name:email:groups,comma,seperated
434        $output = $output . "\n" . implode(':', [
435                $d['superuser'],
436                $pass,
437                $d['fullname'],
438                $d['email'],
439                'admin,user',
440            ]) . "\n";
441        $ok = $ok && fileWrite(DOKU_LOCAL . 'users.auth.php', $output);
442
443        // create acl.auth.php
444        $output = <<<EOT
445# acl.auth.php
446# <?php exit()?>
447# Don't modify the lines above
448#
449# Access Control Lists
450#
451# Auto-generated by install script
452# Date: $now
453
454EOT;
455        if ($d['policy'] == 2) {
456            $output .= "*               @ALL          0\n";
457            $output .= "*               @user         8\n";
458        } elseif ($d['policy'] == 1) {
459            $output .= "*               @ALL          1\n";
460            $output .= "*               @user         8\n";
461        } else {
462            $output .= "*               @ALL          8\n";
463        }
464        $ok = $ok && fileWrite(DOKU_LOCAL . 'acl.auth.php', $output);
465    }
466
467    // enable popularity submission
468    if (isset($d['pop']) && $d['pop']) {
469        @touch(DOKU_INC . 'data/cache/autosubmit.txt');
470    }
471
472    // disable auth plugins til needed
473    $output = <<<EOT
474<?php
475/*
476 * Local plugin enable/disable settings
477 *
478 * Auto-generated by install script
479 * Date: $now
480 */
481
482\$plugins['authad']    = 0;
483\$plugins['authldap']  = 0;
484\$plugins['authmysql'] = 0;
485\$plugins['authpgsql'] = 0;
486
487EOT;
488    $ok = $ok && fileWrite(DOKU_LOCAL . 'plugins.local.php', $output);
489
490    return $ok;
491}
492
493/**
494 * Write the given content to a file
495 *
496 * @param string $filename
497 * @param string $data
498 * @return bool
499 *
500 * @author  Chris Smith <chris@jalakai.co.uk>
501 */
502function fileWrite($filename, $data)
503{
504    global $error;
505    global $lang;
506
507    if (($fp = @fopen($filename, 'wb')) === false) {
508        $filename = str_replace($_SERVER['DOCUMENT_ROOT'], '{DOCUMENT_ROOT}/', $filename);
509        $error[] = sprintf($lang['i_writeerr'], $filename);
510        return false;
511    }
512
513    if (!empty($data)) {
514        fwrite($fp, $data);
515    }
516    fclose($fp);
517    return true;
518}
519
520
521/**
522 * check installation dependent local config files and tests for a known
523 * unmodified main config file
524 *
525 * @return bool
526 *
527 * @author      Chris Smith <chris@jalakai.co.uk>
528 */
529function check_configs()
530{
531    global $error;
532    global $lang;
533
534    $ok = true;
535
536    $config_files = [
537        'local' => DOKU_LOCAL . 'local.php',
538        'users' => DOKU_LOCAL . 'users.auth.php',
539        'auth' => DOKU_LOCAL . 'acl.auth.php'
540    ];
541
542    // configs shouldn't exist
543    foreach ($config_files as $file) {
544        if (file_exists($file) && filesize($file)) {
545            $file = str_replace($_SERVER['DOCUMENT_ROOT'], '{DOCUMENT_ROOT}/', $file);
546            $error[] = sprintf($lang['i_confexists'], $file);
547            $ok = false;
548        }
549    }
550    return $ok;
551}
552
553
554/**
555 * Check other installation dir/file permission requirements
556 *
557 * @return bool
558 *
559 * @author      Chris Smith <chris@jalakai.co.uk>
560 */
561function check_permissions()
562{
563    global $error;
564    global $lang;
565
566    $dirs = [
567        'conf' => DOKU_LOCAL,
568        'data' => DOKU_INC . 'data',
569        'pages' => DOKU_INC . 'data/pages',
570        'attic' => DOKU_INC . 'data/attic',
571        'media' => DOKU_INC . 'data/media',
572        'media_attic' => DOKU_INC . 'data/media_attic',
573        'media_meta' => DOKU_INC . 'data/media_meta',
574        'meta' => DOKU_INC . 'data/meta',
575        'cache' => DOKU_INC . 'data/cache',
576        'locks' => DOKU_INC . 'data/locks',
577        'index' => DOKU_INC . 'data/index',
578        'tmp' => DOKU_INC . 'data/tmp'
579    ];
580
581    $ok = true;
582    foreach ($dirs as $dir) {
583        if (!file_exists("$dir/.") || !is_writable($dir)) {
584            $dir = str_replace($_SERVER['DOCUMENT_ROOT'], '{DOCUMENT_ROOT}', $dir);
585            $error[] = sprintf($lang['i_permfail'], $dir);
586            $ok = false;
587        }
588    }
589    return $ok;
590}
591
592/**
593 * Check the availability of functions used in DokuWiki and the PHP version
594 *
595 * @return bool
596 *
597 * @author Andreas Gohr <andi@splitbrain.org>
598 */
599function check_functions()
600{
601    global $error;
602    global $lang;
603    $ok = true;
604
605    if (version_compare(phpversion(), '7.4.0', '<')) {
606        $error[] = sprintf($lang['i_phpver'], phpversion(), '7.4.0');
607        $ok = false;
608    }
609
610    if (ini_get('mbstring.func_overload') != 0) {
611        $error[] = $lang['i_mbfuncoverload'];
612        $ok = false;
613    }
614
615    try {
616        random_bytes(1);
617    } catch (Exception $th) {
618        // If an appropriate source of randomness cannot be found, an Exception will be thrown by PHP 7+
619        $error[] = $lang['i_urandom'];
620        $ok = false;
621    }
622
623    if (ini_get('mbstring.func_overload') != 0) {
624        $error[] = $lang['i_mbfuncoverload'];
625        $ok = false;
626    }
627
628    $funcs = explode(' ', 'addslashes call_user_func chmod copy fgets ' .
629        'file file_exists fseek flush filesize ftell fopen ' .
630        'glob header ignore_user_abort ini_get mkdir ' .
631        'ob_start opendir parse_ini_file readfile realpath ' .
632        'rename rmdir serialize session_start unlink usleep ' .
633        'preg_replace file_get_contents htmlspecialchars_decode ' .
634        'spl_autoload_register stream_select fsockopen pack xml_parser_create');
635
636    if (!function_exists('mb_substr')) {
637        $funcs[] = 'utf8_encode';
638        $funcs[] = 'utf8_decode';
639    }
640
641    if (!function_exists('mail')) {
642        if (strpos(ini_get('disable_functions'), 'mail') !== false) {
643            $disabled = $lang['i_disabled'];
644        } else {
645            $disabled = "";
646        }
647        $error[] = sprintf($lang['i_funcnmail'], $disabled);
648    }
649
650    foreach ($funcs as $func) {
651        if (!function_exists($func)) {
652            $error[] = sprintf($lang['i_funcna'], $func);
653            $ok = false;
654        }
655    }
656    return $ok;
657}
658
659/**
660 * Print language selection
661 *
662 * @author Andreas Gohr <andi@splitbrain.org>
663 */
664function langsel()
665{
666    global $lang;
667    global $LC;
668
669    $dir = DOKU_INC . 'inc/lang';
670    $dh = opendir($dir);
671    if (!$dh) return;
672
673    $langs = [];
674    while (($file = readdir($dh)) !== false) {
675        if (preg_match('/^[._]/', $file)) continue;
676        if (is_dir($dir . '/' . $file) && file_exists($dir . '/' . $file . '/lang.php')) {
677            $langs[] = $file;
678        }
679    }
680    closedir($dh);
681    sort($langs);
682
683    echo '<form action="">';
684    echo $lang['i_chooselang'];
685    echo ': <select name="l" onchange="submit()">';
686    foreach ($langs as $l) {
687        $sel = ($l == $LC) ? 'selected="selected"' : '';
688        echo '<option value="' . $l . '" ' . $sel . '>' . $l . '</option>';
689    }
690    echo '</select> ';
691    echo '<button type="submit">' . $lang['btn_update'] . '</button>';
692    echo '</form>';
693}
694
695/**
696 * Print global error array
697 *
698 * @author Andreas Gohr <andi@splitbrain.org>
699 */
700function print_errors()
701{
702    global $error;
703    if (!empty($error)) {
704        echo '<ul>';
705        foreach ($error as $err) {
706            echo "<li>$err</li>";
707        }
708        echo '</ul>';
709    }
710}
711