PREVIOUS  TABLE OF CONTENTS  NEXT 

Penguin: The First Tentative Waddle

Felix S. Gallo

Last issue, we took a broad look at the Penguin modules and the fashion in which they implement secure "remotable" computing. This time around, your faithful servant weaves details into the tapestry as we examine the development of a neat 'place' and a nifty 'agent'.

Before we begin, let me note that Penguin, like Perl, is a continuing work. This code is written using 'One Penguin,' the first Penguin release; future releases may have slightly different (and hopefully superior) semantics, which will be explained in the release notes.

Now that the disclaimers are over, let's set the stage. By its nature, Perl seems to attract writers, linguists, packrats, hackers, sharers and cooperators. Let's imagine that we're all of these rolled into one, and that we're a public-minded language hacker with a big collection of assorted text. Further, although we're generally trusting, we want to address the net defensively.

We've decided to make our large collection of text available to the public's agents. Actually, it's all in the form of individual paragraphs, ranging from Shakespeare and Plato to fragments of UNIX manual pages and talk.bizarre articles. Further, we'd really rather enlarge our collection than merely slap it up on a web page for hordes of bored click-surfers.

So we hatch a cunning plan, inspired by Big Bill Burroughs, the author who (notably and notoriously) wrote books by writing hundreds of paragraphs, cutting them out, and rearranging them more-or-less randomly on a page. We will let agents retrieve randomly selected paragraphs from our database two at a time. After an agent has received two paragraphs, we expect that she will also send us a brand new paragraph. Perhaps the agent will display the two paragraphs to her human master, who will then mull them over for a bit and try to write a paragraph which could naturally follow from both. Perhaps the agent has no human master, and instead pretends to have one by grabbing a paragraph from Usenet and submitting it blithely. Or perhaps the agent is another Burroughs server trading paragraphs with us. We don't care; we only wish to provide the mechanisms and a loose set of expected rules.

So our plan is set. But before we start scribbling code willy-nilly, it may be illuminating to spend a moment visiting the Penguin architecture.

Penguin is written in object-oriented Perl. It has three main classes: Channels, Frames and Wrappers. A Channel is an abstract conduit through which two Penguin programs communicate; as an example, a TCP Channel is included with Penguin which uses sockets to make the connection. A Frame is the basic unit of communication over a Channel; two Penguin programs chat by sending various kinds of Frames to each other. A Wrapper is an object which knows how to translate usable information (i.e., code and data) into Frames and vice versa, possibly digitally signing it or verifying the signature at the same time.

These concepts may seem alien, but in practice they flow fairly naturally. And, if you get lost, the documentation goes into relatively lavish detail.

Without further ado, a-coding we will go!

#!/usr/bin/perl   

use Penguin;              
use Penguin::Rights; 

# we want to receive code
use Penguin::Frame::Code; 

# we want to send data
use Penguin::Frame::Data; 

# PGP-signed frames are OK
use Penguin::Wrapper::PGP; 

# but we'll accept transparent ones too
use Penguin::Wrapper::Transparent; 

# required for execution of foreign code 
use Penguin::Compartment;

# we'll serve Penguin on a TCP port
use Penguin::Channel::TCP::Server; 

# where getparagraphs, etc. are implemented 
require 'paragraph_db_funcs.pl'; 

Note that 'paragraph_db_funcs.pl' is not explicated here for reasons of space and tediousness. However, it is available, along with the rest of this source code, inside the Penguin distribution and on the TPJ web site.

$PGP_PASSWORD = "kwisatz haderach"; 

If we receive PGP-signed frames, PGP will need to be able to open the local PGP keyfile to find out who signed them; hence, we must provide it with our password. If this were a real application, we'd probably have the program turn echoing off and issue a prompt for the password instead of storing it in plaintext.

$rightsdb = new Penguin::Rights;  

$rightsdb->get();

Every Penguin program that cares about security instantiates a Rights object. This object (along with an associated file, usually called .rightsfile) keeps track of the people allowed to connect to a Penguin service and their associated access levels. See the pod pages for the exhaustively gory details.

