/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
** secutil.c - various functions used by security stuff
**
*/

#include "prtypes.h"
#include "prtime.h"
#include "prlong.h"
#include "prerror.h"
#include "prprf.h"
#include "plgetopt.h"
#include "prenv.h"
#include "prnetdb.h"

#include "basicutil.h"
#include <stdarg.h>
#include <stddef.h>
#include <sys/stat.h>
#include <errno.h>

#ifdef XP_UNIX
#include <unistd.h>
#endif

#include "secoid.h"

extern long DER_GetInteger(const SECItem *src);

static PRBool wrapEnabled = PR_TRUE;

void
SECU_EnableWrap(PRBool enable)
{
    wrapEnabled = enable;
}

PRBool
SECU_GetWrapEnabled(void)
{
    return wrapEnabled;
}

void
SECU_PrintErrMsg(FILE *out, int level, const char *progName, const char *msg,
                 ...)
{
    va_list args;
    PRErrorCode err = PORT_GetError();
    const char *errString = PORT_ErrorToString(err);

    va_start(args, msg);

    SECU_Indent(out, level);
    fprintf(out, "%s: ", progName);
    vfprintf(out, msg, args);
    if (errString != NULL && PORT_Strlen(errString) > 0)
        fprintf(out, ": %s\n", errString);
    else
        fprintf(out, ": error %d\n", (int)err);

    va_end(args);
}

void
SECU_PrintError(const char *progName, const char *msg, ...)
{
    va_list args;
    PRErrorCode err = PORT_GetError();
    const char *errName = PR_ErrorToName(err);
    const char *errString = PR_ErrorToString(err, 0);

    va_start(args, msg);

    fprintf(stderr, "%s: ", progName);
    vfprintf(stderr, msg, args);

    if (errName != NULL) {
        fprintf(stderr, ": %s", errName);
    } else {
        fprintf(stderr, ": error %d", (int)err);
    }

    if (errString != NULL && PORT_Strlen(errString) > 0)
        fprintf(stderr, ": %s\n", errString);

    va_end(args);
}

void
SECU_PrintSystemError(const char *progName, const char *msg, ...)
{
    va_list args;

    va_start(args, msg);
    fprintf(stderr, "%s: ", progName);
    vfprintf(stderr, msg, args);
    fprintf(stderr, ": %s\n", strerror(errno));
    va_end(args);
}

SECStatus
secu_StdinToItem(SECItem *dst)
{
    unsigned char buf[1000];
    PRInt32 numBytes;
    PRBool notDone = PR_TRUE;

    dst->len = 0;
    dst->data = NULL;

    while (notDone) {
        numBytes = PR_Read(PR_STDIN, buf, sizeof(buf));

        if (numBytes < 0) {
            return SECFailure;
        }

        if (numBytes == 0)
            break;

        if (dst->data) {
            unsigned char *p = dst->data;
            dst->data = (unsigned char *)PORT_Realloc(p, dst->len + numBytes);
            if (!dst->data) {
                PORT_Free(p);
            }
        } else {
            dst->data = (unsigned char *)PORT_Alloc(numBytes);
        }
        if (!dst->data) {
            return SECFailure;
        }
        PORT_Memcpy(dst->data + dst->len, buf, numBytes);
        dst->len += numBytes;
    }

    return SECSuccess;
}

SECStatus
SECU_FileToItem(SECItem *dst, PRFileDesc *src)
{
    PRFileInfo info;
    PRInt32 numBytes;
    PRStatus prStatus;

    if (src == PR_STDIN)
        return secu_StdinToItem(dst);

    prStatus = PR_GetOpenFileInfo(src, &info);

    if (prStatus != PR_SUCCESS) {
        PORT_SetError(SEC_ERROR_IO);
        return SECFailure;
    }

    /* XXX workaround for 3.1, not all utils zero dst before sending */
    dst->data = 0;
    if (!SECITEM_AllocItem(NULL, dst, info.size))
        goto loser;

    numBytes = PR_Read(src, dst->data, info.size);
    if (numBytes != info.size) {
        PORT_SetError(SEC_ERROR_IO);
        goto loser;
    }

    return SECSuccess;
loser:
    SECITEM_FreeItem(dst, PR_FALSE);
    dst->data = NULL;
    return SECFailure;
}

