Thursday, February 5, 2015

Compile a headless FreeRDP for credential checking

Lately, I needed to have a command-line tool that was able to check for the validity of RDP credentials from a host with no X Server installed. The goal was to have a webapp that allows to check for the validity of credentials. For SMB-only, I could have used winexe, but even if Windows creds are valid, an RDP session may fail because the user isn't in the correct group, or lacks permissions, etc.

Compiling xfreerdp for this usage is a bit tricky because we want to have a version that loads the least possible libraries in order to have a binary that is as much portable as we can. Also, we don't need to include any rendering / audio libraries at all. We just want to check for credentials.

This tutorials explains how to compile it to be run on a server with no X Server installed, and to avoid the need to install / copy over additional dependencies.

It should be able to run on a 32 or 64 bit ubuntu server without additional configuration. However some system libraries such as LibSSL, must have the same version than the one that was used to compile it.

The version compiled at the time of writing is this commit. The version of cmake used is 2.8 (version >= 3 can cause some issues).

It is highly recommended to perform the following on a development VM and then copy the binary over, rather than on a production server.

The VM used for this writing is Ubuntu Server 14.04 LTS (Trusty) x64.

Compiling


1. Install and update the OS if needed.

2. Install the required dependencies

sudo apt-get install unzip pkg-config build-essential git-core cmake libssl-dev libx11-dev libxext-dev libxinerama-dev libxcursor-dev libxdamage-dev libxv-dev libxkbfile-dev libasound2-dev libcups2-dev libxml2 libxml2-dev libxrandr-dev libgstreamer0.10-dev libgstreamer-plugins-base0.10-dev libavutil-dev libavcodec-dev

3. Download the sources:

cd ~
wget https://github.com/FreeRDP/FreeRDP/archive/master.zip

4. Extract and compile. Here using CMAKE we generate the Makefiles according to the options we specify. In our case, we don't need any rendering or audio libraries etc, so we disable everything we can.
For an explanation of each parameter, run "cmake -L" inside the directory containing the sources.

unzip master.zip
cd FreeRDP-master
cmake -DMONOLITHIC_BUILD=ON -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=Release \
-DWITH_SSE2=OFF -DWITH_ALSA=OFF -DWITH_FFMPEG=OFF -DWITH_MANPAGES=OFF \
-DWITH_SERVER_INTERFACE=OFF -DWITH-PULSE=OFF -DWITH_GSTREAMER_1_0=OFF \
-DWITH_XKBFILE=OFF -DWITH_XINERAMA=OFF -DWITH_XEXT=OFF -DWITH_XCURSOR=OFF \
-DWITH_XV=OFF -DWITH_XI=OFF -DWITH_XRENDER=OFF -DWITH_XFIXES=OFF .
make
make install

5. Link the libraries and update the cache:

echo /usr/local/lib/freerdp > /etc/ld.so.conf.d/freerdp.conf
ldconfig

6. The binary should have been created in ~/FreeRDP-master/client/X11. You can see how many libraries it loads with the following command (With the defaut cmake options, it would need more than 60):

$ ldd `which xfreerdp` | wc -l
13

Modify relevant parts of the code


We want to use the "+auth-only" parameter to instruct xfreerdp to not connect to the X Server and just check for the credentials. But for some reason, it still wants to connect to it and so it fails when there is none.

We must modify the source code of the following file:

~/FreeRDP-master/client/X11/xf_client.c

What we want to do is to "Return true" without even checking for a X Server. Look for the function called:

BOOL xf_pre_connect(freerdp* instance)

We must comment out or remove some blocks inside this function. For simplicty replace the whole function with the following code:

OL xf_pre_connect(freerdp* instance)
{
        rdpChannels* channels;
        rdpSettings* settings;
        xfContext* xfc = (xfContext*) instance->context;

        xfc->codecs = instance->context->codecs;
        xfc->settings = instance->settings;
        xfc->instance = instance;

        settings = instance->settings;
        channels = instance->context->channels;

        settings->OsMajorType = OSMAJORTYPE_UNIX;
        settings->OsMinorType = OSMINORTYPE_NATIVE_XSERVER;

        ZeroMemory(settings->OrderSupport, 32);
        settings->OrderSupport[NEG_DSTBLT_INDEX] = TRUE;
        settings->OrderSupport[NEG_PATBLT_INDEX] = TRUE;
        settings->OrderSupport[NEG_SCRBLT_INDEX] = TRUE;
        settings->OrderSupport[NEG_OPAQUE_RECT_INDEX] = TRUE;
        settings->OrderSupport[NEG_DRAWNINEGRID_INDEX] = FALSE;
        settings->OrderSupport[NEG_MULTIDSTBLT_INDEX] = FALSE;
        settings->OrderSupport[NEG_MULTIPATBLT_INDEX] = FALSE;
        settings->OrderSupport[NEG_MULTISCRBLT_INDEX] = FALSE;
        settings->OrderSupport[NEG_MULTIOPAQUERECT_INDEX] = TRUE;
        settings->OrderSupport[NEG_MULTI_DRAWNINEGRID_INDEX] = FALSE;
        settings->OrderSupport[NEG_LINETO_INDEX] = TRUE;
        settings->OrderSupport[NEG_POLYLINE_INDEX] = TRUE;
        settings->OrderSupport[NEG_MEMBLT_INDEX] = settings->BitmapCacheEnabled;
        settings->OrderSupport[NEG_MEM3BLT_INDEX] = (settings->SoftwareGdi) ? TRUE : FALSE;
        settings->OrderSupport[NEG_MEMBLT_V2_INDEX] = settings->BitmapCacheEnabled;
        settings->OrderSupport[NEG_MEM3BLT_V2_INDEX] = FALSE;
        settings->OrderSupport[NEG_SAVEBITMAP_INDEX] = FALSE;
        settings->OrderSupport[NEG_GLYPH_INDEX_INDEX] = TRUE;
        settings->OrderSupport[NEG_FAST_INDEX_INDEX] = TRUE;
        settings->OrderSupport[NEG_FAST_GLYPH_INDEX] = TRUE;
        settings->OrderSupport[NEG_POLYGON_SC_INDEX] = (settings->SoftwareGdi) ? FALSE : TRUE;
        settings->OrderSupport[NEG_POLYGON_CB_INDEX] = (settings->SoftwareGdi) ? FALSE : TRUE;
        settings->OrderSupport[NEG_ELLIPSE_SC_INDEX] = FALSE;
        settings->OrderSupport[NEG_ELLIPSE_CB_INDEX] = FALSE;
        
        if (!settings->Username)
        {
                char *login_name = getlogin();
                if (login_name)
                {
                        settings->Username = _strdup(login_name);
                        WLog_INFO(TAG, "No user name set. - Using login name: %s", settings->Username);
                }
        }

        if (settings->AuthenticationOnly)
        {
                /* Check +auth-only has a username and password. */
                if (!settings->Password)
                {
                        WLog_INFO(TAG, "auth-only, but no password set. Please provide one.");
                        return FALSE;
                }

                WLog_INFO(TAG, "Authentication only. Don't connect to X.");
        }
        
        return TRUE;
}

Optionally, we can also remove the code that creates the certificates in the user's home directory as we don't want this. Open the following file:

~/FreeRDP-master/libfreerdp/crypto/certificate.c

The first function at the top of the file should be:

int certificate_store_init(rdpCertificateStore* certificate_store)

Simply remove or comment all the code inside this function except the final "return 1;".
Now we can recompile xfreerdp.
cd ~/FreeRDP-master
make
./client/X11/xfreerdp --help

At this point we have a usable, almost-portable, headless version of xfreerdp that we can copy over to another server.

Check for credentials


I have been able to see three different outputs (there might be more):

1. When authentication succeeds

root@ubuntu-server:~/FreeRDP-master# ./client/X11/xfreerdp /cert-ignore +auth-only /v:10.0.2.2:4242 /u:testuser /d:Spoon /p:pass
[11:14:05:745] [1385:-1230230672] [INFO][com.freerdp.client.x11] - Authentication only. Don't connect to X.
[11:14:06:834] [1385:-1230230672] [ERROR][com.freerdp.core] - Authentication only, exit status 0

2. When the password is incorrect, account is locked out, disabled, or expired:

root@ubuntu-server:~/FreeRDP-master# ./client/X11/xfreerdp /cert-ignore +auth-only /v:10.0.2.2:4242 /u:testuser /d:Spoon /p:WRONG_PASS
[11:14:13:130] [1387:-1229997200] [INFO][com.freerdp.client.x11] - Authentication only. Don't connect to X.
[11:14:13:141] [1387:-1229997200] [ERROR][com.freerdp.core] - credssp_recv() error: -1
[11:14:13:141] [1387:-1229997200] [ERROR][com.freerdp.core] - freerdp_set_last_error 0x20009
[11:14:13:141] [1387:-1229997200] [ERROR][com.freerdp.core.transport] - Authentication failure, check credentials.If credentials are valid, the NTLMSSP implementation may be to blame.
[11:14:13:141] [1387:-1229997200] [ERROR][com.freerdp.core.connection] - Error: protocol security negotiation or connection failure
[11:14:13:141] [1387:-1229997200] [ERROR][com.freerdp.core] - Authentication only, exit status 1

3. When credentials are correct but the user is not in the correct group, i.e. "Remote Desktop Users":

root@ubuntu-server:~/FreeRDP-master# ./client/X11/xfreerdp /cert-ignore +auth-only /v:10.0.2.2:4242 /u:testuser /d:Spoon /p:pass
[11:11:28:747] [1377:-1230374032] [INFO][com.freerdp.client.x11] - Authentication only. Don't connect to X.
[11:11:29:811] [1377:-1230374032] [ERROR][com.freerdp.core] - ERRINFO_SERVER_INSUFFICIENT_PRIVILEGES (0x00000009):The user cannot connect to the server due to insufficient access privileges.
[11:11:29:813] [1377:-1230374032] [ERROR][com.freerdp.core.rdp] - DisconnectProviderUltimatum: reason: 1
[11:11:29:813] [1377:-1230374032] [ERROR][com.freerdp.core] - Authentication only, exit status 1