$mychannel = new Penguin::Channel::TCP::Server 
    Bind => 8585, Listen => 5; 

Our Burroughs server will listen on port 8585, and will maintain a queue of 5 agents waiting for a connection.

while(1) {     

    # blocks waiting for an agent to connect  
    $mychannel->open(); 

    ($remotehost, $remoteport) = $mychannel->getinfo();
    print "agent arrived from $remotehost, ". 'date' . "\n";

    # blocks waiting for a frame  
    $frame = $mychannel->getframe(); 

    if ($frame eq undef) {         
        print "agent was confused.\n";         
        $mychannel->close();         
        next;     
    }  

    ($title, $signer, $wrapmethod, $code) = 
	  $frame->disassemble(Password => $PGP_PASSWORD); 

If the frame was signed, and that person's PGP key is on our PGP keyring, then we'll know who they were and possibly be able to give them a higher (or, imaginably, lower) level of access. If the frame is not signed, then they'll get 'default' rights. For the Burroughs server, the 'default' rights are pretty permissive; default users can execute a fairly large set of opcodes, notably excepting file access and many opcodes which could deny service. See the 'SECURITY' file included with the Penguin distribution for a lengthy discussion of same.

    $userrights = 
        $rightsdb->getrights(User => $signer);  

    print "agent is $title from $signer,", 
          " rights: $userrights\n";  

    $compartment = new Penguin::Compartment;     
    $compartment->initialize(Operations => $userrights);  

    $compartment->register(Share => "getparagraphs");    
    $compartment->register(Share => "putparagraph"); 

Above we create a new compartment in which the foreign code will execute; because we don't want the foreign agent to be able to rummage through all of our files, we supply two "helper functions" which are the agent's only access to our paragraph database.

    $result = $compartment->execute(Code => $code);     
    if ($@) { # illegal code tried to execute 

Naughty, naughty! We report the misdeed in case someone was trying to make a nuisance out of themselves, and also return the agent's error message in case a human at the other end made a mistake.

        $result = $@;         
        print "agent overstepped: $code\n";     
    } 

Lastly, we put together a data frame containing the results of the execution of the agent in the code frame, send it back to the waiting application on the other side, and merrily wait for the next agent to arrive.

 
    $resultframe = new Penguin::Frame::Data;     

    # Data frames always Transparent     
    $resultframe->assemble(Text => $result); 

    $mychannel->putframe(Frame => $resultframe); 
    $mychannel->close(); 
} 

As may be evident, Penguin's syntax is fairly simple once you grasp the 'channel' and 'frame' metaphors. Because this application is fairly trusting, non-complex and not particularly demanding, it doesn't perform extensive checking. A more rigorous application might demand that all incoming code packets be PGP-signed, and further that the signer be known to the application before any execution is attempted. Penguin allows for this level of security awareness as well.

So what have we done so far? We've implemented a Penguin server which sits on port 8585, waiting for agents to come calling. When an agent arrives, the server will set up a secure compartment, place the getparagraphs() and putparagraph() functions and the agent in that compartment, and then execute it. The result of the compartment is then sent back across the wire.

But as those of us who were nerds in high school know, it's no fun going to a dance if you go solo. So here's a sample client which connects to our Burroughs server. It's named chick, and its call syntax is 'chick <hostname> <portnumber>'.

#!/usr/bin/perl  

use Penguin; 
use Penguin::Rights; 
use Penguin::Frame::Code; 
use Penguin::Frame::Data; 

# We're not going to PGP-sign our frames.
use Penguin::Wrapper::Transparent;  

use Penguin::Compartment; 
use Penguin::Channel::TCP::Client;  

$targethost = shift; 
$targetport = shift;  

$mychannel = new Penguin::Channel::TCP::Client 
    Peer => $targethost,
    Port => $targetport;  

$mychannel->open() or die 
    "couldn't connect there.\n";  

$frame = new Penguin::Frame::Code 
    Wrapper => 'Penguin::Wrapper::Transparent'; 

This program will actually connect to the server twice; each time, it will send one eentsy agent. Note that it would be very easy to modify this program to perform interesting processing on the server (if the server so allows).

