Esempio n. 1
0
DWORD
DJGetFinalFqdn(
    const JoinProcessOptions *options,
    PSTR *fqdn
    )
{
    DWORD ceError = ERROR_SUCCESS;
    const ModuleState *state = DJGetModuleStateByName((JoinProcessOptions *)options, "hostname");

    *fqdn = NULL;

    if(state != NULL && state->disposition == EnableModule)
    {
        //The fqdn will be set
        ceError = CTAllocateStringPrintf(fqdn, "%s.%s", options->computerName, options->domainName);
        GCE(ceError);
    }
    else
    {
        //The fqdn will not be set
        GCE(ceError = DJGetFQDN(NULL, fqdn));
    }
cleanup:
    return ceError;
}
Esempio n. 2
0
static QueryResult QueryStartDaemons(const JoinProcessOptions *options, LWException **exc)
{
    DWORD dwError = 0;
    BOOLEAN running;
    QueryResult result = FullyConfigured;
    const ModuleState *stopState = DJGetModuleStateByName((JoinProcessOptions *)options, "stop");

    if(!options->joiningDomain)
    {
        result = NotApplicable;
        goto cleanup;
    }

    dwError = DJGetServiceStatus("gpagent", &running);
    if (dwError == LW_ERROR_NO_SUCH_SERVICE)
    {
        /* The gpagentd may not be installed so ignore */
        running = TRUE;
        dwError = 0;
    }
    if (dwError)
    {
        LW_RAISE_EX(exc, dwError,
                "Received error while querying lwsmd.",
                "Received error while querying lwsmd.");
        goto cleanup;
    }

    if(!running)
        result = NotConfigured;

    if(stopState != NULL && stopState->runModule)
        result = NotConfigured;

cleanup:
    return result;
}
Esempio n. 3
0
static QueryResult UpdateSshdConf(struct SshConf *conf, PCSTR testPrefix,
        PCSTR binaryPath, BOOLEAN enable, PSTR *changeDescription,
        const JoinProcessOptions *options, LWException **exc)
{
    size_t i;
    BOOLEAN modified = conf->modified;
    PSTR requiredOptions = NULL;
    PSTR optionalOptions = NULL;
    BOOLEAN supported;
    PSTR temp = NULL;
    QueryResult result = NotConfigured;
    const char *requiredSshdOptions[] = {
        "ChallengeResponseAuthentication",
        "UsePAM", "PAMAuthenticationViaKBDInt",
        "KbdInteractiveAuthentication", NULL};
    const char *optionalSshdOptions[] = {"GSSAPIAuthentication",
        "GSSAPICleanupCredentials", NULL};
    SshdVersion version;
    BOOLEAN compromisedOptions = FALSE;

    if(changeDescription != NULL)
        *changeDescription = NULL;

    LW_TRY(exc, GetSshVersion("", &version, binaryPath, &LW_EXC));

    if(enable)
    {
        LW_CLEANUP_CTERR(exc, CTStrdup("", &requiredOptions));
        LW_CLEANUP_CTERR(exc, CTStrdup("", &optionalOptions));
        for(i = 0; requiredSshdOptions[i] != NULL; i++)
        {
            PCSTR option = requiredSshdOptions[i];
            LW_TRY(exc, supported = TestOption(testPrefix, conf, binaryPath, "-t", option, &LW_EXC));
            if(supported)
            {
                PCSTR value = "yes";

                if(IsNewerThanOrEq(&version, 2, 3, 1, -1) &&
                        IsOlderThanOrEq(&version, 3, 3, -1, -1) &&
                        (!strcmp(option, "ChallengeResponseAuthentication") ||
                         !strcmp(option, "PAMAuthenticationViaKBDInt")))
                {
                    value = "no";
                    compromisedOptions = TRUE;
                }
                conf->modified = FALSE;
                LW_CLEANUP_CTERR(exc, SetOption(conf, option, value));
                if(conf->modified)
                {
                    modified = TRUE;
                    temp = requiredOptions;
                    requiredOptions = NULL;
                    CTAllocateStringPrintf(&requiredOptions, "%s\t%s\n", temp, option);
                    CT_SAFE_FREE_STRING(temp);
                }
            }
        }
        for(i = 0; optionalSshdOptions[i] != NULL; i++)
        {
            PCSTR option = optionalSshdOptions[i];
            LW_TRY(exc, supported = TestOption(testPrefix, conf, binaryPath, "-t", option, &LW_EXC));
            if(supported)
            {
                conf->modified = FALSE;
                LW_CLEANUP_CTERR(exc, SetOption(conf, option, "yes"));
                if(conf->modified)
                {
                    modified = TRUE;
                    temp = optionalOptions;
                    optionalOptions = NULL;
                    LW_CLEANUP_CTERR(exc, CTAllocateStringPrintf(&optionalOptions, "%s\t%s\n", temp, option));
                    CT_SAFE_FREE_STRING(temp);
                }
            }
        }
        result = FullyConfigured;
        if(strlen(optionalOptions) > 0)
        {
            temp = optionalOptions;
            optionalOptions = NULL;
            LW_CLEANUP_CTERR(exc, CTAllocateStringPrintf(
                        &optionalOptions,
                        "The following options will be enabled in %s to provide a better user experience:\n%s",
                        conf->filename, temp));
            CT_SAFE_FREE_STRING(temp);
            result = SufficientlyConfigured;
        }
        if(strlen(requiredOptions) > 0)
        {
            temp = requiredOptions;
            requiredOptions = NULL;
            LW_CLEANUP_CTERR(exc, CTAllocateStringPrintf(
                        &requiredOptions,
                        "The following options must be enabled in %s:\n%s",
                        conf->filename, temp));
            CT_SAFE_FREE_STRING(temp);
            result = NotConfigured;
        }
        if(changeDescription != NULL)
        {
            LW_CLEANUP_CTERR(exc, CTAllocateStringPrintf(
                        changeDescription,
                        "%s%s",
                        requiredOptions, optionalOptions));
        }
    }
    else
    {
        conf->modified = FALSE;
        LW_CLEANUP_CTERR(exc, RemoveOption(conf, "GSSAPIAuthentication"));
        if(conf->modified)
        {
            if(changeDescription != NULL)
            {
                LW_CLEANUP_CTERR(exc, CTAllocateStringPrintf(
                            changeDescription,
                            "In %s, GSSAPIAuthentication may optionally be removed.\n",
                            conf->filename));
            }
            result = SufficientlyConfigured;
        }
        else
            result = FullyConfigured;
    }
    if(changeDescription != NULL && *changeDescription == NULL)
        LW_CLEANUP_CTERR(exc, CTStrdup("", changeDescription));

    if(options != NULL)
    {
        ModuleState *state = DJGetModuleStateByName(options, "ssh");
        if(state->moduleData == (void *)-1)
        {
            //We already showed a warning
        }
        else if(compromisedOptions)
        {
            state->moduleData = (void *)-1;
            if (options->warningCallback != NULL)
            {
                options->warningCallback(options, "Unpatched version of SSH",
                        "The version of your sshd daemon indicates that it is susceptible to the remote exploit described at http://www.openssh.com/txt/preauth.adv . To avoid exposing your system to this exploit, the 'ChallengeResponseAuthentication' and 'PAMAuthenticationViaKBDInt' options will be set to 'no' instead of 'yes'. As a side effect, all login error messages will appear as 'permission denied' instead of more detailed messages like 'account disabled'. Additionally, password changes on login may not work.\n\
\n\
Even when those options are disabled, your system is still vulnerable to http://www.cert.org/advisories/CA-2003-24.html . We recommend upgrading your version of SSH.");
            }
        }
Esempio n. 4
0
void DoLeaveNew(int argc, char **argv, int columns, LWException **exc)
{
    JoinProcessOptions options;
    BOOLEAN advanced = FALSE;
    BOOLEAN preview = FALSE;
    DynamicArray enableModules, disableModules, ignoreModules;
    DynamicArray detailModules;
    size_t i;
    PSTR moduleDetails = NULL;
    PSTR wrapped = NULL;
    int passwordIndex = -1;

    DJZeroJoinProcessOptions(&options);
    memset(&enableModules, 0, sizeof(enableModules));
    memset(&disableModules, 0, sizeof(disableModules));
    memset(&ignoreModules, 0, sizeof(ignoreModules));
    memset(&detailModules, 0, sizeof(detailModules));

    while(argc > 0 && CTStrStartsWith(argv[0], "--"))
    {
        if(!strcmp(argv[0], "--advanced"))
            advanced = TRUE;
        else if(!strcmp(argv[0], "--preview"))
            preview = TRUE;
        else if(argc < 2)
        {
            LW_RAISE(exc, LW_ERROR_SHOW_USAGE);
            goto cleanup;
        }
        else if(!strcmp(argv[0], "--enable"))
        {
            LW_CLEANUP_CTERR(exc, CTArrayAppend(&enableModules, sizeof(PCSTR *), &argv[1], 1));
            argv++;
            argc--;
        }
        else if(!strcmp(argv[0], "--disable"))
        {
            if(!strcmp(argv[1], "ssh")){
                options.ignoreSsh = TRUE;
            }
            else {
                options.ignoreSsh = FALSE;
                LW_CLEANUP_CTERR(exc, CTArrayAppend(&disableModules, sizeof(PCSTR *), &argv[1], 1));
            }
            argv++;
            argc--;
        }
        else if(!strcmp(argv[0], "--ignore"))
        {
            LW_CLEANUP_CTERR(exc, CTArrayAppend(&ignoreModules, sizeof(PCSTR *), &argv[1], 1));
            argv++;
            argc--;
        }
        else if(!strcmp(argv[0], "--details"))
        {
            LW_CLEANUP_CTERR(exc, CTArrayAppend(&detailModules, sizeof(PCSTR *), &argv[1], 1));
            argv++;
            argc--;
        }
        else if(!strcmp(argv[0], "--multiple"))
        {
            options.enableMultipleJoins = TRUE;
            LW_CLEANUP_CTERR(exc, CTStrdup(argv[1], &options.domainName));
            argv++;
            argc--;
        }
        else
        {
            LW_RAISE(exc, LW_ERROR_SHOW_USAGE);
            goto cleanup;
        }
        argv++;
        argc--;
    }

    if(argc == 2)
    {
        LW_CLEANUP_CTERR(exc, CTStrdup(argv[1], &options.password));
        passwordIndex = 1;
    }
    else if(argc > 2)
    {
        LW_RAISE(exc, LW_ERROR_SHOW_USAGE);
        goto cleanup;
    }
    options.joiningDomain = FALSE;

    DJ_LOG_INFO("Domainjoin invoked with %d arg(s) to the leave command:", argc);
    for(i = 0; i < argc; i++)
    {
        DJ_LOG_INFO("    [%s]", i == passwordIndex ? "<password>" : argv[i]);
    }

    if(argc > 0)
    {
        LW_CLEANUP_CTERR(exc, CTStrdup(argv[0], &options.username));
    }

    options.warningCallback = PrintWarning;
    options.showTraces = advanced;
    LW_CLEANUP_CTERR(exc, DJGetComputerName(&options.computerName));

    LW_TRY(exc, DJInitModuleStates(&options, &LW_EXC));

    for(i = 0; i < enableModules.size; i++)
    {
        PCSTR module = *(PCSTR *)CTArrayGetItem(
                    &enableModules, i, sizeof(PCSTR));
        if(CTArrayFindString(&disableModules, module) != -1)
        {
            LW_RAISE_EX(exc, ERROR_INVALID_PARAMETER, "Module already specified", "The module '%s' is listed as being disabled and enabled", module);
            goto cleanup;
        }
        if(CTArrayFindString(&ignoreModules, module) != -1)
        {
            LW_RAISE_EX(exc, ERROR_INVALID_PARAMETER, "Module already specified", "The module '%s' is listed as being ignored and enabled", module);
            goto cleanup;
        }
        LW_TRY(exc, DJSetModuleDisposition(&options, module, EnableModule, &LW_EXC));
    }

    for(i = 0; i < disableModules.size; i++)
    {
        PCSTR module = *(PCSTR *)CTArrayGetItem(
                    &disableModules, i, sizeof(PCSTR));
        if(CTArrayFindString(&enableModules, module) != -1)
        {
            LW_RAISE_EX(exc, ERROR_INVALID_PARAMETER, "Module already specified", "The module '%s' is listed as being disabled and enabled", module);
            goto cleanup;
        }
        if(CTArrayFindString(&ignoreModules, module) != -1)
        {
            LW_RAISE_EX(exc, ERROR_INVALID_PARAMETER, "Module already specified", "The module '%s' is listed as being disabled and ignored", module);
            goto cleanup;
        }
        LW_TRY(exc, DJSetModuleDisposition(&options, module, DisableModule, &LW_EXC));
    }

    for(i = 0; i < ignoreModules.size; i++)
    {
        PCSTR module = *(PCSTR *)CTArrayGetItem(
                    &ignoreModules, i, sizeof(PCSTR));
        if(CTArrayFindString(&enableModules, module) != -1)
        {
            LW_RAISE_EX(exc, ERROR_INVALID_PARAMETER, "Module already specified", "The module '%s' is listed as being ignored and enabled", module);
            goto cleanup;
        }
        if(CTArrayFindString(&disableModules, module) != -1)
        {
            LW_RAISE_EX(exc, ERROR_INVALID_PARAMETER, "Module already specified", "The module '%s' is listed as being ignored and disabled", module);
            goto cleanup;
        }
        LW_TRY(exc, DJSetModuleDisposition(&options, module, IgnoreModule, &LW_EXC));
    }

    for(i = 0; i < detailModules.size; i++)
    {
        PCSTR module = *(PCSTR *)CTArrayGetItem(
                    &detailModules, i, sizeof(PCSTR));
        ModuleState *state = DJGetModuleStateByName(&options, module);
        if(state == NULL)
        {
            LW_RAISE_EX(exc, ERROR_INVALID_PARAMETER, "Unable to find module.", "Please check the spelling of '%s'. This module cannot be found", module);
            goto cleanup;
        }
        PrintModuleState(state);
    }
    if(detailModules.size > 0)
    {
        PrintStateKey();
    }
    for(i = 0; i < detailModules.size; i++)
    {
        PCSTR module = *(PCSTR *)CTArrayGetItem(
                    &detailModules, i, sizeof(PCSTR));
        ModuleState *state = DJGetModuleStateByName(&options, module);
        CT_SAFE_FREE_STRING(moduleDetails);
        CT_SAFE_FREE_STRING(wrapped);
        LW_TRY(exc, moduleDetails = state->module->GetChangeDescription(&options, &LW_EXC));
        LW_CLEANUP_CTERR(exc, CTWordWrap(moduleDetails, &wrapped, 4, columns));
        fprintf(stdout, "\nDetails for '%s':\n%s\n", state->module->longName, wrapped);
    }
    if(detailModules.size > 0)
        goto cleanup;

    LW_TRY(exc, PrintJoinHeader(&options, &LW_EXC));

    if(preview)
    {
        PrintModuleStates(advanced, &options);
        if(!advanced)
            LW_TRY(exc, DJCheckRequiredEnabled(&options, &LW_EXC));
        goto cleanup;
    }

    LW_TRY(exc, DJCheckRequiredEnabled(&options, &LW_EXC));

    if (options.username != NULL && IsNullOrEmptyString(options.password))
    {
        CT_SAFE_FREE_STRING(options.password);

        LW_CLEANUP_CTERR(exc, FillMissingPassword(options.username,
                    &options.password));
    }

    LW_TRY(exc, DJRunJoinProcess(&options, &LW_EXC));
    fprintf(stdout, "SUCCESS\n");

cleanup:
    DJFreeJoinProcessOptions(&options);
    CTArrayFree(&enableModules);
    CTArrayFree(&disableModules);
    CTArrayFree(&ignoreModules);
    CTArrayFree(&detailModules);
    CT_SAFE_FREE_STRING(moduleDetails);
    CT_SAFE_FREE_STRING(wrapped);
}
Esempio n. 5
0
void DoJoin(int argc, char **argv, int columns, LWException **exc)
{
    JoinProcessOptions options;
    BOOLEAN advanced = FALSE;
    BOOLEAN preview = FALSE;
    DynamicArray enableModules, disableModules, ignoreModules;
    DynamicArray detailModules;
    size_t i;
    int passwordIndex = -1;
    PSTR moduleDetails = NULL;
    PSTR wrapped = NULL;

    DJZeroJoinProcessOptions(&options);
    memset(&enableModules, 0, sizeof(enableModules));
    memset(&disableModules, 0, sizeof(disableModules));
    memset(&ignoreModules, 0, sizeof(ignoreModules));
    memset(&detailModules, 0, sizeof(detailModules));

    while(argc > 0 && CTStrStartsWith(argv[0], "--"))
    {
        if(!strcmp(argv[0], "--advanced"))
            advanced = TRUE;
        else if(!strcmp(argv[0], "--preview"))
            preview = TRUE;
        else if(!strcmp(argv[0], "--ignore-firewall-ntp"))
        {
            printf("Warning: --ignore-firewall-ntp is deprecated. This behavior is now default.\n");
        }
        else if(!strcmp(argv[0], "--ignore-pam"))
            options.ignorePam = TRUE;
        else if(!strcmp(argv[0], "--notimesync"))
            options.disableTimeSync = TRUE;
        else if(!strcmp(argv[0], "--multiple"))
            options.enableMultipleJoins = TRUE;
        else if(!strcmp(argv[0], "--nohosts"))
        {
            PCSTR module = "hostname";
            LW_CLEANUP_CTERR(exc, CTArrayAppend(&disableModules, sizeof(PCSTR), &module, 1));
        }
        else if(argc < 2)
        {
            LW_RAISE(exc, LW_ERROR_SHOW_USAGE);
            goto cleanup;
        }
        else if(!strcmp(argv[0], "--enable"))
        {
            LW_CLEANUP_CTERR(exc, CTArrayAppend(&enableModules, sizeof(PCSTR), &argv[1], 1));
            argv++;
            argc--;
        }
        else if(!strcmp(argv[0], "--disable"))
        {
            if(!strcmp(argv[1], "ssh")){
                options.ignoreSsh = TRUE;
            }
            else {
                options.ignoreSsh = FALSE;
                LW_CLEANUP_CTERR(exc, CTArrayAppend(&disableModules, sizeof(PCSTR), &argv[1], 1));
            }
            argv++;
            argc--;
        }
        else if(!strcmp(argv[0], "--ignore"))
        {
            LW_CLEANUP_CTERR(exc, CTArrayAppend(&ignoreModules, sizeof(PCSTR), &argv[1], 1));
            argv++;
            argc--;
        }
        else if(!strcmp(argv[0], "--details"))
        {
            LW_CLEANUP_CTERR(exc, CTArrayAppend(&detailModules, sizeof(PCSTR), &argv[1], 1));
            argv++;
            argc--;
        }
        else if(!strcmp(argv[0], "--ou"))
        {
            DJ_LOG_INFO("Domainjoin invoked with option --ou %s", argv[1]);
            CT_SAFE_FREE_STRING(options.ouName);
            LW_CLEANUP_CTERR(exc, CTStrdup(argv[1], &options.ouName));
            argv++;
            argc--;
        }
        else if(!strcmp(argv[0], "--uac-flags"))
        {
            DJ_LOG_INFO("Domainjoin invoked with option --uac-flags %s", argv[1]);
            CT_SAFE_FREE_STRING(options.ouName);
            options.uacFlags = strtoul(argv[1], NULL, 0);
            argv++;
            argc--;
        }
        else if(!strcmp(argv[0], "--assumeDefaultDomain"))
        {
            DJ_LOG_INFO("Domainjoin invoked with option --assumeDefaultDomain");
            options.setAssumeDefaultDomain = TRUE;

            if (!strcasecmp(argv[1], "yes") || !strcasecmp(argv[1], "true") ||
                !strcasecmp(argv[1], "on"))
            {
                options.assumeDefaultDomain = TRUE;
            }
            else if (!strcasecmp(argv[1], "no") ||
                !strcasecmp(argv[1], "false") ||
                !strcasecmp(argv[1], "off"))
            {
                options.assumeDefaultDomain = FALSE;
            }
            else
            {
                LW_RAISE(exc, LW_ERROR_SHOW_USAGE);
                goto cleanup;
            }
            argv++;
            argc--;
        }
        else if(!strcmp(argv[0], "--userDomainPrefix"))
        {
            DJ_LOG_INFO("Domainjoin invoked with option --userDomainPrefix %s", argv[1]);
            options.setAssumeDefaultDomain = TRUE;
            options.assumeDefaultDomain = TRUE;
            CT_SAFE_FREE_STRING(options.userDomainPrefix);
            LW_CLEANUP_CTERR(exc, CTStrdup(argv[1], &options.userDomainPrefix));
            CTStrToUpper(options.userDomainPrefix);
            argv++;
            argc--;
        }
        else
        {
            LW_RAISE(exc, LW_ERROR_SHOW_USAGE);
            goto cleanup;
        }
        argv++;
        argc--;
    }

    if(argc == 3)
    {
        LW_CLEANUP_CTERR(exc, CTStrdup(argv[2], &options.password));
        passwordIndex = 2;
    }
    // The join username is not required in preview or details mode.
    else if(argc == 1 && (preview || detailModules.size != 0) )
        ;
    else if(argc != 2)
    {
        LW_RAISE(exc, LW_ERROR_SHOW_USAGE);
        goto cleanup;
    }
    options.joiningDomain = TRUE;

    DJ_LOG_INFO("Domainjoin invoked with %d arg(s) to the join command:", argc);
    for(i = 0; i < argc; i++)
    {
        DJ_LOG_INFO("    [%s]", i == passwordIndex ? "<password>" : argv[i]);
    }

    LW_CLEANUP_CTERR(exc, CTStrdup(
        argv[0], &options.domainName));
    if(argc > 1)
    {
        LW_CLEANUP_CTERR(exc, CTStrdup(argv[1], &options.username));
    }

    options.warningCallback = PrintWarning;
    options.showTraces = advanced;
    LW_CLEANUP_CTERR(exc, DJGetComputerName(&options.computerName));

    LW_TRY(exc, DJInitModuleStates(&options, &LW_EXC));

    for(i = 0; i < enableModules.size; i++)
    {
        PCSTR module = *(PCSTR *)CTArrayGetItem(
                    &enableModules, i, sizeof(PCSTR));
        if(CTArrayFindString(&disableModules, module) != -1)
        {
            LW_RAISE_EX(exc, ERROR_INVALID_PARAMETER, "Module already specified", "The module '%s' is listed as being disabled and enabled", module);
            goto cleanup;
        }
        if(CTArrayFindString(&ignoreModules, module) != -1)
        {
            LW_RAISE_EX(exc, ERROR_INVALID_PARAMETER, "Module already specified", "The module '%s' is listed as being ignored and enabled", module);
            goto cleanup;
        }
        LW_TRY(exc, DJSetModuleDisposition(&options, module, EnableModule, &LW_EXC));
    }

    for(i = 0; i < disableModules.size; i++)
    {
        PCSTR module = *(PCSTR *)CTArrayGetItem(
                    &disableModules, i, sizeof(PCSTR));
        if(CTArrayFindString(&enableModules, module) != -1)
        {
            LW_RAISE_EX(exc, ERROR_INVALID_PARAMETER, "Module already specified", "The module '%s' is listed as being disabled and enabled", module);
            goto cleanup;
        }
        if(CTArrayFindString(&ignoreModules, module) != -1)
        {
            LW_RAISE_EX(exc, ERROR_INVALID_PARAMETER, "Module already specified", "The module '%s' is listed as being ignored and enabled", module);
            goto cleanup;
        }
        LW_TRY(exc, DJSetModuleDisposition(&options, module, DisableModule, &LW_EXC));
    }

    for(i = 0; i < ignoreModules.size; i++)
    {
        PCSTR module = *(PCSTR *)CTArrayGetItem(
                    &ignoreModules, i, sizeof(PCSTR));
        if(CTArrayFindString(&enableModules, module) != -1)
        {
            LW_RAISE_EX(exc, ERROR_INVALID_PARAMETER, "Module already specified", "The module '%s' is listed as being enabled and ignored", module);
            goto cleanup;
        }
        if(CTArrayFindString(&disableModules, module) != -1)
        {
            LW_RAISE_EX(exc, ERROR_INVALID_PARAMETER, "Module already specified", "The module '%s' is listed as being disabled and ignored", module);
            goto cleanup;
        }
        LW_TRY(exc, DJSetModuleDisposition(&options, module, IgnoreModule, &LW_EXC));
    }

    for(i = 0; i < detailModules.size; i++)
    {
        PCSTR module = *(PCSTR *)CTArrayGetItem(
                    &detailModules, i, sizeof(PCSTR));
        ModuleState *state = DJGetModuleStateByName(&options, module);
        if(state == NULL)
        {
            LW_RAISE_EX(exc, ERROR_INVALID_PARAMETER, "Unable to find module.", "Please check the spelling of '%s'. This module cannot be found", module);
            goto cleanup;
        }
        PrintModuleState(state);
    }
    if(detailModules.size > 0)
    {
        PrintStateKey();
    }
    for(i = 0; i < detailModules.size; i++)
    {
        PCSTR module = *(PCSTR *)CTArrayGetItem(
                    &detailModules, i, sizeof(PCSTR));
        ModuleState *state = DJGetModuleStateByName(&options, module);
        CT_SAFE_FREE_STRING(moduleDetails);
        CT_SAFE_FREE_STRING(wrapped);
        LW_TRY(exc, moduleDetails = state->module->GetChangeDescription(&options, &LW_EXC));
        LW_CLEANUP_CTERR(exc, CTWordWrap(moduleDetails, &wrapped, 4, columns));
        fprintf(stdout, "\nDetails for '%s':\n%s\n", state->module->longName, wrapped);
    }
    if(detailModules.size > 0)
        goto cleanup;

    LW_TRY(exc, PrintJoinHeader(&options, &LW_EXC));

    if(preview)
    {
        PrintModuleStates(advanced, &options);
        if(!advanced)
            LW_TRY(exc, DJCheckRequiredEnabled(&options, &LW_EXC));
        goto cleanup;
    }

    LW_TRY(exc, DJCheckRequiredEnabled(&options, &LW_EXC));

    if (IsNullOrEmptyString(options.password))
    {
        CT_SAFE_FREE_STRING(options.password);

        LW_CLEANUP_CTERR(exc, FillMissingPassword(options.username,
                    &options.password));
    }

    LW_TRY(exc, DJRunJoinProcess(&options, &LW_EXC));
    fprintf(stdout, "SUCCESS\n");

cleanup:
    DJFreeJoinProcessOptions(&options);
    CTArrayFree(&enableModules);
    CTArrayFree(&disableModules);
    CTArrayFree(&ignoreModules);
    CTArrayFree(&detailModules);
    CT_SAFE_FREE_STRING(moduleDetails);
    CT_SAFE_FREE_STRING(wrapped);
}