Assertion failed

april 30, 2015

Getopt from bad to worse

Note: This is about getopt in shells e.g. bash, not languages like C

Getopt used to be the way to parse arguments in shellscripts, but it has some issues. Most notably it’s very hard to handle whitespaces in arguments correctly. Let’s say you have the flag -f which takes some value e.g. -f myval. If this value contains a whitespace, e.g. -f 'my val', we have a problem. Getting this parsed correctly in the shell is really hard and I can’t remember ever having seen it done in a way without notable issues. Because of this modern shells contain a function called getopts, which solves most of the problems getopt has.

If all this has already been solved why are we here? Well, after searching online for how to parse long options with getopts which it usualy doesn’t support natively, I came across alot of posts where they used getopt instead since at least the GNU version has support for long options. In an effort I belive is to get around the whitespace issue they will send the output from getopt through eval.

Let’s look at some examples. Usually you will see something similar to the following when getopt is used.

opts=$(getopt $optstring "$@")
set -- $opts

But in a lot of the code out there it’s done like this instead.

opts=$(getopt $optstring "$@")
eval set -- "$opts"

With the way the shell works this doesn’t solve any problems with whitespaces, instead it introduces new challenges, which will often let users execute commands in the context of the script.

As an example let’s use the following script as an example.

#!/bin/sh

opts=$(getopt 'f:' "$@")
eval set -- "$opts"

f_arg=

while true ; do
  case "$1" in
    -f)
      f_arg="$2"
      shift 2
      ;;
    --)
      shift
      break
      ;;
    *)
      echo "Something unexpected: $1" >&1
      shift
      ;;
  esac
done

echo "$f_arg"

Now let’s see how it behaves.

% ./getopt.sh

% ./getopt.sh -f
getopt: option requires an argument -- 'f'

% ./getopt.sh -f test
test
% ./getopt.sh -f 'test1 test2'
Something unexpected: test2
test1

At first glance this doesn’t look right, but this is expected behavior, even when quoting $opts and using eval. This is not the most interesting part and you hopefully knew this already, so let’s continue and try some more creative arguments.

% ./getopt.sh -f 'test1 -- ;echo test2 #'
test2
test1

What’s happening here we are able to specify commands to be executed in the context of the script as commandline arguments. In most cases it would be harmless, but I would argue it’s still not a good thing. The bad big issue arises when the script is used by unprivieleged users through for example sudo to interact with some piece of software. E.g. you might have some servers on which some users need to interact with a piece of software. They might need to read out information from the software, and to do this they need to be a special user. To get around this you allow them to run these commands/script through sudo, in which case this issue could have a noteworthy security impact by allowing the user to run arbitrary commands instead of just a few pre-defined ones.

Let’s see this example in action where we have to run the script as a specific user. /usr/local/bin/getopt.sh is the same script as before, but modified to abort as early as possible if running as the wrong user.

% whoami
marius
% which getopt.sh
/usr/local/bin/getopt.sh
% getopt.sh -f test
Not the correct user!
% sudo -l
User marius may run the followig commands on testhost:
    (svcuser) /usr/local/bin/getopt.sh
% sudo -u svcuser getopt.sh -f test
test
% sudo -u svcuser getopt.sh -f 'test -- ;whoami #'
svcuser
test
% sudo -u svcuser getopt.sh -f 'test -- ;sh ;exit #'
$ whoami
svcuser
$ exit

With the last example where we started a shell the original program will stop executing after the shell we spawn exits thanks to the exit in the arguments.

In the end I would recomend using getopts instead of getopt, as it solves the whitespace problem and it’s built into most shells today, and if you for some reason have to use getopt you should not send its output through eval.

posted at 16:00  ·   ·  sh  bash  getopt  linux  bsd

april 30, 2015

XMPP-federation with google

I have my own XMPP-server running ejabberd with federation enabled. But when trying to add a contact using gmail to the roster, it failed and I got the following message in the log.

2015-04-30 13:34:35.086 [info] <0.2063.0>@ejabberd_s2s_out:wait_for_validation:450 Closing s2s connection: lden.org -> gmail.com (invalid dialback key)

After some searching on the web it turns out google does not support TLS when fedrating. Since I had configured ejabberd to require TLS, even with a self-signed certificate this did not work.

To get around this I had to change s2s_use_starttls from ‘required’ to ‘optional’. The caveat with this is you now allow servers to speak unencrypted between each other. I would prefer if this was not allowed, so I have yet to decide if I leave this as optional or change it back to required and survive without being able to federate with google.

