Several weeks ago my friend Madars was in an airport in the Netherlands and he wanted to login into his server via ssh. It turned out that their public internet had only ports 80 and 443 open so he couldn't do that. He asked me if I could proxy either port 80 or 443 to his server. Surely, I had a solution. I modified the tcp proxy server that I had written for my Turn any Linux computer into SOCKS5 proxy in one command article and did:

sudo ./tcp-proxy2.pl 443 madars-server.com:22

This proxied the port 443 on my server to madars-server.com ssh port. Now Madars could do

ssh -p 443 catonmat.net

and he got connected to his server. Mission accomplished.

Here is the code of tcp-proxy2.pl,

use warnings;
use strict;

use IO::Socket::INET;
use IO::Select;

my @allowed_ips = ('all', '10.10.10.5');
my $ioset = IO::Select->new;
my %socket_map;

my $debug = 1;

sub new_conn {
    my ($host, $port) = @_;
    return IO::Socket::INET->new(
        PeerAddr => $host,
        PeerPort => $port
    ) || die "Unable to connect to $host:$port: $!";
}

sub new_server {
    my ($host, $port) = @_;
    my $server = IO::Socket::INET->new(
        LocalAddr => $host,
        LocalPort => $port,
        ReuseAddr => 1,
        Listen    => 100
    ) || die "Unable to listen on $host:$port: $!";
}

sub new_connection {
    my $server = shift;
    my $remote_host = shift;
    my $remote_port = shift;

    my $client = $server->accept;
    my $client_ip = client_ip($client);

    unless (client_allowed($client)) {
        print "Connection from $client_ip denied.\n" if $debug;
        $client->close;
        return;
    }
    print "Connection from $client_ip accepted.\n" if $debug;

    my $remote = new_conn($remote_host, $remote_port);
    $ioset->add($client);
    $ioset->add($remote);

    $socket_map{$client} = $remote;
    $socket_map{$remote} = $client;
}

sub close_connection {
    my $client = shift;
    my $client_ip = client_ip($client);
    my $remote = $socket_map{$client};
    
    $ioset->remove($client);
    $ioset->remove($remote);

    delete $socket_map{$client};
    delete $socket_map{$remote};

    $client->close;
    $remote->close;

    print "Connection from $client_ip closed.\n" if $debug;
}

sub client_ip {
    my $client = shift;
    return inet_ntoa($client->sockaddr);
}

sub client_allowed {
    my $client = shift;
    my $client_ip = client_ip($client);
    return grep { $_ eq $client_ip || $_ eq 'all' } @allowed_ips;
}

die "Usage: $0 <local port> <remote_host:remote_port>" unless @ARGV == 2;

my $local_port = shift;
my ($remote_host, $remote_port) = split ':', shift();


print "Starting a server on 0.0.0.0:$local_port\n";
my $server = new_server('0.0.0.0', $local_port);
$ioset->add($server);

while (1) {
    for my $socket ($ioset->can_read) {
        if ($socket == $server) {
            new_connection($server, $remote_host, $remote_port);
        }
        else {
            next unless exists $socket_map{$socket};
            my $remote = $socket_map{$socket};
            my $buffer;
            my $read = $socket->sysread($buffer, 4096);
            if ($read) {
                $remote->syswrite($buffer);
            }
            else {
                close_connection($socket);
            }
        }
    }
}

Download tcp-proxy2.pl

Download link: tcp proxy 2 (tcp-proxy2.pl)
Download URL: http://www.catonmat.net/download/tcp-proxy2.pl
Downloaded: 3729 times

I also pushed the tcp-proxy2.pl to a new GitHub repository: tcp-proxy2.pl on GitHub.

Enjoy!

I was interviewed by David Weekly recently on 2011-03-19. I copied the interview here in case David's website changes. It was an audio podcast with me and James Halliday.

Original link to the interview.

