Tag Archives: perl

How to Automate Twitter – a bit at least !

Perl

Perl (Photo credit: Wikipedia)

I’ve been trying to push up the readership of the blog here ( and get some people to stick around a bit, subscribe, follow on twitter etc. )  I’m not a Facebooker – I do have an account ( or two … ) but they contain nothing much of interest, they were created in order to investigate how FB worked, rather than anything else, so I’m not exactly the stereotypical user ! I make use of LinkedIn and Twitter as my online “social” tools and I’ve not graduated beyond that. The trouble is, I believe, in the transient nature of Twitter – I Tweet and it disappears off the bottom of the screen in seconds as other’s posts come in and push it down. I’ve watched for a while, and it seems that the “successful” Tweeters post their links frequently – keeping them in view for a longer period of time.

Now, I have to admit that I am lazy, but also geeky – I want to post a tweet advertising the blog frequently, but without user interaction. I’m sure that people will pop-up and tell me of things that automagically do this for me – HootSuite springs to mind – but having used it, it has already upset me with it’s scheduling system – the CSV upload is a pain, and, as of yet, I’ve not managed a single one without an error. Sooo, as I spent a while ago messing around with Twitter and Perl, I thought that the easiest way forward might just be to write my own.

For want of a better methodology, as I intend to post once a week, I want each entry alerted on immediately, and then in increasing intervals until the next post is due out ( 1 week hence ). I don’t want mid-term posts to reset the last weeks worth, but if it is relevant ( like I hope all posts are !) then I do want to publicise it for a full week as well. I’ve tried a couple of exponential increases, (2 * last period, 1.5 * last period ), but to be honest as I’m sure you can imagine, it gets up to over a day fairly quickly … (Google “exponential” if you want to know more !)  So I’m going to say day one is once every two hours, day two is once every three hours, day three once every four hours, day four is four times in the day, day five is three times a day, day six is twice, and just the once on the seventh day – heck, if God can take a rest, so can our program ! That gives us a total of 36 Tweets, weighted towards the start whilst the post is fresh and tailing off as the new post comes along.

As always stated with my programming posts, I’m not a programmer, any similarity to programmers living or dead is entirely coincidental. I like programming in Perl, because, not only is “there more than one way to do it”, I can usually figure out at least one of those particular permutations – elegant as my solution may not be … [ if you want to see elegant programming – and the output of the man that I go to when I get stuck – have a look over here. Shamefully he wastes his time in the world of Microsoft, but we forgive him a lot 😉 ]

It turns out, much to my annoyance that the authentication methods that I was using in the “Hacking around with Twitter” is no longer valid. It seems that I now need to use OAuth1 … However, after several hours of buggering around with it I failed completely to get it to work. So back to the drawing board there …

Python anybody ?

English: Python logo Deutsch: Python Logo

English: Python logo Deutsch: Python Logo (Photo credit: Wikipedia)

I’ve been meaning to get cracking with Python for some time. I was a die hard Perl fan until the day I saw the graphs that came from matplotlib – I was taken by the quality and professionalism of them, and I immediately spent far more money than can be considered sensible on all sorts of Python books so that I too, could make maths and art become one and the same thing. It seems though that I have the same level of programming ability as a garden slug when it comes to moving languages, and the same sort of speed of movement. It took me three years (ish) at university to learn C [ and ML and Prolog – but let’s be honest, neither of those actually count as programming languages ] and it’s taken me countless years since to learn to threaten, coerce and cajole Perl to do my bidding at least 50% of the time.

This, then, is my forced introduction to Python – my baptism of fire ( although God only knows why, if I can’t do it in Perl I stand the least bit of chance in Python ! ). And, not only that, I’m going to push it out here for your ridicule and derision.

Another day, I’d like to walk through the Rackspace cloud with you, but that’s for another day – let us just say, that I quickly threw up an Fedora 15 (Lovelock) instance to play with, and was deeply relieved that Python appears to be a standard part of the distribution. For reference my development environment also consists of Komodo Edit, which is excellent, with supported syntax highlighting for both Perl and Python ( and HTML and C and C++ and … ) also, when correctly configured, is quite happy using scp to remotely edit files and browse remote directories.