nov. 03, 2014

Running FreeBSD 7.2 on 8.4

Just before this weekend we had some issues with an old server which had lost one of the disks in it’s mirror and the second was starting to fail. This is a server we plan to decommission, but unfortunately we can’t do this yet as it still has a lot of important services running. This server was running an FreeBSD 7.2 and thankfully all services we care about was running in a jail. So it was decided, to get a temporary solution to this, we would create a new FreeBSD 8.4 virtual machine and move the jail as is to the new machine.

From my earlier experience with running 9.x on 10 I expected this to go like a charm, but unfortunately this was not the case. After several ours of transfering the jail to the new host, the time had finally come to start it. It started just as expected, but some of the services did not come up. More specifically stunnel and mysql. After some debugging it turned out they both failed on the function ‘kse_create’. This function is a part of the kernel supported user threads (kse(2)). Support for these was removed with FreeBSD 8, and since I was staying with the 7.2 userland in the jail it was not just to recompile the affected programs in the jail.

To solve this I recompiled the affected programs for 8.4, before I installed them to the 7.2 jail. This did have one problem though, the binary was linked against libraries which wasn’t available in FreeBSD 7.2. The natural solution for this would be the compat8x port, but it contained a lot of libraries which wasn’t necessary and was lacking a few which was needed. So instead of using compat8x, I chose to manually copy the missing libraries from the 8.4 machine and install them to the 7.2 jail. Then after some compiling the services which originally used kse was back up again without it.

okt. 15, 2014

Running poudriere in a jail

Poudriere is a great tool when you need to build packages for freebsd, but sometimes one would prefer to run it in a jail. Even though this is not recomended there is a basic guide for how to do this on the poudriere wiki. But this does not work if your poudriere jail is configured with an ip-address instead of inheriting ip-addresses.

In addition to the settings shown in the guide one would have to add the following to poudriere.conf:

LOIP4="192.168.1.10/24" # IPv4 of jail
LOIP6="fec0::10/64"     # IPv6 of jail

But there is still one caveat left to discover. If your kernel supports IPv4/6 and the jail does not have an IPv4/6-address it will fail. This problem can be solved with the following patch assuming you use poudriere 3.1-pre (your miles may wary with other versions).

diff --git a/src/share/poudriere/common.sh b/src/share/poudriere/common.sh
index 6a78e37..ef91e4b 100755
--- a/src/share/poudriere/common.sh
+++ b/src/share/poudriere/common.sh
@@ -4341,6 +4341,13 @@ fi

 : ${LOIP6:=::1}
 : ${LOIP4:=127.0.0.1}
+
+[ "${LOIP4}" = "no" ] && \
+       IPS="0$(echo $IPS | tail -c 1)"
+
+[ "${LOIP6}" = "no" ] && \
+       IPS="$(echo $IPS | head -c 1)0"
+
 case $IPS in
 01)
        localipargs="ip6.addr=${LOIP6}"

After applying this patch you can use the following in poudriere.conf to disable IPv4 and/or IPv6:

LOIP4="no" # Disable IPv4
LOIP6="no" # Disable IPv6

What you will probably want is something similar to the following:

LOIP4="192.168.1.10/24" # IPv4 of jail
LOIP6="no"              # Disable IPv6

or:

LOIP4="no"          # Disable IPv4
LOIP6="fec0::10/64" # IPv6 of jail

Note: When disabling either IPv4 or IPv6 ‘no’ is case sensitive when using this pathch.

okt. 07, 2014

Netbooting mfsbsd

Currently I’m trying to automatically installing FreeBSD on zfs by netbooting a comuter. As a part of this I have to pxeboot a mfsbsd image which actually does the install. So far this is how I got mfsbsd to netboot of a FreeBSD host.

First of all we need to grab an mfsbsd image and setup the root for tftp. The tftp root folder is commonly placed at /tftpboot.

# fetch http://mfsbsd.vx.sk/files/iso/10/amd64/mfsbsd-10.0-RELEASE-amd64.iso
# mkdir -p /tftpboot/mfsroot
# tar -xf mfsbsd-10.0-RELEASE-amd64.iso -C /tftpboot/mfsroot
# cp /boot/pxeboot /tftpboot

