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