I understand that the Python equivalent of CPAN is PyPI – the Python Package Index – and, after installing the package, I’ve used that to install the Tweepy library. I’m not going to repeat the guidance on creating a new application in either (both!) of the blog links below – what I will say though is that you should remember to set your application settings to Read and Write – otherwise it won’t work 😉2

I’ve split the examples out so that there is a config file that holds the various keys. It’s format is as follows:

[consumer_keys]
CONSUMER_KEY = consumer_key_here
CONSUMER_SECRET = consumer_secret_here
[access_keys]
ACCESS_KEY = access_key_here
ACCESS_SECRET = access_secret_here

Obviously insert your own, hard earned keys in here – no inverted commas or anything they get parsed in a minute with ConfigParser. [ Basically, I couldn’t go through the rest of this worrying about accidentally publishing my keys every five minutes. ]. I used the script provided in the example to do this, although it seems that you can generate these keys for your own Twitter account in the developer section of the site without going through the pain or the learning experience.

I’m getting worried how long this post is getting – especially after a discussion with a young man the other day who said that his dissertation was 5000 words only and I’ve written a 5th of that ! – so below is the remainder of the sample code for a command line client, this takes text after the command ( contained in ‘ ‘ ) and updates your status with it ( e.g. ./twitter.py ‘It lives!’ ):

#!/usr/bin/env python

import sys
import tweepy
from ConfigParser import SafeConfigParser

parser = SafeConfigParser()
parser.read('twitter.conf')

CONSUMER_KEY = parser.get('consumer_keys','CONSUMER_KEY')
CONSUMER_SECRET = parser.get('consumer_keys','CONSUMER_SECRET')
ACCESS_KEY = parser.get('access_keys','ACCESS_KEY')
ACCESS_SECRET = parser.get('access_keys','ACCESS_SECRET')

auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET)
auth.set_access_token(ACCESS_KEY, ACCESS_SECRET)
api = tweepy.API(auth)api.update_status(sys.argv[1])

I’ll write a second post within the next week to update the remainder with a full program to automate the remainder of the posting process – I want to get it running asap to be honest, as I think I’m missing out !


1. With thanks to David Moreno’s blog post on the issue as my starting point on OAuth for Perl, and perhaps the first and last bit of it that I understood ! And Jeff Miller’s blog post for the Python equivalent.
2. Which may well be why I couldn’t get the darn Perl version to work, I realise now. However, a kick in the pants, is a kick in the pants for whatever reason it comes …

Tagged , , , , , , , , , , ,

Hacking around with Twitter (Part 3 !) …

Well that took a little while, but it is now all done 🙂

Et voila :

#!/usr/bin/perl -w
#use strict;

use Getopt::Long;
use Storable;
use Net::Twitter::Lite;
use Crypt::OpenSSL::Random;
use Crypt::OpenSSL::RSA;
use MIME::Base64;

#----------------------------------------------#
#                  This is :                   #
 $clientname = "twitter crypt";       #
#                  Version :                   #
 $clientver = "0.1";            #
#----------------------------------------------#
#            By : Simon Biles                  #
$clienturl="http://computersecurityonline.com";#
#----------------------------------------------#

# Saved Public Keys File
$Public_Keys_File = "twc_public_keys";

GetOptions("genkey=s" => \$genkey,
 "publishkey=s" => \$publishkey,
 "getkeys" => \$getkeys,
 "showkeys" => \$showkeys,
 "encrypt=s" => \$encrypt,
 "getmessages=s" => \$getmessages,
 "user=s" => \$user,
 "pass=s" => \$password );

if (defined $genkey){
 $good_entropy = "10010101001010101";

 Crypt::OpenSSL::Random::random_seed($good_entropy);
 Crypt::OpenSSL::RSA->import_random_seed();

 $rsa = Crypt::OpenSSL::RSA->generate_key(640);

 $private = $rsa->get_private_key_string();
 $public = $rsa->get_public_key_string();

 print "\nprivate key is:\n $private\n";
 print "\npublic key (in PKCS1 format) is:\n $public\n"; 

 open(KEYFILE, "> $genkey") or die $!;
 print KEYFILE "$private\n$public";
 close KEYFILE;

 print "\nKeys written to file $genkey ...\n\n";

 exit 0;
}

