LogChanges
[squirrelmail.git] / plugins / squirrelspell / sqspell_functions.php
CommitLineData
849bdf42 1<?php
15e6162e 2/**
d112ed5a 3 * sqspell_functions.php
4 * ----------------------
5 * All SquirrelSpell-wide functions are in this file.
15e6162e 6 *
76911253 7 * Copyright (c) 1999-2003 The SquirrelMail development team
15e6162e 8 * Licensed under the GNU GPL. For full terms see the file COPYING.
9 *
10 * $Id$
d112ed5a 11 *
12 * @author Konstantin Riabitsev <icon@duke.edu> ($Author$)
13 * @version $Date$
15e6162e 14 */
d112ed5a 15
16/**
17 * This function is the GUI wrapper for the options page. SquirrelSpell
18 * uses it for creating all Options pages.
19 *
20 * @param $title The title of the page to display
21 * @param $scriptsrc This is used to link a file.js into the
22 * <script src="file.js"></script> format. This
23 * allows to separate javascript from the rest of the
24 * plugin and place it into the js/ directory.
25 * @param $body The body of the message to display.
26 * @return void
27 */
28function sqspell_makePage($title, $scriptsrc, $body){
8a9f9d09 29 global $color, $SQSPELL_VERSION;
30
fb694681 31 if ( !check_php_version(4,1) ) {
8a9f9d09 32 global $_GET;
33 }
34 if (isset($_GET['MOD'])) {
35 $MOD = $_GET['MOD'];
36 }
37 else {
38 $MOD = 'options_main';
39 }
40
d112ed5a 41 displayPageHeader($color, 'None');
42 echo "&nbsp;<br>\n";
43 /**
44 * Check if we need to link in a script.
45 */
46 if($scriptsrc) {
47 echo "<script type=\"text/javascript\" src=\"js/$scriptsrc\"></script>\n";
48 }
484ed7c9 49 echo html_tag( 'table', '', 'center', '', 'width="95%" border="0" cellpadding="2" cellspacing="0"' ) . "\n"
50 . html_tag( 'tr', "\n" .
51 html_tag( 'td', '<strong>' . $title .'</strong>', 'center', $color[9] )
52 ) . "\n"
53 . html_tag( 'tr', "\n" .
54 html_tag( 'td', '<hr>', 'left' )
55 ) . "\n"
56 . html_tag( 'tr', "\n" .
57 html_tag( 'td', $body, 'left' )
58 ) . "\n";
d112ed5a 59 /**
60 * Generate a nice "Return to Options" link, unless this is the
61 * starting page.
62 */
63 if ($MOD != "options_main"){
484ed7c9 64 echo html_tag( 'tr', "\n" .
65 html_tag( 'td', '<hr>', 'left' )
66 ) . "\n"
67 . html_tag( 'tr', "\n" .
68 html_tag( 'td', '<a href="sqspell_options.php">'
69 . _("Back to &quot;SpellChecker Options&quot; page")
70 . '</a>',
71 'center' )
72 ) . "\n";
d112ed5a 73 }
74 /**
75 * Close the table and display the version.
76 */
484ed7c9 77 echo html_tag( 'tr', "\n" .
78 html_tag( 'td', '<hr>', 'left' )
79 ) . "\n"
80 . html_tag( 'tr',
81 html_tag( 'td', 'SquirrelSpell ' . $SQSPELL_VERSION, 'center', $color[9] )
f6536dcf 82 ) . "\n</table>\n";
d112ed5a 83}
84
85/**
86 * Function similar to the one above. This one is a general wrapper
87 * for the Squirrelspell pop-up window. It's called form nearly
88 * everywhere, except the check_me module, since that one is highly
89 * customized.
90 *
91 * @param $onload Used to indicate and pass the name of a js function
92 * to call in a <body onload="function()" for automatic
93 * onload script execution.
94 * @param $title Title of the page.
95 * @param $scriptsrc If defined, link this javascript source page into
96 * the document using <script src="file.js"> format.
97 * @param $body The content to include.
98 * @return void
99 */
100function sqspell_makeWindow($onload, $title, $scriptsrc, $body){
101 global $color, $SQSPELL_VERSION, $theme_css;
102 echo "<html>\n"
103 . "<head>\n"
104 . "<title>$title</title>\n";
105 /**
106 * Check if we have a defined css theme to use.
107 */
108 if ($theme_css != "") {
109 echo "<LINK REL=\"stylesheet\" TYPE=\"text/css\" HREF=\"$theme_css\">\n";
110 }
111 /**
112 * Link in the .js file if needed
113 */
114 if ($scriptsrc){
115 echo "<script type=\"text/javascript\" src=\"js/$scriptsrc\"></script>\n";
116 }
117 echo "</head>\n"
118 . "<body text=\"$color[8]\" bgcolor=\"$color[4]\" link=\"$color[7]\" "
119 . "vlink=\"$color[7]\" alink=\"$color[7]\"";
120 /**
121 * Provide an onload="jsfunction()" if asked to.
122 */
123 if ($onload) {
124 echo " onload=\"$onload\"";
125 }
126 /**
127 * Draw the rest of the page.
128 */
129 echo '>'
484ed7c9 130 . html_tag( 'table', "\n" .
131 html_tag( 'tr', "\n" .
132 html_tag( 'td', '<strong>' . $title . '</strong>', 'center', $color[9] )
133 ) . "\n" .
134 html_tag( 'tr', "\n" .
135 html_tag( 'td', '<hr>', 'left' )
136 ) . "\n" .
137 html_tag( 'tr', "\n" .
138 html_tag( 'td', $body, 'left' )
139 ) . "\n" .
140 html_tag( 'tr', "\n" .
141 html_tag( 'td', '<hr>', 'left' )
142 ) . "\n" .
143 html_tag( 'tr', "\n" .
144 html_tag( 'td', 'SquirrelSpell ' . $SQSPELL_VERSION, 'center', $color[9] )
145 ) ,
146 '', '', 'width="100%" border="0" cellpadding="2"' )
d112ed5a 147 . "</body>\n</html>\n";
148}
149
150/**
151 * This function does the encryption and decryption of the user
152 * dictionary. It is only available when PHP is compiled with
153 * mcrypt support (--with-mcrypt). See doc/CRYPTO for more
154 * information.
155 *
156 * @param $mode A string with either of the two recognized values:
157 * "encrypt" or "decrypt".
158 * @param $ckey The key to use for processing (the user's password
159 * in our case.
160 * @param $input Content to decrypt or encrypt, according to $mode.
161 * @return encrypted/decrypted content, or "PANIC" if the
162 * process bails out.
163 */
164function sqspell_crypto($mode, $ckey, $input){
165 /**
166 * Double-check if we have the mcrypt_generic function. Bail out if
167 * not so.
168 */
169 if (!function_exists(mcrypt_generic)) {
170 return 'PANIC';
171 }
172 /**
173 * Setup mcrypt routines.
174 */
175 $td = mcrypt_module_open(MCRYPT_Blowfish, "", MCRYPT_MODE_ECB, "");
176 $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size ($td), MCRYPT_RAND);
177 mcrypt_generic_init($td, $ckey, $iv);
178 /**
179 * See what we have to do depending on $mode.
180 * 'encrypt' -- Encrypt the content.
181 * 'decrypt' -- Decrypt the content.
182 */
183 switch ($mode){
184 case 'encrypt':
185 $crypto = mcrypt_generic($td, $input);
186 break;
187 case 'decrypt':
188 $crypto = mdecrypt_generic($td, $input);
189 /**
190 * See if it decrypted successfully. If so, it should contain
191 * the string "# SquirrelSpell". If not, then bail out.
192 */
193 if (!strstr($crypto, "# SquirrelSpell")){
194 $crypto='PANIC';
9804bcde 195 }
d112ed5a 196 break;
197 }
198 /**
199 * Finish up the mcrypt routines and return the processed content.
200 */
201 mcrypt_generic_end ($td);
202 return $crypto;
203}
204
205/**
206 * This function transparently upgrades the 0.2 dictionary format to the
207 * 0.3 format, since user-defined languages have been added in 0.3 and
208 * the new format keeps user dictionaries selection in the file.
209 *
210 * This function will be retired soon, as it's been a while since anyone
211 * has been using SquirrelSpell-0.2.
212 *
213 * @param $words_string Contents of the 0.2-style user dictionary.
214 * @return Contents of the 0.3-style user dictionary.
215 */
216function sqspell_upgradeWordsFile($words_string){
217 global $SQSPELL_APP_DEFAULT, $SQSPELL_VERSION;
218 /**
219 * Define just one dictionary for this user -- the default.
220 * If the user wants more, s/he can set them up in personal
221 * preferences. See doc/UPGRADING for more info.
222 */
223 $new_words_string =
224 substr_replace($words_string,
225 "# SquirrelSpell User Dictionary $SQSPELL_VERSION\n# "
226 . "Last Revision: " . date("Y-m-d")
227 . "\n# LANG: $SQSPELL_APP_DEFAULT\n# $SQSPELL_APP_DEFAULT",
228 0, strpos($words_string, "\n")) . "# End\n";
229 sqspell_writeWords($new_words_string);
230 return $new_words_string;
231}
232
233/**
234 * Right now it just returns an array with the dictionaries
235 * available to the user for spell-checking. It will probably
236 * do more in the future, as features are added.
237 *
238 * @param $words The contents of the user's ".words" file.
239 * @return a strings array with dictionaries available
240 * to this user, e.g. {"English", "Spanish"}, etc.
241 */
242function sqspell_getSettings($words){
243 global $SQSPELL_APP, $SQSPELL_APP_DEFAULT;
244 /**
245 * Check if there is more than one dictionary configured in the
246 * system config.
247 */
248 if (sizeof($SQSPELL_APP) > 1){
249 /**
250 * Now load the user prefs. Check if $words was empty -- a bit of
251 * a dirty fall-back. TODO: make it so this is not required.
252 */
253 if(!$words){
254 $words=sqspell_getWords();
9804bcde 255 }
d112ed5a 256 if ($words){
257 /**
258 * This user has a ".words" file.
259 * Find which dictionaries s/he wants to use and load them into
260 * the $langs array.
261 */
262 preg_match("/# LANG: (.*)/i", $words, $matches);
263 $langs=explode(", ", $matches[1]);
264 } else {
265 /**
266 * User doesn't have a personal dictionary. Grab the default
267 * system setting.
268 */
269 $langs[0]=$SQSPELL_APP_DEFAULT;
9804bcde 270 }
d112ed5a 271 } else {
272 /**
273 * There is no need to read the ".words" file as there is only one
274 * dictionary defined system-wide.
275 */
276 $langs[0]=$SQSPELL_APP_DEFAULT;
277 }
278 return $langs;
279}
280
281/**
282 * This function returns only user-defined dictionary words that correspond
283 * to the requested language.
284 *
285 * @param $words The contents of the user's ".words" file.
286 * @param $lang Which language words to return, e.g. requesting
287 * "English" will return ONLY the words from user's
288 * English dictionary, disregarding any others.
289 * @return The list of words corresponding to the language
290 * requested.
291 */
292function sqspell_getLang($words, $lang){
293 $start=strpos($words, "# $lang\n");
294 /**
295 * strpos() will return -1 if no # $lang\n string was found.
296 * Use this to return a zero-length value and indicate that no
297 * words are present in the requested dictionary.
298 */
299 if (!$start) return '';
300 /**
301 * The words list will end with a new directive, which will start
302 * with "#". Locate the next "#" and thus find out where the
303 * words end.
304 */
305 $end=strpos($words, "#", $start+1);
306 $lang_words = substr($words, $start, $end-$start);
307 return $lang_words;
308}
309
310/**
311 * This function operates the user dictionary. If the format is
312 * clear-text, then it just reads the file and returns it. However, if
313 * the file is encrypted (well, "garbled"), then it tries to decrypt
314 * it, checks whether the decryption was successful, troubleshoots if
315 * not, then returns the clear-text dictionary to the app.
316 *
317 * @return the contents of the user's ".words" file, decrypted if
318 * necessary.
319 */
320function sqspell_getWords(){
321 global $SQSPELL_WORDS_FILE, $SQSPELL_CRYPTO;
322 $words="";
323 if (file_exists($SQSPELL_WORDS_FILE)){
324 /**
325 * Gobble it up.
326 */
327 $fp=fopen($SQSPELL_WORDS_FILE, 'r');
328 $words=fread($fp, filesize($SQSPELL_WORDS_FILE));
329 fclose($fp);
330 }
331 /**
332 * Check if this is an encrypted file by looking for
333 * the string "# SquirrelSpell" in it (the crypto
334 * function does that).
335 */
336 if ($words && !strstr($words, "# SquirrelSpell")){
337 /**
338 * This file is encrypted or mangled. Try to decrypt it.
339 * If fails, complain loudly.
340 *
341 * $old_key would be a value submitted by one of the modules with
342 * the user's old mailbox password. I admin, this is rather dirty,
343 * but efficient. ;)
344 */
8a9f9d09 345 $key = $_COOKIE['key'];
346 $onetimepad = $_SESSION['onetimepad'];
347 $old_key = $_POST['old_key'];
348
349 if ($old_key != '') {
350 $clear_key=$old_key;
d112ed5a 351 } else {
352 /**
353 * Get user's password (the key).
354 */
355 $clear_key = OneTimePadDecrypt($key, $onetimepad);
9804bcde 356 }
d112ed5a 357 /**
358 * Invoke the decryption routines.
359 */
360 $words=sqspell_crypto("decrypt", $clear_key, $words);
361 /**
362 * See if decryption failed.
363 */
364 if ($words=="PANIC"){
365 /**
366 * AAAAAAAAAAAH!!!!! OK, ok, breathe!
367 * Let's hope the decryption failed because the user changed his
368 * password. Bring up the option to key in the old password
369 * or wipe the file and start over if everything else fails.
370 *
371 * The _("SquirrelSpell...) line has to be on one line, otherwise
372 * gettext will bork. ;(
373 */
484ed7c9 374 $msg = html_tag( 'p', "\n" .
375 '<strong>' . _("ATTENTION:") . '</strong><br>'
376 . _("SquirrelSpell was unable to decrypt your personal dictionary. This is most likely due to the fact that you have changed your mailbox password. In order to proceed, you will have to supply your old password so that SquirrelSpell can decrypt your personal dictionary. It will be re-encrypted with your new password after this.<br>If you haven't encrypted your dictionary, then it got mangled and is no longer valid. You will have to delete it and start anew. This is also true if you don't remember your old password -- without it, the encrypted data is no longer accessible.") ,
377 'left' ) . "\n"
378 . '<blockquote>' . "\n"
379 . '<form method="post" onsubmit="return AYS()">' . "\n"
380 . '<input type="hidden" name="MOD" value="crypto_badkey">' . "\n"
381 . html_tag( 'p', "\n" .
382 '<input type="checkbox" name="delete_words" value="ON">'
383 . _("Delete my dictionary and start a new one") . '<br>'
384 . _("Decrypt my dictionary with my old password:")
385 . '<input name="old_key" size=\"10\">' ,
386 'left' ) . "\n"
387 . '</blockquote>' . "\n"
388 . html_tag( 'p', "\n" .
389 '<input type="submit" value="'
390 . _("Proceed") . ' &gt;&gt;">' ,
391 'center' ) . "\n"
392 . '</form>' . "\n";
d112ed5a 393 /**
a8380e75 394 * Add some string vars so they can be i18n'd.
395 */
396 $msg .= "<script type='text/javascript'><!--\n"
397 . "var ui_choice = \"" . _("You must make a choice") ."\";\n"
398 . "var ui_candel = \"" . _("You can either delete your dictionary or type in the old password. Not both.") . "\";\n"
399 . "var ui_willdel = \"" . _("This will delete your personal dictionary file. Proceed?") . "\";\n"
400 . "//--></script>\n";
401 /**
d112ed5a 402 * See if this happened in the pop-up window or when accessing
403 * the SpellChecker options page.
404 * This is a dirty solution, I agree. TODO: make this prettier.
405 */
406 global $SCRIPT_NAME;
407 if (strstr($SCRIPT_NAME, "sqspell_options")){
a8380e75 408 sqspell_makePage(_("Error Decrypting Dictionary"),
d112ed5a 409 "decrypt_error.js", $msg);
410 } else {
411 sqspell_makeWindow(null, _("Error Decrypting Dictionary"),
412 "decrypt_error.js", $msg);
413 }
414 exit;
415 } else {
416 /**
417 * OK! Phew. Set the encryption flag to true so we can later on
418 * encrypt it again before saving to HDD.
419 */
420 $SQSPELL_CRYPTO=true;
9804bcde 421 }
d112ed5a 422 } else {
423 /**
424 * No encryption is/was used. Set $SQSPELL_CRYPTO to false,
425 * in case we have to save the dictionary later.
426 */
427 $SQSPELL_CRYPTO=false;
428 }
429 /**
430 * Check if we need to upgrade the dictionary from version 0.2.x
431 * This is going away soon.
432 */
433 if (strstr($words, "Dictionary v0.2")){
434 $words=sqspell_upgradeWordsFile($words);
435 }
436 return $words;
437}
438
439/**
440 * Writes user dictionary into the $username.words file, then changes mask
441 * to 0600. If encryption is needed -- does that, too.
442 *
443 * @param $words The contents of the ".words" file to write.
444 * @return void
445 */
446function sqspell_writeWords($words){
447 global $SQSPELL_WORDS_FILE, $SQSPELL_CRYPTO;
448 /**
449 * if $words is empty, create a template entry by calling the
450 * sqspell_makeDummy() function.
451 */
452 if (!$words){
453 $words=sqspell_makeDummy();
454 }
455 if ($SQSPELL_CRYPTO){
456 /**
457 * User wants to encrypt the file. So be it.
458 * Get the user's password to use as a key.
459 */
8a9f9d09 460 $key = $_COOKIE['key'];
461 $onetimepad = $_SESSION['onetimepad'];
462
d112ed5a 463 $clear_key=OneTimePadDecrypt($key, $onetimepad);
464 /**
465 * Try encrypting it. If fails, scream bloody hell.
466 */
467 $save_words = sqspell_crypto("encrypt", $clear_key, $words);
468 if ($save_words == 'PANIC'){
469 /**
470 * AAAAAAAAH! I'm not handling this yet, since obviously
471 * the admin of the site forgot to compile the MCRYPT support in
472 * when upgrading an existing PHP installation.
473 * I will add a handler for this case later, when I can come up
474 * with some work-around... Right now, do nothing. Let the Admin's
475 * head hurt.. ;)))
476 */
9804bcde 477 }
d112ed5a 478 } else {
479 $save_words = $words;
480 }
481 /**
482 * Do the actual writing.
483 */
484 $fp=fopen($SQSPELL_WORDS_FILE, "w");
485 fwrite($fp, $save_words);
486 fclose($fp);
487 chmod($SQSPELL_WORDS_FILE, 0600);
488}
9804bcde 489
d112ed5a 490function sqspell_deleteWords(){
491 /**
492 * So I open the door to my enemies,
493 * and I ask can we wipe the slate clean,
494 * but they tell me to please go...
495 * uhm... Well, this just erases the user dictionary file.
496 */
497 global $SQSPELL_WORDS_FILE;
498 if (file_exists($SQSPELL_WORDS_FILE)){
499 unlink($SQSPELL_WORDS_FILE);
500 }
501}
502/**
503 * Creates an empty user dictionary for the sake of saving prefs or
504 * whatever.
505 *
506 * @return The template to use when storing the user dictionary.
507 */
508function sqspell_makeDummy(){
509 global $SQSPELL_VERSION, $SQSPELL_APP_DEFAULT;
510 $words = "# SquirrelSpell User Dictionary $SQSPELL_VERSION\n"
511 . "# Last Revision: " . date('Y-m-d')
512 . "\n# LANG: $SQSPELL_APP_DEFAULT\n# End\n";
513 return $words;
514}
515
516/**
517 * This function checks for security attacks. A $MOD variable is
518 * provided in the QUERY_STRING and includes one of the files from the
519 * modules directory ($MOD.mod). See if someone is trying to get out
520 * of the modules directory by providing dots, unicode strings, or
521 * slashes.
522 *
523 * @param $rMOD the name of the module requested to include.
524 * @return void, since it bails out with an access error if needed.
525 */
526function sqspell_ckMOD($rMOD){
527 if (strstr($rMOD, '.')
528 || strstr($rMOD, '/')
529 || strstr($rMOD, '%')
530 || strstr($rMOD, "\\")){
531 echo _("Cute.");
532 exit;
533 }
534}
535
536/**
537 * SquirrelSpell version. Don't modify, since it identifies the format
538 * of the user dictionary files and messing with this can do ugly
539 * stuff. :)
540 */
d88c744e 541$SQSPELL_VERSION="v0.3.8";
15e6162e 542?>