r2411 added deleteRow functionality and fixed a nasty bug in makeHrefProcess()
[racktables] / upgrade.php
1 <?php
2
3 $relnotes = array
4 (
5 '0.17.0' => "This release requires more options to secret.php. Add the " .
6 "following into inc/secret.php:<br><br>" .
7 "\$user_auth_src = 'database';<br>\$require_valid_user = TRUE;<br><br>" .
8 "(and adjust to your needs, if necessary)<br>" .
9 "Another change is the addition of support for file uploads. Files are stored<br>" .
10 "in the database. There are several settings in php.ini which you may need to modify:<br>" .
11 "<ul><li>file_uploads - needs to be On</li>" .
12 "<li>upload_max_filesize - max size for uploaded files</li>" .
13 "<li>post_max_size - max size of all form data submitted via POST (including files)</li></ul><br>" .
14 "User accounts used to have 'enabled' flag, which allowed individual blocking and<br>" .
15 "unblocking of each. This flag was dropped in favor of existing mean of access<br>" .
16 "setup (RackCode). An unconditional denying rule is automatically added into RackCode<br>" .
17 "for such blocked account, so the effective security policy remains the same.<br>",
18 );
19
20 // At the moment we assume, that for any two releases we can
21 // sequentally execute all batches, that separate them, and
22 // nothing will break. If this changes one day, the function
23 // below will have to generate smarter upgrade paths, while
24 // the upper layer will remain the same.
25 // Returning an empty array means that no upgrade is necessary.
26 // Returning NULL indicates an error.
27 function getDBUpgradePath ($v1, $v2)
28 {
29 $versionhistory = array
30 (
31 '0.16.4',
32 '0.16.5',
33 '0.16.6',
34 '0.17.0',
35 );
36 if (!in_array ($v1, $versionhistory) or !in_array ($v2, $versionhistory))
37 return NULL;
38 $skip = TRUE;
39 $path = NULL;
40 // Now collect all versions > $v1 and <= $v2
41 foreach ($versionhistory as $v)
42 {
43 if ($skip and $v == $v1)
44 {
45 $skip = FALSE;
46 $path = array();
47 continue;
48 }
49 if ($skip)
50 continue;
51 $path[] = $v;
52 if ($v == $v2)
53 break;
54 }
55 return $path;
56 }
57
58 // Upgrade batches are named exactly as the release where they first appear.
59 // That is simple, but seems sufficient for beginning.
60 function executeUpgradeBatch ($batchid)
61 {
62 $query = array();
63 global $dbxlink;
64 switch ($batchid)
65 {
66 case '0.16.5':
67 $query[] = "INSERT INTO `Config` (varname, varvalue, vartype, emptyok, is_hidden, description) VALUES ('IPV4_TREE_SHOW_USAGE','yes','string','no','no','Show address usage in IPv4 tree')";
68 $query[] = "update Config set varvalue = '0.16.5' where varname = 'DB_VERSION'";
69 break;
70 case '0.16.6':
71 $query[] = "update Config set varvalue = '0.16.6' where varname = 'DB_VERSION'";
72 break;
73 case '0.17.0':
74 // create tables for storing files (requires InnoDB support)
75 if (!isInnoDBSupported ())
76 {
77 showError ("Cannot upgrade because InnoDB tables are not supported by your MySQL server. See the README for details.", __FILE__);
78 die;
79 }
80 // Many dictionary changes were made... remove all dictvendor entries and install fresh.
81 // Take care not to erase locally added records. 0.16.x ends with max key 797
82 $query[] = 'DELETE FROM Dictionary WHERE ((chapter_no BETWEEN 11 AND 14) or (chapter_no BETWEEN 16 AND 19) ' .
83 'or (chapter_no BETWEEN 21 AND 24)) and dict_key <= 797';
84 $f = fopen ("install/init-dictvendors.sql", 'r');
85 if ($f === FALSE)
86 {
87 showError ("Failed to open install/init-dictvendors.sql for reading");
88 die;
89 }
90 $longq = '';
91 while (!feof ($f))
92 {
93 $line = fgets ($f);
94 if (ereg ('^--', $line))
95 continue;
96 $longq .= $line;
97 }
98 fclose ($f);
99 foreach (explode (";\n", $longq) as $dict_query)
100 {
101 if (empty ($dict_query))
102 continue;
103 $query[] = $dict_query;
104 }
105
106 // schema changes for file management
107 $query[] = "
108 CREATE TABLE `File` (
109 `id` int(10) unsigned NOT NULL auto_increment,
110 `name` char(255) NOT NULL,
111 `type` char(255) NOT NULL,
112 `size` int(10) unsigned NOT NULL,
113 `ctime` datetime NOT NULL,
114 `mtime` datetime NOT NULL,
115 `atime` datetime NOT NULL,
116 `contents` longblob NOT NULL,
117 `comment` text,
118 PRIMARY KEY (`id`)
119 ) ENGINE=InnoDB";
120 $query[] = "
121 CREATE TABLE `FileLink` (
122 `id` int(10) unsigned NOT NULL auto_increment,
123 `file_id` int(10) unsigned NOT NULL,
124 `entity_type` enum('ipv4net','ipv4rspool','ipv4vs','object','rack','user') NOT NULL default 'object',
125 `entity_id` int(10) NOT NULL,
126 PRIMARY KEY (`id`),
127 UNIQUE KEY `FileLink-unique` (`file_id`,`entity_type`,`entity_id`),
128 KEY `FileLink-file_id` (`file_id`),
129 CONSTRAINT `FileLink-File_fkey` FOREIGN KEY (`file_id`) REFERENCES `File` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
130 ) ENGINE=InnoDB";
131 $query[] = "ALTER TABLE TagStorage MODIFY COLUMN target_realm enum('file','ipv4net','ipv4rspool','ipv4vs','object','rack','user') NOT NULL default 'object'";
132
133 // add network security as an object type
134 $query[] = "INSERT INTO `Chapter` (`chapter_no`, `sticky`, `chapter_name`) VALUES (24,'no','network security models')";
135 $query[] = "INSERT INTO `AttributeMap` (`objtype_id`, `attr_id`, `chapter_no`) VALUES (798,1,0)";
136 $query[] = "INSERT INTO `AttributeMap` (`objtype_id`, `attr_id`, `chapter_no`) VALUES (798,2,24)";
137 $query[] = "INSERT INTO `AttributeMap` (`objtype_id`, `attr_id`, `chapter_no`) VALUES (798,3,0)";
138 $query[] = "INSERT INTO `AttributeMap` (`objtype_id`, `attr_id`, `chapter_no`) VALUES (798,5,0)";
139 $query[] = "INSERT INTO `AttributeMap` (`objtype_id`, `attr_id`, `chapter_no`) VALUES (798,14,0)";
140 $query[] = "INSERT INTO `AttributeMap` (`objtype_id`, `attr_id`, `chapter_no`) VALUES (798,16,0)";
141 $query[] = "INSERT INTO `AttributeMap` (`objtype_id`, `attr_id`, `chapter_no`) VALUES (798,17,0)";
142 $query[] = "INSERT INTO `AttributeMap` (`objtype_id`, `attr_id`, `chapter_no`) VALUES (798,18,0)";
143 $query[] = "INSERT INTO `AttributeMap` (`objtype_id`, `attr_id`, `chapter_no`) VALUES (798,20,0)";
144 $query[] = "INSERT INTO `AttributeMap` (`objtype_id`, `attr_id`, `chapter_no`) VALUES (798,21,0)";
145 $query[] = "INSERT INTO `AttributeMap` (`objtype_id`, `attr_id`, `chapter_no`) VALUES (798,22,0)";
146 $query[] = "INSERT INTO `AttributeMap` (`objtype_id`, `attr_id`, `chapter_no`) VALUES (798,24,0)";
147 $query[] = "UPDATE Dictionary SET dict_value = 'Network switch' WHERE dict_key = 8";
148 $query[] = 'alter table IPBonds rename to IPv4Allocation';
149 $query[] = 'alter table PortForwarding rename to IPv4NAT';
150 $query[] = 'alter table IPRanges rename to IPv4Network';
151 $query[] = 'alter table IPAddress rename to IPv4Address';
152 $query[] = 'alter table IPLoadBalancer rename to IPv4LB';
153 $query[] = 'alter table IPRSPool rename to IPv4RSPool';
154 $query[] = 'alter table IPRealServer rename to IPv4RS';
155 $query[] = 'alter table IPVirtualService rename to IPv4VS';
156 $query[] = "alter table TagStorage change column target_realm entity_realm enum('file','ipv4net','ipv4vs','ipv4rspool','object','rack','user') NOT NULL default 'object'";
157 $query[] = 'alter table TagStorage change column target_id entity_id int(10) unsigned NOT NULL';
158 $query[] = 'alter table TagStorage drop key entity_tag';
159 $query[] = 'alter table TagStorage drop key target_id';
160 $query[] = 'alter table TagStorage add UNIQUE KEY `entity_tag` (`entity_realm`,`entity_id`,`tag_id`)';
161 $query[] = 'alter table TagStorage add KEY `entity_id` (`entity_id`)';
162 $query[] = "delete from Config where varname = 'USER_AUTH_SRC' limit 1";
163 $query[] = "INSERT INTO `Config` (varname, varvalue, vartype, emptyok, is_hidden, description) VALUES ('PREVIEW_TEXT_MAXCHARS','10240','uint','yes','no','Max chars for text file preview')";
164 $query[] = "INSERT INTO `Config` (varname, varvalue, vartype, emptyok, is_hidden, description) VALUES ('PREVIEW_TEXT_ROWS','25','uint','yes','no','Rows for text file preview')";
165 $query[] = "INSERT INTO `Config` (varname, varvalue, vartype, emptyok, is_hidden, description) VALUES ('PREVIEW_TEXT_COLS','80','uint','yes','no','Columns for text file preview')";
166 $query[] = "INSERT INTO `Config` (varname, varvalue, vartype, emptyok, is_hidden, description) VALUES ('PREVIEW_IMAGE_MAXPXS','320','uint','yes','no','Max pixels per axis for image file preview')";
167 $query[] = "alter table TagTree add column valid_realm set('file','ipv4net','ipv4vs','ipv4rspool','object','rack','user') not null default 'file,ipv4net,ipv4vs,ipv4rspool,object,rack,user' after parent_id";
168 $result = $dbxlink->query ("select user_id, user_name, user_realname from UserAccount where user_enabled = 'no'");
169 while ($row = $result->fetch (PDO::FETCH_ASSOC))
170 $query[] = "update Script set script_text = concat('deny {\$userid_${row['user_id']}} # ${row['user_name']} (${row['user_realname']})\n', script_text) where script_name = 'RackCode'";
171 $query[] = "update Script set script_text = NULL where script_name = 'RackCodeCache'";
172 unset ($result);
173 $query[] = "alter table UserAccount drop column user_enabled";
174 $query[] = "UPDATE Config SET varvalue = '0.17.0' WHERE varname = 'DB_VERSION'";
175 $query[] = "alter table Chapter change chapter_no id int(10) unsigned NOT NULL auto_increment";
176 $query[] = "alter table Chapter change chapter_name name char(128) NOT NULL";
177 $query[] = "alter table Chapter drop key chapter_name";
178 $query[] = "alter table Chapter add UNIQUE KEY name (name)";
179 $query[] = "alter table Attribute change attr_id id int(10) unsigned NOT NULL auto_increment";
180 $query[] = "alter table Attribute change attr_type type enum('string','uint','float','dict') default NULL";
181 $query[] = "alter table Attribute change attr_name name char(64) default NULL";
182 $query[] = "alter table Attribute drop key attr_name";
183 $query[] = "alter table Attribute add UNIQUE KEY name (name)";
184 $query[] = "alter table AttributeMap change chapter_no chapter_id int(10) unsigned NOT NULL";
185 $query[] = "alter table Dictionary change chapter_no chapter_id int(10) unsigned NOT NULL";
186 $query[] = "CREATE TABLE RackRow ( id int(10) unsigned NOT NULL auto_increment, name char(255) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM";
187
188 $result = $dbxlink->query ("select dict_key, dict_value from Dictionary where chapter_no = 3");
189 while($row = $result->fetch(PDO::FETCH_NUM))
190 {
191 $query[] = "insert into RackRow set id=${row[0]}, name='${row[1]}'";
192 }
193 $query[] = "delete from Dictionary where chapter_no = 3";
194
195 break;
196 default:
197 showError ("executeUpgradeBatch () failed, because batch '${batchid}' isn't defined", __FILE__);
198 die;
199 break;
200 }
201 $failures = array();
202 echo "<tr><th>Executing batch '${batchid}'</th><td>";
203 foreach ($query as $q)
204 {
205 $result = $dbxlink->query ($q);
206 if ($result == NULL)
207 {
208 $errorInfo = $dbxlink->errorInfo();
209 $failures[] = array ($q, $errorInfo[2]);
210 }
211 }
212 if (!count ($failures))
213 echo "<strong><font color=green>done</font></strong>";
214 else
215 {
216 echo "<strong><font color=red>The following queries failed:</font></strong><br><pre>";
217 foreach ($failures as $f)
218 {
219 list ($q, $i) = $f;
220 echo "${q} -- ${i}\n";
221 }
222 echo "</pre>";
223 }
224 echo '</td></tr>';
225 }
226
227 // ******************************************************************
228 //
229 // Execution starts here
230 //
231 // ******************************************************************
232
233 $root = (empty($_SERVER['HTTPS'])?'http':'https').
234 '://'.
235 (isset($_SERVER['HTTP_HOST'])?$_SERVER['HTTP_HOST']:($_SERVER['SERVER_NAME'].($_SERVER['SERVER_PORT']=='80'?'':$_SERVER['SERVER_PORT']))).
236 dirname($_SERVER['PHP_SELF']);
237 if (substr ($root, -1) != '/')
238 $root .= '/';
239
240 // The below will be necessary as long as we rely on showError()
241 require_once 'inc/interface.php';
242
243 require_once 'inc/config.php';
244 require_once 'inc/database.php';
245 if (file_exists ('inc/secret.php'))
246 require_once 'inc/secret.php';
247 else
248 die ("Database connection parameters are read from inc/secret.php file, " .
249 "which cannot be found.\nCopy provided inc/secret-sample.php to " .
250 "inc/secret.php and modify to your setup.\n\nThen reload the page.");
251
252 try
253 {
254 $dbxlink = new PDO ($pdo_dsn, $db_username, $db_password);
255 }
256 catch (PDOException $e)
257 {
258 die ("Database connection failed:\n\n" . $e->getMessage());
259 }
260
261 // Now we need to be sure that the current user is the administrator.
262 // The rest doesn't matter within this context.
263
264 function authenticate_admin ($username, $password)
265 {
266 global $dbxlink;
267 $hash = hash (PASSWORD_HASH, $password);
268 $query = "select count(*) from UserAccount where user_id = 1 and user_name = '${username}' and user_password_hash = '${hash}'";
269 if (($result = $dbxlink->query ($query)) == NULL)
270 die ('SQL query failed in ' . __FUNCTION__);
271 $rows = $result->fetchAll (PDO::FETCH_NUM);
272 return $rows[0][0] == 1;
273 }
274
275 switch ($user_auth_src)
276 {
277 case 'database':
278 case 'ldap': // authenticate against DB as well
279 if
280 (
281 !isset ($_SERVER['PHP_AUTH_USER']) or
282 !strlen ($_SERVER['PHP_AUTH_USER']) or
283 !isset ($_SERVER['PHP_AUTH_PW']) or
284 !strlen ($_SERVER['PHP_AUTH_PW']) or
285 !authenticate_admin (escapeString ($_SERVER['PHP_AUTH_USER']), escapeString ($_SERVER['PHP_AUTH_PW']))
286 )
287 {
288 header ('WWW-Authenticate: Basic realm="RackTables upgrade"');
289 header ('HTTP/1.0 401 Unauthorized');
290 showError ('You must be authenticated as an administrator to complete the upgrade.', __FILE__);
291 die;
292 }
293 break; // cleared
294 case 'httpd':
295 if
296 (
297 !isset ($_SERVER['REMOTE_USER']) or
298 !strlen ($_SERVER['REMOTE_USER'])
299 )
300 {
301 showError ('System misconfiguration. The web-server didn\'t authenticate the user, although ought to do.');
302 die;
303 }
304 break; // cleared
305 default:
306 showError ('authentication source misconfiguration', __FILE__);
307 die;
308 }
309
310 $dbver = getDatabaseVersion();
311 echo '<table border=1>';
312 echo "<tr><th>Current status</th><td>Data version: ${dbver}<br>Code version: " . CODE_VERSION . "</td></tr>\n";
313
314 $path = getDBUpgradePath ($dbver, CODE_VERSION);
315 if ($path === NULL)
316 {
317 echo "<tr><th>Upgrade path</th><td><font color=red>not found</font></td></tr>\n";
318 echo "<tr><th>Summary</th><td>Check README for more information.</td></tr>\n";
319 }
320 else
321 {
322 if (!count ($path))
323 echo "<tr><th>Summary</th><td>Come back later.</td></tr>\n";
324 else
325 {
326 echo "<tr><th>Upgrade path</th><td>${dbver} &rarr; " . implode (' &rarr; ', $path) . "</td></tr>\n";
327 foreach ($path as $batchid)
328 {
329 executeUpgradeBatch ($batchid);
330 if (isset ($relnotes[$batchid]))
331 echo "<tr><th>Release notes for ${batchid}</th><td>" . $relnotes[$batchid] . "</td></tr>\n";
332 }
333 echo "<tr><th>Summary</th><td>Upgrade complete, it is Ok to <a href='${root}'>enter</a> the system.</td></tr>\n";
334 }
335 }
336 echo '</table>';
337
338 ?>