David Weekly interviews James Halliday and Peteris Krumins, the CEO and CTO (respectively) of the newly-established Browserling, a browser testing firm in which David made his first angel investment. (The interview was done a mere hour after closing the round!) James describes his drive down from Alaska to the Bay Area to pursue funding for his startup and Peteris describes his childhood with computers in Latvia and his meeting James on IRC.

Download The David Weekly podcast with Peteris Krumins and James Halliday.

Here's a pic we took on the investment day:


James Halliday, David Weekly, Peteris Krumins

There are two ways you can change file permissions in Unix - one is using chmod's symbolic (text) modes (like chmod ug+x file), the other is using the octal modes (like chmod 0660 file). It turns out that symbolic modes are more powerful because you can mask out the permission bits you want to change! Octal permission modes are absolute and can't be used to change individual bits. Octal modes are also sometimes called absolute because of that.

Here is an example that illustrates that.

Suppose you have this file,

$ ls -las file
0 -rw-rw----  1 pkrumins  cats  0 Feb 25 12:49 file

and you want to set the execute x bit for everyone. If you use symbolic modes, you can just do,

$ chmod a+x file
$ ls -las file
0 -rwxrwx--x  1 pkrumins  cats  0 Feb 25 12:49 file

But, if you're used only to absolute modes, then you first need to calculate the existing permission mask, which would be 0660, and then calculate the new one by adding the 1 to each group, so the end result would be 0771,

$ chmod 0771 file
$ ls -las file
0 -rwxrwx--x  1 pkrumins  cats  0 Feb 25 12:49 file

It's still easy with octal modes, but why do it the harder way when you can just chmod a+x file, right?

I'd also like to note that symbolic modes are a feature of chmod utility. If you look at chmod system call, it only takes the octal mode. The chmod utility first does a stat to determine the existing mode, then calculates the new one. It does the calculation for you. So don't do it again yourself!

For my Browserling startup I decided to use Haskell to write Windows software that manages the browsers. As I haven't written much about Haskell here on catonmat before, I decided to do a small write-up on how to create a TCP server in Haskell. To keep the article simple, this TCP server will just listen on the given port on command line and execute some simple commands.

Sidenote - People often ask me how do I learn new languages. Talking about Haskell, I learned the basics from Graham Hutton's awesome book Programming in Haskell and after that asked questions in #haskell Freenode channel, and Googled a ton.

Okay, let's get started. So first we need to include the necessary functions and data constructors from Haskell libraries. This particular TCP server will use the Network library to listen on a port and accept connections, System library to get command line arguments, System.IO library to change socket handle's buffering mode, to read and write it, and Control.Concurrent library to spawn new Haskell threads.

So let's import the necessary stuff,

import Network (listenOn, withSocketsDo, accept, PortID(..), Socket)
import System (getArgs)
import System.IO (hSetBuffering, hGetLine, hPutStrLn, BufferMode(..), Handle)
import Control.Concurrent (forkIO)

Every Haskell program begins with the main function, so let's write one. Our main function will parse the port command line argument, start the server on the port, and then call sockHandler function that will accept connections and put them in a new thread,

main :: IO ()
main = withSocketsDo $ do
    args <- getArgs
    let port = fromIntegral (read $ head args :: Int)
    sock <- listenOn $ PortNumber port
    putStrLn $ "Listening on " ++ (head args)
    sockHandler sock

The withSocketsDo function is only necessary for Windows and it initializes the Windows winsock stuff.

The getArgs function returns a list of arguments that were passed on command line. For example, if we run the program as server.exe 5555, then args gets assigned a list ["5555"].

The next line converts the first element in the argument list to an Integer.

Next we start listening on the port by using listenOn. The code listenOn $ PortNumber port is just short for listenOn (PortNumber port). The dollar sign function $ allows to avoid using parenthesis.

Then we simply print a string to console which port we're listening by using putStrLn from Prelude Haskell library, which is almost always implicitly imported.

