1#!/usr/bin/env php
2<?php
3
4/**
5 * Kolab storage cache modification script
6 *
7 * @version 3.1
8 * @author Thomas Bruederli <bruederli@kolabsys.com>
9 *
10 * Copyright (C) 2012-2014, Kolab Systems AG <contact@kolabsys.com>
11 *
12 * This program is free software: you can redistribute it and/or modify
13 * it under the terms of the GNU Affero General Public License as
14 * published by the Free Software Foundation, either version 3 of the
15 * License, or (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU Affero General Public License for more details.
21 *
22 * You should have received a copy of the GNU Affero General Public License
23 * along with this program. If not, see <http://www.gnu.org/licenses/>.
24 */
25
26define('INSTALL_PATH', __DIR__ . '/../../../');
27ini_set('display_errors', 1);
28
29require_once INSTALL_PATH . 'program/include/clisetup.php';
30
31function print_usage()
32{
33	print "Usage:  modcache.sh ACTION [OPTIONS] [USERNAME ARGS ...]\n";
34	print "Possible actions are: expunge, clear, prewarm\n";
35	print "-a, --all      Clear/expunge all caches\n";
36	print "-h, --host     IMAP host name\n";
37	print "-u, --user     IMAP user name to authenticate\n";
38	print "-t, --type     Object types to clear/expunge cache\n";
39	print "-l, --limit    Limit the number of records to be expunged\n";
40}
41
42// read arguments
43$opts = rcube_utils::get_opt(array(
44    'a' => 'all',
45    'h' => 'host',
46    'u' => 'user',
47    'p' => 'password',
48    't' => 'type',
49    'l' => 'limit',
50    'v' => 'verbose',
51));
52
53$opts['username'] = !empty($opts[1]) ? $opts[1] : $opts['user'];
54$action = $opts[0];
55
56$rcmail = rcube::get_instance(rcube::INIT_WITH_DB | rcube::INIT_WITH_PLUGINS);
57
58// Make --host argument optional where the default_host is a simple string
59if (empty($opts['host'])) {
60    $opts['host'] = imap_host();
61}
62
63// connect to database
64$db = $rcmail->get_dbh();
65$db->db_connect('w');
66if (!$db->is_connected() || $db->is_error())
67    die("No DB connection\n");
68
69ini_set('display_errors', 1);
70
71// All supported object types
72$all_types = array('contact','configuration','event','file','journal','note','task');
73
74/*
75 * Script controller
76 */
77switch (strtolower($action)) {
78
79/*
80 * Clear/expunge all cache records
81 */
82case 'expunge':
83    $folder_types = $opts['type'] ? explode(',', $opts['type']) : $all_types;
84    $folder_types_db = array_map(array($db, 'quote'), $folder_types);
85    $expire = strtotime(!empty($opts[2]) ? $opts[2] : 'now - 10 days');
86    $sql_where = "type IN (" . join(',', $folder_types_db) . ")";
87
88    if ($opts['username']) {
89        $sql_where .= ' AND resource LIKE ?';
90    }
91
92    $sql_query = "DELETE FROM %s WHERE folder_id IN (SELECT folder_id FROM kolab_folders WHERE $sql_where) AND created <= " . $db->quote(date('Y-m-d 00:00:00', $expire));
93    if ($opts['limit']) {
94        $sql_query .= ' LIMIT ' . intval($opts['limit']);
95    }
96    foreach ($folder_types as $type) {
97        $table_name = 'kolab_cache_' . $type;
98        $db->query(sprintf($sql_query, $table_name), resource_prefix($opts).'%');
99        echo $db->affected_rows() . " records deleted from '$table_name'\n";
100    }
101
102    $db->query("UPDATE kolab_folders SET ctag='' WHERE $sql_where", resource_prefix($opts).'%');
103    break;
104
105case 'clear':
106    $folder_types = $opts['type'] ? explode(',', $opts['type']) : $all_types;
107    $folder_types_db = array_map(array($db, 'quote'), $folder_types);
108
109    if ($opts['all']) {
110        $sql_query = "DELETE FROM kolab_folders WHERE 1";
111    }
112    else if ($opts['username']) {
113        $sql_query = "DELETE FROM kolab_folders WHERE type IN (" . join(',', $folder_types_db) . ") AND resource LIKE ?";
114    }
115
116    if ($sql_query) {
117        $db->query($sql_query, resource_prefix($opts).'%');
118        echo $db->affected_rows() . " records deleted from 'kolab_folders'\n";
119    }
120    break;
121
122
123/*
124 * Prewarm cache by synchronizing objects for the given user
125 */
126case 'prewarm':
127    // make sure libkolab classes are loaded
128    $rcmail->plugins->load_plugin('libkolab');
129
130    if (authenticate($opts)) {
131        $folder_types = $opts['type'] ? explode(',', $opts['type']) : $all_types;
132        foreach ($folder_types as $type) {
133            // sync every folder of the given type
134            foreach (kolab_storage::get_folders($type) as $folder) {
135                echo "Synching " . $folder->name . " ($type) ... ";
136                echo $folder->count($type) . "\n";
137
138                // also sync distribution lists in contact folders
139                if ($type == 'contact') {
140                    echo "Synching " . $folder->name . " (distribution-list) ... ";
141                    echo $folder->count('distribution-list') . "\n";
142                }
143            }
144        }
145    }
146    else
147        die("Authentication failed for " . $opts['user']);
148    break;
149
150/**
151 * Update the cache meta columns from the serialized/xml data
152 * (might be run after a schema update)
153 */
154case 'update':
155    // make sure libkolab classes are loaded
156    $rcmail->plugins->load_plugin('libkolab');
157
158    $folder_types = $opts['type'] ? explode(',', $opts['type']) : $all_types;
159    foreach ($folder_types as $type) {
160        $class = 'kolab_storage_cache_' . $type;
161        $sql_result = $db->query("SELECT folder_id FROM kolab_folders WHERE type=? AND synclock = 0", $type);
162        while ($sql_result && ($sql_arr = $db->fetch_assoc($sql_result))) {
163            $folder = new $class;
164            $folder->select_by_id($sql_arr['folder_id']);
165            echo "Updating " . $sql_arr['folder_id'] . " ($type) ";
166            foreach ($folder->select() as $object) {
167                $object['_formatobj']->to_array();  // load data
168                $folder->save($object['_msguid'], $object, $object['_msguid']);
169                echo ".";
170            }
171            echo "done.\n";
172        }
173    }
174    break;
175
176
177/*
178 * Unknown action => show usage
179 */
180default:
181    print_usage();
182    exit;
183}
184
185
186/**
187 * Compose cache resource URI prefix for the given user credentials
188 */
189function resource_prefix($opts)
190{
191    return 'imap://' . str_replace('%', '\\%', urlencode($opts['username'])) . '@' . $opts['host'] . '/';
192}
193
194
195/**
196 * Authenticate to the IMAP server with the given user credentials
197 */
198function authenticate(&$opts)
199{
200    global $rcmail;
201
202    // prompt for password
203    if (empty($opts['password']) && ($opts['username'] || $opts['user'])) {
204        $opts['password'] = prompt_silent("Password: ");
205    }
206
207    // simulate "login as" feature
208    if ($opts['user'] && $opts['user'] != $opts['username'])
209        $_POST['_loginas'] = $opts['username'];
210    else if (empty($opts['user']))
211        $opts['user'] = $opts['username'];
212
213    // let the kolab_auth plugin do its magic
214    $auth = $rcmail->plugins->exec_hook('authenticate', array(
215        'host' => trim($opts['host']),
216        'user' => trim($opts['user']),
217        'pass' => $opts['password'],
218        'cookiecheck' => false,
219        'valid' => !empty($opts['user']) && !empty($opts['host']),
220    ));
221
222    if ($auth['valid']) {
223        $storage = $rcmail->get_storage();
224        if ($storage->connect($auth['host'], $auth['user'], $auth['pass'], 143, false)) {
225            if ($opts['verbose'])
226                echo "IMAP login succeeded.\n";
227            if (($user = rcube_user::query($opts['username'], $auth['host'])) && $user->ID)
228                $rcmail->user = $user;
229        }
230        else
231            die("Login to IMAP server failed!\n");
232    }
233    else {
234        die("Invalid login credentials!\n");
235    }
236
237    return $auth['valid'];
238}
239
240function imap_host()
241{
242    global $rcmail;
243
244    $default_host = $rcmail->config->get('default_host');
245
246    if (is_array($default_host)) {
247        $key = key($default_host);
248        $imap_host = is_numeric($key) ? $default_host[$key] : $key;
249    }
250    else {
251        $imap_host = $default_host;
252    }
253
254    // strip protocol prefix
255    $uri = parse_url($imap_host);
256    if (!empty($uri['host'])) {
257        return $uri['host'];
258    }
259}
260