# frames not signed, password not needed
$frame->assemble( Password => '', 
                      Text => "&getparagraphs()\n", 
                     Title => "Chick",              
                      Name => "Joe Random User");  

$mychannel->putframe(Frame => $frame);

Having dropped a frame on the server, we now switch roles and wait for the server to let us know how our little agent did. Since our agent is composed of merely the 'getparagraphs' function call, we won't even check to see if there was a problem. More conscientious (and conscious) agents would probably work differently.

$returnframe = $mychannel->getframe();  

$results = $frame1->disassemble(Password => '');  

print "Paragraphs from $targethost, $targetport\n"; 
print "Enter your paragraph following the second line.\n"; 
print "Terminate it with a blank line.\n"; 
print "--------------\n"; 
print $results, "\n"; 
print "--------------\n";  

$paragraph = ""; 
Now we ask our user for a paragraph so we can send it to the server. It's important to note that the server, being somewhat simplistic, has hung up on us by now, so we'll have to reattach; on the other hand, this is a good thing, because if the user is playing by the rules, she may be sallying forth on a serious literary effort.

while (chomp($line = <STDIN>)) {     
    last if $line eq '';     
    $paragraph = $paragraph . $line . "\n"; 
}  

# make minor attempt to escape single quotes to prevent 
# agent from screwing up  
$paragraph =~ s/\'/\\\'/; 

$mychannel->close();  

$mychannel = new Penguin::Channel::TCP::Client 
    Peer => $targethost, 
    Port => $portnumber;  

$frame = new Penguin::Frame::Code 
    Wrapper => 'Penguin::Wrapper::Transparent'; 

# frames not signed, password not needed.
$frame->assemble( Password => '', 
    Text  => "&putparagraph('$paragraph')\n"; 
    Title => "Chick",
    Name  => "Joe Random User"); 

$mychannel->putframe(Frame => $frame); 
$returnframe = $mychannel->getframe();  

$results = $frame1->disassemble(Password => ''); 
print "Server says: $results\n";

At last, a pair of programs that can make beautiful music together. Here's a sample run of chick:

 
$ chick coriolan.amicus.com 8585 
Paragraphs from coriolan.amicus.com, 8585 
Enter your paragraph following the second line. 
Terminate it with a blank line. 
-------------- 
Breathes there a man with soul so dead, that to himself has 
never said, this is my home, my native net?  

Gosling crouched behind the burning trashcan, the wild flames 
dancing in the twin moons of his glasses.  Must keep moving, he 
thought to himself.  In the store window across the street, a 
wall of televisions kept replaying the footage of the black 
wave that still streamed into California, waddling towards 
JavaSoft headquarters... 
-------------- 
And thus falls the great chilly tuxedoed wrath upon all who 
create ugly languages!  

Server says: Thanks! 
$  

Whups, got carried away for a minute. As you can see, the operation was a success; with just a few lines of Perl, we have created a full fledged interactive Internet literary depository. With just a little imagination, the methods and code we've used could be put to use in games, distributed prime number factoring platforms, shared address books, and object brokers, just to name a few applications.

Grab Penguin and waddle forth!

A brief word about the Burroughs server: as of this writing, this server does exist, and stands ready to serve paragraph requests and accept submissions. chick, the client program depicted above, is available from the Penguin distribution site; however, I'd be happy to see other agents working on the Burroughs server, doing whatever they felt like doing. Modify chick for your own nefarious purposes, or assemble your own agent.

The Penguin distribution site is currently alive and well at http://coriolan.amicus.com/penguin.html; however, Penguin is also available through CPAN, so you may wish to try there first. The Penguin mailing list, penguin@amicus.com, is a good place to ask questions; subscribe and unsubscribe to it by sending mail to penguin-request@amicus.com.

Next issue, in Penguin: Safe and Secure, we travel to distant lands and other modules to explore in depth how Penguin implements its security model. Until then, stay frosty!

__END__


Felix Gallo goes with the floe in Austin, Texas as the lead Perl hacker for Amicus Networks.


PREVIOUS  TABLE OF CONTENTS  NEXT