Next we call sockHandler function and pass it the sock that was returned from the listenOn function.

Now let's take a look at sockHandler function. This function takes a Socket and returns "nothing", so its type signature is sockHandler :: Socket -> IO (),

sockHandler :: Socket -> IO ()
sockHandler sock = do
    (handle, _, _) <- accept sock
    hSetBuffering handle NoBuffering
    forkIO $ commandProcessor handle
    sockHandler sock

If we look at accept function's documentation, we find that it returns a tuple of 3 elements (Handle, HostName, PortNumber). In this tutorial we are not interested in the connecting party's hostname or port, so we use _ to discard last two args. accept is a blocking call, so it will return the handle only when a new connection comes in.

Next we use hSetBuffering to change buffering mode for the client's socket handle to NoBuffering, so we didn't have buffering surprises.

Then we use forkIO to call commandProcessor function in a new Haskell thread. We do this so that we could handle more than one client connection. We'll write commandProcessor soon.

Finally we recurse and call the same sockHandler function again to handle more incoming connections. Haskell optimizes for tail recursion, so we never get stack overflows in long run.

Now let's write commandProcessor. It takes a Handle and returns IO () so its type is commandProcessor :: Handle -> IO ()

commandProcessor :: Handle -> IO ()
commandProcessor handle = do
    line <- hGetLine handle
    let cmd = words line
    case (head cmd) of
        ("echo") -> echoCommand handle cmd
        ("add") -> addCommand handle cmd
        _ -> do hPutStrLn handle "Unknown command"
    commandProcessor handle

Here we use hGetLine to get a line of text from handle, then we use the words from Prelude to split the line into words, and then we use the case statement on head of the command to find out which command was sent to the server, and call the right command function.

For example, if you send echo hello world to the server, line gets set to the string "echo hello world", then words function splits it into a list of words, ["echo", "hello", "world"], and head ["echo", "hello", "world"] is just "echo", so the echoCommand executes. Same for addCommand.

If it's an unknown command, we send the string "Unknown command" back to the client by using hPutStrLn function that writes the given string followed by a new line to the given handle.

Finally we recurse on commandProcessor so that we could execute several commands over the same connection.

Here is the echoCommand. It sends everything it receives back to the client. This function's type is Handle -> [String] -> IO (), because it takes the client socket handle, the list of command words, which are string, and returns nothing, so

echoCommand :: Handle -> [String] -> IO ()
echoCommand handle cmd = do
    hPutStrLn handle (unwords $ tail cmd)

Here hPutStrLn sends the given string to the given handle. The string in this case is unwords $ tail cmd, which is basically everything but the first word. So for example, if cmd was ["echo", "hello", "world"], then tail cmd is ["hello", "world"] and unwords ["hello", "world"] is the string "hello world".

The other command is addCommand, it takes the two numbers the client sends to it, adds them together and sends back,

addCommand :: Handle -> [String] -> IO ()
addCommand handle cmd = do
    hPutStrLn handle $ show $ (read $ cmd !! 1) + (read $ cmd !! 2)

Here we first take the second cmd !! 1 and third cmd !! 2 elements from cmd string list, then read it converts it to integers, which get added together, and then show converts the result back to a string, which gets sent back to client.

Here is the whole program, server.hs:

import Network (listenOn, withSocketsDo, accept, PortID(..), Socket)
import System (getArgs)
import System.IO (hSetBuffering, hGetLine, hPutStrLn, BufferMode(..), Handle)
import Control.Concurrent (forkIO)

main :: IO ()
main = withSocketsDo $ do
    args <- getArgs
    let port = fromIntegral (read $ head args :: Int)
    sock <- listenOn $ PortNumber port
    putStrLn $ "Listening on " ++ (head args)
    sockHandler sock

sockHandler :: Socket -> IO ()
sockHandler sock = do
    (handle, _, _) <- accept sock
    hSetBuffering handle NoBuffering
    forkIO $ commandProcessor handle
    sockHandler sock

