vbox-greeter.cpp revision 46633cdb66707c52c31cadcfbc4ce38c68f47d2e
/* $Id$ */
/** @file
* vbox-greeter - an own LightDM greeter module supporting auto-logons
* controlled by the host.
*/
/*
* Copyright (C) 2012 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#include <pwd.h>
#include <syslog.h>
#include <stdlib.h>
#include <lightdm.h>
#ifdef VBOX_WITH_FLTK
# include <FL/Fl_Button.H>
# include <FL/Fl_Double_Window.H>
# include <FL/Fl_Input.H>
# include <FL/Fl_Menu_Button.H>
# ifdef VBOX_GREETER_WITH_PNG_SUPPORT
# include <FL/Fl_PNG_Image.H>
# include <FL/Fl_Shared_Image.H>
# endif
# include <FL/Fl_Secret_Input.H>
#else
# include <cairo-xlib.h>
#endif
#include <package-generated.h>
#include "product-generated.h"
#include <iprt/buildconfig.h>
#include <iprt/initterm.h>
#include <VBox/VBoxGuestLib.h>
/* The greeter's full name for logging. */
#define VBOX_MODULE_NAME "vbox-lightdm-greeter"
/* UI elements used in this greeter. */
#define VBOX_GREETER_UI_WND_GREETER "wnd_greeter"
#define VBOX_GREETER_UI_EDT_USER "edt_username"
#define VBOX_GREETER_UI_EDT_PASSWORD "edt_password"
#define VBOX_GREETER_UI_BTN_LOGIN "btn_login"
#define VBOX_GREETER_UI_LBL_INFO "lbl_info"
/* UI display options. */
/** Show the restart menu entry / button. */
#define VBOX_GREETER_UI_SHOW_RESTART RT_BIT(0)
/** Show the shutdown menu entry / button. */
/** Show the (customized) top banner. */
/** Enable custom colors */
/** Extracts the 8-bit red component from an uint32_t. */
/** Extracts the 8-bit green component from an uint32_t. */
/** Extracts the 8-bit blue component from an uint32_t. */
#ifdef VBOX_WITH_GUEST_PROPS
using namespace guestProp;
#endif
/** The program name (derived from argv[0]). */
char *g_pszProgName = (char *)"";
/** For debugging. */
#ifdef DEBUG
static int g_iVerbosity = 99;
#else
static int g_iVerbosity = 0;
#endif
/** Logging parameters. */
/** @todo Make this configurable later. */
/**
* Context structure which contains all needed
* data within callbacks.
*/
typedef struct VBOXGREETERCTX
{
/** Pointer to this greeter instance. */
#ifdef VBOX_WITH_FLTK
#else
/** The GTK builder instance for accessing
* the UI elements. */
#endif
/** The timeout (in ms) to wait for credentials. */
/** The starting timestamp (in ms) to calculate
* the timeout. */
/** Timestamp of last abort message. */
/** The HGCM client ID. */
/** The credentials. */
char *pszUsername;
char *pszPassword;
char *pszDomain;
static void vboxGreeterError(const char *pszFormat, ...)
{
char *buf;
{
}
}
static void vboxGreeterLog(const char *pszFormat, ...)
{
if (g_iVerbosity)
{
char *buf;
{
/* Only do normal logging in debug mode; could contain
* sensitive data! */
}
}
}
/** @tood Move the following two functions to VbglR3 (also see pam_vbox). */
#ifdef VBOX_WITH_GUEST_PROPS
/**
* Reads a guest property.
*
* @return IPRT status code.
* @param hPAM PAM handle.
* @param uClientID Guest property service client ID.
* @param pszKey Key (name) of guest property to read.
* @param fReadOnly Indicates whether this key needs to be
* checked if it only can be read (and *not* written)
* by the guest.
* @param pszValue Buffer where to store the key's value.
* @param cbValue Size of buffer (in bytes).
* @param puTimestamp Timestamp of the value
* retrieved. Optional.
*/
{
/* puTimestamp is optional. */
int rc;
uint64_t u64Timestamp = 0;
char *pszValTemp;
/* The buffer for storing the data and its initial size. We leave a bit
* of space here in case the maximum values are raised. */
/* Because there is a race condition between our reading the size of a
* property and the guest updating it, we loop a few times here and
* hope. Actually this should never go wrong, as we are generous
* enough with buffer space. */
for (unsigned i = 0; i < 10; i++)
{
if (pvTmpBuf)
{
&cbBuf);
}
else
rc = VERR_NO_MEMORY;
switch (rc)
{
case VERR_BUFFER_OVERFLOW:
{
/* Buffer too small, try it with a bigger one next time. */
continue; /* Try next round. */
}
default:
break;
}
/* Everything except VERR_BUFFER_OVERLOW makes us bail out ... */
break;
}
if (RT_SUCCESS(rc))
{
/* Check security bits. */
if (pszFlags)
{
if (fReadOnly
{
/* If we want a property which is read-only on the guest
* and it is *not* marked as such, deny access! */
}
}
else /* No flags, no access! */
if (RT_SUCCESS(rc))
{
/* If everything went well copy property value to our destination buffer. */
if (puTimestamp)
}
}
return rc;
}
/**
* Waits for a guest property to be changed.
*
* @return IPRT status code.
* @param hPAM PAM handle.
* @param uClientID Guest property service client ID.
* @param pszKey Key (name) of guest property to wait for.
* @param uTimeoutMS Timeout (in ms) to wait for the change. Specify
* RT_INDEFINITE_WAIT to wait indefinitly.
*/
{
int rc;
/* The buffer for storing the data and its initial size. We leave a bit
* of space here in case the maximum values are raised. */
for (int i = 0; i < 10; i++)
{
if (pvTmpBuf)
{
uint64_t u64TimestampOut = 0;
0 /* Last timestamp; just wait for next event */, uTimeoutMS,
}
else
rc = VERR_NO_MEMORY;
if (rc == VERR_BUFFER_OVERFLOW)
{
/* Buffer too small, try it with a bigger one next time. */
continue; /* Try next round. */
}
/* Everything except VERR_BUFFER_OVERLOW makes us bail out ... */
break;
}
return rc;
}
#endif /* VBOX_WITH_GUEST_PROPS */
/**
* Checks for credentials provided by the host / HGCM.
*
* @return IPRT status code. VERR_NOT_FOUND if no credentials are available,
* VINF_SUCCESS on successful retrieval or another IPRT error.
* @param pCtx Greeter context.
*/
{
int rc = VbglR3CredentialsQueryAvailability();
if (RT_FAILURE(rc))
{
if (rc != VERR_NOT_FOUND)
vboxGreeterError("vboxGreeterCheckCreds: could not query for credentials! rc=%Rrc. Aborting\n", rc);
#ifdef DEBUG
else
vboxGreeterLog("vboxGreeterCheckCreds: no credentials available\n");
#endif
}
else
{
if (RT_FAILURE(rc))
{
}
else
{
#ifdef DEBUG
vboxGreeterLog("vboxGreeterCheckCreds: credentials retrieved: user=%s, password=%s, domain=%s\n",
#else
/* Don't log passwords in release mode! */
vboxGreeterLog("vboxGreeterCheckCreds: credentials retrieved: user=%s, password=XXX, domain=%s\n",
#endif
lightdm_greeter_authenticate(pCtx->pGreeter, pCtx->pszUsername); /* Must be the real user name from host! */
/** @todo Add handling domains as well. */
/** @todo Move into context destroy! */
#if 0
3 /* Three wipe passes */);
#endif
}
}
#ifdef DEBUG
#endif
return rc;
}
/**
* Called by LightDM when greeter is not needed anymore.
*
* @param signum Signal number.
*/
static void cb_sigterm(int signum)
{
}
/**
* Callback for showing a user prompt, issued by the LightDM server.
*
* @param pGreeter Pointer to this greeter instance.
* @param pszText Text to display.
* @param enmType Type of prompt to display.
* @param pvData Pointer to user-supplied data.
*/
{
switch (enmType)
{
case 1: /* Password. */
{
if (pCtx->pszPassword)
{
}
else
{
#ifdef VBOX_WITH_FLTK
#else
#endif
}
break;
}
default:
break;
}
3 /* Three wipe passes */);
}
/**
* Callback for showing a message, issued by the LightDM server.
*
* @param pGreeter Pointer to this greeter instance.
* @param pszText Text to display.
* @param enmType Type of message to display.
* @param pvData Pointer to user-supplied data.
*/
{
#ifdef VBOX_WITH_FLTK
#else
#endif
}
/**
* Callback for authentication completion, issued by the LightDM server.
*
* @param pGreeter Pointer to this greeter instance.
*/
{
vboxGreeterLog("cb_lightdm_auth_complete\n");
{
if (pszSession)
{
{
}
}
else
vboxGreeterError("unable to get default session\n");
}
else
vboxGreeterLog("user not authenticated successfully (yet)\n");
}
/**
* Callback for clicking on the "Login" button.
*
* @param pWidget Widget this callback is bound to.
* @param pvData Pointer to user-supplied data.
*/
#ifdef VBOX_WITH_FLTK
#else
#endif
{
#ifdef VBOX_WITH_FLTK
#else
GtkEntry *pEdtPwd = GTK_ENTRY(gtk_builder_get_object(pCtx->pBuilder, VBOX_GREETER_UI_EDT_PASSWORD));
#endif
/** @todo Add domain handling? */
#ifdef DEBUG
vboxGreeterLog("login requested: greeter=%p, user=%s, password=%s\n",
#endif
}
/**
* Callback for clicking on the "Menu" button.
*
* @param pWidget Widget this callback is bound to.
* @param pvData Pointer to user-supplied data.
*/
#ifdef VBOX_WITH_FLTK
#else
#endif
{
}
/**
* Callback for clicking on the "Restart" button / menu entry.
*
* @param pWidget Widget this callback is bound to.
* @param pvData Pointer to user-supplied data.
*/
#ifdef VBOX_WITH_FLTK
#else
#endif
{
bool fRestart = true;
#ifdef VBOX_WITH_FLTK
#endif
if (fRestart)
{
vboxGreeterLog("restart requested\n");
#ifndef DEBUG
#endif
}
}
/**
* Callback for clicking on the "Shutdown" button / menu entry.
*
* @param pWidget Widget this callback is bound to.
* @param pvData Pointer to user-supplied data.
*/
#ifdef VBOX_WITH_FLTK
#else
#endif
{
bool fShutdown = true;
#ifdef VBOX_WITH_FLTK
#endif
if (fShutdown)
{
vboxGreeterLog("shutdown requested\n");
#ifndef DEBUG
#endif
}
}
#ifdef VBOX_WITH_FLTK
#else
#endif
{
vboxGreeterLog("cb_edt_username called\n");
#ifdef VBOX_WITH_FLTK
#endif
}
#ifdef VBOX_WITH_FLTK
#else
#endif
{
vboxGreeterLog("cb_edt_password called\n");
#ifdef VBOX_WITH_FLTK
#endif
}
/**
* Callback for the timer event which is checking for new credentials
* from the host.
*
* @param pvData Pointer to user-supplied data.
*/
#ifdef VBOX_WITH_FLTK
static void cb_check_creds(void *pvData)
#else
#endif
{
#ifdef DEBUG
vboxGreeterLog("cb_check_creds called, clientId=%RU32, timeoutMS=%RU32\n",
#endif
int rc = VINF_SUCCESS;
#ifdef VBOX_WITH_GUEST_PROPS
bool fAbort = false;
char szVal[255];
{
true /* Read-only on guest */,
switch (rc)
{
case VINF_SUCCESS:
#ifdef DEBUG
vboxGreeterLog("cb_check_creds: tsAbort %RU64 <-> %RU64\n",
#endif
fAbort = true; /* Timestamps differs, abort. */
break;
case VERR_TOO_MUCH_DATA:
vboxGreeterError("cb_check_creds: temporarily unable to get abort notification\n");
break;
case VERR_NOT_FOUND:
/* Value not found, continue checking for credentials. */
break;
default:
fAbort = true; /* Abort on error. */
break;
}
}
if (fAbort)
{
/* Get optional message. */
true /* Read-only on guest */,
if (RT_SUCCESS(rc2))
{
# ifdef VBOX_WITH_FLTK
/** @todo */
# else
# endif /* VBOX_WITH_FLTK */
}
vboxGreeterLog("cb_check_creds: got notification from host to abort waiting\n");
}
else
{
#endif /* VBOX_WITH_GUEST_PROPS */
if (RT_SUCCESS(rc))
{
/* Credentials retrieved. */
}
else if (rc == VERR_NOT_FOUND)
{
/* No credentials found, but try next round (if there's
* time left for) ... */
}
#ifdef VBOX_WITH_GUEST_PROPS
}
#endif /* VBOX_WITH_GUEST_PROPS */
{
/* Calculate timeout value left after process has been started. */
/* Is it time to bail out? */
{
#ifdef VBOX_WITH_GUEST_PROPS
true /* Read-only on guest */,
if (RT_SUCCESS(rc2))
{
# ifdef VBOX_WITH_FLTK
/** @todo */
# else
# endif
}
#endif /* VBOX_WITH_GUEST_PROPS */
vboxGreeterLog("cb_check_creds: waiting thread has reached timeout (%dms), exiting ...\n",
pCtx->uTimeoutMS);
rc = VERR_TIMEOUT;
}
}
#ifdef DEBUG
#endif
/* At the moment we only allow *one* shot from the host,
* so setting credentials in a second attempt won't be possible
* intentionally. */
if (rc == VERR_NOT_FOUND)
#ifdef VBOX_WITH_FLTK
#else
return TRUE; /* No credentials found, do another round. */
return FALSE; /* Remove timer source on every other error / status. */
#endif
}
/**
* Release logger callback.
*
* @return IPRT status code.
* @param pLoggerRelease
* @param enmPhase
* @param pfnLog
*/
static void vboxGreeterLogHeaderFooter(PRTLOGGER pLoggerRelease, RTLOGPHASE enmPhase, PFNRTLOGPHASEMSG pfnLog)
{
/* Some introductory information. */
static RTTIMESPEC s_TimeSpec;
char szTmp[256];
if (enmPhase == RTLOGPHASE_BEGIN)
switch (enmPhase)
{
case RTLOGPHASE_BEGIN:
{
"vbox-greeter %s r%s (verbosity: %d) %s (%s %s) release log\n"
"Log opened %s\n",
/* the package type is interesting for Linux distributions */
char szExecName[RTPATH_MAX];
"Executable: %s\n"
"Process ID: %u\n"
"Package type: %s"
#ifdef VBOX_OSE
" (OSE)"
#endif
"\n",
RTProcSelf(),
break;
}
case RTLOGPHASE_PREROTATE:
break;
case RTLOGPHASE_POSTROTATE:
break;
case RTLOGPHASE_END:
break;
default:
/* nothing */;
}
}
/**
* Creates the default release logger outputting to the specified file.
*
* @return IPRT status code.
* @param pszLogFile Filename for log output. Optional.
*/
static int vboxGreeterLogCreate(const char *pszLogFile)
{
/* Create release logger (stdout + file). */
static const char * const s_apszGroups[] = VBOX_LOGGROUP_NAMES;
#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
#endif
if (RT_SUCCESS(rc))
{
/* register this logger as the release logger */
/* Explicitly flush the log in case of VBOXGREETER_RELEASE_LOG_FLAGS=buffered. */
}
return rc;
}
static void vboxGreeterLogDestroy(void)
{
}
static int vboxGreeterUsage(void)
{
RTPrintf("Usage:\n"
" %-12s [-h|-?|--help] [-F|--logfile <file>]\n"
" [-v|--verbose] [-V|--version]\n", g_pszProgName);
RTPrintf("\n"
return RTEXITCODE_SYNTAX;
}
{
if (RT_FAILURE(rc))
return RTMsgInitFailure(rc);
static const RTGETOPTDEF s_aOptions[] =
{
};
int ch;
&& RT_SUCCESS(rc))
{
/* For options that require an argument, ValueUnion has received the value. */
switch (ch)
{
case 'F':
break;
case 'h':
case '?':
return vboxGreeterUsage();
case 'v': /* Raise verbosity. */
g_iVerbosity++;
break;
case 'V': /* Print version and exit. */
return RTEXITCODE_SUCCESS;
break; /* Never reached. */
default:
}
}
if (RT_FAILURE(rc))
return RTEXITCODE_SYNTAX;
rc = VbglR3InitUser();
if (RT_FAILURE(rc))
if (RT_FAILURE(rc))
vboxGreeterLog("init\n");
/** @todo This function already is too long. Move code into
* functions. */
/* UI parameters. */
char szBannerPath[RTPATH_MAX];
/* By default most UI elements are shown. */
#ifdef VBOX_WITH_GUEST_PROPS
if (RT_SUCCESS(rc))
{
char szVal[256];
true /* Read-only on guest */,
if ( RT_SUCCESS(rc2)
{
}
true /* Read-only on guest */,
if ( RT_SUCCESS(rc2)
{
}
# ifdef VBOX_GREETER_WITH_PNG_SUPPORT
/* Load the banner. */
true /* Read-only on guest */,
if (RT_SUCCESS(rc2))
{
if (RTFileExists(szBannerPath))
{
}
else
}
# endif /* VBOX_GREETER_WITH_PNG_SUPPORT */
/* Use theming?. */
true /* Read-only on guest */,
if ( RT_SUCCESS(rc2)
{
vboxGreeterLog("custom theming enabled\n");
}
{
/* Get background color. */
true /* Read-only on guest */,
if (RT_SUCCESS(rc2))
{
/* Change conversion base when having a 0x prefix. */
}
/* Logon dialog. */
/* Get header color. */
true /* Read-only on guest */,
if (RT_SUCCESS(rc2))
{
/* Change conversion base when having a 0x prefix. */
}
/* Get dialog color. */
true /* Read-only on guest */,
if (RT_SUCCESS(rc2))
{
/* Change conversion base when having a 0x prefix. */
}
/* Get button color. */
true /* Read-only on guest */,
if (RT_SUCCESS(rc2))
{
/* Change conversion base when having a 0x prefix. */
}
}
}
#endif
#ifdef VBOX_WITH_FLTK
if (!rc2)
vboxGreeterLog("warning: unable to set visual scheme\n");
else /* Default colors. */
pWndGreeter->set_modal();
else /* Default colors. */
/**
* For now we're using a simple Y offset for moving all elements
* down if a banner needs to be shown on top of the greeter. Not
* very clean but does the job. Use some more layouting stuff
* when this gets more complex.
*/
# ifdef VBOX_GREETER_WITH_PNG_SUPPORT
/** @todo Add basic image type detection based on file
* extension. */
{
/** @todo Make the banner size configurable via guest
* properties. For now it's hardcoded to 460 x 90px. */
uOffsetY = 120;
}
# endif
"Desktop Login");
/** Note to use an own font:
* Fl_Font myfnt = FL_FREE_FONT + 1;
* Fl::set_font(myfnt, "MyFont"); */
else /* Default color. */
uOffsetY += 40;
/** @todo Add basic NLS support. */
300, 20, "User Name");
300, 20, "Password");
100, 40, "Log In");
else /* Default color. */
100, 40, "Options");
else /* Default color. */
char szLabel[255];
pWndGreeter->end();
pWndMain->fullscreen();
pWndGreeter->show();
#else
/* Set default cursor */
{
return RTEXITCODE_FAILURE;
}
gdk_screen_get_monitor_geometry(gdk_screen_get_default(), gdk_screen_get_primary_monitor(gdk_screen_get_default()), &rectScreen);
#endif
/* GType is needed in any case (for LightDM), whether we
* use GTK3 or not. */
g_type_init();
{
vboxGreeterError("unable to connect to LightDM server, aborting\n");
return RTEXITCODE_FAILURE;
}
vboxGreeterLog("connected to LightDM server\n");
#ifdef VBOX_WITH_GUEST_PROPS
bool fCheckCreds = false;
if (uClientId)
{
char szVal[256];
true /* Read-only on guest */,
if (RT_SUCCESS(rc))
{
/* All calls which are checked against rc2 are not critical, e.g. it does
* not matter if they succeed or not. */
true /* Read-only on guest */,
if (RT_SUCCESS(rc2))
{
if (!uTimeoutMS)
{
vboxGreeterError("pam_vbox_authenticate: invalid waiting timeout value specified, defaulting to infinite timeout\n");
}
else
}
true /* Read-only on guest */,
if (RT_SUCCESS(rc2))
#ifdef VBOX_WITH_FLTK
#else
#endif
/* Get initial timestamp so that we can compare the time
* whether the value has been changed or not in our event callback. */
true /* Read-only on guest */,
if (RT_SUCCESS(rc))
{
/* Before we actuall wait for credentials just make sure we didn't already get credentials
* set so that we can skip waiting for them ... */
if (rc2 == VERR_NOT_FOUND)
{
/* Get current time stamp to later calculate rest of timeout left. */
fCheckCreds = true;
}
}
}
/* Start the timer to check credentials availability. */
if (fCheckCreds)
#ifdef VBOX_WITH_FLTK
#else
#endif
}
#endif /* VBOX_WITH_GUEST_PROPS */
#ifdef VBOX_WITH_FLTK
/*
* Do own GDK main loop processing because FLTK also needs
* to have the chance of processing its events.
*/
for (;;)
{
FALSE /* No blocking */);
}
# ifdef VBOX_GREETER_WITH_PNG_SUPPORT
if (pImgBanner)
{
delete pImgBanner; /* Call destructor to free bitmap data. */
pImgBanner = NULL;
}
# endif /* VBOX_GREETER_WITH_PNG_SUPPORT */
#else
gtk_main();
#endif
vboxGreeterLog("terminating\n");
VbglR3Term();
}
#ifdef DEBUG
DECLEXPORT(void) RTAssertMsg1Weak(const char *pszExpr, unsigned uLine, const char *pszFile, const char *pszFunction)
{
}
#endif