800×600 has been the standard browser screen size for years; however, only 8% of the population still use this resolution. 1024×768 and larger now represent over 90% of the Internet. With this in mind, it’s time to start developing websites with 1024×768 in mind. However, that doesn’t mean that a person actually has this much room to view their pages, and many people with better resolutions don’t have their browser maximized.
With a couple of standard browser plugins installed, here is what I found for the two current browsers, IE7 & FF2.
1024×768
Internet Explorer Version 7 - 1003×517 usable space.
Firefox Version 2.0.0.6 - 999×546 usable space.
1280×1024
Internet Explorer 7 - 1259×773
Firefox 2.0.0.6 - 1263×810
Therefore, above the fold browser space for the majority of today’s browsers is 999×517.
X-Cart is an e-commerce package that is written in PHP with the use of Smarty tags for its template system. For about $200, it includes a lot of functionality out of the box, and you can buy prebuilt skins for another $60 or so. For those interested in SEO, there is a module called CDSEO that you can purchase by a third party that’ll change the URLs from including GET request variables to a folder style SEO friendly format for another $150 or so. After the add ons, you can have a cart up and running for about $400.
The smarty system leaves a lot to be desired, so beware that it’s going to take longer than you’d normally think if you want to skin the site yourself, especially the first time you work with X-cart. One of the biggest challenges is knowing which template to modify. X-cart helps with this by providing a “webmaster mode”, which changes the status bar to reflect the folder and template structure of a particular section of the page. It’s quite handy because many of the template files are in strange places, and there are a ton of different template files.
X-Cart also loads pages slower than you would expect from a PHP site, which might be because of Smarty templates, but I’m not certain of that.
Note: this article is referring to version 4.0. 4.1 is out now, but I’ve heard that the newer versions are somewhat unstable until they’ve been out there for a while. 4.1 hasn’t been around long enough for me to use in a production environment yet. These instructions will
likely help with 4.1 as well. Of course, you’ll need to modify them to fit the newer codebase.
One issue I’ve come across is the need to put a small thumbnail image for each product listed on the shopping cart checkout pages. Below is how I modified the code to allow a 2nd picture to be uploaded in X-Cart’s admin area. I use one upload for a 250 pixel wide picture on the product detail page, and a 70 pixel wide image on the cart checkout page.
This article is assuming that you know PHP pretty well and can either grasp the Smarty concepts easily or have a familiarity with them. If you need further explanation about Smarty, please comment and I’ll write more about that in another post.
——-
Modifying X-cart to allow 2 picture upload fields
First, back up your x-cart files and database in case something goes wrong.
Then, modify the following files:
In include/product_modify.php:
Add this code directly below the ‘if’ block with the comment “Check if image selected is not expired”:
If that comment can’t be found, look for the same block of code except that it’s looking for an imtype of “T” instead of “S”.
The two “if” blocks could probably be combined into 1, if you’d like. Please note that as is, you can only upload one image at a time.
If you want to be able to upload both at once, you’ll need to modify the code beyond the example to handle that.
//Cart Image
if ($file_upload_data["imtype"] == "S") {
if ($file_upload_data["counter"] == 1) {
$file_upload_data["counter"]++;
$smarty->assign("file_upload_data", $file_upload_data);
}
else {
if ($file_upload_data["source"] == "L")
@unlink($file_upload_data["file_path"]);
x_session_unregister("file_upload_data");
}
}
Also in include/product_modify.php, change the # Prepare for thumbnail updating (around line 278) to this:
# Prepare for thumbnail updating
$image_posted = func_check_image_posted($file_upload_data, "T");
$cartimage_posted = func_check_image_posted($file_upload_data, "S");
$store_in = ($config["Images"]["thumbnails_location"] == "FS"?"FS":"DB");
$cartstore_in = ($config["Images"]["cartimages_location"] == "FS"?"FS":"DB");
And add this code block directly after the $image_posted “if” block:
if ($cartimage_posted) {
$cartimage_data = func_get_image_content($file_upload_data, $productid);
$row_exists = func_query_first("select * from $sql_tbl[thumbnails] where productid = '$productid'");
if ($row_exists['productid'] == "") {
db_query("INSERT INTO $sql_tbl[thumbnails] (productid, ".($cartstore_in == "FS"?"cartimage_path":"cartimage").", cartimage_type) VALUES ('$productid', '$cartimage_data[image]', '$cartimage_data[image_type]')");
} else {
db_query("UPDATE $sql_tbl[thumbnails] SET ".($cartstore_in == "FS"?"cartimage_path":"cartimage")." = '$cartimage_data[image]', cartimage_type = '$cartimage_data[image_type]' WHERE productid = '$productid'");
}
}
On skin1/main/product_details.tpl, add this text below the normal thumbnail table row:
<!-- Cart Image -->
<TR>
{if $productids ne ''}<TD width="15" class="TableSubHead"> </TD>{/if}
<TD colspan="2">{include file="main/subheader.tpl" title="Small Cart Image"}</TD>
</TR>
<TR>
{if $productids ne ''}<TD width="15" class="TableSubHead"><INPUT type="checkbox" value="Y" name="fields[thumbnail]"></TD>{/if}
<TD class="ProductDetails" valign="top"><FONT class="FormButton">Small Image</FONT><BR>(recommended size 50x50 or smaller)</TD>
<TD class="ProductDetails">
{include file="product_cartthumbnail.tpl" productid=$product.productid product=$product.product}
<BR>
<TABLE border="0" cellpadding="0" cellspacing="0" width="100%">
<TR>
<TD>
<INPUT type="button" value="{$lng.lbl_change_image}" onclick='javascript: if (confirm("{$lng.txt_change_image_text|strip_tags}")){ldelim}popup_image_selection("S", "{$product.productid}", "{$query_string}");{rdelim}'>
<!--
<INPUT type="button" value="{$lng.lbl_delete_image}" onclick='javascript: if (confirm("{$lng.txt_change_image_text|strip_tags}")){ldelim}self.location="product_modify.php?mode=delete_thumbnail&productid={$product.productid}"{rdelim}'>
-->
</TD>
<TR>
{if $file_upload_data.file_path}
<TR>
<TD>
<BR><BR>
{$lng.txt_save_thumbnail_note}
</TD>
</TR>
<!-- End Cart Image -->
In include/image_selection.php, add this case in the $imtype switch:
case "S":
$config_data["location"] = $config["Images"]["cartimages_location"];
$config_data["path"] = $config["Images"]["cartimages_path"];
$config_data["path_only"] = $config["Images"]["cartimages_path_only"];
break;
Save image.php as cartimage.php, then change the code portion to the following (leave the x-cart license comment stuff alone):
Also note that I’ve included <?php even though it shows up above the comments. Of course, you only need this in one spot.
<?php
require "./top.inc.php";
require "./config.php";
if (empty($productid)) $productid = "";
$image_out = ""; $image_type = ""; $image_path = "";
if (!empty($tmp)) {
x_session_register("file_upload_data");
if (!empty($file_upload_data["file_path"]) && $file_upload_data["id"]==$productid && $file_upload_data["imtype"]=="S") {
$image_out = func_file_get($file_upload_data["file_path"], true);
}
}
if (empty($image_out)) {
if(!empty($variantid))
$result = db_query("SELECT cartimage as image, cartimage_path as image_path, cartimage_type as image_type FROM $sql_tbl[thumbnails] WHERE productid='$productid' AND variantid = '$variantid'");
if(empty($result))
$result = db_query("SELECT cartimage as image, cartimage_path as image_path, cartimage_type as image_type FROM $sql_tbl[thumbnails] WHERE productid='$productid' AND variantid = ''");
if (db_num_rows($result)) {
list($image, $image_path, $image_type) = db_fetch_row($result);
if ($image == "") {
header("Content-type: image/gif");
func_readfile($default_image, true);
exit;
}
} else {
header("Content-type: image/gif");
func_readfile($default_image, true);
//echo "Image: " . $image_type;
exit;
}
db_free_result($result);
if ($config["Images"]["thumbnails_location"] == "DB") {
if (!empty($image))
$image_out = $image;
else
$no_image_db = true;
}
if ($config["Images"]["thumbnails_location"] == "FS" || !empty($no_image_db)) {
if (!empty($image_path)) {
header("Content-type: $image_type");
func_readfile($image_path, true);
exit;
}
}
}
if (!empty($image_out)) {
header("Content-type: $image_type");
echo $image_out;
} else {
header("Content-type: image/gif");
func_readfile($default_image, true);
}
?>
Create a new file called skin1/product_cartthumbnail.tpl, and put this code in it:
{* $Id: product_cartthumbnail.tpl,v 1.14 2007/09/14 09:53:29 max Exp $ *}
{if $config.Appearance.show_thumbnails eq "Y"}
<IMG id="{$id}" src="{if $tmbn_url}{$tmbn_url}{else}{if $full_url}{$http_location}{else}{$xcart_web_dir}{/if}/cartimage.php?productid={$productid}{if $file_upload_data.file_path}&tmp=y{/if}{/if}" alt="{$product|escape}" border="0">
{/if}
In include/func.php, in the func_get_image_content function, add this to the $file_upload_data[”imtype”] switch:
case "S":
$config_data["location"] = $config["Images"]["cartimages_location"];
break;
In the database:
Modify xcart_thumbnails (assuming you used the prefix “xcart” when installing it, otherwise modify the table with the appropriate prefix)
to include the following fields:
cartimage mediumblob allow nulls
cartimage_path varchar(255) allow nulls
cartimage_type varchar(64) allow nulls
In xcart_config, add the following rows (modify the table name if you used a different prefix other than xcart):
INSERT INTO xcart_config (name, comment, value, category, orderby, type, defvalue)
values ('cartimages_location', 'location of small cart images', 'DB', 'Images', '130', 'text', 'DB')
INSERT INTO xcart_config (name, comment, value, category, orderby, type, defvalue)
values ('cartimages_path', 'FS junk - not needed', '', 'Images', '140', 'text', '')
INSERT INTO xcart_config (name, comment, value, category, orderby, type, defvalue)
values ('cartimages_path_only', 'FS junk - not needed', 'N', 'Images', '150', 'checkbox', 'N')
After that, test it and see if everything works. If not, you’ll need to troubleshoot the code to see where you missed something.
Good Luck,
Brian
The repair table command in MySQL is useful to repair database indicies when they become corrupt. A prime example of when an index can become corrupt is when the power is shut off unexpectedly to a server. If the power is shut off after a row has been inserted into the table, but before its index has been updated, the table will become corrupt and unusable.
Unfortunately, there isn’t a “repair all tables” option that I know of. Instead, you can use this simple PHP script to do the trick for you. It simply calls “show databases” to loop through a list of your databases, and then “show tables” so that it knows which tables exist for the repair command. Depending on how many databases you have and the size of their tables, this can take several minutes to run.
<?
### Enter your username and password into the connection string: ###
$dbLink = mysql_connect("localhost", "username", "password") or die("Unable to connect to the database.");
$sql = "show databases";
$query = mysql_query($sql) or die("error fetching database names");
while ($rs=mysql_fetch_array($query)) {
//echo "Database: " . $rs['Database'] . "\n";
mysql_select_db($rs['Database'],$dbLink) or die("Unable to select database: " . $rs['Database']);
$sql = "show tables";
$query2 = mysql_query($sql) or die("error fetching table names");
while ($rs2=mysql_fetch_array($query2)) {
$key = "Tables_in_" . $rs['Database'];
//echo "Table: " . $rs2[$key] . "\n";
$sql = "repair table " . $rs2[$key];
$query3 = mysql_query($sql) or die("Error repairing a table - $sql");
$rs3 = mysql_fetch_array($query3);
echo $rs3['Table'] . " | " . $rs3['Op'] . " | " . $rs3['Msg_type'] . " | " . $rs3['Msg_text'] . "\n";
}
}
echo "Finished!\n";
?>
After setting up a new site in Plesk and restarting Apache, the site would only display 403 Forbidden errors. The same error occured whether we went to the exclusive IP address or the domain name so we knew that it wasn’t a DNS problem. We checked the logs and it showed that Apache was unable to read the .htaccess file. We changed the permissions on this file, which didn’t do any good.
It turned out that when Plesk set up the domain, it gave the httpdocs directory the wrong permissions. Instead of the permissions that it assigned it, we changed it to 755 or rwxr-xr-x and everything works now.
If you get permission errors in the future, be sure to check the directory the file belongs in as well.
You can save and restore your iptable rules by using the iptables-save and iptables-restore commands. An example of an iptables-save file is below, and as you see it allows access to http (port 80), https (port 443), smtp and pop3 (ports 110 and 25), ftp (port 21), dns (port 53), mysql (port 3306), ssh (port 22), plesk (port 8443), and a few others. It also allows traffic through ports 51000 through 51010. This is because ProFTPd is configured to forward established connections to these ports; Otherwise, it’ll use any high port and you won’t be able to lock anything down.
# Generated by iptables-save v1.2.7a on Fri Oct 15 18:04:43 2004
*filter
:INPUT ACCEPT [6496247:1563147047]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [10757411:8360947636]
:RH-Lokkit-0-50-INPUT - [0:0]
-A INPUT -j RH-Lokkit-0-50-INPUT
-A FORWARD -j RH-Lokkit-0-50-INPUT
-A RH-Lokkit-0-50-INPUT -p tcp -m tcp –dport 443 –tcp-flags SYN,RST,ACK SYN -j ACCEPT
-A RH-Lokkit-0-50-INPUT -p tcp -m tcp –sport 443 –tcp-flags SYN,RST,ACK SYN -j ACCEPT
-A RH-Lokkit-0-50-INPUT -p tcp -m tcp –dport 8443 –tcp-flags SYN,RST,ACK SYN -j ACCEPT
-A RH-Lokkit-0-50-INPUT -p tcp -m tcp –dport 110 –tcp-flags SYN,RST,ACK SYN -j ACCEPT
-A RH-Lokkit-0-50-INPUT -p udp -m udp –dport 110 -j ACCEPT
-A RH-Lokkit-0-50-INPUT -p tcp -m tcp –dport 25 –tcp-flags SYN,RST,ACK SYN -j ACCEPT
-A RH-Lokkit-0-50-INPUT -p udp -m udp –dport 25 -j ACCEPT
-A RH-Lokkit-0-50-INPUT -p tcp -m tcp –dport 21 –tcp-flags SYN,RST,ACK SYN -j ACCEPT
-A RH-Lokkit-0-50-INPUT -p udp -m udp –dport 21 -j ACCEPT
-A RH-Lokkit-0-50-INPUT -p tcp -m tcp –dport 20 –tcp-flags SYN,RST,ACK SYN -j ACCEPT
-A RH-Lokkit-0-50-INPUT -p udp -m udp –dport 20 -j ACCEPT
-A RH-Lokkit-0-50-INPUT -p tcp -m tcp –dport 3306 –tcp-flags SYN,RST,ACK SYN -j ACCEPT
-A RH-Lokkit-0-50-INPUT -p udp -m udp –dport 3306 -j ACCEPT
-A RH-Lokkit-0-50-INPUT -p tcp -m tcp –dport 53 –tcp-flags SYN,RST,ACK SYN -j ACCEPT
-A RH-Lokkit-0-50-INPUT -p udp -m udp –dport 53 -j ACCEPT
-A RH-Lokkit-0-50-INPUT -p udp -m udp –sport 53 -j ACCEPT
-A RH-Lokkit-0-50-INPUT -p tcp -m tcp –dport 51000 –tcp-flags SYN,RST,ACK SYN -j ACCEPT
-A RH-Lokkit-0-50-INPUT -p tcp -m tcp –dport 51001 –tcp-flags SYN,RST,ACK SYN -j ACCEPT
-A RH-Lokkit-0-50-INPUT -p tcp -m tcp –dport 51002 –tcp-flags SYN,RST,ACK SYN -j ACCEPT
-A RH-Lokkit-0-50-INPUT -p tcp -m tcp –dport 51003 –tcp-flags SYN,RST,ACK SYN -j ACCEPT
-A RH-Lokkit-0-50-INPUT -p tcp -m tcp –dport 51004 –tcp-flags SYN,RST,ACK SYN -j ACCEPT
-A RH-Lokkit-0-50-INPUT -p tcp -m tcp –dport 51005 –tcp-flags SYN,RST,ACK SYN -j ACCEPT
-A RH-Lokkit-0-50-INPUT -p tcp -m tcp –dport 51006 –tcp-flags SYN,RST,ACK SYN -j ACCEPT
-A RH-Lokkit-0-50-INPUT -p tcp -m tcp –dport 51007 –tcp-flags SYN,RST,ACK SYN -j ACCEPT
-A RH-Lokkit-0-50-INPUT -p tcp -m tcp –dport 51008 –tcp-flags SYN,RST,ACK SYN -j ACCEPT
-A RH-Lokkit-0-50-INPUT -p tcp -m tcp –dport 51009 –tcp-flags SYN,RST,ACK SYN -j ACCEPT
-A RH-Lokkit-0-50-INPUT -p tcp -m tcp –dport 51010 –tcp-flags SYN,RST,ACK SYN -j ACCEPT
-A RH-Lokkit-0-50-INPUT -p tcp -m tcp –dport 22 –tcp-flags SYN,RST,ACK SYN -j ACCEPT
-A RH-Lokkit-0-50-INPUT -p tcp -m tcp –dport 80 –tcp-flags SYN,RST,ACK SYN -j ACCEPT
-A RH-Lokkit-0-50-INPUT -i lo -j ACCEPT
-A RH-Lokkit-0-50-INPUT -s 217.160.242.226 -p udp -m udp –dport 53 -j ACCEPT
-A RH-Lokkit-0-50-INPUT -s 217.160.242.226 -p udp -m udp –sport 53 -j ACCEPT
-A RH-Lokkit-0-50-INPUT -s 217.160.243.251 -p udp -m udp –sport 53 -j ACCEPT
-A RH-Lokkit-0-50-INPUT -s 195.20.224.99 -p udp -m udp –sport 53 -j ACCEPT
-A RH-Lokkit-0-50-INPUT -s 195.20.224.234 -p udp -m udp –sport 53 -j ACCEPT
-A RH-Lokkit-0-50-INPUT -p tcp -m tcp –tcp-flags SYN,RST,ACK SYN -j REJECT –reject-with icmp-port-unreachable
-A RH-Lokkit-0-50-INPUT -p udp -m udp -j REJECT –reject-with icmp-port-unreachable
COMMIT
# Completed on Fri Oct 15 18:04:43 2004
Here is an example of a cronjob that restores the Iptables rules. I added the cronjob to ensure that IPtables was always loaded with the correct rules. This was on a dev box and we were messing with a lot of ports at the time so we wanted to make sure that it was reset periodically. It also helps for when the box was rebooted to make sure the rules were loaded. Of course, a better way to do that would be to modify the startup scripts. Also, you can put the firewall.txt file where ever you want. It doesn’t need to be in root’s home directory.
### Restore Iptables Rules
5 7,14,21 * * * /sbin/iptables-restore -c < /root/firewall.txt
And finally, to set up the ftp server to only use the specified ports, configure /etc/proftpd.conf and add this line:
PassivePorts 51000 51010
The actual ports could be any unused high port, so keep that in mind. It can also be more than 10 ports (which essentially would allow 10 concurrent connections). Whatever you do, you’ll need to make sure the iptable rules allow the ports you’re expecting FTP to transfer data on.
When I have my headphones on and am listening to music, it’s really annoying to hear that little clicking sound everytime I click on something in Internet Explorer.
If this is annoying to you too, here is how to turn it off:
Go to Start > Settings > Control Panel > Sounds and Audio devices
Click the ’sounds’ tab. In the window, scroll down to ‘Windows Explorer’ and in the sub menu, highlight ‘Start navigation’
Click on the drop down box below where it says ‘Start’, ‘Windows Start’, or ‘Windows XP start’ (or something similar depending on your operating system) and choose ‘(none)’ instead. Click ‘Apply’, then click OK, and close the Control Panel.
That annoying clicking sound is now a thing of the past.
Here are some instructions on how to encrypt a file using GPG, the open source version of PGP. It’s assumed that somebody needs you to send them an encrypted file, they’ve given you their public key in a trustworthy manner, and they have the private key. It’s also assumed that you have GPG installed, but this shouldn’t be a problem because it’s usually installed by default on Linux.
First, you’ll need to create a default gpg key. To do this, you’ll need to have a directory in your home directory called “.gnupg”. If you don’t have permissions to add directories into your root (common on shared hosting plans), you’ll need an admin to create the folder (mkdir) and set the owner to your user (chown).
Once you have the folder (or if you already have permissions, it’ll create it for you), run this command and follow the instructions. If you’re not sure the answer, accept the defaults or the first option.
gpg –gen-key
They may have armored their public key, which from the help file means to “create ascii armored output”. I’m not really sure what that means, but it seems like it’s in a text friendly format. If armored, you need to dearmor it first:
gpg –dearmor some_public_key.asc
This will create a new file with the gpg extention. Next you’ll need to import the dearmored key.
gpg –import some_public_key.asc.gpg
Once imported, you’ll need to find out it’s name.
gpg –list-keys
If you want the ability to encrypt files in a script, it’s a good idea to sign the key. Otherwise, the encryption process prompts you with a question, and the only way I’ve found to bypass that question is to sign the key.
Assuming that the name of the key is “somepublic”, here is the command:
gpg –sign-key somepublic
Follow the prompts and as long as you trust that the public key is legit, tell it you trust it explicitly. To see that everything was signed correctly, run this command:
gpg –list-sigs
Now try encrypting something using the public key:
gpg -e -r somepublic testfile.txt
If everything worked correctly, you’ll now have a file called testfile.txt.gpg (and the non-encrypted version), and it should not have prompted you for anything during the process. The only way to decrypt this file is to have the private key, which is what the other person would have installed.
In x-cart, we couldn’t upload an image because the form gave us an error. After a lot of troubleshooting, here’s what we found to make the upload script work:
We made sure these folders and their subfolders had ‘write’ (777) permissions set:
1.
2.
3.
Also, we made sure the directory set in the PHP configuration option ‘upload_tmp_dir’ (in the php.ini file) had the permissions set to 777.
Pretty easy fix. Where we got tripped up was the fact that the log folder had to have write permissions. Why that is related to the image files, I’ll never know!
SecureCRT is a great SSH and telnet terminal client. It’s not free, but for the price, it’s well worth it. It handles ssh2 very nicely, and has a username system that’s better than PuTTY, in my opinion.
The only thing that I don’t like about it is that it makes a little beep by default when you hit certain keys; For example, if you type cd somedir and hit tab, it’ll autofill what it can (the functionality actually comes from the Linux shell), however, if it can’t complete a filename because there are two similar ones, it’ll play a little beep. As with another post of mine, little beeps for things are extremely annoying when I’m wearing headphones, so here’s how to remove the beep in SecureCRT version 3.3 (I realize this is an old version, but I’ve never needed to upgrade):
Go to Options -> Session Options. Click on “Options”, uncheck the “Audio Bell”.
This will remove that little beep in most situations.
I consider myself fairly good at regular expressions, which is a way to match specific patterns within a string of text; however, today, I learned a new little nugget of information regarding pattern matching.
We needed to match any filename that ended in html or php, but didn’t start with the word “form”. The regex syntax that I learned today was the Zero-width negative lookahead assertion which follows the form (?!pattern). This matches only if the subexpression denoted by the parenthesis does not match at this position on the right. Therefore (?!form) would match as long as form wasn’t at the specified position.
The whole regular expression was: ^(?!form).*\.(php|html)$
The first carot ^ denotes the start of the string, then our negative lookahead makes sure it doesn’t start with the word “form”. After that the dot star matches anything until it finds a period (the backslash or escape character makes it look for a period instead of it’s default “any character”. Then it looks for php or html (the pipe or | represents the boolean OR), and finally the $ indicates the end of the string. It does a lot in just a few characters.
Incidentally, (?=pattern) is a zero-width positive lookahead, which means that it must match that pattern at the position indicated in the overall regular expression. (?!pattern) is the zero-width negative lookahead. (?<=pattern) is a zero-width positive lookbehind assertion, and (? pattern) is a nonbacktracking subexpression or so called greedy subexpression. So far, I haven’t needed to make things specifically greedy, usually it’s the opposite, I’m forcing it to not be greedy. What that means is that it’ll match the largest part of the string that it can; For example, if you used the regex /*.\.txt/ and the string was some.txt.txt, a greedy expression would return “some.txt.txt”, while an ungreedy expression would return “some.txt”. It’s a minor detail that can really throw you off on occasion.
My secure and messages log files were getting really big, and after analyzing them to see if there was a reason, I decided to clear them out.
To do so, I first typed: cat > messages, then typed a couple of blank lines, and hit control-c to quit. I did the same for the secure log file. This cleared the file, but no new messages were being added to the logs. After kicking myself for being stupid, I searched around and found two important things.
First, it’s easier to clear a file by simply typing “> filename”, and second, the syslog daemon is reponsible for writing to those files. Restarting it restores its ability to write to the file. I restarted it by going to /etc/init.d and running ./syslogd restart
Over the past week, there have been 10 separate attempts to do a brute force attack on my server. Each day, I’d check my logs and manually ban the IP address responsible for the attempted crack. They’ve been attempting to break in through SSH and FTP.
While they didn’t succeed in breaking in, they did succeed in slowing down the server to the point that it was unresponsive for a few minutes during each attack.
Fortunately, IPTables has a cool feature that checks for recent connections. You tell it how many connection attempts from a particular IP address for a length of time, and if that amount succeeds, that IP address is banned for the specified amount of time.
It consists of 2 easy commands. The first one sets up the recent table:
iptables -I INPUT -p tcp --dport 22 -i eth0 -m state --state NEW -m recent --set
In this case, it’s looking for connections on port 22 (SSH). Do the same for any other port that you want to limit like this such as your ftp ports.
The next command tells it to drop packets that exceed your specifications:
iptables -I INPUT -p tcp --dport 22 -i eth0 -m state --state NEW -m recent --update --seconds 60 --hitcount 2 -j DROP
This tells iptables to drop the 3rd connection from a particular IP address if it happens within 60 seconds of the previous 2. You’ll probably want to set it a little harsher than that such as 3-5 minutes, or if you really want to be strict, set it for an hour or more. The downside is that the longer the timeframe, the greater chance that a legitimate person will get blocked because they mistyped their password a couple of times.
I’ve found that a range of 3-5 minutes is enough. My logs show a couple of attempts, 5 minutes later, a couple more, 5 minutes later a couple more. The rest are just dropped. My server doesn’t show down in the slightest with this kind of load, and I believe it’ll take the crackers a few hundred years to break a password at 2 attempts every 3-5 minutes.
At home, I run Windows XP x64, and I’ve had a tough time finding an antivirus program that will work on this operating system. I waited a year for McAfee or Symantec to come out with a version for this OS, but as of yet, neither of them had one that I could find on their unuser-friendly websites.
After searching around, I found Avast, which is free for non-commercial home use. I installed it and ran a full scan, and it found a couple of dormant virus files. It has a real-time scanner, which hasn’t noticably affected the speed of my system (something I can’t say for either McAffee or Symantec).
What are your thoughts of Avast and antivirus software in general? I haven’t been too thrilled with McAfee, as their update process has always been a bit unstable and problematic, and all of their products except for VirusScan has been a joke. Symantec has been pretty good to me, up until I got an OS that they don’t seem to support.
At any rate, until Windows can design a user-rights system that’s as good as Linux, we’re stuck with Antivirus software, and Avast is the only thing I’ve found to work on the 64 bit version of XP. It’s free, which is nice, but only time will tell if it actually prevents virus infections.
Lately, discussions seem to rage about whether to lay out your web pages using tables or divs. One side of the coin suggests all HTML related to laying out a page should use CSS, and thus the use of divs and spans to achieve this. It’s reported to be faster to render divs than tables because the browser can’t render a table until it reads the closing table tag. Using divs also typically makes the webpage’s file size smaller, which allows it to download faster, saves room on your hard drive, and it is rumored that search engines like div style pages better perhaps because of keyword density issues. If the overall html is less then the percentage of content on a page would be more, and some say that this is a benefit when dealing with search engine spiders.
On the practical side though, using divs and CSS to do your entire layout can be kludgy at best. Sure it’s fine for really simple layouts, but most layouts quickly move beyond this simplistic approach. Most of today’s browsers render CSS a little different, which sometimes results in the need for browser specific CSS kludges. Also, with a tableless layout, you often have to resort to odd hacks to make things work that people took for granted with tables such as the so-called faux columns and sliding door hacks. Complicate this with browser differences, and it can quickly end up looking like a nice bowl of spaghetti.
In fairness, CSS layouts can do things that tables cannot. Floating elements are really cool, but come with their own set of problems; For example, try to float 9 different elements with varying heights so that it ends up a 3×3 grid. Tables did this without a second thought. Without tables, you have to add an invisible span so that the next “row” actually drops all the way without getting hung up on a shorter element. Height parameters, in this case, just make a mess because they tend to leave way too much whitespace.
In theory, CSS should be used for all layouts. In practice, it’s sometimes way more of a pain than it should be.
Let me give you another example. I had a set of links sitting next to each other horizontally in a menu bar. I wanted a small form that consisted of a username and password box to sit on that same menu bar so that it all looked nice and level. For 3 hours, I played with tons of different ways to do with CSS, but ran into problem after problem. I tried floating left and right, but it wouldn’t line up with the menu links. I tried using various other html elements like putting both the links and the form into their own divs and using the divs padding, margin, positioning, etc to make them line up, but Firefox and Internet Explorer kept putting the two in different positions so making it look right in IE made Firefox screw up, etc. I could have put conditional CSS code for IE, but I’m stubborn and wanted to make it work without resorting to kludgy CSS. Eventually, I gave up and made a 2 cell table with 1 row and set the form to 0 pixel margin and padding and it worked fine in both browsers. The table solution took me all of 5 minutes. The CSS solution was not resolved after 2-3 hours.
I agree with the purists that all layouts should be rendered using CSS, but CSS isn’t practical for all layout situations yet. Tables, while seemingly archaic, are handled far closer to the same on Firefox and IE, which makes them more appealing in the cases where CSS falls short.
For the time being, I’m going to continue to learn new ways to deal with CSS shortcomings, but I’m not ready to abandon my old friend, the HTML table.
Outlook Express is installed for free by default with Windows and thus, it’s a common default choice among people new to the Internet and Email. Outlook Express does the basics of what an email client must do, but that’s the extent of its scope. You can have multiple email accounts, but all of your email drops into the same folder. To get around this, you can use rules to forward your email to other folders based on the “To” address. It does nothing for spam or phishing scams, and the extent of its security measures is the fact that it doesn’t auto-download embedded images.
Mozilla Thunderbird is another free email client, but it does a lot more than Outlook Express. It has a built-in spam filter, which learns over time what a person considers spam based on what they flag as spam. It is aware of spam assassin headers and can be configured to trust spam assassin’s headers. Between the two, it catches nearly all spam and gets better over time as it continues to analyze new spam email.
Thunderbird also has a phishing detection feature, where it will notify you if it thinks the email is a phishing scam. Phishing essentially is the attempt to mislead somebody into thinking they are going to a particular site, when in fact, they are going to a scammer’s site. Typically, phishing scams revolve around getting people to mistakenly put in their username and password, which is saved so that the scam artist can then log into their real account and gather personal information. This is a crucial feature for those new to the Internet as that warning can save a person from having their identity or credit card information stolen. Most people that have been on the Internet for a while can spot a scam. One such trick is to move your mouse over the link and look at where it’s really going in the status bar. If the address doesn’t match up to the real website, then it’s probably a scam. For example, if somebody wants you to log into “Paypal”, and the address is really going to www.paypal.com.someothersite.com, then it’s a scam.
Thunderbird can use multiple inboxes for each email address that you use, and it has a rules system that is at least as good as Outlook Express. It also allows you to set up RSS folders to take advantage of syndicated content on the Internet from your chosen sites.
As far as email is concerned, Mozilla has it covered.
The only drawback to using Thunderbird exclusively is that it doesn’t have a built in calendar & meeting system like you can get with Microsoft Outlook (the full version, not Express) and Exchange. There are calendar plugins, but to my knowledge, there isn’t a server available that will manage a shared calendar between many people like you can get with Exchange. If you know of such a system, please write a comment as I’d love to use it.
Bottom line, the best free email client available is Mozilla Thunderbird. If you need the ability to schedule meetings, then Microsoft Outlook with an Exchange server is probably the best option; however, Outlook and Exchange are not free. The prices vary depending on how many people you need and how you purchase Outlook such as by itself or inside Microsoft Office, but a small office will typically spend a few thousand dollars in licensing and setup fees.