SECStatus
SECU_TextFileToItem(SECItem *dst, PRFileDesc *src)
{
    PRFileInfo info;
    PRInt32 numBytes;
    PRStatus prStatus;
    unsigned char *buf;

    if (src == PR_STDIN)
        return secu_StdinToItem(dst);

    prStatus = PR_GetOpenFileInfo(src, &info);

    if (prStatus != PR_SUCCESS) {
        PORT_SetError(SEC_ERROR_IO);
        return SECFailure;
    }

    buf = (unsigned char *)PORT_Alloc(info.size);
    if (!buf)
        return SECFailure;

    numBytes = PR_Read(src, buf, info.size);
    if (numBytes != info.size) {
        PORT_SetError(SEC_ERROR_IO);
        goto loser;
    }

    if (buf[numBytes - 1] == '\n')
        numBytes--;
#ifdef _WINDOWS
    if (buf[numBytes - 1] == '\r')
        numBytes--;
#endif

    /* XXX workaround for 3.1, not all utils zero dst before sending */
    dst->data = 0;
    if (!SECITEM_AllocItem(NULL, dst, numBytes))
        goto loser;

    memcpy(dst->data, buf, numBytes);

    PORT_Free(buf);
    return SECSuccess;
loser:
    PORT_Free(buf);
    return SECFailure;
}

#define INDENT_MULT 4
void
SECU_Indent(FILE *out, int level)
{
    int i;

    for (i = 0; i < level; i++) {
        fprintf(out, "    ");
    }
}

void
SECU_Newline(FILE *out)
{
    fprintf(out, "\n");
}

void
SECU_PrintAsHex(FILE *out, const SECItem *data, const char *m, int level)
{
    unsigned i;
    int column = 0;
    PRBool isString = PR_TRUE;
    PRBool isWhiteSpace = PR_TRUE;
    PRBool printedHex = PR_FALSE;
    unsigned int limit = 15;

    if (m) {
        SECU_Indent(out, level);
        fprintf(out, "%s:", m);
        level++;
        if (wrapEnabled)
            fprintf(out, "\n");
    }

    if (wrapEnabled) {
        SECU_Indent(out, level);
        column = level * INDENT_MULT;
    }
    if (!data->len) {
        fprintf(out, "(empty)\n");
        return;
    }
    /* take a pass to see if it's all printable. */
    for (i = 0; i < data->len; i++) {
        unsigned char val = data->data[i];
        if (!val || !isprint(val)) {
            isString = PR_FALSE;
            break;
        }
        if (isWhiteSpace && !isspace(val)) {
            isWhiteSpace = PR_FALSE;
        }
    }

    /* Short values, such as bit strings (which are printed with this
    ** function) often look like strings, but we want to see the bits.
    ** so this test assures that short values will be printed in hex,
    ** perhaps in addition to being printed as strings.
    ** The threshold size (4 bytes) is arbitrary.
    */
    if (!isString || data->len <= 4) {
        for (i = 0; i < data->len; i++) {
            if (i != data->len - 1) {
                fprintf(out, "%02x:", data->data[i]);
                column += 3;
            } else {
                fprintf(out, "%02x", data->data[i]);
                column += 2;
                break;
            }
            if (wrapEnabled &&
                (column > 76 || (i % 16 == limit))) {
                SECU_Newline(out);
                SECU_Indent(out, level);
                column = level * INDENT_MULT;
                limit = i % 16;
            }
        }
        printedHex = PR_TRUE;
    }
    if (isString && !isWhiteSpace) {
        if (printedHex != PR_FALSE) {
            SECU_Newline(out);
            SECU_Indent(out, level);
            column = level * INDENT_MULT;
        }
        for (i = 0; i < data->len; i++) {
            unsigned char val = data->data[i];

            if (val) {
                fprintf(out, "%c", val);
                column++;
            } else {
                column = 77;
            }
            if (wrapEnabled && column > 76) {
                SECU_Newline(out);
                SECU_Indent(out, level);
                column = level * INDENT_MULT;
            }
        }
    }

    if (column != level * INDENT_MULT) {
        SECU_Newline(out);
    }
}

