/* 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 
 * <http://www.gnu.org/licenses/>.
 *
 * 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 "sysauth.h"

#define GCE(x) GOTO_CLEANUP_ON_DWORD((x))

typedef enum
{
    ENABLE_TYPE_ENABLE = 0,
    ENABLE_TYPE_DISABLE,
    /* Enable if joined to the domain, otherwise disable */
    ENABLE_TYPE_AUTO,
} EnableType;

static
void
ShowUsage()
{
    fprintf(stdout, "usage: lw-sysauth command [args...]\n\n");
    fprintf(stdout, "  where options are:\n\n");
    fprintf(stdout, "    --help                                     Display this help information.\n");
    fprintf(stdout, "    --logfile {.|path}                         Log to a file (or \".\" to log\n"
                    "                                               to console).\n");
    fprintf(stdout, "    --loglevel {error|warning|info|verbose}    Adjusts how much logging is\n"
                    "                                               produced by lw-sysauth.\n");
    fprintf(stdout, "\n");
    fprintf(stdout, "  and commands are:\n\n");
    fprintf(stdout, "    configure {--enable|--disable} {nsswitch|pam}\n");
}

BOOLEAN GetEnableBoolean(EnableType dwEnable)
{
    if(dwEnable == ENABLE_TYPE_AUTO)
    {
        dwEnable = ENABLE_TYPE_ENABLE;
    }

    return dwEnable == ENABLE_TYPE_ENABLE;
}

void PrintWarning(const JoinProcessOptions *options, const char *title, const char *message)
{
    PSTR      wrapped = NULL;
    int columns;
    if(CTGetTerminalWidth(fileno(stdout), &columns))
        columns = -1;

    //This function doesn't return a DWORD, so we have to recover as much
    //as possible.
    if(!CTWordWrap(message, &wrapped, 4, columns))
        fprintf(stdout, "Warning: %s\n%s\n\n", title, wrapped);
    else
        fprintf(stdout, "Warning: %s\n%s\n\n", title, message);
    CT_SAFE_FREE_STRING(wrapped);
    DJ_LOG_WARNING("%s\n%s", title, message);
}

void PrintModuleState(ModuleState *state)
{
    char resultChar;
    if(state->lastResult != FullyConfigured &&
            state->lastResult != CannotConfigure)
    {
        fprintf(stdout, "[%c] ", state->runModule? 'X' : ' ');
    }
    else
    {
        fprintf(stdout, "    ");
    }
    switch(state->lastResult)
    {
        default:
        case NotApplicable:
            //This case should not occur
            resultChar = 'E';
            break;
        case FullyConfigured:
            resultChar = 'F';
            break;
        case SufficientlyConfigured:
            resultChar = 'S';
            break;
        case NotConfigured:
            resultChar = 'N';
            break;
        case CannotConfigure:
            //This is distinguishable from NotConfigured because the
            //checkbox to the left doesn't appear.
            resultChar = 'N';
            break;
    }
    fprintf(stdout, "[%c] %-15s- %s\n", resultChar, state->module->shortName,
            state->module->longName);
}

void PrintStateKey()
{
    fprintf(stdout,
"\n"
"Key to flags\n"
"[F]ully configured        - the system is already configured for this step\n"
"[S]ufficiently configured - the system meets the minimum configuration\n"
"                            requirements for this step\n"
"[N]ecessary               - this step must be run or manually performed.\n"
"\n"
"[X]                       - this step is enabled and will make changes\n"
"[ ]                       - this step is disabled and will not make changes\n");
}

void PrintModuleStates(BOOLEAN showTristate, JoinProcessOptions *options)
{
    size_t i;
    if(showTristate)
    {
        for(i = 0; i < options->moduleStates.size; i++)
        {
            PrintModuleState(DJGetModuleState(options, i));
        }
        PrintStateKey();
    }
    else
    {
        fprintf(stdout, "The following stages are currently configured to be run during the domain join:\n");
        for(i = 0; i < options->moduleStates.size; i++)
        {
            ModuleState *state = DJGetModuleState(options, i);
            if(state->runModule)
            {
                fprintf(stdout, "%-15s- %s\n", state->module->shortName,
                        state->module->longName);
            }
        }
    }
}

void DoConfigure(int argc, char **argv, LWException **exc)
{
    EnableType dwEnable = ENABLE_TYPE_AUTO;
    PCSTR testPrefix = NULL;
    JoinProcessOptions options = { 0 };

    while(argc > 0 && CTStrStartsWith(argv[0], "--"))
    {
        if(!strcmp(argv[0], "--autoenable"))
            dwEnable = ENABLE_TYPE_AUTO;
        else if(!strcmp(argv[0], "--enable"))
            dwEnable = ENABLE_TYPE_ENABLE;
        else if(!strcmp(argv[0], "--disable"))
            dwEnable = ENABLE_TYPE_DISABLE;
        else if(argc < 2)
        {
            LW_RAISE(exc, LW_ERROR_SHOW_USAGE);
            goto cleanup;
        }
        else if(!strcmp(argv[0], "--testprefix"))
        {
            testPrefix = argv[1];
            argv++;
            argc--;
        }
        else
        {
            LW_RAISE(exc, LW_ERROR_SHOW_USAGE);
            goto cleanup;
        }
        argv++;
        argc--;
    }

    if(argc < 1)
    {
        LW_RAISE(exc, LW_ERROR_SHOW_USAGE);
        goto cleanup;
    }

    options.joiningDomain = GetEnableBoolean(dwEnable);
    options.warningCallback = PrintWarning;

    if(!strcmp(argv[0], "pam"))
        LW_TRY(exc, DJNewConfigurePamForADLogin(testPrefix, NULL, PrintWarning, GetEnableBoolean(dwEnable), &LW_EXC));
    else if(!strcmp(argv[0], "nsswitch"))
    {
        LW_TRY(exc, DoNsswitch(&options, &LW_EXC));
    }
    else
    {
        LW_RAISE(exc, LW_ERROR_SHOW_USAGE);
        goto cleanup;
    }
    fprintf(stdout, "SUCCESS\n");
cleanup:
    ;
}

