WooWOO! We’ve connected! “Uhh, now what?” Now we log in. This will be your first and most important step to actually WORKING with (rather then creating) socket connections.
Step 7: Working with our connection…
Now, unfortunately I can’t just JUMP into logging on without first figuring out whether we can. Depending on the service YOU are trying to connect to, this may be accomplished in a number of ways, in accordance with how the server has been set up. With our FTP server, we can read (buried DEEP within the RFCs) that every command given should evoke a response from the server; When I say “hello” to the server, it’s appropriate for the server to acknowledge me. FTP does this by giving you number codes. Anything starting with a 1, 2, or 3 (we’re going to lump all of those into one category here for the sake of brevity) means that the last command was completed successfully–OR that the server is waiting for something more. A response of 4 or 5 is BAD JUJU–we don’t want those.
We’ll build our cmd_status code based on those assumptions above
<?php
function cmd_status() {
// first we make sure the socket is set properly
$this->control_socket_reset();
// We’ll get ALL of the data from the control sockets buffer
while ( $data=fgets($this->contsock, 9999) ) {
// And from the buffer we will find the reply we want
// (in this case we are only concerned with the VERY
// first number of the VERY last line)
$reply=substr($data, 0, 1);
}
if ( $reply == 1 || $reply == 2 || $reply == 3 ) {
// Let’s give our socket some more coffee
$this->control_socket_reset();
// we make sure the socket WILL
// work properly in the future…
// (get used to redundancy… it
// can be a little work in the ‘now’
// but it saves a lot of work in
// the future
// Now let’s return TRUE
return(1);
} else {
// return FALSE, because our command didn’t do ANYTHING of any use
// at all…
return(0);
}
}
?>
Step 8: Logging In (FINALLY!)
If all has gone well so far, we are now ready to take our first steps! Let’s log in!
We’ll make our login() function like this:
<?php
function login($username=‘anonymous’, $password=‘[email protected]’) {
$this->username=$username; // again, we tuck our variables away
$this->password=$password; // for safe keeping…
fputs($this->contsock, ‘USER ‘.$this->username.chr(10));
// WHAT IN THE HELL WAS THAT?! Well, our FTP RFC says that to
// log in we need to type ‘LOGIN <username> <carriage return>’
// which is exactly what we just did. We used the concatenation
// operator (http://www.php.net/manual/en/language.operators.string.php)
// to put those three things together (the chr() function lets you
// convert an ASCII code into its character (see http://www.asciitable.com/
// for an explanation of what ASCII is…))
if ( $this->cmd_status() ) {
// IF the command returned a GOOD code…
fputs($this->contsock, ‘PASS ‘.$this->password.chr(10));
if ( $this->cmd_status() ) {
return(1);
} else {
return(-1); // -1 means ‘bad password’
}
}
else {
return(0); // 0 means ‘bad username’
}
}
?>
AGAIN, it’s important to TEST TEST TEST TEST! So, let’s modify our testing code (down at the bottom of the page) again…
<?php
$ftp = new ftp;
if ( $ftp->control_socket(‘ftp.cdrom.com’, 21) ) {
echo ‘We have successfully connected!<br>’;
flush();
$test=$ftp->login();
if ( $test == 1 ) {
echo ‘We have successfully logged in!<br>’;
flush();
}
else if ( $test == 0 ) {
die(‘Bad username!<br>’);
}
else if ( $test == –1 ) {
die(‘Bad password!<br>’);
}
}
else {
die(‘We have failed to connect!<br>’);
}
?>
SUCCESS! We’ve successfully logged into our FTP! Congratulations–we’re almost half way there! And I decree it time to go see a movie, or go get a rental, have some popcorn, or drink some water and let loose. This stuff is hard to grasp, and if you don’t reward yourself, then you’ll have a hard time pushing on into the more exciting areas of the language. So soak it up. If you understood everything so far, then you’re WAY ahead of the curve!
Step 9: Our data connection (passive mode)
If you haven’t already got the idea of how FTP data is transmitted (and you probably don’t–you didn’t do the RFC homework I gave you, did you?!) you’ll need to understand it now, or you’ll be completely and utterly lost when we begin this phase of the project. By the way, be ready for a headache, because this is, arguably, the most difficult part of the tutorial to grasp in its entirety.
Passive mode was invented to get around firewalls. This is how it works:
a) you tell the server “enable passive mode”
b) the server says, “ok, connect to this ip and this port, and wait”
c) you connect, and wait
d) you give a command (like “LIST”) on the control port
e) you receive the data output from your list command–which you gave on the control port–from the passive data connection you just made
f) you close your passive connection (because you have to re-enable passive mode and connect when you need it again).
If you got all of that, then good–you’ve officially learned more than most people will ever know about how they get files! 🙂 If you didn’t catch it, try reading this: (which gave me MY inspiration http://neworder.box.sk/newsread.php?newsid=4056).
Here’s our data_socket() function, which is almost identical to control_socket() except that it does not keep track of the hostname and port number, because those may (the port number WILL) change each time you use passive(), and you have to use passive() every time you use your data connection 🙂
<?php
function control_socket_reset() {
if ( $this->contsock ) {
socket_set_timeout($this->contsock, 1);
// Reset (or set) the time-
// out on our control socket
return(1);
} else {
return(0);
}
}
?>
And here’s one of our most complicated functions… passive()…
<?php
function passive() {
// First we are going to ‘clear’ our buffer
while ( $tmp=fgets($this->contsock, 9999) ) {
unset($tmp);
}
$this->control_socket_reset();
fputs($this->contsock, ‘pasv’.chr(10));
// We should get a line which looks like this when we give this command
// ‘227 Entering Passive Mode (206,100,24,34,217,120)’
while ( $tmp=fgets($this->contsock, 512) ) {
$data.=$tmp;
}
// We need to get everything from in between ( and ), so let’s
// make those into something we can work with. We’ll probably
// not see this ‘{|}’ in that line, EVER, so we’ll use that…
$t=str_replace(‘(‘, ‘{|}’, $data);
$t=str_replace(‘)’, ‘{|}’, $t);
// Now we should have a line which looks like this:
// ‘227 Entering Passive Mode {|}206,100,24,34,217,120{|}’
// and we can ‘explode’ it into an array using that string
$te=explode(‘{|}’, $t);
// This should give us:
// $te[0] == ‘227 Entering Passive Mode’
// $te[1] == ‘206,100,24,34,217,120’
// $te[2] == ”
// That wasn’t so bad, was it?
$tem=explode(‘,’, $te[1]);
// This should give us another array which looks like this:
// $tem[0] == 206
// $tem[1] == 100
// $tem[2] == 24
// $tem[3] == 34
// $tem[4] == 217
// $tem[5] == 120
// See http://neworder.box.sk/newsread.php?newsid=4056 for an
// explanation of the next 2 steps…
$port=( ( $tem[4] * 256 ) + $tem[5] );
// You NEED to use the ip address you got with the pasv command… and here’s
// why: do an nslookup on ftp.simtel.net, and you get MANY (15 at the time
// I wrote this article) ip addresses associated with this one host name.
// When you connect to the NAME ‘ftp.simtel.net’ you get a random single ip
// from the host name. And if you tried to do it again, chances are
// that you’d get a different ip the second time. And since only one
// server (the ip that you connected to originally) is expecting you to
// connect on the passive port, you’ll find yourself knee deep in problems
// like self doubt and low self esteem!
$host=$tem[0].‘.’.$tem[1].‘.’.$tem[2].‘.’.$tem[3];
if ( $this->data_socket($host, $port) ) {
return(1);
} else {
return(0);
}
}
?>
And once again, like good little boys and girls, we need to test our new configuration!
<?php
$ftp=new ftp;
if ( $ftp->control_socket(‘ftp.cdrom.com’, 21) ) {
echo ‘We have successfully connected!<br>’;
flush();
$test=$ftp->login();
if ( $test == 1 ) {
echo ‘We have successfully logged in!<br>’;
flush();
if ( $ftp->passive() ) {
echo ‘We have successfully entered PASSIVE mode!<br>’;
} else {
die(‘Passive mode could NOT be initiated!<br>’);
}
} else if ( $test == 0 ) {
die(‘Bad username!<br>’);
} else if ( $test == –1 ) {
die(‘Bad password!<br>’);
}
} else {
die(‘We have failed to connect!<br>’);
}
?>
Congratulations! If you’ve gotten this fat, and understood everything that’s gone on, you’re almost a php-sockets guru! Only a little ways to go, and we’ll have a functional application using one of the least understood function sets in php!
Its nap time! Go to bed! You’ve worked hard. Come back in the morning with a clear mind, and a fresh view on life and a cup of coffee, and we can start out your day by getting to the bottom of this application and these functions!
Sleep well? GREAT! Now let’s finish everything up, and get done with this whole ordeal!
We’ll start with our directory listing. Now, here’s where most people cringe in fear at the thought of having to work with FTP, and all of those pesky socket connections, but its ALSO where YOU learn that it isn’t that difficult at all!
<?php
function ftp_list() {
fputs($this->contsock, ‘LIST’.chr(10));
// Let the FTP server know you
// are interested in a directory
// listing.
if ( $this->cmd_status() ) {
// IF the list command went through OK, then collect our data into
// a nicely packaged array
while ( $data=fgets($this->datasock, 9999) ) {
$return[]=$data;
}
// Close the data socket (because its not useful anymore. Another
// directory listing would require another passive connection.
fclose($this->datasock);
// Return our data! THE HOLY GRAIL! WE’VE MADE IT!
return($return);
} else {
// DOH!
return(0);
}
}
?>
We’ll finish everything off by being courteous to the site owners, and logging out properly…
<?php
function logout() {
// We’ll give the Micro-Crap way of logging out, and the
// handy-dandy-always-setting-standards-and-coming-out-ahead
// unix way of saying goodbye to the server…
fputs($this->contsock, ‘QUIT’.chr(10).‘BYE’.chr(10));
// Then we’ll close the control connection
fclose($this->contsock);
// And since we don’t care one way or another, we assume that
// the function succeeded!
// YAY! THE FINISH LINE!
return(1);
}
?>
And we’ll again modify our test code, and make sure that our now fully functioning, handy dandy FTP directory listing software is fully functional before we approach marketing with an idea for a great new product 😉
<?php
$ftp=new ftp;
if ( $ftp->control_socket(‘ftp.cdrom.com’, 21) ) {
echo ‘We have successfully connected!<br>’;
flush();
$test=$ftp->login();
if ( $test == 1 ) {
echo ‘We have successfully logged in!<br>’;
flush();
if ( $ftp->passive() ) {
echo ‘We have successfully entered PASSIVE mode!<br>’;
$listing=$ftp->ftp_list();
if ( is_array($listing) ) {
echo ‘<pre>’;
foreach ( $listing as $num => $file ) {
echo ‘# ‘.$num.‘ ‘.$file.‘<br>’;
}
echo ‘</pre>’;
$ftp->logout();
} else {
die(‘So close, yet so far… no dir list!<br>’);
}
} else {
die(‘Passive mode could NOT be initiated!<br>’);
}
} else if ( $test == 0 ) {
die(‘Bad username!<br>’);
} else if ( $test == –1 ) {
die(‘Bad password!<br>’);
}
} else {
die(‘We have failed to connect!<br>’);
}
?>
WooWOO! Congratulations! We have a winner! And your prize? Well… er… um… a directory listing? OK, maybe this isn’t the MOST useful socket code ever (seeing as how there are already FTP functions in php). But in reading this article, you’ve accomplished several fundamentally important things in regards to your php coding abilities. You’ve seen how you can use object oriented programming to bundle everything into a neat package–which would have otherwise only been a spaghetti-string-consortium of functions calling one another. You’ve seen how to effectively use sockets and work with data streams (MULTIPLE data streams no less). And you’ve officially let me rant, rave, and talk, more than anyone should ever have had to bear… Congratulations (I think 😉
-Demitrious
Home page: http://www.apokalyptik.com/
I’m also available for *nix and php consulting, e-mail [email protected] if interested. Cheers!