const char *hex = "0123456789abcdef";

const char printable[257] = {
    "................"  /* 0x */
    "................"  /* 1x */
    " !\"#$%&'()*+,-./" /* 2x */
    "0123456789:;<=>?"  /* 3x */
    "@ABCDEFGHIJKLMNO"  /* 4x */
    "PQRSTUVWXYZ[\\]^_" /* 5x */
    "`abcdefghijklmno"  /* 6x */
    "pqrstuvwxyz{|}~."  /* 7x */
    "................"  /* 8x */
    "................"  /* 9x */
    "................"  /* ax */
    "................"  /* bx */
    "................"  /* cx */
    "................"  /* dx */
    "................"  /* ex */
    "................"  /* fx */
};

void
SECU_PrintBuf(FILE *out, const char *msg, const void *vp, int len)
{
    const unsigned char *cp = (const unsigned char *)vp;
    char buf[80];
    char *bp;
    char *ap;

    fprintf(out, "%s [Len: %d]\n", msg, len);
    memset(buf, ' ', sizeof buf);
    bp = buf;
    ap = buf + 50;
    while (--len >= 0) {
        unsigned char ch = *cp++;
        *bp++ = hex[(ch >> 4) & 0xf];
        *bp++ = hex[ch & 0xf];
        *bp++ = ' ';
        *ap++ = printable[ch];
        if (ap - buf >= 66) {
            *ap = 0;
            fprintf(out, "   %s\n", buf);
            memset(buf, ' ', sizeof buf);
            bp = buf;
            ap = buf + 50;
        }
    }
    if (bp > buf) {
        *ap = 0;
        fprintf(out, "   %s\n", buf);
    }
}

/* This expents i->data[0] to be the MSB of the integer.
** if you want to print a DER-encoded integer (with the tag and length)
** call SECU_PrintEncodedInteger();
*/
void
SECU_PrintInteger(FILE *out, const SECItem *i, const char *m, int level)
{
    int iv;

    if (!i || !i->len || !i->data) {
        SECU_Indent(out, level);
        if (m) {
            fprintf(out, "%s: (null)\n", m);
        } else {
            fprintf(out, "(null)\n");
        }
    } else if (i->len > 4) {
        SECU_PrintAsHex(out, i, m, level);
    } else {
        if (i->type == siUnsignedInteger && *i->data & 0x80) {
            /* Make sure i->data has zero in the highest bite
             * if i->data is an unsigned integer */
            SECItem tmpI;
            char data[] = { 0, 0, 0, 0, 0 };

            PORT_Memcpy(data + 1, i->data, i->len);
            tmpI.len = i->len + 1;
            tmpI.data = (void *)data;

            iv = DER_GetInteger(&tmpI);
        } else {
            iv = DER_GetInteger(i);
        }
        SECU_Indent(out, level);
        if (m) {
            fprintf(out, "%s: %d (0x%x)\n", m, iv, iv);
        } else {
            fprintf(out, "%d (0x%x)\n", iv, iv);
        }
    }
}

#if defined(DEBUG) || defined(FORCE_PR_ASSERT)
/* Returns true iff a[i].flag has a duplicate in a[i+1 : count-1]  */
static PRBool
HasShortDuplicate(int i, secuCommandFlag *a, int count)
{
    char target = a[i].flag;
    int j;

    /* duplicate '\0' flags are okay, they are used with long forms */
    for (j = i + 1; j < count; j++) {
        if (a[j].flag && a[j].flag == target) {
            return PR_TRUE;
        }
    }
    return PR_FALSE;
}

