Informatique

Lightweight music streamer with Icecast

Wednesday, 03 February 2021
|
Écrit par
Grégory Soutadé

My Cubox server offers a lot of services and one I specially appreciate when I'm not at home is that it contains all my music I can access through HTTP(S) interface. This is really fine for Linux clients where mplayer is installed, but it's not the case for Windows : VLC refuse to play my music (which requires login/password) and Windows player doesn't support m3u playlists, so I have to play each track individually.

I started to look for streamer software and the biggest open source one is Icecast which implement SHOUTcast standard. Like for all my online services, software must be lightweight (I don't have a lot of RAM) ! Plus I don't have any sound card plugged and don't want to spare cpu bandwidth with decoding/encoding files. This is a reason why basic HTTP(S) transfert is good : files are trasfered as is. Even if it's not clearly indicated by the documentation, icecast coupled with ezstream has all the qualities I need ! I was really suprised to find how it was easy to setup !

Here is a tutorial for basic setup (with current Debian stable distribution) :

If ezstream is available in your repository

sudo apt-get install icecast2 ezstream

If not (for an ARM target)

sudo apt-get install icecast2 libshout3 libtagc0 libxml2
wget http://ftp.de.debian.org/debian/pool/main/e/ezstream/ezstream_1.0.1-1_armhf.deb
sudo dpkg -i ezstream_1.0.1-1_armhf.deb

Now, we have to configure icecast. You must edit /etc/icecast2/icecast.xml. Update (at least) :

  • Admin name/address
  • Server address
  • Optional : server port

Some of these values has already been configured by installer. There is a lot of avaible options not needed for basic setting. After that, restart icecast :

sudo service icecast restart

Now, add a NAT rule to redirect external port (8000 by default) to your server. Then, copy an example of ezstream configuration :

cp /usr/share/doc/ezstream/examples/ezstream-minimal.xml .

and edit it (be careful, here end tag are crafted by my editor) :

<ezstream>

  <servers>
    <server>
      <hostname>127.0.0.1</hostname>
      <port>8000</port>
      <password>XXX</password>
    </server>
  </servers>

  <streams>
    <stream>
      <mountpoint>/stream.mp3</mountpoint>
      <format>MP3</format>
    </stream>
  </streams>

  <intakes>
    <intake>
      <filename>/media/MyTrack.mp3</filename>
      <stream_once>1</stream_once>
    </intake>
  </intakes>

</ezstream>

I choose to play track only once. If not set, it will be played indefinitely. Now, we can run ezstream :

ezstream -c ezstream-minimal.xml

On Windows, we can use VLC to read this stream by opening :

http://icecast.soutade.fr:8000/stream.mp3

This is a basic setup, but we can do a lot of more complex things by autogenerating config file and auto start ezsteram from a web frontend for example.

Gnome Shell Generic Monitor v3

Thursday, 03 December 2020
|
Écrit par
Grégory Soutadé

Capture Gnome Shell Generic Monitor

La version 3 de mon extension Generic Monitor pour Gnome Shell vient d'être revue et validée ! Tout est parti d'un ticket ouvert sur ma forge me demandant si l'on ne pouvait pas ajouter un popup pour faire une prévisualisation d'une image ou autre. Je me suis dit qu'il suffirait de dériver mon objet principal d'un objet menu et le tour serait joué.

Finalement j'ai dû retravailler tout le code, changer le protocole et ajouter des nouveaux signaux !

Changements principaux :

  • Nouveau protocole DBUS un peu plus verbeux car l'on passe désormais des "objets" JSON en paramètre (pour le moment la rétro compatibilité est assurée)
  • Ajout d'un exemple picture.py permettant d'afficher une image aléatoire du site unsplash.com
  • Possibilité d'avoir des actions différentes pour les sous catégories de clicks : gauche, double et droits
  • Ajout des signaux onEnter, onLeave, onScrollUp et onScrollDown
  • Ajout d'un objet popup pouvant accepter des objets textes et images

Les sources sont disponibles sur ma forge

Trust your SSH server

Friday, 23 October 2020
|
Écrit par
Grégory Soutadé

This article you're reading is hosted on my own server. This last one runs a lot of services : web, mail, database, XMPP... and to manage it I need an SSH connection which is the more secure way to connect to a remote server. But, how I can trust this connection in an hostile environment ?

Connection protocols and key exchange has greatly evolved the last 20 years, but there are still based on a root asymmetric key pair (RSA, DSA, ECDSA...). When you connect to a server for the first time, you get a message like this :

The authenticity of host 'mint.phcomp.co.uk (78.32.209.33)' can't be established.
RSA key fingerprint is 6a:de:e0:af:56:f8:0c:04:11:5b:ef:4d:49:ad:09:23.
Are you sure you want to continue connecting (yes/no)? no

This is a human readable fingerprint of the root key used to establish a connection. Personally, I don't know my server fingerprint by heart. There is some solutions to check it :

  • Manually by printing it on a paper/on your phone/on a USB key
  • Register it with a DNS record, but DNS server/response can be easily spoofed
  • Using a public key based connection (you need to keep it on a USB key)

The better remains having the secret (key or fingerprint) somewhere you could access it. I propose in this article an other solution you can always run in an hostile environment without any previous setup.

The idea is to create a restricted user that can only run a verification script that will check fingerprint once connection is established which avoid Man In The Middle attacks !

Setup

First, we'll have to create this user named check-user :

su
useradd --create-home --no-user-group --shell /bin/rbash check-user
cd /home/check-user

You can set a password or not. I don't do it, so I cannot open a connection from external nor internal as my server always checks for password (we can still use su/sudo command). I also set a restricted shell (rbash).

Then, we have to create a key pair

su check-user
ssh-keygen
cp .ssh/id_rsa.pub .ssh/authorized_keys

You can set or not a password for this key. Then, edit .ssh/authorized_keys and add :

command="rbash check_ssh_server.sh" ssh-rsa AAAA...

Now, downloads check_ssh_server.sh in /home/check-user and set execution permissions.

Then, go to your webserver directory were you can put some downloadable files (something like /var/www) and copy SSH the private key.

cd /var/www
cp /home/check-user/.ssh/id_rsa ssh_check
chmod a+r ssh_check

Now, you can edit and run check_ssh_client.sh from any network !

How does it works ?

The client starts by establishing an SSH connection and close it immediately in order to retrieve remote fingerprint. Then, it downloads check-user SSH private key and use it to connect to the server and send the fingerprint. The only command that can be run with this key is rbash check_ssh_server.sh which get the fingerprint and compare with the ones installed on the server side. A message is then displayed which indicates if the connection is secure or not.

Scripts

check_ssh_server.sh

#!/bin/bash

target_key=`echo $SSH_ORIGINAL_COMMAND| tr -d "\r\n"`

if [ -z "${target_key}" ] ; then
    echo "Empty key provided, abort"
    exit 0
fi

for keyfile in /etc/ssh/ssh_host_*_key.pub ; do
    a=`ssh-keygen -l -f ${keyfile}|grep "${target_key}"` # To avoid print
    if [ $? -eq 0 ] ; then
        echo "Target key found, your connection is secure !"
        exit 0
    fi
done

echo "!!! WARNING !!! Key not found, the connection may not be secure"

exit 1

check_ssh_client.sh

#!/bin/bash

KEY_TRACE="Server host key:"
SSH_CHECK_KEY="https://soutade.fr/files/ssh_check/ssh_check"
REMOTE_USER="check-user"

if [ -z "$1" ] ; then
    echo "usage : $@ <ssh server>"
    exit 0
fi

echo "Retrieve remote key for $1"
tmp_file=`mktemp`
ssh -v -o "NumberOfPasswordPrompts=0" $@ >${tmp_file} 2>&1
key=`cat ${tmp_file}|grep "${KEY_TRACE}"`
key=`echo ${key}|cut -d" " -f6`
rm -f ${tmp_file}

echo "Retrieve SSH private key from ${SSH_CHECK_KEY}"
wget -O ssh_check ${SSH_CHECK_KEY}
chmod 0400 ssh_check

echo "Check for key ${key}"
ssh -l ${REMOTE_USER} -i ssh_check $@ "${key}"
echo "Cleaning"
rm -f ssh_check

gPass 1.0

Friday, 16 October 2020
|
Écrit par
Grégory Soutadé

Logo gPass

Petit rappel : gPass est un gestionnaire de mot de passes en ligne. C'est une alternative libre à lastpass. Il permet d'héberger un serveur de mot de passe, qui stockera un mot de passe fort et unique pour chaque site web. Les mots de passes sont chiffrés par une "clé maître" que seul l'utilisateur connaît et sont remplacés à la volée dans le formulaire d'authentification.

gPass version 1.0 est dehors ! En réalité, cela ne constitue pas un tournant majeur dans la vie de ce greffon, mais simplement une "dixième" version qui évolue régulièrement de manière incrémentale. La liste des modifications n'est d'ailleurs pas énorme. On pourra toutefois noter un petit nettoyage de code.

Côté serveur :

  • Nouveau bouton "clear form"
  • Défilement automatique sur l'entrée correspondante lors de la validation d'une clé maître si le champs URL est renseigné (ce qui est le cas quand on clique sur le lien "Your server" du popup)

Addon :

  • Suppression de la compatibilité avec l'ancien code de cryptographie
  • Suppression du code de l'addon de Firefox pour ne garder que la webextension
  • Suppression de la fonction de blocage des requêtes
  • Copie du mot de passe dans le presse papier quand on utilise "@_" dans la fenêtre popup même s'il est rempli dans le formulaire
  • Correction d'un petit bug lors de la copie du mot de passe dans le presse papier

Les addons sont disponibles ici (firefox) et (Chrome). La partie serveur est à télécharger sur la page du projet.

C Macro stuff

Saturday, 22 August 2020
|
Écrit par
Grégory Soutadé

Let's play with some strangefun C stuff related to macro implementation in GCC compiler.

In the next code, we will work on this kind of structure which is a basic implementation of a matrix structure :

typedef struct {
  int width;
  int height;
  double* data;
} matrix;

In a standard algorithm implementation, we would use malloc() for at least .data field. But, in embedded world, it's preferable to use static allocations (if we use "static" matrices with known parameters).

Use an array as a C macro parameter

First trap of our compiler. We want to statically initalize an array like this :

#define STATIC_ARRAY(_name, _values) double _name[] = _values

STATIC_ARRAY(myArray, {0.0, 0.1, 0.2};

Compilation returns this error :

b.c:5:38: error: macro "STATIC_ARRAY" passed 4 arguments, but takes just 2
STATIC_ARRAY(myArray, {0.0, 0.1, 0.2});

The second argument is not considered as an array, but a list of arguments. My question is WHY ??? This seems a non sense as the goal of macros is to copy paste without interpretation.

Solution I found is to use a variadic macro parameter. This will only works for one array value.

#define STATIC_ARRAY(_name, ...) double _name[] = __VA_ARGS__

STATIC_ARRAY(myArray, {0.0, 0.1, 0.2});

In flight shadow array

Another problem come if I make a more complex macro to initialize my structure. Basically, we may do something like this :

#define STATIC_MATRIX(_name, _width, _height, ...)          \
  static matrix _name = {.width = _width, .height=_height, .data=__VA_ARGS__}

STATIC_MATRIX(myMat, 2,2, {0.0, 0.1, 0.2, 0.3});

Which give a wonderful error :

b.c:13:1: warning: braces around scalar initializer
STATIC_MATRIX(myMat, 2,2, {0.0, 0.1, 0.2, 0.3});
^
b.c:13:1: note: (near initialization for ‘myMat.data’)
b.c:13:28: error: incompatible types when initializing type ‘double *’ using type ‘double’
STATIC_MATRIX(myMat, 2,2, {0.0, 0.1, 0.2, 0.3});

The correct way to initialize our structure is to declare an array and affect it to .data field. We can embbed this declaration in macro definition :

#define CONCAT_DIRECT(s1, s2) s1##s2
#define CONCAT(s1, s2) CONCAT_DIRECT(s1, s2)

#define STATIC_MATRIX(_mat, _w, _h, ...)            \
static double CONCAT(sarr_,__LINE__)[]=__VA_ARGS__;static matrix _mat={.width=_w,.height=_h,.data=CONCAT(sarr_,__LINE__)}

STATIC_MATRIX(myMat, 2,2, {0.0, 0.1, 0.2, 0.3});

This a bit long, but it works ! The macro creates two statics objects, one named by user and one which is unique thanks to line number concatenation. Note, that we cannot split it on two lines due to line number in the name.

We can modify this macro (or create a new one) by removing static attributes. In this case, be careful if you define it in an header, and use it in two separate file at the same line. It could be interesting to use static objects or not depending on variables usage.

Another improvement could be to create const array if data is not updated. Thus, compiler can use a the same memory space for multiple matrix arrays with the same values.

Last but not least. Here, the created array has a variable length that depends on value in parameter. We can fix its size with :

static double CONCATENATE(static_darray_,__LINE__)[_width*_height]