Your validation tests look reasonable. One optimisation would be to use an array for the error messages, like so:
# untested, change to suit
if ( ....test.... )
push @messages, "The error message";
# more tests
PrintError if @messages;
# any initial html stuff, or use a template
print join('<br>', @messages);
...which means you don't need to maintain a flag.
Looking on CPAN, I see CGI::Validate which appears to a a Getopt style validator and Params::Validate which may be useful.
Update:Podmaster suggested Data::FormValidator, which is what I was trying to find.
Update2: I left the & in there because that's what the supplicant had - now removed. For reasons why not to do this, see perlsub. Basically, with the &, the argument list to a sub is optional and you will get the @_ array visible at the time of calling in the subroutine.