/* Returns true iff a[i].longform has a duplicate in a[i+1 : count-1] */
static PRBool
HasLongDuplicate(int i, secuCommandFlag *a, int count)
{
    int j;
    char *target = a[i].longform;

    if (!target)
        return PR_FALSE;

    for (j = i + 1; j < count; j++) {
        if (a[j].longform && strcmp(a[j].longform, target) == 0) {
            return PR_TRUE;
        }
    }
    return PR_FALSE;
}

/* Returns true iff a has no short or long form duplicates
 */
PRBool
HasNoDuplicates(secuCommandFlag *a, int count)
{
    int i;

    for (i = 0; i < count; i++) {
        if (a[i].flag && HasShortDuplicate(i, a, count)) {
            return PR_FALSE;
        }
        if (a[i].longform && HasLongDuplicate(i, a, count)) {
            return PR_FALSE;
        }
    }
    return PR_TRUE;
}
#endif

SECStatus
SECU_ParseCommandLine(int argc, char **argv, char *progName,
                      const secuCommand *cmd)
{
    PRBool found;
    PLOptState *optstate;
    PLOptStatus status;
    char *optstring;
    PLLongOpt *longopts = NULL;
    int i, j;
    int lcmd = 0, lopt = 0;

    PR_ASSERT(HasNoDuplicates(cmd->commands, cmd->numCommands));
    PR_ASSERT(HasNoDuplicates(cmd->options, cmd->numOptions));

    optstring = (char *)PORT_Alloc(cmd->numCommands + 2 * cmd->numOptions + 1);
    if (optstring == NULL)
        return SECFailure;

    j = 0;
    for (i = 0; i < cmd->numCommands; i++) {
        if (cmd->commands[i].flag) /* single character option ? */
            optstring[j++] = cmd->commands[i].flag;
        if (cmd->commands[i].longform)
            lcmd++;
    }
    for (i = 0; i < cmd->numOptions; i++) {
        if (cmd->options[i].flag) {
            optstring[j++] = cmd->options[i].flag;
            if (cmd->options[i].needsArg)
                optstring[j++] = ':';
        }
        if (cmd->options[i].longform)
            lopt++;
    }

    optstring[j] = '\0';

    if (lcmd + lopt > 0) {
        longopts = PORT_NewArray(PLLongOpt, lcmd + lopt + 1);
        if (!longopts) {
            PORT_Free(optstring);
            return SECFailure;
        }

        j = 0;
        for (i = 0; j < lcmd && i < cmd->numCommands; i++) {
            if (cmd->commands[i].longform) {
                longopts[j].longOptName = cmd->commands[i].longform;
                longopts[j].longOption = 0;
                longopts[j++].valueRequired = cmd->commands[i].needsArg;
            }
        }
        lopt += lcmd;
        for (i = 0; j < lopt && i < cmd->numOptions; i++) {
            if (cmd->options[i].longform) {
                longopts[j].longOptName = cmd->options[i].longform;
                longopts[j].longOption = 0;
                longopts[j++].valueRequired = cmd->options[i].needsArg;
            }
        }
        longopts[j].longOptName = NULL;
    }

    optstate = PL_CreateLongOptState(argc, argv, optstring, longopts);
    if (!optstate) {
        PORT_Free(optstring);
        PORT_Free(longopts);
        return SECFailure;
    }
    /* Parse command line arguments */
    while ((status = PL_GetNextOpt(optstate)) == PL_OPT_OK) {
        const char *optstatelong;
        char option = optstate->option;

        /*  positional parameter, single-char option or long opt? */
        if (optstate->longOptIndex == -1) {
            /* not a long opt */
            if (option == '\0')
                continue; /* it's a positional parameter */
            optstatelong = "";
        } else {
            /* long opt */
            if (option == '\0')
                option = '\377'; /* force unequal with all flags */
            optstatelong = longopts[optstate->longOptIndex].longOptName;
        }

        found = PR_FALSE;

        for (i = 0; i < cmd->numCommands; i++) {
            if (cmd->commands[i].flag == option ||
                cmd->commands[i].longform == optstatelong) {
                cmd->commands[i].activated = PR_TRUE;
                if (optstate->value) {
                    cmd->commands[i].arg = (char *)optstate->value;
                }
                found = PR_TRUE;
                break;
            }
        }

        if (found)
            continue;

        for (i = 0; i < cmd->numOptions; i++) {
            if (cmd->options[i].flag == option ||
                cmd->options[i].longform == optstatelong) {
                cmd->options[i].activated = PR_TRUE;
                if (optstate->value) {
                    cmd->options[i].arg = (char *)optstate->value;
                } else if (cmd->options[i].needsArg) {
                    status = PL_OPT_BAD;
                    goto loser;
                }
                found = PR_TRUE;
                break;
            }
        }

        if (!found) {
            status = PL_OPT_BAD;
            break;
        }
    }

loser:
    PL_DestroyOptState(optstate);
    PORT_Free(optstring);
    if (longopts)
        PORT_Free(longopts);
    if (status == PL_OPT_BAD)
        return SECFailure;
    return SECSuccess;
}