commandProcessor :: Handle -> IO ()
commandProcessor handle = do
    line <- hGetLine handle
    let cmd = words line
    case (head cmd) of
        ("echo") -> echoCommand handle cmd
        ("add") -> addCommand handle cmd
        _ -> do hPutStrLn handle "Unknown command"
    commandProcessor handle

echoCommand :: Handle -> [String] -> IO ()
echoCommand handle cmd = do
    hPutStrLn handle (unwords $ tail cmd)

addCommand :: Handle -> [String] -> IO ()
addCommand handle cmd = do
    hPutStrLn handle $ show $ (read $ cmd !! 1) + (read $ cmd !! 2)

To use it, just run it via runhaskell server.hs 5555, and the server will start on port 5555. Or you can compile it to binary with ghc -threaded --make server.hs and then execute server binary.

And server.hs is also available for download,

Download server.hs

Download link: haskell tcp server, server.hs
Downloaded: 2939 times

This tutorial didn't cover error handling, but it's pretty easy to add through exception catching. I'll cover that in the 2nd part of the article! Until next time!

At Browserling I just solved an interesting Windows sysadmin problem of how to allow Windows users to run just a few programs and nothing else. Since anyone can use Browserling for free, I had to find a way to restrict them from downloading and running viruses and trojans. So I took time to investigate and found a very elegant solution. No one had really documented this so this blog post will be very handy for people who can't figure it out themselves.

The solution is to configure the Software Restriction Policy (SRP) in the user's Group Policy Object (GPO) and disallow the user to run everything except the programs that are necessary to login and the programs you want the user to use.

The hardest part was figuring out all the programs that the user absolutely must be allowed to run for him/her to login the system:

  • C:\Windows\explorer.exe
  • C:\Windows\System32\csrss.exe
  • C:\Windows\System32\dwm.exe
  • C:\Windows\System32\rdclip.exe
  • C:\Windows\System32\taskhost.exe
  • C:\Windows\System32\TSTheme.exe
  • C:\Windows\System32\userinit.exe

If you don't have these in the SRP, then the user will never be able to login. So make sure they are in the SRP's "Additional Rules" and their security level is "Unrestricted."

After you add these, the user will be able to login but he wont be able to run anything! He'll just see desktop and that's it. If you want to allow the user to run, let's say, Paint, then add C:\Windows\System32\paint.exe the Unrestricted Access list.

Here is a precise list of steps to take to disallow running programs. Run them from Administrator account:

1. Open up the Microsoft Management Console (Start -> Run -> mmc):

2. Select File -> Add/Remove Snap-in.

3. Select Group Policy Object.

4. Click Add.

5. Click Browse, select the user you want to configure the GPO for.

6. Click Finish, and OK. Now you'll see the tree view with "<username> Policy."

7. Navigate to User Configuration -> Windows Settings -> Security Settings -> Software Restriction Policies.

8. Right click on Software Restriction Policies and click New Software Restriction Policies.

9. Two new items will appear the tree. Click the Security Levels.

10. Double click the Disallowed security level and click "Set as Default."

11. Go to the other item, the Additional Rules.

12. Delete the crap that Windows adds there by default (%HKEY_LOCAL_MACHINE\... something).

13. Add the rules that I documented in the beginning of the article. They are absolutely necessary for the user to login into the machine (either via desktop or remote desktop).

14. Add full paths to programs that you wish to allow the user to run (such as C:\windows\system32\paint.exe, ... etc.). Make sure the users can't overwrite the programs with their own, otherwise they might be able to execute their own programs.

15. Save the GPO.

16. Done!

Thanks to lewellyn for helping out with all this!

Ps. As I like to joke, the new title of this blog soon will be "Peteris Krumins's blog on Windows Administration" as I have spent so much time during past month messing with Windows. ;)