if (defined $publishkey && defined $user && defined $password){

 my $nt = Net::Twitter::Lite->new(
 username => $user,
 password => $password,
 clientname => $clientname,
 clientver => $clientver,
 clienturl => $clienturl
 );

 open (KEYFILE, "< $publishkey");
 $flag = 0;
 $public_key = "";
 while (<KEYFILE>){
 if ($_ =~ /-----END RSA PUBLIC KEY-----/) { last; };
 if ($flag == 1) { $public_key = $public_key.$_; };
 if ($_ =~ /-----BEGIN RSA PUBLIC KEY-----/) { $flag = 1; };
 }

 $public_key = "twc-public -".$public_key;

 $key_length = length $public_key;

 print "Length: $key_length \n$public_key";

 if($key_length > 140){
 print "\nFor some reason we've over shot our 140 chars for Twitter, complain ...\n";
 exit 0;
 }

 my $result = eval { $nt->update($public_key) };

 exit 0;
}

if($getkeys && defined $user && defined $password){

if(-e $Public_Keys_File){
 %keys_hash = %{retrieve($Public_Keys_File)};
} else {
 %keys_hash = ();
}

 my $nt = Net::Twitter::Lite->new(
 username => $user,
 password => $password,
 clientname => $clientname,
 clientver => $clientver,
 clienturl => $clienturl
 );

 eval {
 my $statuses = $nt->friends_timeline();
 for my $status ( @$statuses ) {
 if ($status->{text} =~ /twc-public -/){
 ($public_key_string = $status->{text}) =~ s/twc-public -//;
 $keys_hash{ $status->{user}{screen_name} } = $public_key_string;
 }
 }
 };
 warn "$@\n" if $@;

store(\%keys_hash, $Public_Keys_File) or die "\nCan't save public keys to $Public_Keys_File.\n\n";

 exit 0;
}

if($showkeys){

if(-e $Public_Keys_File){
 %keys_hash = %{retrieve($Public_Keys_File)};
 while (($key, $value) = each(%keys_hash)){
 print "$key - $value\n";
 }

} else {

 print "\n\n Public Keys file $Public_Keys_File does not exist, create it using the --getkeys option.";

}

 exit 0;
}

if(defined $encrypt && defined $user && defined $password){

if(-e $Public_Keys_File){
 %keys_hash = %{retrieve($Public_Keys_File)};

 $encryption_key = "-----BEGIN RSA PUBLIC KEY-----\n"."$keys_hash{$encrypt}\n"."-----END RSA PUBLIC KEY-----\n";

 print "$encryption_key";

 $rsa_pub = Crypt::OpenSSL::RSA->new_public_key($encryption_key);
 print "                                           |------------------------------------|\n";
 print "Enter your message : (between the lines !) ";
 $plaintext = <STDIN>;    

 if ((length $plaintext) > 38){
 print "Message too long I'm afraid, I did say to stay between the lines ...\n";
 exit 0;
 }

 $ciphertext = $rsa_pub->encrypt($plaintext);

 $encoded_ciphertext = "twc-msg-$encrypt-".encode_base64($ciphertext);
 $encoded_length = length $encoded_ciphertext;

 if ($encoded_length > 140){
 print "Uh-oh, we've had an over Twitter length moment there !\n";
 exit 0;
 }

 my $nt = Net::Twitter::Lite->new(
 username => $user,
 password => $password,
 clientname => $clientname,
 clientver => $clientver,
 clienturl => $clienturl
 );

 my $result = eval { $nt->update($encoded_ciphertext) };

} else {
 print "\n\n Public Keys file $Public_Keys_File does not exist, so there is no key for $encrypt create it using the --getkeys option.";
 exit 0;
}

 exit 0;
}

if(defined $getmessages && defined $user && defined $password){

$private_key = "";

 if (-e $getmessages){
 open (FILEHANDLE, "< $getmessages");
 while(<FILEHANDLE>){
 if ($_ =~ /-----BEGIN RSA PRIVATE KEY-----/){
 $flag = 1;
 }
 if ($flag == 1){
 $private_key = "$private_key"."$_";
 }
 if($_ =~ /-----END RSA PRIVATE KEY-----/){ last; }
 }

 $rsa_priv = Crypt::OpenSSL::RSA->new_private_key($private_key);

 my $nt = Net::Twitter::Lite->new(
 username => $user,
 password => $password,
 clientname => $clientname,
 clientver => $clientver,
 clienturl => $clienturl
 );

 eval {
 my $statuses = $nt->friends_timeline();
 for my $status ( @$statuses ) {
 if ($status->{text} =~ /twc-msg-$user-/){
 $from = $status->{user}{screen_name};
 ($encoded_ciphertext = $status->{text}) =~ s/twc-msg-$user-//;
 $ciphertext = decode_base64($encoded_ciphertext);
 $decrypted = $rsa_priv->decrypt($ciphertext);
 print "Message from $from : $decrypted\n";
 }
 }
 };
 warn "$@\n" if $@;

 exit 0;
 } else {
 print "That key file doesn't appear to exist ...\n";
 exit 0;
 }

 exit 0;
}