char *
SECU_GetOptionArg(const secuCommand *cmd, int optionNum)
{
    if (optionNum < 0 || optionNum >= cmd->numOptions)
        return NULL;
    if (cmd->options[optionNum].activated)
        return PL_strdup(cmd->options[optionNum].arg);
    else
        return NULL;
}

void
SECU_PrintPRandOSError(const char *progName)
{
    char buffer[513];
    PRInt32 errLenInt = PR_GetErrorTextLength();
    size_t errLen = errLenInt < 0 ? 0 : (size_t)errLenInt;
    if (errLen > 0 && errLen < sizeof buffer) {
        PR_GetErrorText(buffer);
    }
    SECU_PrintError(progName, "function failed");
    if (errLen > 0 && errLen < sizeof buffer) {
        PR_fprintf(PR_STDERR, "\t%s\n", buffer);
    }
}

SECOidTag
SECU_StringToSignatureAlgTag(const char *alg)
{
    SECOidTag hashAlgTag = SEC_OID_UNKNOWN;

    if (alg) {
        if (!PL_strcmp(alg, "MD2")) {
            hashAlgTag = SEC_OID_MD2;
        } else if (!PL_strcmp(alg, "MD4")) {
            hashAlgTag = SEC_OID_MD4;
        } else if (!PL_strcmp(alg, "MD5")) {
            hashAlgTag = SEC_OID_MD5;
        } else if (!PL_strcmp(alg, "SHA1")) {
            hashAlgTag = SEC_OID_SHA1;
        } else if (!PL_strcmp(alg, "SHA224")) {
            hashAlgTag = SEC_OID_SHA224;
        } else if (!PL_strcmp(alg, "SHA256")) {
            hashAlgTag = SEC_OID_SHA256;
        } else if (!PL_strcmp(alg, "SHA384")) {
            hashAlgTag = SEC_OID_SHA384;
        } else if (!PL_strcmp(alg, "SHA512")) {
            hashAlgTag = SEC_OID_SHA512;
        }
    }
    return hashAlgTag;
}

/* Caller ensures that dst is at least item->len*2+1 bytes long */
void
SECU_SECItemToHex(const SECItem *item, char *dst)
{
    if (dst && item && item->data) {
        unsigned char *src = item->data;
        unsigned int len = item->len;
        for (; len > 0; --len, dst += 2) {
            snprintf(dst, 3, "%02x", *src++);
        }
    }
}

static unsigned char
nibble(char c)
{
    c = PORT_Tolower((unsigned char)c);
    return (c >= '0' && c <= '9') ? c - '0' : (c >= 'a' && c <= 'f') ? c - 'a' + 10
                                                                     : -1;
}

