1<?php 2 3/* This script is in the public domamin. Please give it out freely to help the spread of atheme, and assist others in your position. */ 4/* 5convert.php is a work in progress, but it has successfully converted production 6databases. It is to be considered a release candidate. It reads an ircservices 7XML database export from STDIN and prints a atheme database to STDOUT. 8 9NOTE: The use of a custom password encryption module (included) is required! 10 It (ircs_crypto_trans.c) replaces the current ircservices.c in modules/crypto/. 11 12The password encryption module includes code to allow a transition to the 13encryption module of your choice. Uncommenting the following line will 14cause the module to execute the listed program every time a password is hashed. 15The program recieves two lines on stdin, newline separated. They are 16the unencrypted password followed by the currently recorded password. Your 17program may encrypt the unencrypted password however it chooses and store it 18with the pre-encrypted version. After all nicks have been identified to 19(after the expiry time for nicks), use this map to replace the passwords stored 20in the database. The password location in the database is shown below. 21 22NOTE: There is no harm in leaving some few ircservices passwords in the 23 database after switching to POSIX (others not tested), if you so choose. 24 Those nicks will simply not be able to log in, and need their password 25 reset, such as by the use of SENDPASS. 26 27Program Activation Line: 28// #define PW_TRANSITION_LOG "./pwtransition.sh" 29 30Password Location: 31MU nick CRYPTED_PW ... 32 33NOTE: if channels for some reason cannot be converted, errors.log will be populated with 34information about what channels & why. 35 36Bugs & Patches to Jason@WinSE.ath.cx 37*/ 38 39$nicks = array(); 40$chans = array(); 41 42 43$raw_lines = array(); 44while (!feof(STDIN)) 45 $raw_lines[] = html_entity_decode(fgets(STDIN)); 46 47 48/* Read in and parse IRCservices 49 */ 50$category = ''; 51foreach($raw_lines as $raw_line) 52{ 53 $line = trim($raw_line); 54 55 if ($line == '<nickgroupinfo>') 56 $category = 'nickgroupinfo'; 57 if ($line == '<nickinfo>') 58 $category = 'nickinfo'; 59 if ($line == '<channelinfo>') 60 $category = 'channelinfo'; 61 if ($line == '</'. $category .'>') 62 $category = ''; 63 64 switch ($category) 65 { 66 case 'nickgroupinfo': read_nickgroupinfo($line); break; 67 case 'nickinfo': read_nickinfo($line); break; 68 case 'channelinfo': read_channelinfo($line); break; 69 } 70} 71 72//print_r($nicks); 73//print_r($chans); 74 75/* Print out atheme 76 */ 77$MUout = 0; 78$MCout = 0; 79$CAout = 0; 80 81printf("DBV 6\n"); 82printf("CF +vVoOtsriRfhHAb\n"); 83 84// Nicks 85foreach ($nicks as $id => $data) 86{ 87 $first_reg = 0; 88 $last_login = 0; 89 $nick_vhost = ''; 90 $nick_rhost = ''; 91 $nick_noexpire = false; 92 foreach ($data['nicks'] as $nick) 93 { 94 $first_reg = ($first_reg == 0 ? $nick['time_registered'] : min($first_reg,$nick['time_registered'])); 95 if ($last_login < $nick['last_seen']) 96 { 97 $last_login = $nick['last_seen']; 98 $nick_rhost = $nick['realhost']; 99 $nick_vhost = $nick['fakehost']; 100 } 101 $nick_noexpire = $nick_noexpire || $nick['noexpire']; 102 } 103 $password = substr(bin2hex(de_ircs($data['pass'],false)),0,16); 104 if (!$password) 105 $password = "nopass"; 106 printf("MU %s %s %s %u %u 0 0 0 %u\n", 107 $data['mainnick'], 108 $password, // My god, what were they smoking?! 109 // Atheme's IRCservices compatibility module had to be edited. 110 // IRCservices database dump gives custom-encoded md5()+remainder_of_buffer 111 $data['email'] ? $data['email'] : 'nomail', 112 $first_reg, 113 $last_login, 114 (1280 | ( $nick_noexpire ? 0x1 : 0 ) | ( $data['hide-email'] ? 0x10 : 0 )) & ~0x100 115 ); 116 $MUout++; 117 118 printf("MD U %s private:host:vhost %s\n",$data['mainnick'],$nick_vhost); 119 printf("MD U %s private:host:actual %s\n",$data['mainnick'],$nick_rhost); 120 if ($data['enforce']) 121 printf("MD U %s private:doenforce 1\n",$data['mainnick']); 122 123 foreach ($data['nicks'] as $nick) 124 printf("MN %s %s %u %u\n", 125 $data['mainnick'], 126 $nick['nick'], 127 $nick['time_registered'], 128 $nick['last_seen'] 129 ); 130 131 if (isset($data['access_list'])) 132 foreach ($data['access_list'] as $entry) 133 printf("AC %s %s\n", $data['mainnick'], $entry); 134 135 if (isset($data['memos'])) 136 foreach ($data['memos'] as $memo) 137 printf("ME %s %s %u %u %s\n", 138 $data['mainnick'], 139 $memo['from'], 140 $memo['time'], 141 $memo['read'], 142 $memo['text'] 143 ); 144 145 $sAjoin = ""; 146 if (isset($data['ajoin'])) 147 foreach ($data['ajoin'] as $sChan) 148 $sAjoin .= $sChan . ","; 149 150 if (!empty($sAjoin)) 151 { 152 printf("MD U %s private:autojoin %s\n", $data['mainnick'], $sAjoin); 153 } 154} 155 156// Chans 157foreach ($chans as $name => $data) 158{ 159 if (empty($name) || empty($nicks[$data['founder']]['mainnick']) || 160 empty($data['time_registered']) || empty($data['time_used']) || 161 empty($data['mlock_on']) || empty($data['mlock_off']) || 162 empty($data['mlock_limit'])) 163 { 164 $sErrors[] = "*** WARNING: potentially skipping entry for $name"; 165 166 $bFatal = false; 167 168 if (empty($name)) 169 { 170 $sErrors[] = "Name is empty."; // should never happen 171 $bFatal = true; 172 } 173 if (empty($nicks[$data['founder']]['mainnick'])) 174 { 175 $sErrors[] = "Main nick is empty"; // forbidden channels can get this 176 $bFatal = true; 177 } 178 if (empty($data['time_registered'])) 179 { 180 $sErrors[] = "Time registered is empty"; 181 $bFatal = true; 182 } 183 if (empty($data['time_used'])) 184 { 185 $sErrors[] = "Time used is empty"; 186 $bFatal = true; 187 } 188 if (empty($data['mlock_on'])) 189 { 190 $sErrors[] = "MLOCK on is empty"; 191 $data['mlock_on'] = 0; 192 } 193 if (empty($data['mlock_off'])) 194 { 195 $sErrors[] = "MLOCK off is empty"; 196 $data['mlock_off'] = 0; 197 } 198 if (empty($data['mlock_limit'])) 199 { 200 $sErrors[] = "MLOCK limit is empty"; 201 $data['mlock_limit'] = 0; 202 } 203 204 if ($bFatal) 205 { 206 $sErrors[] = "Cannot write this channel, ignoring"; 207 continue; 208 } 209 } 210 211 printf("MC %s 0 %s %u %u 64 %u %u %u\n", 212 $name, 213 $nicks[$data['founder']]['mainnick'], 214 $data['time_registered'], 215 $data['time_used'], 216 $data['mlock_on'], 217 $data['mlock_off'], 218 $data['mlock_limit'] 219 ); 220 $MCout++; 221 222 if (isset($data['topic'])) 223 { 224 printf("MD C %s private:topic:setter %s\n", $name, ($data['topic_by'] == '<unknown>' ? 'IRC' : $data['topic_by']) ); 225 printf("MD C %s private:topic:text %s\n", $name, $data['topic']); 226 printf("MD C %s private:topic:ts %u\n", $name, $data['topic_time']); 227 } 228 229 foreach ($data['access'] as $id => $flags) 230 { 231 if ($id{0} == '+') 232 $id = $nicks[substr($id,1)]['mainnick']; 233 else 234 $id = substr($id,1); 235 236 printf("CA %s %s %s %u\n", 237 $name, 238 $id, 239 $flags, 240 0 // Timestamp. Hopefully, 0 is good enough. Not sure how exactly atheme's compatibility code works. 241 ); 242 $CAout++; 243 } 244 245 if (isset($data['akicks'])) 246 foreach ($data['akicks'] as $akick) 247 { 248 printf("CA %s %s +b %u\n", 249 $name, 250 $akick['mask'], 251 $akick['set'] 252 ); 253 $CAout++; 254 255 printf("MD A %s:%s reason %s\n", 256 $name, 257 $akick['mask'], 258 $akick['reason'] 259 ); 260 } 261} 262 263printf("DE %d %d %d 0", $MUout, $MCout, $CAout ); 264 265 266 267file_put_contents("errors.log", implode("\n", $sErrors)); 268 269 270 271 272/* Helper functions 273 */ 274function read_nickgroupinfo($line) 275{ 276 global $nicks; 277 static $id = 0; 278 static $section = ''; 279 static $memo_num = 0; 280 281 if ($line == '<nickgroupinfo>') 282 $id = 0; 283 284 if ($line == '<memo>') 285 $section = 'memo'; 286 if (substr($line,0,7) == '<nicks ') 287 $section = 'nicks'; 288 if (substr($line,0,8) == '<access ') 289 $section = 'access'; 290 if (substr($line, 0, 7) == '<ajoin ') 291 $section = 'ajoin'; 292 if ($line == '</'. $section .'>') 293 $section = ''; 294 295 if ($section == '' && preg_match('~^<([a-z_]+)(?: [^>]+)?>(.*)</\1>$~', $line, &$parts) > 0) 296// XXX old ircservices. if ($section == '' && preg_match('~^<([a-z_]+)>(.*)</\1>$~',$line,&$parts) > 0) 297 { 298 if ($parts[1] == 'id') 299 $id = $parts[2]; 300 301 if ($parts[1] == 'pass') 302 $nicks[$id]['pass'] = $parts[2]; 303 304 if ($parts[1] == 'email') 305 $nicks[$id]['email'] = $parts[2]; 306 307 if ($parts[1] == 'flags') 308 { 309 $nicks[$id]['hide-email'] = $parts[2] & 0x80; 310 $nicks[$id]['enforce'] = $parts[2] & 0x1; 311 } 312 } 313 elseif ($section == 'memo' && preg_match('~^<([a-z_]+)>(.*)</\1>$~',$line,&$parts) > 0) 314 { 315 if ($parts[1] == 'number') 316 $memo_num = $parts[2]; 317 318 if ($parts[1] == 'flags') 319 $nicks[$id]['memos'][$memo_num]['read'] = ( $parts[2] == 0 ? true : false ); 320 321 if ($parts[1] == 'sender') 322 $nicks[$id]['memos'][$memo_num]['from'] = $parts[2]; 323 324 if ($parts[1] == 'time') 325 $nicks[$id]['memos'][$memo_num]['time'] = $parts[2]; 326 327 if ($parts[1] == 'text') 328 $nicks[$id]['memos'][$memo_num]['text'] = de_ircs($parts[2],true); 329 } 330 elseif ($section == 'nicks' && preg_match('~^<([a-z_-]+)>(.*)</\1>$~',$line,&$parts) > 0) 331 { 332 if ($parts[1] == 'array-element' && !isset($nicks[$id]['mainnick'])) 333 $nicks[$id]['mainnick'] = $parts[2]; 334 } 335 elseif ($section == 'ajoin' && preg_match('~^<([a-z_-]+)>(.*)</\1>$~',$line,&$parts) > 0) 336 { 337 if ($parts[1] == 'array-element') 338 $nicks[$id]['ajoin'][] = $parts[2]; 339 } 340 elseif ($section == 'access' && preg_match('~^<([a-z_-]+)>(.*)</\1>$~',$line,&$parts) > 0) 341 { 342 if ($parts[1] == 'array-element') 343 $nicks[$id]['access_list'][] = $parts[2]; 344 } 345} 346 347function read_nickinfo($line) 348{ 349 global $nicks; 350 static $data = array(); 351 352 if ($line == '<nickinfo>') 353 $data = array(); 354 355 if (preg_match('~^<([a-z_]+)>(.*)</\1>$~',$line,&$parts) > 0) 356 { 357 if ($parts[1] == 'nick') 358 $data['nick'] = $parts[2]; 359 360 if ($parts[1] == 'last_usermask') 361 $data['fakehost'] = $parts[2]; 362 363 if ($parts[1] == 'last_realmask') 364 $data['realhost'] = $parts[2]; 365 366 if ($parts[1] == 'time_registered') 367 $data['time_registered'] = $parts[2]; 368 369 if ($parts[1] == 'last_seen') 370 $data['last_seen'] = $parts[2]; 371 372 if ($parts[1] == 'status') 373 $data['noexpire'] = ( $parts[2] == 4 ? true : false ); 374 375 if ($parts[1] == 'nickgroup') 376 { 377 // Seriously... 378 if ($parts[2] == 0) 379 return; 380 381 // Now that we have an id to associate it with, commit. 382 $nickid = count($nicks[$parts[2]][nicks]); 383 foreach($data as $type => $info) 384 $nicks[$parts[2]][nicks][$nickid][$type] = $info; 385 } 386 } 387} 388 389function read_channelinfo($line) 390{ 391 global $chans; 392 static $chan = ''; 393 static $levels = array(); 394 static $akick = array(); 395 static $acc_who = 0; 396 static $section = ''; 397 static $mode_map = array( 398 'i'=>0x00000001, 'l'=>0x00000004, 'm'=>0x00000008, 'n'=>0x00000010, 'p'=>0x00000040, 399 's'=>0x00000080, 't'=>0x00000100, 'c'=>0x00001000, 'M'=>0x00002000, 'R'=>0x00004000, 400 'O'=>0x00008000, 'A'=>0x00010000, 'Q'=>0x00020000, 'S'=>0x00040000, 'K'=>0x00080000, 401 'V'=>0x00100000, 'C'=>0x00200000, 'u'=>0x00400000, 'z'=>0x00800000, 'N'=>0x01000000, 402 'G'=>0x04000000 403 ); 404 // Above mode_map is for UnrealIRCd. 405 406 407 if ($line == '<channelinfo>') 408 { 409 $levels = array('b'=>-100, 410 'A'=>0, 411 'v'=>30, 'V'=>30, 412 'h'=>40, 'H'=>40, 'r'=>40, 't'=>40, 413 'o'=>50, 'O'=>50, 'i'=>50, 414 'f'=>100,'R'=>100, 415 's'=>1000 416 ); 417 } 418 419 if ($line == '<levels>') 420 $section = 'levels'; 421 if ($line == '<chanaccess>') 422 $section = 'chanaccess'; 423 if ($line == '<akick>') 424 $section = 'akick'; 425 if ($line == '</'. $section .'>') 426 $section = ''; 427 428 if ($section == '' && preg_match('~^<([a-z_]+)>(.*)</\1>$~',$line,&$parts) > 0) 429 { 430 if ($parts[1] == 'name') 431 $chan = $parts[2]; 432 433 if ($parts[1] == 'founder') 434 { 435 $chans[$chan]['founder'] = $parts[2]; 436 $chans[$chan]['access']['+'.$parts[2]] = '+voOtsriRfhA'; 437 } 438 439 if ($parts[1] == 'time_registered') 440 $chans[$chan]['time_registered'] = $parts[2]; 441 442 if ($parts[1] == 'last_used') 443 $chans[$chan]['time_used'] = $parts[2]; 444 445 if ($parts[1] == 'last_topic') 446 $chans[$chan]['topic'] = de_ircs($parts[2],true); // Some control codes can segfault atheme 447 448 if ($parts[1] == 'last_topic_setter') 449 $chans[$chan]['topic_by'] = de_ircs($parts[2],true); 450 451 if ($parts[1] == 'last_topic_time') 452 $chans[$chan]['topic_time'] = $parts[2]; 453 454 if ($parts[1] == 'mlock_on') 455 { 456 $chans[$chan]['mlock_on'] = 0; 457 for ($i = 0; $i < strlen($parts[2]); $i++) 458 { 459 if ($parts[2]{$i} == ' ') 460 break; 461 if (array_key_exists($parts[2]{$i}, $mode_map)) 462 $chans[$chan]['mlock_on'] = $chans[$chan]['mlock_on'] | $mode_map[$parts[2]{$i}]; 463 } 464 } 465 466 if ($parts[1] == 'mlock_off') 467 { 468 $chans[$chan]['mlock_off'] = 0; 469 for ($i = 0; $i < strlen($parts[2]); $i++) 470 { 471 if ($parts[2]{$i} == ' ') 472 break; 473 if (array_key_exists($parts[2]{$i}, $mode_map)) 474 $chans[$chan]['mlock_off'] = $chans[$chan]['mlock_off'] | $mode_map[$parts[2]{$i}]; 475 } 476 } 477 478 if ($parts[1] == 'mlock_limit') 479 $chans[$chan]['mlock_limit'] = $parts[2]; 480 } 481 elseif ($section == 'levels' && preg_match('~.*<([a-zA-Z_]+)>(.+)</\1>.*~',$line,&$parts) > 0) 482 { 483 if ($parts[1] == 'CA_INVITE') $levels['i'] = ($parts[2] == -1000 ? 1000 : $parts[2]); 484 if ($parts[1] == 'CA_SET') $levels['s'] = ($parts[2] == -1000 ? 1000 : $parts[2]); 485 if ($parts[1] == 'CA_AUTOOP') $levels['O'] = ($parts[2] == -1000 ? 1000 : $parts[2]); 486 if ($parts[1] == 'CA_AUTOHALFOP') $levels['H'] = ($parts[2] == -1000 ? 1000 : $parts[2]); 487 if ($parts[1] == 'CA_AUTOVOICE') $levels['V'] = ($parts[2] == -1000 ? 1000 : $parts[2]); 488 if ($parts[1] == 'CA_AUTOPROTECT') $levels['a'] = ($parts[2] == -1000 ? 1000 : $parts[2]); 489 if ($parts[1] == 'CA_OPDEOP') $levels['o'] = ($parts[2] == -1000 ? 1000 : $parts[2]); 490 if ($parts[1] == 'CA_HALFOP') { $levels['h'] = ($parts[2] == -1000 ? 1000 : $parts[2]); 491 $levels['t'] = ($parts[2] == -1000 ? 1000 : $parts[2]); 492 } 493 if ($parts[1] == 'CA_VOICE') $levels['v'] = ($parts[2] == -1000 ? 1000 : $parts[2]); 494 if ($parts[1] == 'CA_ACCESS_LIST') $levels['A'] = ($parts[2] == -1000 ? 1000 : $parts[2]); 495 if ($parts[1] == 'CA_ACCESS_CHANGE') $levels['f'] = ($parts[2] == -1000 ? 1000 : $parts[2]); 496 if ($parts[1] == 'CA_CLEAR') $levels['R'] = ($parts[2] == -1000 ? 1000 : $parts[2]); 497 if ($parts[1] == 'CA_NOJOIN') $levels['b'] = $parts[2]; 498 } 499 elseif ($line == '</levels>') 500 { 501 $acc_anon = '+'; 502 foreach($levels as $flag => $level) 503 { 504 if ($flag == 'b' && 0 <= $level) 505 $acc_anon .= $flag; 506 elseif ($flag != 'b' && 0 >= $level) 507 $acc_anon .= $flag; 508 } 509 if ($acc_anon != '+') 510 $chans[$chan]['access']['-*!*@*'] = $acc_anon; 511 } 512 elseif ($section == 'chanaccess' && preg_match('~^<([a-z_]+)>(.*)</\1>$~',$line,&$parts) > 0) 513 { 514 if ($parts[1] == 'nickgroup') 515 $acc_who = '+' . $parts[2]; 516 if ($parts[1] == 'level' && $acc_who != 0) 517 { 518 $chans[$chan]['access'][$acc_who] = '+'; 519 foreach($levels as $flag => $level) 520 { 521 if ($flag == 'b' && $parts[2] <= $level) 522 $chans[$chan]['access'][$acc_who] .= $flag; 523 elseif ($flag != 'b' && $parts[2] >= $level) 524 $chans[$chan]['access'][$acc_who] .= $flag; 525 } 526 } 527 } 528 elseif ($section == 'akick' && preg_match('~^<([a-z_]+)>(.*)</\1>$~',$line,&$parts) > 0) 529 { 530 if ($parts[1] == 'mask') 531 $akick['mask'] = $parts[2]; 532 if ($parts[1] == 'reason') 533 $akick['reason'] = $parts[2]; 534 if ($parts[1] == 'who') 535 $akick['by'] = $parts[2]; 536 if ($parts[1] == 'set') 537 { 538 $akick['set'] = $parts[2]; 539 if (isset($akick['mask'])) // Sigh. Empty akicks! Woo! 540 $chans[$chan]['akicks'][] = $akick; 541 $akick = array(); 542 } 543 } 544} 545 546function de_ircs($in,$safe) 547{ 548 $in_escape = false; 549 $buf = ''; 550 $out = ''; 551 for ($i = 0; $i < strlen($in); $i++) 552 { 553 if ($in{$i} == '&') 554 { 555 $buf = ''; 556 $in_escape = true; 557 } 558 elseif ($in_escape) 559 { 560 if ($in{$i} == ';') 561 { 562 $in_escape = false; 563 switch ($buf{0}) 564 { 565 case 'l': $out .= '<'; break; 566 case 'g': $out .= '>'; break; 567 case 'a': $out .= '&'; break; 568 case '#': 569 if ($safe) 570 switch (substr($buf,1)) 571 { 572 case 3: // color 573 case 2: // bold 574 case 31: // underline 575 case 22: // reverse 576 $out .= chr(substr($buf,1)); 577 } 578 else 579 $out .= chr(substr($buf,1)); 580 break; 581 } 582 $in_escape = false; 583 } 584 else 585 $buf .= $in{$i}; 586 } 587 else 588 $out .= $in{$i}; 589 } 590 return $out; 591} 592 593?> 594