If you need to validate a *real* unix password on a system using shadowed passwords, the posix_getpwnam() function in PHP won't work (as mentioned, 'x', or '*', will be in the password field).
I have a need to verify a user/pass within PHP (using SSL!).  I don't know if this is the best way, but it's what I'm doing at the moment (works well!).
First, you need some help from the OS.  I wrote a tiny C utility that does the shadow look-up for me... It requires root access to read /etc/shadow.  So after you compile (gcc -O2 -s -o spasswd -lcrypt spasswd.c), you need to either use sudo to run it, or
# chown root spasswd && chmod u+s spasswd
To code that I'm using to authenticate a user/pass from PHP looks like:
function Authenticate($realm)
{
 global $PHP_AUTH_USER;
 global $PHP_AUTH_PW;
 if(!isset($PHP_AUTH_USER))
 {
  header("WWW-Authenticate: Basic realm=\"$realm\"");
  header("HTTP/1.0 401 Unauthorized");
  return false;
 }
 else
 {
  if(($fh = popen("/usr/sbin/spasswd", "w")))
  {
   fputs($fh, "$PHP_AUTH_USER $PHP_AUTH_PW");
   $r = pclose($fh);
   if(!$r)
    return true;
  }
 }
 header("WWW-Authenticate: Basic realm=\"$realm\"");
 header("HTTP/1.0 401 Unauthorized");
 return false;
}
The C source for spasswd.c:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <crypt.h>
#include <shadow.h>
static char salt[12], user[128], pass[128];
void die(void)
{
 memset(salt, '\0', 12);
 memset(user, '\0', 128);
 memset(pass, '\0', 128);
}
int main(int argc, char *argv[])
{
 struct spwd *passwd;
 atexit(die); die();
 if(fscanf(stdin, "%127s %127s", user, pass) != 2)
  return 1;
 if(!(passwd = getspnam(user)))
  return 1;
 strncpy(salt, passwd->sp_pwdp, 11);
 strncpy(pass, crypt(pass, salt), 127);
 if(!strncmp(pass, passwd->sp_pwdp, 127))
  return 0;
 return 1;
}
Hope this helps someone...