SECStatus
SECU_SECItemHexStringToBinary(SECItem *srcdest)
{
    unsigned int i;

    if (!srcdest) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }
    if (srcdest->len < 4 || (srcdest->len % 2)) {
        /* too short to convert, or even number of characters */
        PORT_SetError(SEC_ERROR_BAD_DATA);
        return SECFailure;
    }
    if (PORT_Strncasecmp((const char *)srcdest->data, "0x", 2)) {
        /* wrong prefix */
        PORT_SetError(SEC_ERROR_BAD_DATA);
        return SECFailure;
    }

    /* 1st pass to check for hex characters */
    for (i = 2; i < srcdest->len; i++) {
        char c = PORT_Tolower(srcdest->data[i]);
        if (!((c >= '0' && c <= '9') ||
              (c >= 'a' && c <= 'f'))) {
            PORT_SetError(SEC_ERROR_BAD_DATA);
            return SECFailure;
        }
    }

    /* 2nd pass to convert */
    for (i = 2; i < srcdest->len; i += 2) {
        srcdest->data[(i - 2) / 2] = (nibble(srcdest->data[i]) << 4) +
                                     nibble(srcdest->data[i + 1]);
    }

    /* adjust length */
    srcdest->len -= 2;
    srcdest->len /= 2;
    return SECSuccess;
}

SECItem *
SECU_HexString2SECItem(PLArenaPool *arena, SECItem *item, const char *str)
{
    int i = 0;
    int byteval = 0;
    int tmp = PORT_Strlen(str);

    PORT_Assert(item);

    if ((tmp % 2) != 0) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }

    item = SECITEM_AllocItem(arena, item, tmp / 2);
    if (item == NULL) {
        return NULL;
    }

    while (str[i]) {
        if ((str[i] >= '0') && (str[i] <= '9')) {
            tmp = str[i] - '0';
        } else if ((str[i] >= 'a') && (str[i] <= 'f')) {
            tmp = str[i] - 'a' + 10;
        } else if ((str[i] >= 'A') && (str[i] <= 'F')) {
            tmp = str[i] - 'A' + 10;
        } else {
            if (!arena) {
                SECITEM_FreeItem(item, PR_FALSE);
            }
            return NULL;
        }

        byteval = byteval * 16 + tmp;
        if ((i % 2) != 0) {
            item->data[i / 2] = byteval;
            byteval = 0;
        }
        i++;
    }

    return item;
}

SECStatus
SECU_ecName2params(ECCurveName curve, SECItem *params)
{
    SECOidTag oidTag;
    SECOidData *oidData = NULL;

    switch (curve) {
        case ECCurve_NIST_P256:
            oidTag = SEC_OID_ANSIX962_EC_PRIME256V1;
            break;
        case ECCurve_NIST_P384:
            oidTag = SEC_OID_SECG_EC_SECP384R1;
            break;
        case ECCurve_NIST_P521:
            oidTag = SEC_OID_SECG_EC_SECP521R1;
            break;
        case ECCurve25519:
            oidTag = SEC_OID_CURVE25519;
            break;
        case ECCurve_Ed25519:
            oidTag = SEC_OID_ED25519_PUBLIC_KEY;
            break;
        default:
            PORT_SetError(SEC_ERROR_UNSUPPORTED_ELLIPTIC_CURVE);
            return SECFailure;
    }

    oidData = SECOID_FindOIDByTag(oidTag);
    if (oidData == NULL) {
        PORT_SetError(SEC_ERROR_UNSUPPORTED_ELLIPTIC_CURVE);
        return SECFailure;
    }

    if (SECITEM_AllocItem(NULL, params, (2 + oidData->oid.len)) == NULL) {
        return SECFailure;
    }
    /*
     * params->data needs to contain the ASN encoding of an object ID (OID)
     * representing the named curve. The actual OID is in
     * oidData->oid.data so we simply prepend 0x06 and OID length
     */
    params->data[0] = SEC_ASN1_OBJECT_ID;
    params->data[1] = oidData->oid.len;
    memcpy(params->data + 2, oidData->oid.data, oidData->oid.len);

    return SECSuccess;
}