int main(
    int argc,
    char* argv[]
    )
{
    LWException *exc = NULL;
    int columns;
    PSTR pszLogFilePath = NULL;
    BOOLEAN bNoLog = FALSE;
    PSTR logLevel = "warning";
    DWORD dwLogLevel;
    BOOLEAN showHelp = FALSE;
    int remainingArgs = argc;
    char **argPos = argv;
    BOOLEAN directoryExists = FALSE;

    if(CTGetTerminalWidth(fileno(stdout), &columns))
        columns = -1;

    /* Skip the program name */
    argPos++;
    remainingArgs--;

    setlocale(LC_ALL, "");

    while(remainingArgs > 0 && CTStrStartsWith(argPos[0], "--"))
    {
        if(!strcmp(argPos[0], "--help"))
            showHelp = TRUE;
        else if(!strcmp(argPos[0], "--nolog"))
            bNoLog = TRUE;
        //All options after this point take an argument
        else if(remainingArgs < 2)
            showHelp = TRUE;
        else if(!strcmp(argPos[0], "--logfile"))
        {
            pszLogFilePath = (++argPos)[0];
            remainingArgs--;
        }
        else if(!strcmp(argPos[0], "--loglevel"))
        {
            logLevel = (++argPos)[0];
            remainingArgs--;
        }
        else
            break;
        remainingArgs--;
        argPos++;
    }

    if(remainingArgs < 1)
        showHelp = TRUE;

    if (showHelp) {
        ShowUsage();
        goto cleanup;
    }

    if (!strcasecmp(logLevel, "error"))
        dwLogLevel = LOG_LEVEL_ERROR;
    else if (!strcasecmp(logLevel, "warning"))
        dwLogLevel = LOG_LEVEL_WARNING;
    else if (!strcasecmp(logLevel, "info"))
        dwLogLevel = LOG_LEVEL_INFO;
    else if (!strcasecmp(logLevel, "verbose"))
        dwLogLevel = LOG_LEVEL_VERBOSE;
    else {
        LW_CLEANUP_CTERR(&exc, LW_ERROR_INVALID_LOG_LEVEL);
    }

    if (pszLogFilePath == NULL)
    {
        // Determine the default log path
        LW_CLEANUP_CTERR(&exc,
                CTCheckDirectoryExists("/var/log", &directoryExists));
        if (directoryExists)
        {
            pszLogFilePath = "/var/log/lw-sysauth.log";
        }
        else
        {
            pszLogFilePath = "/var/adm/lw-sysauth.log";
        }
    }

    if (bNoLog) {
        LW_CLEANUP_CTERR(&exc, dj_disable_logging());
    } else if (!strcmp(pszLogFilePath, ".")) {
        LW_CLEANUP_CTERR(&exc, dj_init_logging_to_console(dwLogLevel));
    } else {
        DWORD ceError = dj_init_logging_to_file(dwLogLevel, pszLogFilePath);
        if(ceError == ERROR_ACCESS_DENIED)
        {
            fprintf(stderr, "Warning: insufficient permissions to log to %s. To enable logging, please specify a different filename with --logfile <file>.\n",
                    pszLogFilePath);
            ceError = ERROR_SUCCESS;
            LW_CLEANUP_CTERR(&exc, dj_disable_logging());
        }
        else if (ceError == ERROR_FILE_NOT_FOUND)
        {
            fprintf(stderr, "Warning: parent directory of log file %s does not exist. To enable logging, please specify a different filename with --logfile <file>.\n",
                    pszLogFilePath);
            ceError = ERROR_SUCCESS;
            LW_CLEANUP_CTERR(&exc, dj_disable_logging());
        }
        LW_CLEANUP_CTERR(&exc, ceError);
    }

    if(!strcmp(argPos[0], "configure"))
    {
        argPos++;
        remainingArgs--;
        LW_TRY(&exc, DoConfigure(remainingArgs, argPos, &LW_EXC));
    }
    else
    {
        LW_RAISE(&exc, LW_ERROR_SHOW_USAGE);
        goto cleanup;
    }

cleanup:

    if (!LW_IS_OK(exc) && exc->code == LW_ERROR_SHOW_USAGE)
    {
        ShowUsage();
        LWHandle(&exc);
    }
    else if (!LW_IS_OK(exc))
    {
        //Ignoring the return value from this because we can't do anything
        //if there is an error
        fprintf(stdout, "\n");
        LWPrintException(stdout, exc, FALSE);
        DJLogException(LOG_LEVEL_ERROR, exc);
        LWHandle(&exc);
        dj_close_log();
        return 1;
    }

    dj_close_log();

    return 0;
}
