Entries : Category [ Hacks ]
Hacks published in the hacks section from the wiki
[OpenBSD]  [BSD]  [FreeBSD]  [Linux]  [Security]  [Python]  [Zope]  [Daily]  [e-shell]  [Hacks]  [PostgreSQL]  [OSX]  [Nintendo DS]  [enlightenment]  [Apache]  [Nintendo Wii]  [Django]  [Music]  [Plone]  [Varnish]  [Lugo]  [Sendmail]  [europython]  [Cherokee]  [self]  [Nature]  [Hiking]  [uwsgi]  [nginx]  [bikes] 

30 octubre

Quick mailing list using Sendmail and Python

a new hack came to town!

I've published a new Hack in http://wiki.e-shell.org. This one explains how you can create a little mailing list using only Sendmail aliases and a simple Python script.

It is a quick way to have a mailing list, without having to install and set up MailMan or Majordomo, and it would be easy to add more features to that simple script like, for example, a little archive or some content filtering.

Posted by wu at 16:50 | Comments (0) | Trackbacks (0)
11 diciembre

SSH Chroot in FreeBSD

or how to secure remote access to your server...

A new Hack in HackTown!

In this one:

I'll show you how to limit access to a server through a SSH link. This is useful when we need to provide some users with FTP-like access to a server, just for upload or download files, but limiting their scope inside the server filesystem.

I hope you will find this one as useful as in fact it was for me last days.

Posted by wu at 17:51 | Comments (0) | Trackbacks (0)
30 abril

Using Expect with sieveshell...

...to provide passwords interactively

Yesterday I found an interesting task on my TODO list. I had to activate a sieve script like that one:

require "fileinto";

