1<?php
2/* vim: set expandtab sw=4 ts=4 sts=4: */
3/**
4 * Get user's global privileges and some db-specific privileges
5 *
6 * @package PhpMyAdmin
7 */
8namespace PhpMyAdmin;
9
10use PhpMyAdmin\DatabaseInterface;
11use PhpMyAdmin\Util;
12
13/**
14 * PhpMyAdmin\CheckUserPrivileges class
15 *
16 * @package PhpMyAdmin
17 */
18class CheckUserPrivileges
19{
20    /**
21     * @var DatabaseInterface
22     */
23    private $dbi;
24
25    /**
26     * Constructor
27     *
28     * @param DatabaseInterface $dbi DatabaseInterface object
29     */
30    public function __construct(DatabaseInterface $dbi)
31    {
32        $this->dbi = $dbi;
33    }
34
35    /**
36     * Extracts details from a result row of a SHOW GRANT query
37     *
38     * @param string $row grant row
39     *
40     * @return array
41     */
42    public function getItemsFromShowGrantsRow($row)
43    {
44        $db_name_offset = mb_strpos($row, ' ON ') + 4;
45
46        $tblname_end_offset = mb_strpos($row, ' TO ');
47        $tblname_start_offset = false;
48
49        if ($__tblname_start_offset = mb_strpos($row, '`.', $db_name_offset)) {
50            if ($__tblname_start_offset < $tblname_end_offset) {
51                $tblname_start_offset = $__tblname_start_offset + 1;
52            }
53        }
54
55        if (!$tblname_start_offset) {
56            $tblname_start_offset = mb_strpos($row, '.', $db_name_offset);
57        }
58
59        $show_grants_dbname = mb_substr(
60            $row,
61            $db_name_offset,
62            $tblname_start_offset - $db_name_offset
63        );
64
65        $show_grants_dbname = Util::unQuote($show_grants_dbname, '`');
66
67        $show_grants_str = mb_substr(
68            $row,
69            6,
70            (mb_strpos($row, ' ON ') - 6)
71        );
72
73        $show_grants_tblname = mb_substr(
74            $row,
75            $tblname_start_offset + 1,
76            $tblname_end_offset - $tblname_start_offset - 1
77        );
78        $show_grants_tblname = Util::unQuote($show_grants_tblname, '`');
79
80        return array(
81            $show_grants_str,
82            $show_grants_dbname,
83            $show_grants_tblname
84        );
85    }
86
87    /**
88     * Check if user has required privileges for
89     * performing 'Adjust privileges' operations
90     *
91     * @param string $show_grants_str     string containing grants for user
92     * @param string $show_grants_dbname  name of db extracted from grant string
93     * @param string $show_grants_tblname name of table extracted from grant string
94     *
95     * @return void
96     */
97    public function checkRequiredPrivilegesForAdjust(
98        $show_grants_str,
99        $show_grants_dbname,
100        $show_grants_tblname
101    ) {
102        // '... ALL PRIVILEGES ON *.* ...' OR '... ALL PRIVILEGES ON `mysql`.* ..'
103        // OR
104        // SELECT, INSERT, UPDATE, DELETE .... ON *.* OR `mysql`.*
105        if ($show_grants_str == 'ALL'
106            || $show_grants_str == 'ALL PRIVILEGES'
107            || (mb_strpos(
108                    $show_grants_str, 'SELECT, INSERT, UPDATE, DELETE'
109                ) !== false)
110        ) {
111            if ($show_grants_dbname == '*'
112                && $show_grants_tblname == '*'
113            ) {
114                $GLOBALS['col_priv'] = true;
115                $GLOBALS['db_priv'] = true;
116                $GLOBALS['proc_priv'] = true;
117                $GLOBALS['table_priv'] = true;
118
119                if ($show_grants_str == 'ALL PRIVILEGES'
120                    || $show_grants_str == 'ALL'
121                ) {
122                    $GLOBALS['is_reload_priv'] = true;
123                }
124            }
125
126            // check for specific tables in `mysql` db
127            // Ex. '... ALL PRIVILEGES on `mysql`.`columns_priv` .. '
128            if ($show_grants_dbname == 'mysql') {
129                switch ($show_grants_tblname) {
130                    case "columns_priv":
131                        $GLOBALS['col_priv'] = true;
132                        break;
133                    case "db":
134                        $GLOBALS['db_priv'] = true;
135                        break;
136                    case "procs_priv":
137                        $GLOBALS['proc_priv'] = true;
138                        break;
139                    case "tables_priv":
140                        $GLOBALS['table_priv'] = true;
141                        break;
142                    case "*":
143                        $GLOBALS['col_priv'] = true;
144                        $GLOBALS['db_priv'] = true;
145                        $GLOBALS['proc_priv'] = true;
146                        $GLOBALS['table_priv'] = true;
147                        break;
148                    default:
149                }
150            }
151        }
152    }
153
154    /**
155     * sets privilege information extracted from SHOW GRANTS result
156     *
157     * Detection for some CREATE privilege.
158     *
159     * Since MySQL 4.1.2, we can easily detect current user's grants using $userlink
160     * (no control user needed) and we don't have to try any other method for
161     * detection
162     *
163     * @todo fix to get really all privileges, not only explicitly defined for this user
164     * from MySQL manual: (https://dev.mysql.com/doc/refman/5.0/en/show-grants.html)
165     * SHOW GRANTS displays only the privileges granted explicitly to the named
166     * account. Other privileges might be available to the account, but they are not
167     * displayed. For example, if an anonymous account exists, the named account
168     * might be able to use its privileges, but SHOW GRANTS will not display them.
169     *
170     * @return void
171     */
172    public function analyseShowGrant()
173    {
174        if (Util::cacheExists('is_create_db_priv')) {
175            $GLOBALS['is_create_db_priv'] = Util::cacheGet(
176                'is_create_db_priv'
177            );
178            $GLOBALS['is_reload_priv'] = Util::cacheGet(
179                'is_reload_priv'
180            );
181            $GLOBALS['db_to_create'] = Util::cacheGet(
182                'db_to_create'
183            );
184            $GLOBALS['dbs_where_create_table_allowed'] = Util::cacheGet(
185                'dbs_where_create_table_allowed'
186            );
187            $GLOBALS['dbs_to_test'] = Util::cacheGet(
188                'dbs_to_test'
189            );
190
191            $GLOBALS['db_priv'] = Util::cacheGet(
192                'db_priv'
193            );
194            $GLOBALS['col_priv'] = Util::cacheGet(
195                'col_priv'
196            );
197            $GLOBALS['table_priv'] = Util::cacheGet(
198                'table_priv'
199            );
200            $GLOBALS['proc_priv'] = Util::cacheGet(
201                'proc_priv'
202            );
203
204            return;
205        }
206
207        // defaults
208        $GLOBALS['is_create_db_priv']  = false;
209        $GLOBALS['is_reload_priv'] = false;
210        $GLOBALS['db_to_create'] = '';
211        $GLOBALS['dbs_where_create_table_allowed'] = array();
212        $GLOBALS['dbs_to_test'] = $this->dbi->getSystemSchemas();
213        $GLOBALS['proc_priv'] = false;
214        $GLOBALS['db_priv'] = false;
215        $GLOBALS['col_priv'] = false;
216        $GLOBALS['table_priv'] = false;
217
218        $rs_usr = $this->dbi->tryQuery('SHOW GRANTS');
219
220        if (! $rs_usr) {
221            return;
222        }
223
224        $re0 = '(^|(\\\\\\\\)+|[^\\\\])'; // non-escaped wildcards
225        $re1 = '(^|[^\\\\])(\\\)+'; // escaped wildcards
226
227        while ($row = $this->dbi->fetchRow($rs_usr)) {
228            list(
229                $show_grants_str,
230                $show_grants_dbname,
231                $show_grants_tblname
232            ) = $this->getItemsFromShowGrantsRow($row[0]);
233
234            if ($show_grants_dbname == '*') {
235                if ($show_grants_str != 'USAGE') {
236                    $GLOBALS['dbs_to_test'] = false;
237                }
238            } elseif ($GLOBALS['dbs_to_test'] !== false) {
239                $GLOBALS['dbs_to_test'][] = $show_grants_dbname;
240            }
241
242            if (
243                mb_strpos($show_grants_str,'RELOAD') !== false
244            ) {
245                $GLOBALS['is_reload_priv'] = true;
246            }
247
248            // check for the required privileges for adjust
249            $this->checkRequiredPrivilegesForAdjust(
250                $show_grants_str,
251                $show_grants_dbname,
252                $show_grants_tblname
253            );
254
255            /**
256             * @todo if we find CREATE VIEW but not CREATE, do not offer
257             * the create database dialog box
258             */
259            if ($show_grants_str == 'ALL'
260                || $show_grants_str == 'ALL PRIVILEGES'
261                || $show_grants_str == 'CREATE'
262                || strpos($show_grants_str, 'CREATE,') !== false
263            ) {
264                if ($show_grants_dbname == '*') {
265                    // a global CREATE privilege
266                    $GLOBALS['is_create_db_priv'] = true;
267                    $GLOBALS['is_reload_priv'] = true;
268                    $GLOBALS['db_to_create']   = '';
269                    $GLOBALS['dbs_where_create_table_allowed'][] = '*';
270                    // @todo we should not break here, cause GRANT ALL *.*
271                    // could be revoked by a later rule like GRANT SELECT ON db.*
272                    break;
273                } else {
274                    // this array may contain wildcards
275                    $GLOBALS['dbs_where_create_table_allowed'][] = $show_grants_dbname;
276
277                    $dbname_to_test = Util::backquote($show_grants_dbname);
278
279                    if ($GLOBALS['is_create_db_priv']) {
280                        // no need for any more tests if we already know this
281                        continue;
282                    }
283
284                    // does this db exist?
285                    if ((preg_match('/' . $re0 . '%|_/', $show_grants_dbname)
286                        && ! preg_match('/\\\\%|\\\\_/', $show_grants_dbname))
287                        || (! $this->dbi->tryQuery(
288                            'USE ' .  preg_replace(
289                                '/' . $re1 . '(%|_)/', '\\1\\3', $dbname_to_test
290                            )
291                        )
292                        && mb_substr($this->dbi->getError(), 1, 4) != 1044)
293                    ) {
294                        /**
295                         * Do not handle the underscore wildcard
296                         * (this case must be rare anyway)
297                         */
298                        $GLOBALS['db_to_create'] = preg_replace(
299                            '/' . $re0 . '%/',     '\\1',
300                            $show_grants_dbname
301                        );
302                        $GLOBALS['db_to_create'] = preg_replace(
303                            '/' . $re1 . '(%|_)/', '\\1\\3',
304                            $GLOBALS['db_to_create']
305                        );
306                        $GLOBALS['is_create_db_priv'] = true;
307
308                        /**
309                         * @todo collect $GLOBALS['db_to_create'] into an array,
310                         * to display a drop-down in the "Create database" dialog
311                         */
312                         // we don't break, we want all possible databases
313                         //break;
314                    } // end if
315                } // end elseif
316            } // end if
317
318        } // end while
319
320        $this->dbi->freeResult($rs_usr);
321
322        // must also cacheUnset() them in
323        // PhpMyAdmin\Plugins\Auth\AuthenticationCookie
324        Util::cacheSet('is_create_db_priv', $GLOBALS['is_create_db_priv']);
325        Util::cacheSet('is_reload_priv', $GLOBALS['is_reload_priv']);
326        Util::cacheSet('db_to_create', $GLOBALS['db_to_create']);
327        Util::cacheSet(
328            'dbs_where_create_table_allowed',
329            $GLOBALS['dbs_where_create_table_allowed']
330        );
331        Util::cacheSet('dbs_to_test', $GLOBALS['dbs_to_test']);
332
333        Util::cacheSet('proc_priv', $GLOBALS['proc_priv']);
334        Util::cacheSet('table_priv', $GLOBALS['table_priv']);
335        Util::cacheSet('col_priv', $GLOBALS['col_priv']);
336        Util::cacheSet('db_priv', $GLOBALS['db_priv']);
337    } // end function
338}
339