/* Editor Settings: expandtabs and use 4 spaces for indentation
* ex: set softtabstop=4 tabstop=8 expandtab shiftwidth=4: *
* -*- mode: c, c-basic-offset: 4 -*- */
/*
* Copyright Likewise Software 2004-2008
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details. You should have received a copy of the GNU General
* Public License along with this program. If not, see
* .
*
* LIKEWISE SOFTWARE MAKES THIS SOFTWARE AVAILABLE UNDER OTHER LICENSING
* TERMS AS WELL. IF YOU HAVE ENTERED INTO A SEPARATE LICENSE AGREEMENT
* WITH LIKEWISE SOFTWARE, THEN YOU MAY ELECT TO USE THE SOFTWARE UNDER THE
* TERMS OF THAT SOFTWARE LICENSE AGREEMENT INSTEAD OF THE TERMS OF THE GNU
* GENERAL PUBLIC LICENSE, NOTWITHSTANDING THE ABOVE NOTICE. IF YOU
* HAVE QUESTIONS, OR WISH TO REQUEST A COPY OF THE ALTERNATE LICENSING
* TERMS OFFERED BY LIKEWISE SOFTWARE, PLEASE CONTACT LIKEWISE SOFTWARE AT
* license@likewisesoftware.com
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include "joindialog.h"
#include "joinprogress.h"
#include "joinerror.h"
#include "joinauth.h"
#include "statusdialog.h"
#include
#include
#include
#include
#include
FILE* log_handle;
JOINSTATE join_state;
JoinDialog* global_join_dialog = NULL;
StatusDialog* global_status_dialog = NULL;
// This is a hack to prevent SCIM from being
// reloaded when we switch dialogs
static gboolean
close_stale_dialogs(gpointer data)
{
if (global_join_dialog)
{
joindialog_delete(global_join_dialog);
global_join_dialog = NULL;
}
if (global_status_dialog)
{
statusdialog_delete(global_status_dialog);
global_status_dialog = NULL;
}
return FALSE;
}
static void
log_begin()
{
log_handle = tmpfile();
if (!log_handle)
{
fprintf(stderr, "Could not open log file: %s\n", strerror(errno));
exit(1);
}
// Log to file
dj_init_logging_to_file_handle(LOG_LEVEL_VERBOSE, log_handle);
}
static void
log_end()
{
dj_close_log();
log_handle = NULL;
}
static void
log_copy(const char* dest)
{
char buffer[2048];
FILE* destfile = fopen(dest, "w");
size_t amount;
fseek(log_handle, 0, SEEK_SET);
while ((amount = fread(buffer, 1, sizeof(buffer), log_handle)) > 0)
{
fwrite(buffer, 1, amount, destfile);
}
clearerr(log_handle);
fseek(log_handle, 0, SEEK_END);
fclose(destfile);
}
static char*
safe_strdup(const char* src)
{
if (!src)
return NULL;
else
return strdup(src);
}
static
void
safe_free(void** ptr)
{
if (*ptr)
free(*ptr);
*ptr = NULL;
}
#define SAFE_FREE(var) safe_free((void**) &var);
static void
fill_state(JoinDialog* dialog)
{
SAFE_FREE(join_state.computer);
SAFE_FREE(join_state.domain);
SAFE_FREE(join_state.ou);
join_state.ou_active = FALSE;
join_state.noModifyHosts = FALSE;
join_state.computer = safe_strdup(joindialog_get_computer_name(dialog));
join_state.domain = safe_strdup(joindialog_get_domain_name(dialog));
join_state.ou = safe_strdup(joindialog_get_ou_name(dialog));
join_state.ou_active = joindialog_get_ou_active(dialog);
join_state.noModifyHosts = !joindialog_get_modify_hosts(dialog);
}
static void
fill_state_auth(JoinAuthDialog* auth_dialog)
{
SAFE_FREE(join_state.user);
SAFE_FREE(join_state.password);
join_state.user = safe_strdup(joinauth_get_user(auth_dialog));
join_state.password = safe_strdup(joinauth_get_password(auth_dialog));
}
static
void
free_state()
{
SAFE_FREE(join_state.computer);
SAFE_FREE(join_state.domain);
SAFE_FREE(join_state.ou);
SAFE_FREE(join_state.user);
SAFE_FREE(join_state.password);
}
static
void
free_state_computer()
{
SAFE_FREE(join_state.computer);
}
static
void
free_state_domain()
{
SAFE_FREE(join_state.domain);
}
static
void
free_state_password()
{
SAFE_FREE(join_state.password);
}
/* Dirty, horrible hacks to allow use of new file chooser dialog widget
despite old RHEL3 build machine */
#if GTK_MINOR_VERSION < 6
#define GTK_FILE_CHOOSER_ACTION_SAVE 1
typedef int GtkFileChooserAction;
static GtkWidget *(*gtk_file_chooser_dialog_new)(const gchar *title,
GtkWindow *parent,
GtkFileChooserAction action,
const gchar *first_button_text,
...);
static const gchar* (*gtk_file_chooser_get_filename)(void*);
#define GTK_FILE_CHOOSER(ptr) ((void*)(ptr))
#endif
static void
show_error_dialog(GtkWindow* parent, LWException* exc)
{
if(gtk_minor_version < 6)
{
GtkDialog* dialog = gtk_message_dialog_new(
parent,
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_CLOSE,
"%s: %s",
exc->shortMsg,
exc->longMsg);
gtk_dialog_run(dialog);
gtk_widget_destroy(dialog);
return;
}
else
{
JoinErrorDialog* error_dialog = joinerror_new(parent, exc);
int result;
#if GTK_MINOR_VERSION < 6
{
void* handle = dlopen(NULL, RTLD_LAZY);
gtk_file_chooser_dialog_new = dlsym(handle, "gtk_file_chooser_dialog_new");
gtk_file_chooser_get_filename = dlsym(handle, "gtk_file_chooser_get_filename");
dlclose(handle);
}
#endif
do
{
switch ((result = joinerror_run(error_dialog)))
{
case JOINERROR_CLOSE:
break;
case JOINERROR_SAVE_LOG:
{
GtkDialog* file_dialog;
file_dialog = GTK_DIALOG(gtk_file_chooser_dialog_new(
"Save Log", parent, GTK_FILE_CHOOSER_ACTION_SAVE,
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, NULL));
if (gtk_dialog_run(file_dialog) == GTK_RESPONSE_ACCEPT)
{
log_copy(
gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(file_dialog)));
}
gtk_widget_destroy(GTK_WIDGET(file_dialog));
break;
}
}
} while (result != JOINERROR_CLOSE);
joinerror_delete(error_dialog);
}
}
typedef struct JoinInfo
{
JoinProgressDialog* dialog;
JoinProcessOptions options;
} JoinInfo;
static
void
PrintWarning(
JoinProcessOptions *options,
const char *title,
const char *message
)
{
LWException *_exc = NULL;
PSTR warningTitle = NULL;
if(CENTERROR_IS_OK(CTAllocateStringPrintf(&warningTitle, "Warning: %s", title)))
{
LW_RAISE_EX(&_exc, CENTERROR_DOMAINJOIN_WARNING, warningTitle, "%s", message);
gdk_threads_enter();
show_error_dialog(joinprogress_get_gtk_window(
(JoinProgressDialog *)options->userData), _exc);
gdk_threads_leave();
}
LW_HANDLE(&_exc);
CT_SAFE_FREE_STRING(warningTitle);
}
static
void*
join_worker(
gpointer data
)
{
JoinInfo *info = (JoinInfo*) data;
JoinProgressDialog* dialog = info->dialog;
JoinProcessOptions *options = &info->options;
LWException* exc = NULL;
ModuleState *hostnameState;
LW_TRY(&exc, DJInitModuleStates(options, &LW_EXC));
joinprogress_update(dialog, 0.0, "Joining");
hostnameState = DJGetModuleStateByName(options, "hostname");
if(join_state.noModifyHosts)
{
if(hostnameState != NULL)
hostnameState->runModule = FALSE;
}
else if(hostnameState->lastResult < FullyConfigured)
hostnameState->runModule = TRUE;
options->userData = dialog;
options->warningCallback = PrintWarning;
LW_TRY(&exc, DJRunJoinProcess(options, &LW_EXC));
cleanup:
if (exc)
{
joinprogress_raise_error(dialog, exc);
}
else
{
joinprogress_done(dialog);
}
return NULL;
}
static
void*
leave_worker(gpointer data)
{
JoinProgressDialog* dialog = (JoinProgressDialog*) data;
JoinProcessOptions options;
LWException* exc = NULL;
DJZeroJoinProcessOptions(&options);
options.joiningDomain = FALSE;
options.warningCallback = PrintWarning;
LW_CLEANUP_CTERR(&exc, DJGetComputerName(&options.computerName));
LW_TRY(&exc, DJInitModuleStates(&options, &LW_EXC));
joinprogress_update(dialog, 0.0, "Leaving");
options.userData = dialog;
LW_TRY(&exc, DJRunJoinProcess(&options, &LW_EXC));
cleanup:
if (exc)
{
joinprogress_raise_error(dialog, exc);
}
else
{
joinprogress_done(dialog);
}
DJFreeJoinProcessOptions(&options);
return NULL;
}
static
void
do_join(JoinDialog* dialog, LWException** exc)
{
JoinProgressDialog* progress_dialog = NULL;
JoinAuthDialog* auth_dialog = NULL;
fill_state(dialog);
auth_dialog = joinauth_new(&join_state, joindialog_get_gtk_window(dialog));
if (!auth_dialog)
{
fprintf(stderr, "Could not create window: out of memory");
exit(1);
}
if (joinauth_run(auth_dialog) == JOINAUTH_OK)
{
JoinInfo info = {0};
fill_state_auth(auth_dialog);
DJZeroJoinProcessOptions(&info.options);
info.options.username = safe_strdup(join_state.user);
info.options.password = safe_strdup(join_state.password);
info.options.computerName = safe_strdup(join_state.computer);
if (join_state.ou_active)
info.options.ouName = safe_strdup(join_state.ou);
else
info.options.ouName = NULL;
info.options.domainName = safe_strdup(join_state.domain);
info.options.joiningDomain = TRUE;
joinauth_delete(auth_dialog);
progress_dialog = joinprogress_new(joindialog_get_gtk_window(dialog), "Joining Domain");
if (!progress_dialog)
{
fprintf(stderr, "Could not create window: out of memory");
exit(1);
}
info.dialog = progress_dialog;
g_thread_create(join_worker, &info, FALSE, NULL);
if (joinprogress_run(progress_dialog) == JOINPROGRESS_ERROR)
{
LWException* _exc = joinprogress_get_error(progress_dialog);
show_error_dialog(joindialog_get_gtk_window(dialog), _exc);
LW_HANDLE(&_exc);
// Now that the error has been displayed, switch
// back to running the progress dialog so the user
// can close it.
joinprogress_run(progress_dialog);
}
joinprogress_delete(progress_dialog);
DJFreeJoinProcessOptions(&info.options);
}
else
{
// Just close the dialog
joinauth_delete(auth_dialog);
}
}
static
gboolean
join_mode(PJOINSTATE pJoinState, LWException** exc)
{
JoinDialog* dialog = NULL;
int result;
gboolean quit = FALSE;
g_idle_add(close_stale_dialogs, NULL);
dialog = joindialog_new(pJoinState);
if (!dialog)
{
LW_CLEANUP_CTERR(exc, CENTERROR_OUT_OF_MEMORY);
}
switch ((result = joindialog_run(dialog)))
{
case JOINDIALOG_CLOSE:
case GTK_RESPONSE_DELETE_EVENT:
quit = TRUE;
break;
case JOINDIALOG_JOIN:
LW_TRY(exc, do_join(dialog, &LW_EXC));
quit = FALSE;
break;
}
cleanup:
if (dialog)
global_join_dialog = dialog;
return quit;
}
static
gboolean
status_mode(LWException** exc)
{
StatusDialog* dialog = NULL;
int result;
gboolean quit = FALSE;
g_idle_add(close_stale_dialogs, NULL);
dialog = statusdialog_new(join_state.computer, join_state.domain);
if (!dialog)
{
LW_CLEANUP_CTERR(exc, CENTERROR_OUT_OF_MEMORY);
}
switch ((result = statusdialog_run(dialog)))
{
case STATUSDIALOG_CLOSE:
case GTK_RESPONSE_DELETE_EVENT:
quit = TRUE;
break;
case STATUSDIALOG_LEAVE:
{
JoinProgressDialog* progress_dialog;
progress_dialog = joinprogress_new(
statusdialog_get_gtk_window(dialog),
"Leaving Domain");
if (!progress_dialog)
{
fprintf(stderr, "Could not create window: out of memory");
exit(1);
}
g_thread_create(leave_worker, progress_dialog, FALSE, NULL);
if (joinprogress_run(progress_dialog) == JOINPROGRESS_ERROR)
{
LWException* _exc = joinprogress_get_error(progress_dialog);
show_error_dialog(statusdialog_get_gtk_window(dialog), _exc);
LW_HANDLE(&_exc);
// Now that the error has been displayed, switch
// back to running the progress dialog so the user
// can close it.
joinprogress_run(progress_dialog);
}
joinprogress_delete(progress_dialog);
quit = FALSE;
break;
}
}
cleanup:
if (dialog)
global_status_dialog = dialog;
return quit;
}
void
ensure_gtk_version(int major, int minor, int micro, LWException** exc)
{
const char* msg;
if ((msg = gtk_check_version(major, minor, micro)))
{
LW_RAISE_EX(exc, CENTERROR_INCOMPATIBLE_LIBRARY,
"Incompatible library detected",
"%s. Likewise does not support graphical domain joins on this platform. "
"Please use the command-line domain join application instead.",
msg);
}
}
int
main(int argc, char** argv)
{
LWException* exc = NULL;
gboolean quit = FALSE;
memset(&join_state, 0, sizeof(join_state));
join_state.ou_active = FALSE;
join_state.noModifyHosts = FALSE;
log_begin();
g_thread_init(NULL);
gdk_threads_init();
gdk_threads_enter();
gtk_init(&argc, &argv);
LW_TRY(&exc, DJNetInitialize(TRUE, &LW_EXC));
do
{
char* computer = NULL;
char* domain = NULL;
LW_TRY(&exc, ensure_gtk_version(2, 6, 0, &LW_EXC));
LW_TRY(&exc, DJQuery((char**) &computer, (char**) &domain, NULL, &LW_EXC));
free_state_password();
// If DJQuery reports a domain, remember it since we need to leave it.
// Otherwise, keep any current value since a user may have got back
// here because of entering a bad value.
if (domain)
{
free_state_domain();
join_state.domain = domain;
free_state_computer();
join_state.computer = computer;
}
else
{
if (join_state.computer == NULL)
{
join_state.computer = computer;
}
else
{
SAFE_FREE(computer);
}
}
// If DJQuery reports a domain, then we are a part of a domain.
if (domain)
{
quit = status_mode(&exc);
}
else
{
quit = join_mode(&join_state, &exc);
}
} while (!quit);
// Try to shutdown the net api and report any failures
LW_TRY(&exc, DJNetShutdown(&LW_EXC));
cleanup:
if (exc)
{
show_error_dialog(NULL, exc);
}
// Shutdown the net api without reporting failures (it is ok if
// DJNetShutdown is called twice).
DJNetShutdown(NULL);
close_stale_dialogs(NULL);
gdk_threads_leave();
log_end();
free_state();
return 0;
}