#Useage information ... should get here if all else fails !

print "\nSorry the correct useage of twcrypt.pl is :\n\n";
print "twcrypt.pl --genkey keyfile\n\t- generate a key file of name keyfile.\n\n";
print "twcrypt.pl --publishkey keyfile --user username --pass password\n\t- publish the keyfile to twitter for specified account.\n\n";
print "twcrypt.pl --getkeys --user username --pass password\n\t- collect keys published by people you are following.\n\n";
print "twcrypt.pl --showkeys\n\t- show cached public keys.\n\n";
print "twcrypt.pl --encrypt to --user username --pass password\n\t- encrypt a message to a given user.\n\n";
print "twcrypt.pl --getmessages --user username --pass password\n\t- get messages sent to you.\n\n\n";
print "I do realise this isn't the best interface, but hell, live with it ...\n\n";

exit 0;

It works too ! Creating, uploading, encrypting and decrypting through Twitter – Twitter PKI – you saw it here first 😛

I only made one small modification to the planed implementation, and that was to include the name of the recipient – I found that the attempt to decrypt a message with the wrong private key for the encryption caused a crash out, so I though that it would be best to keep it to only decrypting what it is supposed to.

All comments and critique of my code welcome … Please remember though that it’s only a proof of concept – I would be interested in any major flaws though … ( A low key length isn’t a flaw, it’s a practicality by the way – 640 bits should keep most people at bay for a fairly reasonable amount of time 🙂 and is all I could fit into Twitter. ) I hope that the code is self explanatory, if you do have any questions, please drop me a line at si at thinking-security.com

I’m going to go and have a break from the computer now … I might fiddle with this some more later to make it a little more useable, maybe a GUI or iPhone version 😉

Tagged , , ,

Hacking around with Twitter … ( Part 2 ! )

Cryptographically secure pseudorandom number g...

 (Photo credit: Wikipedia)

Well, I’m into day two of this project – well into the code now, and it’s comming on ok …

Phase 4(a) : Get Net::Twitter::Lite to connect to, pick up and update an account. This is pretty well documented in the examples on CPAN, although I’ve pared it right down to the minimum in just to get the post, as the date was throwing up an error, as it isn’t important to the proof of concept at this point ( might be a nicety later on … ) so the code stands like this just now …

#!/usr/bin/perl -w
#use strict;

use Net::Twitter::Lite;

#----------------------------------------------#
#                  This is :                   #
 $clientname = "twitter crypt";       #
#                  Version :                   #
 $clientver = "0.1";            #
#----------------------------------------------#
#            By : Simon Biles                  #
$clienturl="http://computersecurityonline.com";#
#----------------------------------------------#

$user="twcrypt";
$password="*********";

 my $nt = Net::Twitter::Lite->new(
 username => $user,
 password => $password,
 clientname => $clientname,
 clientver => $clientver,
 clienturl => $clienturl
 );

 my $result = eval { $nt->update('Hello, world!') };

 eval {
 my $statuses = $nt->friends_timeline();
 for my $status ( @$statuses ) {
 print "<$status->{user}{screen_name}> $status->{text}\n";
 }
 };
 warn "$@\n" if $@;

In theory the $clientname and $clientver should change the “via” entry, however this doesn’t seem to be the case as it appears to remain as “via Perl Net::Twitter” in TweetDeck.

This gives out the result of :

MacBook:TWCrypt si$ ./twcrypt.pl
 Hullooo ?
 twcrypt_a test
 Hello, world!

Which are either from <twcrypt> the result of the update or are pulling from the following list posts. So far so good !

