Kyle and Jeff rightly pointed out that yesterday’s bout of MT-theorizing (aka part 1) — however marginally interesting it might have been — wasn’t all that helpful. They’re right. I didn’t write it in a practically-minded sort of way. So here’s a shot at explaining how you’d use the stuff I discussed. And, happily enough, some of these steps are necessary for you to use the upcoming method that I alluded to at the end of the last post — so I would’ve had to write a large part of this anyway.
First things first. The Javascript obfuscator function that I wrote about is written in PHP. More importantly, the rotating comment script functionality I keep alluding to (and which will be a considerably more powerful anti-spam measure) will rely on PHP. But if your Movable Type site was installed with the default settings, its pages aren’t PHP — they’re just HTML (Jeff and Kyle: I checked, and this applies to both of you).
So what does this mean, and how do we fix it? Well, PHP is a server-side scripting language. When it serves a webpage out to a user it looks for PHP code in it (marked by blocks beginning with “<?php” and ending with “?>”) and runs it before sending the resulting HTML to a user’s web browser. You can do all kinds of useful things with PHP — talk to databases, manage user sessions, create graphics, compose PDFs — but we’ll just be using some of its most basic functionality. But even if you weren’t trying to fight comment spam I think that converting your site to PHP would be a good idea. There are a lot of neat things that people have written in it that you might want to use someday.
By default PHP doesn’t run on every web page coming from a server. Instead, it only runs on pages whose names end in .php — this is done for efficiency’s sake, so that PHP is only invoked when necessary. If you have complete control over the server you can change this. But for you, me, and anyone else bothering to read this, you won’t have that level of control. So instead we need to make Movable Type begin to generate .php files instead of .html.
Doing this is actually pretty easy. First, go to the templates section of your blog’s MT administrative interface. By default you should be looking at the Index Templates. You’ll notice that there’s a list of “Output Files”. Find one of these that ends in “.html” and click on the template name. You should see a screen that looks like this:
I’ve highlighted the extension in yellow. Just change it from “html” to “php” and save the template. Do this for each template in the Index Templates section that has an Output File ending in .html. The next time that your site is rebuilt, the index templates will have .php extensions — so instead of index.html, you’ll get index.php.
But the Index Templates are only half of the problem. There are a lot of other files generated by MT: the pages for individual entries, for example. These share a common template — it’s under the “Archives” tab in the templates section. But if you click to it, you’ll find that there’s no place to specify the extension.
Instead, click on “Settings” in the left sidebar, then on the “Publishing” tab. You’ll see a screen like this:
Again, note the yellow box, change its contents from “html” to “php” and hit “Save Changes”.
You’re almost done with the HTML » PHP conversion. Click on the “Rebuild Site” button in the left sidebar, select “Rebuild All Files” from the dropdown and click the “Rebuild” button. Depending on the size of your site, this might take a little while.
Okay. What Movable Type has just done is regenerated every file on your site as a .php file. But there’s a problem: the .html files are still there. It’s not a big problem — the links that are generated by MT to things like your archives and individual entries will all have been changed automatically. But incoming links to old entries will be pointing at the .html file, which is no longer the right thing for them to point to. Fortunately, we can fix this. Open up Notepad or another text editor and enter this text:
# This file automatically sends users looking for .html files to the # equivalent .php file, if it exists. It should be named .htaccess and placed # in the directory that corresponds to the root of your website. <IfModule mod_rewrite.c> # turn on the rewriting engine RewriteEngine on # make this equivalent to the part that comes after your # domain name. for example, if your blog lives at # http://www.server.com/my/blog, make the next line read # RewriteBase /my/blog RewriteBase / # parse out basename, but remember if we did RewriteRule ^(.*)\.html$ $1 [NC,E=WasHTM:yes] # rewrite to document.php if exists RewriteCond %{REQUEST_FILENAME}.php -f RewriteRule ^(.*)$ $1.php [R=301,S=1] # else reverse the previous removal of .html RewriteCond %{ENV:WasHTM} ^yes$ RewriteRule ^(.*)$ $1.html </IfModule> # adapted from http://www.totalchoicehosting.com/forums/lofiversion/index.php/t25151.html
Be sure you edit the line with “RewriteBase” in it if your blog lives in a subdirectory of your website (if it lives at the domain’s root — e.g. http://www.manifestdensity.net instead of http://www.zunta.org/blog — then you can leave it alone). Save this as a file called “htaccess”. Using an FTP client, upload it to the root public directory of your website — the directory which, if I uploaded a file called test.txt to it, would result in the file being available at http://www.manifestdensity.net/test.txt (it’s frequently the directory called “public_html” that you see as soon as you connect with FTP). Then, still using the FTP client, rename it to “.htaccess” (we’re adding the dot at this point because on some systems adding it earlier would have made it invisible, and consequently quite a bit harder to work with). If an .htaccess file already existed, you probably shouldn’t overwrite it. Email me the contents and we can sort through how to merge the two.
If your webserver is configured in a typical way, this .htaccess file will make the server look at every incoming request for a file ending in .html and check to see if there’s an identically-named file ending in .php. If there is, it’ll forward the user to the .php version. If there isn’t, it’ll serve up the original .html. Handy, huh? Have a look around your site (and via old links to it on other sites) and make sure that everything is working the way you expect it to.
Okay. It’s taken us a while to get here, I admit. But things should get easier from here on out. You now have the ability to put PHP in your templates in order to generate obfuscated Javascript that will in turn obfuscate the comment script’s location. The first thing to do is to change the comment script’s name — after all, if the spammers already know its location, hiding that location won’t do us much good.
Fire up your FTP client again and go to the directory where MT is installed. Use it to rename mt-comments.cgi to something hard to guess — for the purposes of this post, I’ll pretend you chose toms-replacement-mt-comments.cgi, but you should select something unique and hard to guess. From the same directory, download mt-config.cgi and open it up in a text editor. Look for a line like this:
CommentScript mt-comments.cgi
If there is one, change mt-comments.cgi to
toms-replacement-mt-comments.cgi. If there isn’t one, add one:
CommentScript toms-replacement-mt-comments.cgi
Save the file and upload it over your old mt-config.cgi.
Congratulations: your comments are now broken! This is because all the comment forms on your site are now pointing at the script’s old name. If you rebuilt the site they’d be automatically fixed (assuming your templates are properly written), since MT would swap in the new script name. But we don’t want to do that just yet.
Instead, go into your MT Admin Panel’s Templates section again. Go to the Archives tab and open the Individual Entry Archive template. Find the opening <form> tag for the comment form — in the default MT 3.2 templates, it looks like this:
<form method="post" action="<$MTCGIPath$><$MTCommentScript$>" name="comments_form" onsubmit="if (this.bakecookie.checked) rememberMe(this)">
Notice the action=”<$MTCGIPath$><$MTCommentScript$>” line. This is what we’re going to change, so that instead it’s set by Javascript. In order to do that, we need to know how to manipulate the form in Javascript. As in the Bible/Hellboy comics, naming a thing gives you power over it: make sure that the name=”comments_form” portion of the <form> tag is present in your comment form (if name is there but set to something else, change the Javascript I’ve provided to use that name instead of comments_form).
Alright. Almost there. First, we need to point the form’s action attribute at the wrong place, to lead spammers on a wild goose chase. That’s easy enough to do — setting action=”/” should be sufficient. Now let’s use some Javascript to set the form’s action to the right thing. Right after the form’s closing tag, add the following Javascript (I’ve included the <form> tag, too, to remind you where it goes. Don’t paste this in as well — you only need one <form> tag.).
<form method="post" action="/" name="comments_form" onsubmit="if (this.bakecookie.checked) rememberMe(this)"> All of the form's HTML </form> <script type="text/javascript"> document.comments_form.action = '<$MTCGIPath$><$MTCommentScript$>'; </script>
There! Your form’s action is now somewhat hidden. If you’d like to hide it further (and make use of our exciting new PHP powers), just paste in the function that I wrote yesterday and use it to scramble the JS. It should end up looking like so:
<form method="post" action="/" name="comments_form" onsubmit="if (this.bakecookie.checked) rememberMe(this)"> All of the form's HTML </form> <?php function ObfuscateJS($string_to_obfuscate, $number_of_variables=5, $max_chars_per_chunk=3) { // create some random variables that will be used by JS later on $vars = array(); for($i=0;$i<$number_of_variables;$i++) $vars[] = rand(); // begin building the JS string $js = "if(typeof(Obfuscator)!='object')\n var Obfuscator = new Object();\n"; for($i=0;$i<sizeof($vars);$i++) $js .= "Obfuscator.v$i = " . $vars[$i] . ";\n"; $js .= "eval(''"; while(strlen($string_to_obfuscate)) { // figure out how many characters we're going to encode this time $num_chars_this_time = rand(1,$max_chars_per_chunk); // grab that chunk of characters $this_chunk = str_replace("'","\\'",substr($string_to_obfuscate,0,$num_chars_this_time)); // remove that chunk from the string we're working on $string_to_obfuscate = substr($string_to_obfuscate,$num_chars_this_time); // create a plausible-seeming alternate, garbage chunk $rotated_chunk = ''; for($i=0;$i<strlen($this_chunk);$i++) { $thischar = substr($this_chunk,$i,1); if((($thischar>='A')&&($thischar<='Z'))||(($thischar>='a')&&($thischar<='z'))) $thischar = chr(ord($thischar) + (((rand()%2)==1) ? 1 : -1)); $rotated_chunk .= $thischar; } // figure out which of our random variables we're going to use $v1index = rand(0,sizeof($vars)-1); $v2index = rand(0,sizeof($vars)-1); // write out a short-form JS if-then that uses the random vars to decide its order $js .= " + ((Obfuscator.v$v1index > Obfuscator.v$v2index) ? " . (($vars[$v1index]>$vars[$v2index]) ? "'$this_chunk' : '$rotated_chunk')" : "'$rotated_chunk' : '$this_chunk')"); } $js .= ");\n"; return $js; } ?> <script type="text/javascript"> <?php print ObfuscateJS("document.comments_form.action = '<$MTCGIPath$><$MTCommentScript$>';");?> </script>
That’s it! Well, okay, almost it: you’ll have to make the same changes on any other templates where a comment form appears. The comment preview template springs to mind (see update). If you have comment forms in any other places on your site, you’ll have to hunt those down, too. But that shouldn’t be too hard. Once you’re done, rebuild your site again, verify that comments are working, and revel in your now somewhat-better-protected comment system.
Next: a way to rename mt-comments.cgi on an hourly basis. That’ll show ‘em.
UPDATE: Forget what I said about the comment preview template needing to be changed in a similar way. What you should actually do is remove the action property entirely from the comment preview template’s <form> tag. I explain why in part 3 which — hey! — is now available. How about that.
I’m so glad Tom is on our side.
testing!
if this goes through, then part 3 of this series will be up soon.
success! alright, time to go spew out a few hundred words…
Very wonderful guestbook design. What CMS do you use ?