if header :contains "Subject" "[SPAM]" {
      fileinto "INBOX.SPAM";
else {
fileinto "INBOX";

(just put every message with the string [SPAM] into a predefined mail box (INBOX.SPAM))

If you have used sieve before, you should know of sieveshell, an interactive perl-based shell to manage such scripts. Using sieveshell, each user can manage it's own scripts, and the admin could manage scripts for every user.

[prunus] ~> sieveshell -u borja@pexego.es -a borja@pexego.es localhost
connecting to localhost
Please enter your password:
> help
  sieveshell [-u username] [-a authname] [-r realm] <server>

help             - this screen
list             - list scripts on server
put <filename> [<target name>]
               - upload script to server
get <name> [<filename>]
               - get script. if no filename display to stdout
delete <name>    - delete script.
activate <name>  - set a script as the active script
deactivate       - deactivate all scripts
quit             - quit

You can even use some kind of a batch mode to run commands directly, without entering the shell:

[prunus] ~> sieveshell -u borja@pexego.es -a borja@pexego.es --exec='list' localhost
connecting to localhost
Please enter your password:
bogom_filter_spam.txt  <- active script
[prunus] ~>

"Fine, I can create a little script to call the needed commands to activate the script for every user in the database" - That was my first thought, but I found a little problem about it, everytime I called sieveshell, it asked me for a password (the admin password in my case). And that was a real problem, as I needed to activate the script for more than 3000 email accounts.

Thnk the holy cow that people in #BSDcow always help me!

First, I needed a script that got a list of email address, and call sieveshell with the proper commands, something like:

# Script to automatically add and activate sieve
# scripts

for i in `ldapsearch -LL -H ldap://localhost -b"o=pexego.es,ou=Hosting,dc=pxgo,dc=es" -x "(uid=*)" | grep "mail:" | cut -f 2 -d ' '`
  echo "User $i, uploading script"
  /usr/local/bin/sieveshell -u $i -a root --exec='put /home/pxgo/bogom_filter_spam.txt' localhost
  echo "User $i, enabling script"
  /usr/local/bin/sieveshell -u $i -a root --exec='activate bogom_filter_spam.txt' localhost
  echo "User $i, script enabled"
/usr/local/bin/sieveshell -u $i -a root --exec='list' localhost

As you can see, OpenLDAP is the backend where user information is stored, so the script performs a query against the ldap server using ldapsearch and (with some grep and cut magic) it gets the list of avaliable email addresses. Then it goes through the list, calling sieveshell accordingly.

If I've used the script just like that, I should have to provide the admin password 3 times per user, which in my case is (3x3000=9000) 9000 times!. Impossible, for sure.

After some googling on sieveshell batch mode, I found some results in the official mailing list about a guy who wrotes a perl script to do the job, but that script have some dependencies I didn't want to install on my server. So that was not an option.

After a little more research I ended up talking with ajacoutot and betabug on #BSDcow. And they told me to use expect (and they helped me a lot understading and troobleshooting my tests).

Never used it before, but after some reading I got some basic knowledge about it. First, I needed to install it on my server, pretty easy using the ports collection:

[prunus] /usr/ports/lang/expect-devel> sudo make install clean

Then, I created an expect script, just in the same place I created the other one before, that gets the command I want to run (sieveshell), with the needed arguments, then it calls the command properly inside the expect environment, and provide the password automatically:

# Script to call the script that adds and
# activates sieve scripts in an unattended way

set Sieveshell [lindex $argv 0]
set User [lindex $argv 1]
set Auth [lindex $argv 2]
set Exec [lindex $argv 3]
set Host [lindex $argv 4]

spawn $Sieveshell -u $User -a $Auth --exec=$Exec $Host

expect "Please enter your password:"
send "XXXXX\n";

NOTE: the \n at the end of the password is needed so send sends the password, if you do not put that \n there, the password will be only echoed after the Please enter your password: prompt.

Once the expect script was created, I needed to modify the original script, to use this one instead of calling sieveshell directly:

# Script to automatically add and activate
# sieve scripts

for i in `ldapsearch -LL -H ldap://localhost -b"o=ayselucus.es,ou=Hosting,dc=pxgo,dc=es" -x "(uid=*)" | grep "mail:" | cut -f 2 -d ' '`
  echo "User $i, uploading script"
  ./sieveshell_expect /usr/local/bin/sieveshell $i root "put /home/pxgo/bogom_filter_spam.txt" localhost
  echo "User $i, enabling script"
  ./sieveshell_expect /usr/local/bin/sieveshell $i root "activate bogom_filter_spam.txt" localhost
  echo "User $i, script enabled"
  ./sieveshell_expect /usr/local/bin/sieveshell $i root "list" localhost

Of course, it would be great if we could provide all commands inside a single call to sieveshell, but seems that this is not posible (as far as I know), and it would be better if I had more time to put some checks inside the scripts (know they seem pretty ugly/insecure), but hey, that's a quick hack for desperate sysadmins!

Posted by wu at 11:48 | Comments (0) | Trackbacks (0)
10 julio

SMTP connection using telnet

another reminder for my damaged brain...

Did you ever need to test a SMTP server quickly?

If the answer is yes, probably you had hear something about using telnet to send emails directly.

I've used this trick a thousand times myself, but I always forget the correct commands and the order I have to provide them to the SMTP server.

Transcription follows:

[Fenris] ~> telnet mail.e-shell.org 25
Connected to mail.e-shell.org.
Escape character is '^]'.
220 [ ESMTP Codigo23 mail server ] - [ Will Hack Unix for food ]
ehlo localhost
250-Frey.e-shell.org Hello cm82227.red.mundo-r.com [], pleased to meet you
250-SIZE 5242880
250 HELP
mail from: you@yahoo.com
250 2.1.0 you@yahoo.com... Sender ok
rcpt to: jarjarthisisnotmymail@e-shell.org
250 2.1.5 jarjarthisisnotmymail@e-shell.org... Recipient ok
354 Enter mail, end with "." on a line by itself
This is a sample mail sent to you via telnet!
250 2.0.0 m6AB3LDP071599 Message accepted for delivery


1- Connect to the server using telnet:

telnet mail.e-shell.org 25

2- Say hello to the server:

ehlo localhost

3- Tell the server who is sending the email:

mail from: you@yahoo.com

4- Then tell it the destination address:

rcpt to: jarjarthisisnotmymail@e-shell.org

5- Finally tell the server you are about to write the message contents:


NOTE: If you want to add a subject to your mail, just add a line like:

Subject: this is my subject

at the beginning of the body. You will need to press enter twice for it to work properly.

6- Once you are done, push enter, then add a single dot (.) and push enter again.

7- Mail sent!

(Anyone who can teach us how to do that using netcat??)

Posted by wu at 11:11 | Comments (0) | Trackbacks (0)
27 febrero

Rapid DNS Zone serial update

a one-liner again, I really like these ones!

Today, while doing a DNS server migration, I found myself having to update a bunch of DNS zone files. First, I had to change the ip address for some records in every zone file (as the DNS server is a web server too, so moving the server means I had to move every web service there too). That was pretty easy (as I've done it before).

But then I realized I had to update the serial code for each zone file too, and that would be a little bit tricky, as each zone had a different serial code. Of course, sed to the rescue again!, the one-liner:

sed -i.bak '/serial/s/[0-9][0-9]*/2009022701/' *

and et voilá, all the serial codes are changed into 2009022701, doing a .bak backup of the file before changing it. Basically, it search for lines that match the serial term and then it replaces each numeric code with the new one.

(borrowed from here)

Posted by wu at 19:44 | Comments (0) | Trackbacks (0)
27 octubre

Remove blank lines from the beginning of a file

you can use sed, or you can use the BlankLineEraser! :D

This morning Marcos asked me for a solution to a common issue in a sysadmin/developer daily work. He needed a way to quickly/easily remove blank lines from the beginning of a file, if they exist (hehe, it doesn't work to assume the first lines are blank...).

After some time trying sed and its man page, and even searching through google, I found a quick sed one liner:

sed '/./,$!d' filename

And I quickly build a small shell script that uses it to parse a given file and create a parsed version of the file:


Perhaps some of you could find it useful... who knows... ;D

Posted by wu at 13:31 | Comments (1) | Trackbacks (0)
01 noviembre

Doing a merge between two branches in subversion

they should add this to the svn book

UPDATE: I've fixed a typo in the proper way to do the merge, I wrote -r 210:240 but it should be -r 127:240 (from the point where the branch is created to the latest revision)

I would like to write some lines about this, as it took some time for me to realize how to do it correctly. Let me explain it with an example.

Imagine we have a svn repo like this one:

A scheme of a sample svn repo

In this repo we have the usual trunk, branches and tags repo layout. Let's forget about tags, as we are not going to use it for the purpose of this post. We start to use the repo with the initial import (r1, aka revision 1) inside the trunk. Then we continue adding more changes to the code, up to revision 127 (r127), where we create a new branch (called mybranch) using the svn copy command (r128).

At this point the development continue, but some developers work on the mybranch branch (doing commits up to r240) and some other developers work on the trunk code (doing commits up to r210).

And now is when the problem happens. Imagine we need to unify the development process in only one branch of development. As our main development was inside the mybranch branch we try to get the changes made into trunk (since we created mybranch) back into mybranch itself.

So, let's go to the Subversion book and search for how to do this. If you read the nightly build of the book, you will find that, in the basic merging section, the merge would be done like:

$ svn merge svn+ssh://svn.myproject.org/svn/trunk .

(you have to be in a clean checkout of the branch)

The problem with that is that it doesn't work at all. It will do some strange merges (for example, it will try to merge back things from revisions like r29 which changes are already in mybranch).

So, we try another approach:

$ svn merge svn+ssh://svn.myproject.org/svn/branches/mybranch \
svn+ssh://svn.myproject.org/svn/trunk .

(you have to be in a clean checkout of the branch too)

This doesn't work either, it will put your working copy into r210, deleting all the changes specific from mybranch...

So, what else could we do? Well, we can do it just using the -r flag to force the revisions between we want to do the merge, just like this:

$ svn merge -r 127:240 svn+ssh://svn.myproject.org/svn/trunk .

(once again, you have to be in a clean checkout of the branch)

This will merge back the changes made in trunk into mybranch.

And, now to end my post, just a curious annotation about the Subversion book. If you search for subversion book in google and you click in the direct links (Multi-page html for example), you will access the 1.4 version of the book, this one, where you can find the proper way to do the merge. But, if you go to the main subversion book page, you only have access to the nightly build and older versions of the book, where you can find the other two options I've mentioned...

Posted by wu at 16:06 | Comments (0) | Trackbacks (0)
24 septiembre

sshd: Corrupted MAC on input.

rsync + Received disconnect from xxx.xxx.xxx.xxx: 2: Packet corrupt

Quite a strange error, don't you think?

It took me some time to realized what was happening, so I hope sharing this information with the rest of the world could help somebody in the future.

Some days ago I was syncing some data between two FreeBSD 8.x servers using rsync when I found that the data transmission just stopped suddenly and I could see in the origin server:

Received disconnect from xxx.xxx.xxx.xxx: 2: Packet corrupt
rsync: writefd_unbuffered failed to write 4 bytes to socket [sender]: Broken pipe (32)
rsync: connection unexpectedly closed (14093 bytes received so far) [sender]
rsync error: unexplained error (code 255) at io.c(601) [sender=3.0.7]

(I've replaced the current host ip address with xxx.xxx.xxx.xxx)

At first I thought it should have been some network error, so I tried again but, after a while, I found that the error was apppearing randomly.

Next thing I did was to check the logs in the destination server, and I found some interesting lines in /var/log/auth.log:

Sep 21 07:59:12 mangosta sshd[48978]: Corrupted MAC on input.
Sep 21 07:59:12 mangosta sshd[48978]: Disconnecting: Packet corrupt

(being mangosta the hostname of the destination server)

Wow, Corrupted MAC on input ¿? Could it be that someone was doing something nasty with my servers?

Short answer: NO.

The problem was that the origin server had some big load on it, the server was running out of memory and it was swaping a lot (extracted from the top command):

Swap: 1024M Total, 577M Used, 447M Free, 56% Inuse

As soon as I stopped some services in that server, the load went down a little and there was plenty of memory for rsync to eat, it works nicely (as usually).

So, this is one of those cases where the error messages are totally useless. Probably it was all rsync's fault, because it usually needs a lot of memory to doing that kind of syncs (-vza) so, as soon as the system was running out of memory, rsync wasn't able to send data over the wire properly. In the other end, as soon as sshd receives some malformed data from the origin server, it probably cut the connection with the Packet Corrupt message.

Perhaps using wireshark or tcpdump to gather some more information could be helpful in this kind of scenario.

Posted by wu at 17:01 | Comments (5) | Trackbacks (0)
30 diciembre

Soekris BIOS upgrade using MacOSx Snow Leopard

How to upgrade your soekris' BIOS using a macbook and a usb-to-serial adapter

I've been working with soekris hardware for quite some time now, but I didn't need to upgrade the firmware/BIOS of any soekris box until today. Even if the process is well documented in the soekris wiki, I've found that it was a little bit tricky to do it using a MacBook running Snow Leopard. So let me explain how I managed to do it.

First let's do some inventory for this small tutorial. We will need:

1- A soekris box/board (I used a 5501 but probably this instructions will work with previous versions of soekris hardware like the 4801 or the 4501)

The soekris board itself

2- A macbook/macbookpro (as all the trouble I've found following the official docs for upgrading the soekris BIOS seems to be related to MacOSx)

The unibody aluminium MacBook

3- A usb-to-serial adapter (we need this, because the MacBook does not have a serial connector)

A usual usb-to-serial adapter, there are multiple types of devices like this one, with different shapes.

4- A RS-232 serial cable (to connect the MacBook to the soekris and get a serial console)

A RS-232 serial cable

5- The needed firmware/BIOS upgrade files for your soekris hardware, just check http://www.soekris.com/downloads.htm to see which files you will need.

6- Some utilities to send the firmware files to the soekris using the RS-232 link. The easiest way to install them is using MacPorts (so if you don't have MacPorts installed on your system go ahead and install them now):

sudo port -v install lrzsz

Ok, now that we've everything we need, let's go for it!

First we have to attach one end of the RS-232 cable to the soekris box (I don't have pictures of this but you can use google to see how to do it with some pictures) and then we have to attach the other end to the usb-to-serial adapter. Then we have to connect the usb adapter to the MacBook.

Now we have to open Terminal.app and see if a device called /dev/tty.PL2303-SOMETHING exists. Just use ls this way:

ls -l /dev/tty.PL2303-*

You should see something like /dev/tty.PL2303-00001004 (the number after the - will be probably different, as it changes depending on the OSx version and the usb-to-serial adapter itself). If the device does not exist, OSx does not recognize the hardware and you need to install some stuff before using it. Just get the needed drivers from here:


Install the needed stuff and repeat the process until you find the device in the correct place.

Once you find the device, it is time to establish a connection between the MacBook and the soekris box using a serial console. We've multiple options to do that. we can use screen:

screen /dev/tty.PL2303-00001004 19200

we can use cu:

sudo cu -l /dev/tty.PL2303-00001004 -s 19200

and there are even more options, like minicom and some others.

We are going to use cu because it will allow us to send the needed firmware files to the soekris using XMODEM (Yes!, like in the old times!). So, we run cu:

sudo cu -l /dev/tty.PL2303-00001004 -s 19200

and we plug the power cord in the soekris power socket. As soon as it boots up, we will see something like this in the screen where we are running cu:

POST: 012345689bcefghips1234ajklnopqr,,,tvwxy

comBIOS ver. 1.32  20070606  Copyright (C) 2000-2007 Soekris Engineering.


0256 Mbyte Memory                        CPU Geode LX 434 Mhz

Slot   Vend Dev  ClassRev Cmd  Stat CL LT HT  Base1    Base2   Int
0:01:2 1022 2082 10100000 0006 0220 08 00 00 A0000000 00000000 10
0:06:0 1106 3053 02000096 0117 0210 08 40 00 0000E101 A0004000 11
0:07:0 1106 3053 02000096 0117 0210 08 40 00 0000E201 A0004100 05
0:08:0 1106 3053 02000096 0117 0210 08 40 00 0000E301 A0004200 09
0:09:0 1106 3053 02000096 0117 0210 08 40 00 0000E401 A0004300 12
0:17:0 1814 0301 02800000 0117 0410 08 40 00 A0008000 00000000 15
0:20:0 1022 2090 06010003 0009 02A0 08 40 80 00006001 00006101
0:20:2 1022 209A 01018001 0005 02A0 08 00 00 00000000 00000000
0:20:4 1022 2094 0C031002 0006 0230 08 00 00 A0010000 00000000 07
0:20:5 1022 2095 0C032002 0006 0230 08 00 00 A0011000 00000000 07

 3 Seconds to automatic boot.   Press Ctrl-P for entering Monitor.

Good, this is the usual information we will see when booting a soekris board. If we look carefully, we will see the version of the current firmware running in that board:

comBIOS ver. 1.32  20070606  Copyright (C) 2000-2007 Soekris Engineering.

This time it is version 1.32, a little bit outdated as latest version is 1.33c.

Ok, we have to press ctrl+p when the BIOS asked us to do it (otherwise the boot process will go on, trying to boot from an attached CompactFlash card, a hardrive or using PXE Boot). Once we've pressed the shortcut, we will see a command prompt where we can use some commands to interact with the soekris BIOS. There is even a small help containing a list of available commands:

comBIOS Monitor.   Press ? for help.

> ?
comBIOS Monitor Commands

boot [drive][:partition] INT19 Boot
reboot                   cold boot
download                 download a file using XMODEM/CRC
flashupdate              update flash BIOS with downloaded file
time [HH:MM:SS]          show or set time
date [YYYY/MM/DD]        show or set date
d[b|w|d] [adr]           dump memory bytes/words/dwords
e[b|w|d] adr value [...] enter bytes/words/dwords
i[b|w|d] port            input from 8/16/32-bit port
o[b|w|d] port value      output to 8/16/32-bit port
run adr                  execute code at adr
cmosread [adr]           read CMOS RAM data
cmoswrite adr byte [...] write CMOS RAM data
cmoschecksum             update CMOS RAM Checksum
set parameter=value      set system parameter to value
show [parameter]         show one or all system parameters
?/help                   show this help


For the purpose of upgrading the firmware/BIOS, we need two of those commands, download (to get a copy of the new firmware) and flashupdate (to perform the upgrade).

This is the tricky part. In the prompt, we run the following command:

> download -

We will see a message like this one:

Start sending file using XMODEM/CRC protocol.

And we will have some time to send the new firmware file using XMODEM. As we can read in the official docs, we are supposed to press ~+ and then write a command (lsz -X) that will send the firmware files to the soekris.

But, as I found out after so many tries, it doesn't work. I tried using so many key strokes and combinations and I wasn't able to do it, until I tried one hell of a dirty trick: I just wrote the whole command somewhere else (in another tab of Terminal.app for example):

~+sz -X b5501_133c.bin

I copied it (command+c) and I pasted it (command+v) in the Terminal.app tab where cu was running. and it worked!:

> download -

Start sending file using XMODEM/CRC protocol.
~+sz -X b5501_133c.bin
Sending b5501_133c.bin, 784 blocks: Give your local XMODEM receive command now.
Bytes Sent: 100352   BPS:961

Transfer complete

File downloaded succesfully, size 784 Blocks.

Two important things here:

Fine, now that a copy of the new firmware file is in the soekris we only have to install it, using flashupdate:

> flashupdate
Updating BIOS Flash ,,,,,,,,,,,,,,,,,,,,,,,,,,,,..,,,,.... Done.

Once the BIOS has been updated, we just have to reboot the soekris box:

> reboot

And we will see the updated BIOS version on boot:

comBIOS ver. 1.33c 20080626  Copyright (C) 2000-2008 Soekris Engineering.

And et voilá, we've done the firmware upgrade. As I told you at the beginning of this small article, it was difficult at first, because I was getting on my nerves trying to reproduce the steps from the official docs one time and another. I still don't know what the problem is, and why it is not working just writing the command, but the copy&paste trick just worked fine.

I hope you will find this small tutorial useful some day.

Posted by wu at 00:11 | Comments (2)
13 mayo

OpenSSH port-forwarding only account

How to limit an SSH account so, without a valid shell, it still can create tunnels

OpenSSH is a very powerful and flexible tool (If you didn't know it, go ahead and visit http://openssh.org to learn more about it).

OpenSSH puffy!

Today I had to give access to some localhost-only service to an external user in one of my servers. I've used SSH port forwarding for quite some time now, I'm familiar with it and I thought it would be the better approach. Using port forwarding I wouldn't have to "open" that locahost-only service to the Internet and all the traffic between the external user and the service will pass through the Internet encrypted.

Let me explain you how I achieved that, because It was trickier than expected.

For the purpose of this article, this was tested using FreeBSD 8.2 and OpenSSH 5.4p1 both in the server and the external user box, but it would work with any other Unix-based OS (other BSDs, Linux, OSx, etc)

First - Create the user account

Obviously, if we want the external user to be able to connect to our server, first we need to create an account for him.

In FreeBSD, we can create the account using adduser, passing the needed parameters as a string:

echo "sshguest::::::Guest account:/home/sshguest:/usr/sbin/nologin:" | sudo adduser -w random -f "-"

The username is sshguest (use whatever you like instead) and we let adduser to asign automatically the UID and GID values for this new user (a new group will be created using the username value as the new group name).

We have set /home/sshguest as the home of the user because a home folder will be needed later (to store ssh public key authentication data) but you can set the home for this user to wherever you like in the filesystem (/tmp/sshguest, /var/tmp/sshguest could be good places for that)

As you probably noticed, In the example above we have set the shell to /usr/sbin/nologin, because we don't want the user to have shell access to our server.

Finally, we passed the "-w random" parameter to adduser so the password for this account would be automatically generated. The adduser tool creates some really strong passwords, so this is a good idea (and we are not going to use password-based authentation anyway).

Second - Get the public key for SSH authentication

Now that we've the account, we have to ask our external user for a public key to enable public key authentication.

If your external user has no idea about what public key authentication is (I would bet there is 90% posibilities of that) first point him to the OpenSSH documentation (RTFM!) and then tell him that he can generate the needed keys using, for example, the following command:

ssh-keygen -t rsa -b 2048

Two files will be generated, he has to send you the one called id_rsa.pub

(Of course, you can generate the keys yourself and send him the private key, instead of asking him for the public one)

Third - Enable public key authentication

Once we've received the key, we have to put that key in the file .ssh/authorized_keys inside the sshguest user (if you choose that name in the first place). In our previous example, it would be something like:

# cat /home/sshguest/.ssh/authorized_keys
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAMEAyjtJMRtejQaDafYfaL+qc9LrhqHhAQnc7TwF/M3BDTJrZ9ucXLejRwvq/OOcRSsOnUGQyYc2w/TbKQE77cHzujd+PBKgdnQfu8hjJ2Q8to+q8gAEJEOscvLwAACq74llZBmD8gHWBXP6vthkPTFZyd7fkEsKkSgGFiWCZBl+YkibLoRrJpuk462XzPWC4KkOjiUwtJ+2PP1ZvMYYvxnWn+IxwQtbDqwDhY3DnN1dYTeUPT1umtrCCtzQjjynHFFb someuser@example.net

Note: we have to create the .ssh directory within the sshguest home directory and we need to be sure proper permissions are applied to both the directory and the file:

mkdir /home/sshguest/.ssh
echo id_rsa.pub > /home/sshguest/.ssh/authorized_keys
chown -R sshguest /home/sshguest/.ssh
chmod 700 /home/sshguest/.ssh
chmod 600 /home/sshguest/.ssh/authorized_keys

Once we have added the authorized_keys file, we can test the authentication connecting from the external user box (or asking him to try):

ssh sshguest@example.net

What the user will see in the shell/console will be something like:

$ ssh sshguest@example.net
Copyright (c) 1980, 1983, 1986, 1988, 1990, 1991, 1993, 1994
      The Regents of the University of California.  All rights reserved.

FreeBSD 8.2-STABLE (GENERIC) #1: Tue Apr 12 12:27:33 CEST 2011

[ ... skip ... ]

This account is currently not available.
Connection to example.net closed.

What happened here? Well, the SSH connection was opened, authentication procceded correctly and the user's shell was loaded. As the shell was set to /usr/sbin/nologin, the system refuses to give a shell to the user and simply drops the connection.

Which, in the end, was what we were looking for, wasn't it?

Four - Fixing the shell problem

Well, that was what we wanted from the beginning, if it would allow us to use SSH's port forwarding features.

Sadly, that's not the case. If we try to open an SSH connection doing some port forwarding, the connection will be dropped too, and the tunnel will not be created:

$ ssh -L 8080:localhost:80 sshguest@example.net

[ ... skip ... ]

This account is currently not available.
Connection to example.net closed.

This happens because we have set /usr/sbin/nologin as the shell for the sshguest user. That, at least in FreeBSD, disables that user logins.

We have to replace it but replacing it with sh, tcsh or bash is not an option (we don't want to give shell access to this user) so we need a fix for that.

One option would be to set a real shell (like sh, tcsh or bash) and then restrict the commands the user will be able to execute. But, as I've said, I don't like the idea of the user being able to start a shell, even if we restrict the comments he will be able to execute later.

Another option, which I personally prefer, is to create a small script that will act as the shell for this user, restricting access completely.

This is a small python script that will do the trick:

command = ''

while command != 'exit':
    command = raw_input('Type the "exit" command to close the connection: ')

The script will wait for user input and, when the user provides the "exit" command, the script will stop execution and the SSH connection will be closed.

We could save this script inside the home of the sshguest user:

mkdir /home/sshguest/bin
# then put the contents of the script inside /home/sshguest/bin/shell
chown -R sshguest /home/sshguest/bin
chmod -R 700 /home/sshguest/bin

and then we could use vipw to modify the user entry, from:

sshguest:$1$ICdMwdEn$3d3X83jdyT8ifLPnl8D8x/:1117:1117::0:0:Guest account:/home/sshguest:/usr/sbin/nologin


sshguest:$1$ICdMwdEn$3d3X83jdyT8ifLPnl8D8x/:1117:1117::0:0:Guest account:/home/sshguest:/home/sshguest/bin/shell

Important: These lines above apply for my current example, in your case it would be different because of the password hash and the UID/GID values.

Obviously, while the python script is being executed (with minimal resources usage) the SSH connection will be kept opened and then the SSH tunnel will work.

$ ssh -L 8080:localhost:80 sshguest@example.net

[ ... skip ... ]

Type the "exit" command to close the connection:

If the user provides the "exit" command, the connection is dropped:

Type the "exit" command to close the connection: exit
Connection to example.net closed.


This is a quick hack. As you can imagine, the script could be improved a lot. We could provide a more complete shell-like interface so our external user can perform certain operations on the server or the connection.

One thing I miss here is the posibility to restrict port forwarding too, allowing the external user to create tunnels only to, for example, a given set of ports or even only one port.

BTW, this hack helped me a lot this morning, I hope it will help any of you one day.

Posted by wu at 11:11 | Comments (2) | Trackbacks (0)
[1]   2   Next