Phase 4(b) : Right, now for RSA Encryption … If you aren’t familiar with RSA ( and I only vaugely remember the maths from University … ) it is a public key crypto system developed originally at GCHQ, although the offical secrets act meant that Ron Rivest, Adi Shamir, and Leonard Adleman got there independently. I’m _not_ going to go through how it works, mostly ‘cos I’d confuse the matter more, but the Wikipedia Article is pretty good ! Ignore the maths and work through the example to really get how it works 🙂

The example code for Crypt::OpenSSL::RSA isn’t quite so good, there are some typos which have left me stumped for ages … But I got there in the end, and in conjunction with MIME::Base64 have a way of encoding and decoding the strings into something that Twitter will accept. After playing around with key lengths, I’ve gone with 640bits, as this gives a nice ciphertext size of 110 chars and accomodates an encrypted message of upto 38 chars – not exactly War and Peace – I may have to implement that multi-part message sooner rather than later ! 640 bits also gives an RSA encoded key of 122 chars, so we are still comfortably within the 140 char Twitter limit.

#!/usr/bin/perl -w
#use strict;

use Crypt::OpenSSL::Random;
use Crypt::OpenSSL::RSA;
use MIME::Base64;

$plaintext = "12345678901234567890123456789012345678";
$good_entropy = "10010101001010101";

Crypt::OpenSSL::Random::random_seed($good_entropy);
Crypt::OpenSSL::RSA->import_random_seed();$rsa = Crypt::OpenSSL::RSA->generate_key(640);print "private key is:\n", $rsa->get_private_key_string();
print "public key (in PKCS1 format) is:\n", $rsa->get_public_key_string();print "Plaintext = $plaintext\n";
$ciphertext = $rsa->encrypt($plaintext);
print "Encrypted = -!-$ciphertext-!-\n";
$encoded = encode_base64($ciphertext);
$encoded_length = length $encoded;
print "Encoded = $encoded Length = $encoded_length\n";
$decoded = decode_base64($encoded);
print "Decoded = $decoded\n";
$decrypted = $rsa->decrypt($decoded);
print "Decrypted = $decrypted\n";

This gives us an output of :

MackBook:TWCrypt si$ ./twcrypt.pl
private key is:
-----BEGIN RSA PRIVATE KEY-----
MIIBgwIBAAJRAO2VqyVoQFyJkddP+zPzUuoyfDZVaPgn8LxHKFNETwVKdcAOfvm4
koOBcMcGGgwhaFQjEeg5JpNXBvXg67CbWvGpSLpCxonhFdoJprFAC/GBAgMBAAEC
UQCF0hGuZkQqW0qMTn6dymZfh8QzDnSrokOUqTfrfsRLpJ8iaIfYvL+4wPBb0+9z
1DALyIk9SX+MVTwdE7zanrHAxZVOb/zWVioVca0ih3M4yQIpAPi5mTO0lVU4mSw5
acNoPJhkJp901OYgCXVK75MiKJSb+31TmjSoSo8CKQD0iKdHs1ieaoMMx/K91Zmi
dnb80l7aRkU04OZ1ed201OMyP489D0rvAikA+JxzkOYo+iT3nefJWqO/Jce9f1dF
UrylF5OOgm/7RgffyfadxZKN6wIoRczYCwRrkFSQ8c4FQSC+iPxNvJ8ECkQyrwRf
ZDsUCPEXpRk1dtVtqwIobkZm67KhVm5Hp4NRhWIiFODNuzcfwZfakM8Fhvk9dBtv
1GAKqXOeEQ==
-----END RSA PRIVATE KEY-----
public key (in PKCS1 format) is:
-----BEGIN RSA PUBLIC KEY-----
MFgCUQDtlaslaEBciZHXT/sz81LqMnw2VWj4J/C8RyhTRE8FSnXADn75uJKDgXDH
BhoMIWhUIxHoOSaTVwb14Ouwm1rxqUi6QsaJ4RXaCaaxQAvxgQIDAQAB
-----END RSA PUBLIC KEY-----
Plaintext = 12345678901234567890123456789012345678
Encrypted = -!-g$S9?+?????<}&3~?"?q?    ?=???):??R'?+?/?*3?:??x:??p{?!?o??-??m??ʣ??N?<ɍI?q-!-
Encoded = ZyRTOa8rH531r56PPH99JjN+ryKEcYoJhD2WgqUpOtjIUieLFCuqL5ccKjPuOokT9ng6/NBwe5wh
HOtvkfct4cxtl8LKo4HFTos8yY1J+3E=
 Length = 110