After this is done we will need an tftp server. FreeBSD comes with one in the base system so we will only have to activate this in /etc/inetd.conf. This is done by uncommenting the following lines in /etc/inetd.conf, the second line is only necessary if you want to use ipv6.

tftp    dgram   udp     wait    root    /usr/libexec/tftpd  tftpd -l -s /tftpboot
tftp    dgram   udp6    wait    root    /usr/libexec/tftpd  tftpd -l -s /tftpboot

Unfortunately the freebsd pxe loader only supports nfs without recompiling it. This isn’t a big issue though as FreeBSD as an nfs server in the base system. To tell nfsd to share the location where we placed the mfsbsd image, add the following to /etc/exports

/tftpboot/mfsroot -ro -maproot=root -network=192.168.1.0 -mask=255.255.255.0

Before we are done we will need an dhcp server, if you don’t have one allready you can get one from port by using the following command.

# pkg install isc-dhcp42-server

Following is an example of dhcpd.conf, you will probably have to modify it for your needs.

subnet 192.168.1.1 netmask 255.255.255.0 {
    range 192.168.1.50 192.168.1.100;
    option routers 192.168.1.1;
    option domain-name-servers 192.168.1.1;
    option domain-name "domain.tld";
    default-lease-time 3600;
    max-lease-time 3600;
    next-server 192.168.1.2;
    option root-path "/tftpboot/mfsroot";
    filename "pxeboot";
}

host host.domain.tld {
    ethernet AA:BB:CC:DD:EE:FF;
    option host-name "host";
}

At last before we can netboot mfsbsd we will have to activate and start all the services we just configured. This can be done with the following commands.

# sysrc dhcpd_enable=YES
# sysrc nfsd_enable=YES
# sysrc mountd_enable=YES
# sysrc inetd_enable=YES
# service nfsd start
# service isc-dhcpd start
# service inetd start

Finally we should be able to boot netboot mfsbsd. Going forward I’m looking into modifying the mfsbsd image to include either an installer or some bootstraping to download and execute an installer. The goal is to install FreeBSD without human intervention, only by pxe booting a host in the correct subnet.

okt. 07, 2014

Automagically fetching FreeBSD

While building a custom mfsbsd image I had to fetch the distfiles and verify their hash, to do this simpler and faster I threw together this shellscript, which I’m going leave here for later. The script will automatically download a manifest via http or ftp, then it will continue by downloading all files defined in the manifest check their checksum.

#!/bin/sh
set -e

url=${1:-"http://ftp.uninett.no/FreeBSD/releases/amd64/amd64/10.1-RC1/"}

fetch ${url}/MANIFEST

cat MANIFEST | while read file x; do
        fetch ${url}/${file}
done

cat MANIFEST | while read file sum x; do
        echo -n "${file}: "

        if [ "$(sha256 -q ${file})" = "${sum}" ]; then
                echo "OK"
        else
                echo "FAIL"
        fi
done

sep. 29, 2014

Dotfiles, git and stow

Keeping your dotfiles in a git repository can be very handy, but unforunantly it’s generally not a good idea to put your whole home directory in a git repo. To come around this I have seen some people add “*” to gitignore, but that is more of a hack than a proper solution. Another way of doing this is to place all dotfiles in a seperate directory which is in git and symlink back to where they are expected. I would consider this to be a better solution, but it is a lot more work. Installing and maintaining the symlinks can become tedious if the amount of dotfiles managed becomes a little more than a few.

This is where GNU stow comes in. Although it’s not made to keep track of dotfiles, but rather programs manually compiled and installed to /usr/local. But this doesn’t really matter as it will serve well for our purpuse.

Let’s start by creating a directory for dotfiles and initiating a git repository in it. I’m assuming you know how to install the needed tools.

% mkdir dotfiles
% cd dotfiles
% git init

After this we can continue by moving some of our dotfiles.

% mkdir vim
% mv ../.vimrc vim
% mkdir zsh
% mv ../.zshrc zsh
% git add -A
% git commit -m initial

Now we have moved some of our dotfiles to a new home and added them to git, but we still have to install some symlinks where these files are expected to be found.

% stow vim
% stow zsh

By running the previous commands we installed the symlinks automagically. We can even make stow remove them.

% stow -D zsh

To wrap things up. The great thing about this solution is how easy it is to use and one can even deside which dotfiles one want to have installed by splitting them up in separate subdirectories, or packages as stow calles it. I have been using this solution for some time now without any issues worth mention.

sep. 28, 2014