Pages

Monday, January 31, 2011

Episode #132: Enigma

Tim goes stealth-mode:

We are always looking for new topic ideas from you readers, but this week we only received one email and trying to use it was difficult.

Hey there,

I've been searching for kung fu teachers, and I was excited to find you.

It's too hard to find trustworthy, quality service providers, and <dumb site> is changing that. We're growing really fast, we need more kung fu teachers, and I think you're a perfect fit!

Posting on <dumb site> is a great way to advertise yourself and it's completely free for service providers like you.

All you need to do to post your information is visit: <dumb site's URL>

Thanks!

~Heather


Apparently Heather didn't know that we don't do physical kung fu. We do try to kick bit booty, but we leave any physical effort to people in another line of work, like accountants. Oh, and the site name was redacted because I don't want to give them any business.

Since that didn't give us any great ideas, I came up with one on my own using some really secure encryption.

One method of encryption involves the use of On-time Pads. When used correctly it is impossible to crack. "If the key is truly random, as large as or greater than the plaintext, never reused in whole or part, and kept secret, the ciphertext will be impossible to decrypt or break without knowing the key." That is different from taking billions of years to crack AES, this is uncrackable!

Let's use this technique to send a message to Hal:
I secretly teach Kung Fu

Hal and I meet up, and exchange one-time pads. I generated the pad by flipping a coin, where 1 is head and 0 is tails. The 1s and 0s were converted to bytes to create our encryption key.

PS C:\> $key = [byte[]](0x70,0xB3,0xDE,0xC0,0xDE,0xDF,0xAC,0xE1,0x7B,
0xA5,0x41,0x51,0x33,0x7E,0xD5,0xC1,0x11,0xDF,0x00,0x15,0xDE,0xAD,0xD0,0x0D)


With this clear text...

PS C:\> $cleartext = "I secretly teach Kung Fu"


...we do a bit of encrypting

PS C:\> 0..($cleartext.length -1) | % {
$ciphertext += ($key[$_] -bxor [int]$cleartext[$_]).ToString("x2") }

PS C:\> $ciphertext
3993ada5bdadc99517dc6125561fb6a93194757bb98d9678


This command counts from 0 to 24, the length of the clear text minus 1 (remember, base 0). The current pipeline object ($_) represents the counter and is used as the index in the array to grab the respective bytes from the key and clear text. The -bxor operator does a binary XOR. The result is then converted to a two character Hex string (x2), then is appended it to our cipher text. I would then send Hal this message:



Dear Hal,

Don't tell Heather, but 3993ada5bdadc99517dc6125561fb6a93194757bb98d9678


The cool thing is that the same process can be used to decrypt.

PS C:\> $cipherbytes = [byte[]](0x39,0x93,0xad,0xa5,0xbd,0xad,0xc9,0x95,0x17,
0xdc,0x61,0x25,0x56,0x1f,0xb6,0xa9,0x31,0x94,0x75,0x7b,0xb9,0x8d,0x96,0x78)

PS C:\> 0..($cipherbytes.length -1) | % {
$cipherbytes += ($key[$_] -bxor $cipherbytes[$_]).ToString("x2") }

PS C:\> $cipherbytes
I secretly teach Kung Fu


We can even use PowerShell to convert a hex string to bytes:

PS C:\> "3993ada5bdadc99517dc6125561fb6a93194757bb98d9678" | 
Select-String ".." -AllMatches | % { $_.Matches } | % { [byte]("0x" + $_.Value) }



This command will match on each two characters (..), take each match object, take each byte of the match and convert it to a byte.

That isn't really pretty, but we'll see how Hal will send the message, and if Bash is leet.

Hal goes into suck mode

I've got to be honest. This is a challenge where bash just isn't very conducive to solving the problem. But since a "real" scripting language like Perl or Python is disallowed by the rules of our blog, I'll just have to muddle through somehow.

Before we get to the suckage, let's start by assigning the bytes of our one-time pad to an array, just like Tim did:

$ otp=(0x70 0xB3 0xDE 0xC0 0xDE 0xDF 0xAC 0xE1 0x7B 0xA5 0x41 0x51 
0x33 0x7E 0xD5 0xC1 0x11 0xDF 0x00 0x15 0xDE 0xAD 0xD0 0x0D)

The syntax we're using here "var=(val1 val2 ...)" is a convenient way for assigning a list of values to the elements of an array. We'll use this again in just a moment.

The hard part is when we want to start XOR-ing these bytes with the bytes in our string. The bash XOR operator ("^") wants both operators to be a numeric type. If I tried to do something like "I ^ 0x70", bash treats the "I" as an invalid number and therefore zero-- that's not what we want at all! So what I need to do is somehow convert the ASCII bytes in our input string into their numeric equivalents.

Well guess what? There's no bash built-in function for doing this! So you're left with doing a nasty hack like this:

$ echo -n I secretly teach Kung Fu | xxd -p
49207365637265746c79207465616368204b756e67204675

As you can see, I'm using the hexdumping program xxd as my ASCII-to-hex converter. The "-p" option means "plain mode": just output the hex bytes and nothing else. Of course, the problem here is that the bytes are all run together. But a little sed will fix that:

$ echo -n I secretly teach Kung Fu | xxd -p | sed 's/\(..\)/0x\1 /g'
0x49 0x20 0x73 0x65 0x63 0x72 0x65 0x74 0x6c 0x79 0x20 0x74 0x65 0x61 0x63...

In my sed expression, I'm snapping up two characters at a time and outputting a "0x", then the two characters, then a space.

With a little command substitution, I can now assign these hex bytes to another array:

$ bytes=( $(echo -n I secretly teach Kung Fu | xxd -p | sed 's/\(..\)/0x\1 /g') )

I'm really just doing an array assigment here-- "bytes=( ... )"-- similar to the one we used to set up our one-time pad. But in this case, the values I'm assigning are the output of our shell pipeline.

Now that I've got two arrays of bytes, I can XOR them together with a for loop:

$ for ((i=0; $i < ${#bytes[*]}; i++)); do 
printf "%x" $((${bytes[$i]} ^ ${otp[$i]}));
done

3993ada5bdadc99517dc6125561fb6a93194757bb98d9678$

The expression "${#bytes[*]}" evaluates to the number of elements in the array. Inside the loop, I'm using printf to output the results of my XOR operation as hex digits ("%x"). The only downside, as you can see, is that there's no final trailing newline at the end of the output-- the "$" you see there is the shell prompt for the next line! This is maybe not such a big deal, because most likely I'd want to do something like this:

$ secret=$( for ... )

That would assign the output of the loop to a variable, $secret.

But now what about going the other way and converting the contents of $secret back into the original ASCII? This is actually pretty similar to the steps above with a couple of small mods:

$ bytes=( $(echo $secret | sed 's/\(..\)/0x\1 /g') )
$ for ((i=0; $i < ${#bytes[*]}; i++)); do
printf "%x" $((${bytes[$i]} ^ ${otp[$i]}));
done | xxd -r -p

I secretly teach Kung Fu$

Here I'm resetting the value of our bytes array to be the individual bytes from $secret, converted by sed to "0xNN" format. Then I run through the same for loop to XOR these bytes with our one-time pad. However this time the output goes into "xxd -r -p", which "reverts" the resulting string of bytes into their corresponding ASCII values.

All I can say is, thank the shell gods for xxd!

Tuesday, January 25, 2011

Episode #131: Subject of Attachment

Because Hal sed So:

We got an interesting challenge in the mailbag this week from Ryan Tuthill:

Given the example logs:

562C938C3.A9069|attachment = Interview Results.xls
B1CE33BED.A58D0|subj="Who?" | subj="Who?"
67BD53BED.AF311|subj="January 14.docx.doc" | attachment = January 14.docx.doc
19B4D27D2.A8CE7|subj="FW: Two New Bud Light Commercial's" | attachment = Bud_Lite_Pick_Up_Line.mpg


What I am curious to discover is a way to print only lines that contain subj and attachment, not just one or the other. From there, I would like to print the lines with the same subj and attachment titles.


Looking at the problem, I thought to myself, "Hmmm, irregular pattern matching. Seems like a job for sed." Then I started wondering if I could solve the problem entirely with sed. We haven't spent much time on the blog talking about sed, but you must understand that it's a fully capable programming language in its own right. For proof of that assertion, I refer you to sedtris-- yep, that's a Tetris game written entirely in sed.

Anyway, it turns out there is a sed-only solution to Ryan's problem. Assuming we've got our sample input in a file called "input", simply do this:

$ sed 'h; s/.*subj="\(.*\)".*attachment = \1$/\1/; t hit; d; :hit g' input
67BD53BED.AF311|subj="January 14.docx.doc" | attachment = January 14.docx.doc

I know the syntax is terse and mysterious here, so let's take this slowly. The first thing you need to understand is that sed has two different data spaces you can work with. The one you normally use is the "pattern space", which is the line you just read in. When you do an operation like "s/.../.../", you're operating on the pattern space. However, sed also has a "hold space" where you can store stuff for later. And there are operators that let you copy or append data into the hold space from the pattern space and vice-versa.

With that in mind, let's walk through our sed "program". The first command we give sed is "h", which means "copy the pattern space into the hold space, overwriting the previous value in the hold space". We're using this to store the original version of the line we just read in, so that we can print it out later if it matches our criteria.

Once we've saved a copy of the original line, we can start mangling the copy in the pattern space. The next sed operation we perform is a substitution. The regular expression on the lefthand side matches 'subj="<string>"' anywhere on the line, followed by 'attachment = <string>' at the end of the line. sed is one of the few Unix regular expression syntaxes that supports matching a string early in the regular expression and then testing for the matched string later in the same regex. If our pattern matches, then we end up replacing the entire original line with just the value of the <string> we matched.

This is where it starts to get tricky. It turns out that the replacement we do on the RHS of the "s/.../.../" expression doesn't matter. I'm really only using the substitution to verify that we have a line that matches our criteria. This, in turn, allows me to use sed's branching operator to control whether or not we output the original line. You see sed has a goto-like operator "t <label>" which will jump to the specified <label> if and only if there has been a successful "s/.../.../" operation since the current line was read in (or since the last "t" operation if there's been more than one).

So if our substitution was successful, then this is a line with matching "subj" and "attachment" titles and we want to print out the line. In this case our "t" operation tells sed to jump to the label "hit" and start executing statements from there. If the substitution didn't work, then we just fall through to the next statement after the "t".

Thus when we don't match what happens is the sed operator "d", which simply means "discard the current pattern space and move on to the next line". Think of it like "continue" or "next" in many popular programming languages. The next thing in our sed program is the label ":hit", which is where we'll jump to if the "s/.../.../" operator worked. The sed command we invoke here is "g", which overwrites the current pattern space with the contents of the hold space. Remember how we saved the original line into the hold space back at the beginning of our program? Well now we bring that back. And finally, there's an implicit "print the contents of the pattern space" at the end of every sed block which takes care of actually outputting the line for us.

Pretty neat, huh? I was feeling really good about myself until I got a follow-up email from Ryan with a few more sample log entries:

BE1342109.A1C1F|attachment = ATT49396.txt | subj="FW: snow"
21E3430E7.A8583|attachment = Scan001.pdf | subj="FW: PROJECT/TASK"
8657C30E7.AC7A5|attachment = IMG00005.jpg | subj="IMG00005.jpg"

Yep, it turns out that the "subj" and "attachment" can appear in either order. Well, back to the drawing board:

$ sed 'h; 
s/.*subj="\([^"]*\)".*attachment = \1$/\1/; t hit;
s/.*attachment = \(.*[^ ]\) *|.*subj="\1"$/\1/; t hit; d;
:hit g' input

67BD53BED.AF311|subj="January 14.docx.doc" | attachment = January 14.docx.doc
8657C30E7.AC7A5|attachment = IMG00005.jpg | subj="IMG00005.jpg"

The new code isn't actually all that different from the original solution-- I've just split things onto multiple lines for greater readability. We have the "h" operator, a substitution that checks for 'subj="<string>"' followed by 'attachment = <string>', and the "t" operator just as before. However, this time if we fail to get a hit, then we try looking for 'attachment = <string>' followed by 'subj="<string>"' instead. Only if both of those operations fail, do we give up and do "d". If either operation succeeds, then we jump to ":hit" and do the operations to print out the line.

The pattern match for the 'attachment = <string>' is a little tricky in the second case because there are no quotes around the attachment name and the attachment name can contain spaces. So we have to explicitly match "'attachment<space>=<space>", followed by "some stuff that ends in a non-space character", followed by some spaces, and then a pipe character ("|") which is our field separator. Yeesh!

Anyway, this has been your first high-speed introduction to the kinky joy that is sed. Tim, I bet you don't have anything this dirty in your Powershell arsenal!

Tim feeds his expressions prunes, so they stay regular:

PowerShell may not have sed, but it does have built-in regular expressions. These expression are way cooler than their incontinent brethren of cmd.exe.

Similar to Hal's approach, we first read the file and look for strings containing a subject. This is accomplished by piping Get-Content (alias gc) into Select-String where we filter using a regular expression.

PS C:\> gc log.txt | Select-String 'subj="(?<Subject>[^"]+)
B1CE33BED.A58D0|subj="Who?" | subj="Who?"
67BD53BED.AF311|subj="January 14.docx.doc" | attachment = January 14.docx.doc
19B4D27D2.A8CE7|subj="FW: Two New Bud Light Commercial's" | attachment = Bud_Lite_Pick_Up_Line.mpg
...


This regular expression matches on lines contain something like the following:
subj="<text that doesn't contain a double quote>

The content of the text that doesn't contain double quotes is part of a named group and contains the subject we would like to use later. The syntax for using a named group is:

(?<GroupName>regex)


Until I prepped for this episode, I didn't realize that the Select-String cmdlet will populate the $matches variable; I thought that was only done via the -Match operator. This little trick makes our command much shorter and easier to read than using the -Match operator.

As regular readers (badabing!) might remember, the $matches variable contains the groups matched by our regular expression. Since we used a named group, it makes it much easier to access the group we want by simply using $matches.GroupName. This variable can be used further down the pipeline, and we can use this little trick to get our final command.

PS C:\> cat .\log.txt | Select-String 'subj="(?<Subject>[^"]+)' | 
Select-String "attachment = $($matches.subject)"


67BD53BED.AF311|subj="January 14.docx.doc" | attachment = January 14.docx.doc
8657C30E7.AC7A5|attachment = IMG00005.jpg | subj="IMG00005.jpg"


The results from our first command are piped into Select-String which filters for strings containing "attachment = <subject name> and that gives us the output we are looking for.

Tuesday, January 18, 2011

Episode #130: Eenie, Meanie, Miney, Mo...

Hal gets selective

Recently I was doing an audit of USB devices that had been connected to numerous Windows machines on a network. The input data I had looked like this:

KeyStore XP2G
080909524d94e5
Sat Jun 30 22:42:07 2009
Apple iPod
000A270010C4E86E
Fri Jan 16 23:24:15 2009
M-Sys Dell Memory Key
086086412140E1C2
Fri Jan 16 23:15:30 2009
OLYMPUS u810/S810
000J55024022
Wed Jan 14 19:03:58 2009
...

The input file was a collection of three line "records" for each device. The first line was the "friendly name" of the device, the second line was the device serial number, and the third line was the date the device was last connected. I had one input file per machine.

What I needed to do was to extract just the serial numbers from each input file. Since the serial numbers of USB devices can have widely varying formats, I couldn't easily write a regular expression to match them. Instead I needed to extract the lines by line number-- the 2nd, 5th, 8th lines and so on. awk is actually really useful for this:

$ awk '!((FNR - 2) % 3)' *
080909524d94e5
000A270010C4E86E
086086412140E1C2
000J55024022
...

Recall from Davide Brini's solution a few weeks ago, that FNR is the "record number" in the current file. When using awk's default record separator which is newline, that means that FNR corresponds to the line number in the current file. So I'm subtracting two from the line number and then doing a "modulo 3" operation-- so the expression will be zero on lines 2, 5, 8, 11, ... Therefore "not" that expression, aka "!(...)", will evaluate to true on those lines only. Since there's no command block after the expression "{print}" is assumed, and I output the lines I want.

Now the reason I was pulling out the serial numbers is that I wanted to see which devices had been connected to multiple systems. The easy way to do this is to just slap on a little sort and uniq action:

$ awk '!((FNR - 2) % 3)' * | sort | uniq -d
080909524d94e5
086086412140E1C2
...

But just to save Davide the trouble of sending me an email, here's the "awk-only" version:

$ awk '!((FNR - 2) % 3) && (++a[$1] == 2)' * 
086086412140E1C2
080909524d94e5
...

By adding another clause after a logical "and" ("&&") I ensure that the second clause only gets executed on the lines containing serial numbers. In the second clause I'm creating values in an array that is indexed by the serial number. The values are a count of the number of times we've seen each serial number-- "++a[$1]" adds one to the value of a[$1] before evaluating the conditional. After we've incremented our accumulator value, we check to see if the value is 2, meaning that this is the second time we've encountered a given serial number. If that's true then our implicit "{print}" happens again and we output the serial number. I don't care if the serial number appears in more than two different files, just that it appears in at least two.

Of course the output of the "sort | uniq -d" version is in a different order than the awk-only version because of the "sort" in the first solution. But since the serial number format varies widely anyway, I'm not sure sorting is that useful in any case. You could always pipe the awk output into sort if sorting is important to you.

I'm pretty sure Tim can knock this one out of the park using Powershell. I wonder what a CMD.EXE solution would look like..?

Tim is selective too

Hal likes to taunt me with digs at CMD.EXE, and this week I'll have to concede. I got about 90% of the way through writing the big ol' command and decided it was getting ridiculous. If you want to know what it would have looked like go check out Ed's final episode. The command would have worked but it would be completely impractical and no one would have used it, in short, a circus act. Instead, let's do something practical. On to PowerShell...

The PowerShell cmdlet used to read a file is Get-Content (aliases cat, gc, and type). But we don't just need to read a file, we need to read a file and get every third line. Here's how to do just that:

PS C:\> Get-Content -Path * -ReadCount 3 | % { $_[1] }
080909524d94e5
000A270010C4E86E
086086412140E1C2
000J55024022
...


Get-Content's ReadCount parameter is used to "specifies how many lines of content are sent through the pipeline at a time." The default value is 1, so normally each line is sent down one at a time. Setting this value to 3 means that 3 lines at a time will be sent down the pipeline.

Inside the ForEach-Object's scriptblock, the current pipeline object ($_) contains the three lines passed down the pipeline. In this group of three lines we are looking for the second item in the array. The second item in the array is represented by the array index of 1. Remember, the first item in an array is 0, so the second is 1, and third is 2.

Now that we have the serial numbers, let's look for duplicates. This is really easy with the Group-Object cmdlet.

PS C:\> Get-Content -Path * -ReadCount 3 | % { $_[1] } | Group-Object -NoElement

Count Name
----- ----
1 080909524d94e5
2 000A270010C4E86E
2 086086412140E1C2
...


The NoElement switch means that the original objects are not stored, so we have just the count serial number.

In addition, we can filter for counts greater than 1.

PS C:\> Get-Content -Path * -ReadCount 3 | % { $_[1] } |
Group-Object -NoElement | Where-Object { $_.Count -gt 1 }


Count Name
----- ----
2 000A270010C4E86E
2 086086412140E1C2
...


But that is a long command, and I like to be brief, so this is the short method:

PS C:\> gc * -r 3 | % { $_[1] } | group -n | ? { $_.Count -gt 1 }


So we did what Hal did, now let's up the ante and turn the contents of these files into objects.

PS C:\> gc * -r 3 | select @{Name="Name"; Expression={$_[0]}},
@{Name="Serial";Expression={$_[1]}},@{Name="Date";Expression={$_[2]}}


Name Serial Date
---- ------ ----
KeyStore XP2G 080909524d94e5 Tue Jun 30 22:42:07 2009
Apple iPod 000A270010C4E86E Fri Jan 16 23:24:15 2009
...


The Select-Object cmdlet uses hashtables to allow you to manually create properties. Hashtables use key-value pairs. To create a property the Name is [obviously] used to set the name of the property. The Expression is used to set the value of the property. So we have objetified everything, but the date is still a string. To convert it to a Date object we can use .NET to convert the string to a native Date Object.

PS C:\> gc * -r 3 | select @{Name="Name"; Expression={$_[0]}},
@{Name="Serial";Expression={$_[1]}},
@{Name="Date";Expression={[datetime]::ParseExact($_[2], "ddd MMM dd HH:mm:ss yyyy", $null)}}


Name Serial Date
---- ------ ----
KeyStore XP2G 080909524d94e5 6/30/2009 10:42:07 PM
Apple iPod 000A270010C4E86E 1/16/2009 11:24:15 PM
M-Sys Dell Memory Key 086086412140E1C2 1/16/2009 11:15:30 PM
...


Now we can do whatever we want with it, such as exporting the results to a CSV to use in Excel, filter based on the properties, or look for duplicates as we did above.

One thing that might be useful is knowing into which computers the USB devices were plugged. Assuming the file name is descriptive we can use it to add an additional property to our objects.

PS C:\> ls | % { $file = $_.Name; gc $_ -r 3 |
select @{Name="Name"; Expression={$_[0]}},
@{Name="Serial";Expression={$_[1]}},
@{Name="Date";Expression={[datetime]::ParseExact($_[2], "ddd MMM dd HH:mm:ss yyyy", $null)}},
@{Name="File";Expression={$file}}}


Name Serial Date File
---- ------ ---- ----
KeyStore XP2G 080909524d94e5 6/30/2009 10:42:07 PM Alpha.txt
Apple iPod 000A270010C4E86E 1/16/2009 12:20:00 PM Alpha.txt
...
Apple iPod 000A270010C4E86E 1/21/2009 11:24:15 PM Bravo.txt
M-Sys Dell Memory Key 086086412140E1C2 1/16/2009 11:15:30 PM Bravo.txt
...


This command starts with Get-ChildItem (alias ls) to get each file in the directory. The files are piped into ForEach-Object where the filename is stored in a variable for use later. The rest of the command is very similar as our original command, the only difference is the input to Get-Content is specified by the current pipeline object, instead of our wildcard (*) as before.

Now we can do all sorts of filtering.

PS C:\> ... | ? { $_.Serial -eq "000A270010C4E86E" }
Name Serial Date File
---- ------ ---- ----
Apple iPod 000A270010C4E86E 1/16/2009 12:20:00 PM Alpha.txt
Apple iPod 000A270010C4E86E 1/21/2009 11:24:15 PM Bravo.txt
...


Put that in your pipeline and smoke it, Hal!

Tuesday, January 11, 2011

Episode #129: Writing on the Wall

Tim wants to be heard:

Hal and I realized that in the previous episode we brought up a new topic but never explained it. I broke out the "msg" command, and he used "wall." In case you couldn't figure it out, these commands are used to send a message to users on the system.

The Windows command Msg is used to send a message to one or more users based on username, sessionname, or sessionid. The username is the most common way of directing a message to a user.

C:\> msg hal You have no chance to survive, make your time!


This command simply sends a message to Hal. As mentioned above, a message can also be directed based on the session name or id. To determine the session id or name refer to episode 62.

We can also send a message to all users on the system by using the asterisk.

C:\> msg * Someone set up us the bomb!


What if we don't want to send the message to all the users, but more than one user? We can do that! It does require that we have a file containing a list of usernames to whom we would like to direct our message.

C:\> msg @mostlyeveryone.txt Someone set up us the bomb!


We can also send the messages to users on other systems by using the /SERVER switch.

C:\> msg * /SERVER:otherbox All your base are belong to us!


However, this command doesn't just send messages, but also can be used to get an acknowledgment. The /V option displays information about which actions have been performed, such as sending a message and acknowledgments. The /W option waits for a response from the users. Say we send a message to Hal, and want to make sure he gets its, this is how we would do it:

C:\> msg hal /V /W Did you make your time?
Sending message to session Console, display time 60
Message to session Console responded to by user


The first message lets us know that a message was sent to Hal. The second means that either Hal responded, or the 60 second timer elapsed. Its a bit weird that the message is the same either way, but welcome to the wonderful world of Windows commands.

If we don't think that 60 seconds is long enough for Hal to respond, we can use the /TIME option to explicitly specify the duration of the message.

C:\> msg hal /V /W /TIME:3600 Did you make your time?


This command will wait one hour for a response; more than enough time for Hal to "make his time!"

By the way, these lost-in-translation quotes are taken from the internet meme All Your Base Are Belong To Us!. As a side note, I tried to use these quote in an earlier episode, but Hal corrected my grammar and fixed it. I can't believe Hal doesn't have endless hours to waste watching silly videos on the internet. Is that what the internet is for?

Hal is a little hard of hearing:

I guess I'm just having trouble keeping up with you kids and all your Internet shenanigans. But all of this writing to people's terminals is making me fondly remember my days using dumb terminals to talk with my friends on time-sharing systems. You can't type faster than 9600 baud, so who the heck needs 10Gbps? And get off my lawn!

If I wanted to tell Tim to get off my lawn, I could do that with the write command:

$ write tim
write: tim is logged in more than once; writing to pts/2
Get off my lawn!
^D

Specify the user you want to send a message to as an argument. As you can see, Tim is logged in on multiple PTYs, so the write command will by default send the message to the lowest numbered device. But you can specify a specific device to write to as an optional argument: "write tim pts/2", for example.

Once you hit return on the command line, anything you type in subsequent lines is sent to the specified user (each line normally gets sent immediately when you hit Enter). When you're done entering text, just hit <Ctrl>-D. Tim ends up seeing a message like this:

Message from hal@caribou on pts/1 at 16:43 ...
Get off my lawn!
EOF


Now random grumpy old men shouting into your terminal windows can be distracting when you're trying to get work done. So Tim has the option of blocking messages on his PTY with "mesg n":

$ mesg
is y
$ ls -l /dev/pts/2
crw--w---- 1 tim tty 136, 2 2011-01-10 16:50 /dev/pts/2
$ mesg n
$ ls -l /dev/pts/2
crw------- 1 tim tty 136, 2 2011-01-10 16:50 /dev/pts/2

With no arguments, the mesg command tells you what the current status of your PTY is-- the default is to be accepting messages, or "y". As you can see in the output above, running "mesg n" simply removes the group-writable flag from your PTY. The write command is set-GID to group "tty" so that it can write messages to users terminals when "mesg y" is set.

But in our last Episode, I used the wall command to send New Year's greetings to everybody on the system. In its simplest form, wall accepts input on the standard in and blasts it to all currently connected users:

$ echo Get off my lawn! | wall

And the users see:

Broadcast Message from hal@caribou                                             
(/dev/pts/1) at 16:58 ...

Get off my lawn!

If you are the superuser, wall can also be used to write messages stored in a text file.

# wall /etc/shutdown-message

The other advantage to running wall as the superuser is that your message will even go to the terminals of those users who have set "mesg n". After all, "mesg n" works by changing permissions on the PTY devices, but root can write to any file regardless of permissions.

Now that I've educated you whipper-snappers on this outmoded technology, it's time for my nap. Why don't you kids go shoot some marbles or play with your hula-hoops?

Saturday, January 1, 2011

Episode #128: Happy New Year!

Command Line Kung Fu is taking a little holiday respite. We'll be back with more Fu on 2011-01-11. In the meantime, here's your weekly fix of command-line madness... just a little bit early.

Hal has been replaced by a small shell script

echo "echo 'Happy New Year from your #1 blog!' | wall" | at 11:11 01/01/11


Tim has been replaced by a slightly smaller shell script

at 11:11 /next:1 cmd /c "msg * Happy New Year from your #1 blog!"


See Episode #50 for further explanation...