Decoded = g$S9?+?????<}&3~?"?q?    ?=???):??R'?+?/?*3?:??x:??p{?!?o??-??m??ʣ??N?<ɍI?q
Decrypted = 12345678901234567890123456789012345678

So that all appears to be working ok …

Phase 4(c): Stich the two things together into a useable program ! This is only a proof of concept, so I’m going to just write a simple command line utility with a few switches to show the point, the plan ( at the moment ) is to end up with something like this :

twcrypt [--genkey keyfile] [--publishkey keyfile] [--getkeys] [--showkeys] [--encrypt to] [--getmessages keyfile] [--user user] [--pass password]

I hope that is quite self explanitory, but read on anyhoo and you’ll get the drift I’m sure …

I got somewhat distracted last night by a terrible film rental (“Thick as Thieves”, Morgan Freeman and Antonio Banderas – don’t bother, it’s not worth it … ) so I’ve not finished this off yet – I thought that I’d post this much and then the final part soon.

Tagged , , , , , ,

Hacking around with Twitter …

Image representing Twitter as depicted in Crun...

Image via CrunchBase

Twitter is an interesting beast … I’m still not too sure if I really see that there is any real benefit from it – I do find it fun though 🙂 Anyhoo, it occurred to me as I was driving home tonight, stuck in the pouring rain, in Oxford traffic, that Twitter, like all of these social networking sites, is a great way to send coded messages. ( Watch my hit count rise drastically as the NSA and GCHQ hammer me 😉 ) There are enough tweets, and they are random enough to drown out pretty much any useful data, but even so, I’d be reluctant as a spy or other to use twitter either in code or directly to communicate with my handlers / followers … It’s a bit too obvious – this led me to think of encryption, and rather conveniently, Twitter itself sets the key size ! Allowing for a few control characters to identify the different types of message, I think that it is possible to implement a neat little public key exchange / encryption setup using Twitter & using Perl, a few CPAN libraries, and this blog, I’m going to set out to do it as a proof of concept over the next few days …

Phase 1 : Completed before I started typing this up – get myself a few extra twitter accounts to play with TweetDeck graciously supports multiple accounts so I can monitor the Perl back processes, so I now have twcrypt, twcrypt_a and twcrypt_b to play with …

Phase 2: Raid CPAN for someone else’s hard work 🙂 Net::Twitter::Lite by Marc Mims and Crypt::OpenSSL::RSA by Ian Robertson ( after several false starts with other RSA implementations and much fun with Math::Pari. ). So that I stand some chance of getting _some_ sleep before morning ! ( If you are on MacOS X, as I am, running cpan sudo aids in the installation somewhat …  )

Phase 2(a): Install Komodo Edit on new laptop, because I’ve not done it yet … Great Perl editor, and free !

Phase 3: Quick planning stage … 140 chars – 128 char for the key leaves … drum roll … 12 for signals soooo …

01 02 03 04 05 06 07 08 09 10 11 12t  w  c                          -  = type marker ( note the - at char 12 )
t  w  c  -  p  u  b  l  i  c     -  = public key distro indicator
t  w  c  -  m  s  g              -  = message indicator ( thinking about allowing multipart messages later )

That’s about it for now, either I’m too tired, too stupid or there just plain aren’t any more required at this time ( could abbreviate I guess and have a stronger key ? ) At the moment, I think that the two communicating parties are going to have to be followers, with only 12 chars to spare there’s not enough room to use @names …

Quick Aside : This isn’t a 128 bit key, this is a 128 byte key or 8 times that e.g. 1024 bit – this is considered pretty much the minimum standard in the world of encryption with 2048 and larger keys being in common, difficult to break, use. You should bear in mind though that a 1024 bit key still supplies a fair standard of encryption. Having said all of this, OpenSSL is being a bugger for actually generating a key that _is_ 128 bytes long, if I generate a 1024bit key, I seem to end up with a 217 byte public section when all is said and done … More investigation tomorrow !
 
With thanks to ...

With thanks to xkcd

Phase 4: Start writing program … in the morning 🙂

Tagged , , , , , ,