AuditPrefix(void)
{
    time_t tm;
    char *autime, *s;
    char *tmpBuf;
    int len;

    time(&tm);
    autime = ctime(&tm);
    if ((s = strchr(autime, '\n')))
        *s = '\0';
    len = strlen(AUDIT_PREFIX) + strlen(autime) + 10 + 1;
    tmpBuf = malloc(len);
    if (!tmpBuf)
        return NULL;
    snprintf(tmpBuf, len, AUDIT_PREFIX, autime, (unsigned long) getpid());
    return tmpBuf;
}


LogSWrite(int verb, const char *buf, size_t len, Bool end_line)
{
    static Bool newline = TRUE;
    int ret;

    if (verb < 0 || logVerbosity >= verb)
        ret = write(2, buf, len);

    if (verb < 0 || logFileVerbosity >= verb) {
        if (inSignalContext && logFileFd >= 0) {
            ret = write(logFileFd, buf, len);
#ifndef WIN32
            if (logFlush && logSync)
                fsync(logFileFd);
#endif
        }
        else if (!inSignalContext && logFile) {
            if (newline)
                fprintf(logFile, "[%10.3f] ", GetTimeInMillis() / 1000.0);
            newline = end_line;
            fwrite(buf, len, 1, logFile);
            if (logFlush) {
                fflush(logFile);
#ifndef WIN32
                if (logSync)
                    fsync(fileno(logFile));
#endif
            }
        }
        else if (!inSignalContext && needBuffer) {
            if (len > bufferUnused) {
                bufferSize += 1024;
                bufferUnused += 1024;
                saveBuffer = realloc(saveBuffer, bufferSize);
                if (!saveBuffer)
                    FatalError("realloc() failed while saving log messages\n");
            }
            bufferUnused -= len;
            memcpy(saveBuffer + bufferPos, buf, len);
            bufferPos += len;
        }
    }

    /* There's no place to log an error message if the log write
     * fails...
     */
    (void) ret;
}

LogFilePrep: add a comment to the unsafe format string.

CVE-2018-14665 also made it possible to exploit this to access
memory. With -logfile forbidden when running with elevated privileges
this is no longer an issue.

Signed-off-by: Matthieu Herrb <matthieu@herrb.eu>
Reviewed-by: Adam Jackson <ajax@redhat.com>
(cherry picked from commit 248d164eae27f1f310266d78e52f13f64362f81e)


LogVHdrMessageVerb(MessageType type, int verb, const char *msg_format,
                   va_list msg_args, const char *hdr_format, va_list hdr_args)
{
    const char *type_str;
    char buf[1024];
    const size_t size = sizeof(buf);
    Bool newline;
    size_t len = 0;
    int (*vprintf_func)(char *, int, const char* _X_RESTRICT_KYWD f, va_list args)
            _X_ATTRIBUTE_PRINTF(3, 0);
    int (*printf_func)(char *, int, const char* _X_RESTRICT_KYWD f, ...)
            _X_ATTRIBUTE_PRINTF(3, 4);

    type_str = LogMessageTypeVerbString(type, verb);
    if (!type_str)
        return;

    if (inSignalContext) {
        vprintf_func = vpnprintf;
        printf_func = pnprintf;
    } else {
        vprintf_func = Xvscnprintf;
        printf_func = Xscnprintf;
    }

    /* if type_str is not "", prepend it and ' ', to message */
    if (type_str[0] != '\0')
        len += printf_func(&buf[len], size - len, "%s ", type_str);

    if (hdr_format && size - len > 1)
        len += vprintf_func(&buf[len], size - len, hdr_format, hdr_args);

    if (msg_format && size - len > 1)
        len += vprintf_func(&buf[len], size - len, msg_format, msg_args);

    /* Force '\n' at end of truncated line */
    if (size - len == 1)
        buf[len - 1] = '\n';

    newline = (buf[len - 1] == '\n');
    LogSWrite(verb, buf, len, newline);
}


strlen_sigsafe(const char *s)
{
    size_t len;
    for (len = 0; s[len]; len++);
    return len;
}


VErrorFSigSafe(const char *f, va_list args)
{
    LogVMessageVerbSigSafe(X_ERROR, -1, f, args);
}


ErrorFSigSafe(const char *f, ...)
{
    va_list args;

    va_start(args, f);
    VErrorFSigSafe(f, args);
    va_end(args);
}


LogHdrMessageVerb(MessageType type, int verb, const char *msg_format,
                  va_list msg_args, const char *hdr_format, ...)
{
    va_list hdr_args;

    va_start(hdr_args, hdr_format);
    LogVHdrMessageVerb(type, verb, msg_format, msg_args, hdr_format, hdr_args);
    va_end(hdr_args);
}


LogClose(enum ExitCode error)
{
    if (logFile) {
        int msgtype = (error == EXIT_NO_ERROR) ? X_INFO : X_ERROR;
        LogMessageVerbSigSafe(msgtype, -1,
                "Server terminated %s (%d). Closing log file.\n",
                (error == EXIT_NO_ERROR) ? "successfully" : "with error",
                error);
        fclose(logFile);
        logFile = NULL;
        logFileFd = -1;
    }
}


LogVMessageVerb(MessageType type, int verb, const char *format, va_list args)
{
    const char *type_str;
    char buf[1024];
    const size_t size = sizeof(buf);
    Bool newline;
    size_t len = 0;

    if (inSignalContext) {
        LogVMessageVerbSigSafe(type, verb, format, args);
        return;
    }

    type_str = LogMessageTypeVerbString(type, verb);
    if (!type_str)
        return;

    /* if type_str is not "", prepend it and ' ', to message */
    if (type_str[0] != '\0')
        len += Xscnprintf(&buf[len], size - len, "%s ", type_str);

    if (size - len > 1)
        len += Xvscnprintf(&buf[len], size - len, format, args);

    /* Force '\n' at end of truncated line */
    if (size - len == 1)
        buf[len - 1] = '\n';

    newline = (buf[len - 1] == '\n');
    LogSWrite(verb, buf, len, newline);
}


FatalError(const char *f, ...)
{
    va_list args;
    va_list args2;
    static Bool beenhere = FALSE;

    if (beenhere)
        ErrorFSigSafe("\nFatalError re-entered, aborting\n");
    else
        ErrorFSigSafe("\nFatal server error:\n");

    va_start(args, f);

    /* Make a copy for OsVendorFatalError */
    va_copy(args2, args);

#ifdef __APPLE__
    {
        va_list apple_args;

        va_copy(apple_args, args);
        (void)vsnprintf(__crashreporter_info_buff__,
                        sizeof(__crashreporter_info_buff__), f, apple_args);
        va_end(apple_args);
    }
#endif
    VErrorFSigSafe(f, args);
    va_end(args);
    ErrorFSigSafe("\n");
    if (!beenhere)
        OsVendorFatalError(f, args2);
    va_end(args2);
    if (!beenhere) {
        beenhere = TRUE;
        AbortServer();
    }
    else
        OsAbort();
 /*NOTREACHED*/}


vpnprintf(char *string, int size_in, const char *f, va_list args)
{
    int f_idx = 0;
    int s_idx = 0;
    int f_len = strlen_sigsafe(f);
    char *string_arg;
    char number[21];
    int p_len;
    int i;
    uint64_t ui;
    int64_t si;
    size_t size = size_in;
    int precision;

    for (; f_idx < f_len && s_idx < size - 1; f_idx++) {
        int length_modifier = 0;
        if (f[f_idx] != '%') {
            string[s_idx++] = f[f_idx];
            continue;
        }

        f_idx++;

        /* silently swallow minimum field width */
        if (f[f_idx] == '*') {
            f_idx++;
            va_arg(args, int);
        } else {
            while (f_idx < f_len && ((f[f_idx] >= '0' && f[f_idx] <= '9')))
                f_idx++;
        }

        /* is there a precision? */
        precision = size;
        if (f[f_idx] == '.') {
            f_idx++;
            if (f[f_idx] == '*') {
                f_idx++;
                /* precision is supplied in an int argument */
                precision = va_arg(args, int);
            } else {
                /* silently swallow precision digits */
                while (f_idx < f_len && ((f[f_idx] >= '0' && f[f_idx] <= '9')))
                    f_idx++;
            }
        }

        /* non-digit length modifiers */
        if (f_idx < f_len) {
            int parsed_bytes = parse_length_modifier(&f[f_idx], f_len - f_idx, &length_modifier);
            if (parsed_bytes < 0)
                return 0;
            f_idx += parsed_bytes;
        }

        if (f_idx >= f_len)
            break;

        switch (f[f_idx]) {
        case 's':
            string_arg = va_arg(args, char*);

            for (i = 0; string_arg[i] != 0 && s_idx < size - 1 && s_idx < precision; i++)
                string[s_idx++] = string_arg[i];
            break;

        case 'u':
            if (length_modifier & LMOD_LONGLONG)
                ui = va_arg(args, unsigned long long);
            else if (length_modifier & LMOD_LONG)
                ui = va_arg(args, unsigned long);
            else if (length_modifier & LMOD_SIZET)
                ui = va_arg(args, size_t);
            else
                ui = va_arg(args, unsigned);

            FormatUInt64(ui, number);
            p_len = strlen_sigsafe(number);

            for (i = 0; i < p_len && s_idx < size - 1; i++)
                string[s_idx++] = number[i];
            break;
        case 'i':
        case 'd':
            if (length_modifier & LMOD_LONGLONG)
                si = va_arg(args, long long);
            else if (length_modifier & LMOD_LONG)
                si = va_arg(args, long);
            else if (length_modifier & LMOD_SIZET)
                si = va_arg(args, ssize_t);
            else
                si = va_arg(args, int);

            FormatInt64(si, number);
            p_len = strlen_sigsafe(number);

            for (i = 0; i < p_len && s_idx < size - 1; i++)
                string[s_idx++] = number[i];
            break;

        case 'p':
            string[s_idx++] = '0';
            if (s_idx < size - 1)
                string[s_idx++] = 'x';
            ui = (uintptr_t)va_arg(args, void*);
            FormatUInt64Hex(ui, number);
            p_len = strlen_sigsafe(number);

            for (i = 0; i < p_len && s_idx < size - 1; i++)
                string[s_idx++] = number[i];
            break;

        case 'x':
            if (length_modifier & LMOD_LONGLONG)
                ui = va_arg(args, unsigned long long);
            else if (length_modifier & LMOD_LONG)
                ui = va_arg(args, unsigned long);
            else if (length_modifier & LMOD_SIZET)
                ui = va_arg(args, size_t);
            else
                ui = va_arg(args, unsigned);

            FormatUInt64Hex(ui, number);
            p_len = strlen_sigsafe(number);

            for (i = 0; i < p_len && s_idx < size - 1; i++)
                string[s_idx++] = number[i];
            break;
        case 'f':
            {
                double d = va_arg(args, double);
                FormatDouble(d, number);
                p_len = strlen_sigsafe(number);

                for (i = 0; i < p_len && s_idx < size - 1; i++)
                    string[s_idx++] = number[i];
            }
            break;
        case 'c':
            {
                char c = va_arg(args, int);
                if (s_idx < size - 1)
                    string[s_idx++] = c;
            }
            break;
        case '%':
            string[s_idx++] = '%';
            break;
        default:
            BUG_WARN_MSG(f[f_idx], "Unsupported printf directive '%c'\n", f[f_idx]);
            va_arg(args, char*);
            string[s_idx++] = '%';
            if (s_idx < size - 1)
                string[s_idx++] = f[f_idx];
            break;
        }
    }

    string[s_idx] = '\0';

    return s_idx;
}


LogMessageVerbSigSafe(MessageType type, int verb, const char *format, ...)
{
    va_list ap;
    va_start(ap, format);
    LogVMessageVerbSigSafe(type, verb, format, ap);
    va_end(ap);
}


AuditF(const char *f, ...)
{
    va_list args;

    va_start(args, f);

    VAuditF(f, args);
    va_end(args);
}


LogInit(const char *fname, const char *backup)
{
    char *logFileName = NULL;

    if (fname && *fname) {
        if (displayfd != -1) {
            /* Display isn't set yet, so we can't use it in filenames yet. */
            char pidstring[32];
            snprintf(pidstring, sizeof(pidstring), "pid-%ld",
                     (unsigned long) getpid());
            logFileName = LogFilePrep(fname, backup, pidstring);
            saved_log_tempname = logFileName;

            /* Save the patterns for use when the display is named. */
            saved_log_fname = strdup(fname);
            if (backup == NULL)
                saved_log_backup = NULL;
            else
                saved_log_backup = strdup(backup);
        } else
            logFileName = LogFilePrep(fname, backup, display);
        if ((logFile = fopen(logFileName, "w")) == NULL)
            FatalError("Cannot open log file \"%s\"\n", logFileName);
        setvbuf(logFile, NULL, _IONBF, 0);

        logFileFd = fileno(logFile);

        /* Flush saved log information. */
        if (saveBuffer && bufferSize > 0) {
            fwrite(saveBuffer, bufferPos, 1, logFile);
            fflush(logFile);
#ifndef WIN32
            fsync(fileno(logFile));
#endif
        }
    }

    /*
     * Unconditionally free the buffer, and flag that the buffer is no longer
     * needed.
     */
    if (saveBuffer && bufferSize > 0) {
        free(saveBuffer);
        saveBuffer = NULL;
        bufferSize = 0;
    }
    needBuffer = FALSE;

    return logFileName;
}


pnprintf(char *string, int size, const char *f, ...)
{
    int rc;
    va_list args;

    va_start(args, f);
    rc = vpnprintf(string, size, f, args);
    va_end(args);

    return rc;
}


LogVWrite(int verb, const char *f, va_list args)
{
    return LogVMessageVerb(X_NONE, verb, f, args);
}


LogMessageTypeVerbString(MessageType type, int verb)
{
    if (type == X_ERROR)
        verb = 0;

    if (logVerbosity < verb && logFileVerbosity < verb)
        return NULL;

    switch (type) {
    case X_PROBED:
        return X_PROBE_STRING;
    case X_CONFIG:
        return X_CONFIG_STRING;
    case X_DEFAULT:
        return X_DEFAULT_STRING;
    case X_CMDLINE:
        return X_CMDLINE_STRING;
    case X_NOTICE:
        return X_NOTICE_STRING;
    case X_ERROR:
        return X_ERROR_STRING;
    case X_WARNING:
        return X_WARNING_STRING;
    case X_INFO:
        return X_INFO_STRING;
    case X_NOT_IMPLEMENTED:
        return X_NOT_IMPLEMENTED_STRING;
    case X_UNKNOWN:
        return X_UNKNOWN_STRING;
    case X_NONE:
        return X_NONE_STRING;
    case X_DEBUG:
        return X_DEBUG_STRING;
    default:
        return X_UNKNOWN_STRING;
    }
}


LogHdrMessage(MessageType type, const char *msg_format, va_list msg_args,
              const char *hdr_format, ...)
{
    va_list hdr_args;

    va_start(hdr_args, hdr_format);
    LogVHdrMessageVerb(type, 1, msg_format, msg_args, hdr_format, hdr_args);
    va_end(hdr_args);
}


static int parse_length_modifier(const char *format, size_t len, int *flags_return)
{
    int idx = 0;
    int length_modifier = 0;

    while (idx < len) {
        switch (format[idx]) {
            case 'l':
                BUG_RETURN_VAL(length_modifier & LMOD_SHORT, 0);

                if (length_modifier & LMOD_LONG)
                    length_modifier |= LMOD_LONGLONG;
                else
                    length_modifier |= LMOD_LONG;
                break;
            case 'h':
                BUG_RETURN_VAL(length_modifier & (LMOD_LONG|LMOD_LONGLONG), 0);
                length_modifier |= LMOD_SHORT;
                /* gcc says 'short int' is promoted to 'int' when
                 * passed through '...', so ignored during
                 * processing */
                break;
            case 'z':
                length_modifier |= LMOD_SIZET;
                break;
            default:
                goto out;
        }
        idx++;
    }

out:
    *flags_return = length_modifier;
    return idx;
}


VAuditF(const char *f, va_list args)
{
    char *prefix;
    char buf[1024];
    int len;
    static char oldbuf[1024];

    prefix = AuditPrefix();
    len = vsnprintf(buf, sizeof(buf), f, args);

    if (len == oldlen && strcmp(buf, oldbuf) == 0) {
        /* Message already seen */
        nrepeat++;
    }
    else {
        /* new message */
        if (auditTimer != NULL)
            TimerForce(auditTimer);
        ErrorF("%s%s", prefix != NULL ? prefix : "", buf);
        strlcpy(oldbuf, buf, sizeof(oldbuf));
        oldlen = len;
        nrepeat = 0;
        auditTimer = TimerSet(auditTimer, 0, AUDIT_TIMEOUT, AuditFlush, NULL);
    }
    free(prefix);
}


LogVMessageVerbSigSafe(MessageType type, int verb, const char *format, va_list args)
{
    const char *type_str;
    char buf[1024];
    int len;
    Bool newline;

    type_str = LogMessageTypeVerbString(type, verb);
    if (!type_str)
        return;

    /* if type_str is not "", prepend it and ' ', to message */
    if (type_str[0] != '\0') {
        LogSWrite(verb, type_str, strlen_sigsafe(type_str), FALSE);
        LogSWrite(verb, " ", 1, FALSE);
    }

    len = vpnprintf(buf, sizeof(buf), format, args);

    /* Force '\n' at end of truncated line */
    if (sizeof(buf) - len == 1)
        buf[len - 1] = '\n';

    newline = (len > 0 && buf[len - 1] == '\n');
    LogSWrite(verb, buf, len, newline);
}


LogSetDisplay(void)
{
    if (saved_log_fname && strstr(saved_log_fname, "%s")) {
        char *logFileName;

        logFileName = LogFilePrep(saved_log_fname, saved_log_backup, display);

        if (rename(saved_log_tempname, logFileName) == 0) {
            LogMessageVerb(X_PROBED, 0,
                           "Log file renamed from \"%s\" to \"%s\"\n",
                           saved_log_tempname, logFileName);

            if (strlen(saved_log_tempname) >= strlen(logFileName))
                strncpy(saved_log_tempname, logFileName,
                        strlen(saved_log_tempname));
        }
        else {
            ErrorF("Failed to rename log file \"%s\" to \"%s\": %s\n",
                   saved_log_tempname, logFileName, strerror(errno));
        }

        /* free newly allocated string - can't free old one since existing
           pointers to it may exist in DDX callers. */
        free(logFileName);
        free(saved_log_fname);
        free(saved_log_backup);
    }
}


SProcXkbGetControls(ClientPtr client)
{
    REQUEST(xkbGetControlsReq);

    swaps(&stuff->length);
    REQUEST_SIZE_MATCH(xkbGetControlsReq);
    swaps(&stuff->deviceSpec);
    return ProcXkbGetControls(client);
}

Fix XkbSelectEvents() integer underflow

CVE-2020-14361 ZDI-CAN 11573

This vulnerability was discovered by:
Jan-Niklas Sohn working with Trend Micro Zero Day Initiative

Signed-off-by: Matthieu Herrb <matthieu@herrb.eu>


SProcXkbGetIndicatorMap(ClientPtr client)
{
    REQUEST(xkbGetIndicatorMapReq);

    swaps(&stuff->length);
    REQUEST_SIZE_MATCH(xkbGetIndicatorMapReq);
    swaps(&stuff->deviceSpec);
    swapl(&stuff->which);
    return ProcXkbGetIndicatorMap(client);
}


SProcXkbGetNamedIndicator(ClientPtr client)
{
    REQUEST(xkbGetNamedIndicatorReq);

    swaps(&stuff->length);
    REQUEST_SIZE_MATCH(xkbGetNamedIndicatorReq);
    swaps(&stuff->deviceSpec);
    swaps(&stuff->ledClass);
    swaps(&stuff->ledID);
    swapl(&stuff->indicator);
    return ProcXkbGetNamedIndicator(client);
}


SProcXkbGetNames(ClientPtr client)
{
    REQUEST(xkbGetNamesReq);

    swaps(&stuff->length);
    REQUEST_SIZE_MATCH(xkbGetNamesReq);
    swaps(&stuff->deviceSpec);
    swapl(&stuff->which);
    return ProcXkbGetNames(client);
}


SProcXkbSetControls(ClientPtr client)
{
    REQUEST(xkbSetControlsReq);

    swaps(&stuff->length);
    REQUEST_SIZE_MATCH(xkbSetControlsReq);
    swaps(&stuff->deviceSpec);
    swaps(&stuff->affectInternalVMods);
    swaps(&stuff->internalVMods);
    swaps(&stuff->affectIgnoreLockVMods);
    swaps(&stuff->ignoreLockVMods);
    swaps(&stuff->axOptions);
    swapl(&stuff->affectEnabledCtrls);
    swapl(&stuff->enabledCtrls);
    swapl(&stuff->changeCtrls);
    swaps(&stuff->repeatDelay);
    swaps(&stuff->repeatInterval);
    swaps(&stuff->slowKeysDelay);
    swaps(&stuff->debounceDelay);
    swaps(&stuff->mkDelay);
    swaps(&stuff->mkInterval);
    swaps(&stuff->mkTimeToMax);
    swaps(&stuff->mkMaxSpeed);
    swaps(&stuff->mkCurve);
    swaps(&stuff->axTimeout);
    swapl(&stuff->axtCtrlsMask);
    swapl(&stuff->axtCtrlsValues);
    swaps(&stuff->axtOptsMask);
    swaps(&stuff->axtOptsValues);
    return ProcXkbSetControls(client);
}


SProcXkbSetDeviceInfo(ClientPtr client)
{
    REQUEST(xkbSetDeviceInfoReq);

    swaps(&stuff->length);
    REQUEST_AT_LEAST_SIZE(xkbSetDeviceInfoReq);
    swaps(&stuff->deviceSpec);
    swaps(&stuff->change);
    swaps(&stuff->nDeviceLedFBs);
    return ProcXkbSetDeviceInfo(client);
}


SProcXkbSetCompatMap(ClientPtr client)
{
    REQUEST(xkbSetCompatMapReq);

    swaps(&stuff->length);
    REQUEST_AT_LEAST_SIZE(xkbSetCompatMapReq);
    swaps(&stuff->deviceSpec);
    swaps(&stuff->firstSI);
    swaps(&stuff->nSI);
    return ProcXkbSetCompatMap(client);
}


SProcXkbSetDebuggingFlags(ClientPtr client)
{
    REQUEST(xkbSetDebuggingFlagsReq);

    swaps(&stuff->length);
    REQUEST_AT_LEAST_SIZE(xkbSetDebuggingFlagsReq);
    swapl(&stuff->affectFlags);
    swapl(&stuff->flags);
    swapl(&stuff->affectCtrls);
    swapl(&stuff->ctrls);
    swaps(&stuff->msgLength);
    return ProcXkbSetDebuggingFlags(client);
}


SProcXkbListComponents(ClientPtr client)
{
    REQUEST(xkbListComponentsReq);

    swaps(&stuff->length);
    REQUEST_AT_LEAST_SIZE(xkbListComponentsReq);
    swaps(&stuff->deviceSpec);
    swaps(&stuff->maxNames);
    return ProcXkbListComponents(client);
}


SProcXkbSetGeometry(ClientPtr client)
{
    REQUEST(xkbSetGeometryReq);

    swaps(&stuff->length);
    REQUEST_AT_LEAST_SIZE(xkbSetGeometryReq);
    swaps(&stuff->deviceSpec);
    swapl(&stuff->name);
    swaps(&stuff->widthMM);
    swaps(&stuff->heightMM);
    swaps(&stuff->nProperties);
    swaps(&stuff->nColors);
    swaps(&stuff->nDoodads);
    swaps(&stuff->nKeyAliases);
    return ProcXkbSetGeometry(client);
}


SProcXkbBell(ClientPtr client)
{
    REQUEST(xkbBellReq);

    swaps(&stuff->length);
    REQUEST_SIZE_MATCH(xkbBellReq);
    swaps(&stuff->deviceSpec);
    swaps(&stuff->bellClass);
    swaps(&stuff->bellID);
    swapl(&stuff->name);
    swapl(&stuff->window);
    swaps(&stuff->pitch);
    swaps(&stuff->duration);
    return ProcXkbBell(client);
}


SProcXkbGetIndicatorState(ClientPtr client)
{
    REQUEST(xkbGetIndicatorStateReq);

    swaps(&stuff->length);
    REQUEST_SIZE_MATCH(xkbGetIndicatorStateReq);
    swaps(&stuff->deviceSpec);
    return ProcXkbGetIndicatorState(client);
}


SProcXkbUseExtension(ClientPtr client)
{
    REQUEST(xkbUseExtensionReq);

    swaps(&stuff->length);
    REQUEST_SIZE_MATCH(xkbUseExtensionReq);
    swaps(&stuff->wantedMajor);
    swaps(&stuff->wantedMinor);
    return ProcXkbUseExtension(client);
}


SProcXkbLatchLockState(ClientPtr client)
{
    REQUEST(xkbLatchLockStateReq);

    swaps(&stuff->length);
    REQUEST_SIZE_MATCH(xkbLatchLockStateReq);
    swaps(&stuff->deviceSpec);
    swaps(&stuff->groupLatch);
    return ProcXkbLatchLockState(client);
}


SProcXkbSetNamedIndicator(ClientPtr client)
{
    REQUEST(xkbSetNamedIndicatorReq);

    swaps(&stuff->length);
    REQUEST_SIZE_MATCH(xkbSetNamedIndicatorReq);
    swaps(&stuff->deviceSpec);
    swaps(&stuff->ledClass);
    swaps(&stuff->ledID);
    swapl(&stuff->indicator);
    swaps(&stuff->virtualMods);
    swapl(&stuff->ctrls);
    return ProcXkbSetNamedIndicator(client);
}


SProcXkbGetDeviceInfo(ClientPtr client)
{
    REQUEST(xkbGetDeviceInfoReq);

    swaps(&stuff->length);
    REQUEST_SIZE_MATCH(xkbGetDeviceInfoReq);
    swaps(&stuff->deviceSpec);
    swaps(&stuff->wanted);
    swaps(&stuff->ledClass);
    swaps(&stuff->ledID);
    return ProcXkbGetDeviceInfo(client);
}


SProcXkbGetState(ClientPtr client)
{
    REQUEST(xkbGetStateReq);

    swaps(&stuff->length);
    REQUEST_SIZE_MATCH(xkbGetStateReq);
    swaps(&stuff->deviceSpec);
    return ProcXkbGetState(client);
}


SProcXkbSetNames(ClientPtr client)
{
    REQUEST(xkbSetNamesReq);

    swaps(&stuff->length);
    REQUEST_AT_LEAST_SIZE(xkbSetNamesReq);
    swaps(&stuff->deviceSpec);
    swaps(&stuff->virtualMods);
    swapl(&stuff->which);
    swapl(&stuff->indicators);
    swaps(&stuff->totalKTLevelNames);
    return ProcXkbSetNames(client);
}


SProcXkbSelectEvents(ClientPtr client)
{
    REQUEST(xkbSelectEventsReq);

    swaps(&stuff->length);
    REQUEST_AT_LEAST_SIZE(xkbSelectEventsReq);
    swaps(&stuff->deviceSpec);
    swaps(&stuff->affectWhich);
    swaps(&stuff->clear);
    swaps(&stuff->selectAll);
    swaps(&stuff->affectMap);
    swaps(&stuff->map);
    if ((stuff->affectWhich & (~XkbMapNotifyMask)) != 0) {
        union {
            BOOL *b;
            CARD8 *c8;
            CARD16 *c16;
            CARD32 *c32;
        } from;
        register unsigned bit, ndx, maskLeft, dataLeft, size;

        from.c8 = (CARD8 *) &stuff[1];
        dataLeft = (client->req_len * 4) - SIZEOF(xkbSelectEventsReq);
        maskLeft = (stuff->affectWhich & (~XkbMapNotifyMask));
        for (ndx = 0, bit = 1; (maskLeft != 0); ndx++, bit <<= 1) {
            if (((bit & maskLeft) == 0) || (ndx == XkbMapNotify))
                continue;
            maskLeft &= ~bit;
            if ((stuff->selectAll & bit) || (stuff->clear & bit))
                continue;
            switch (ndx) {
            case XkbNewKeyboardNotify:
            case XkbStateNotify:
            case XkbNamesNotify:
            case XkbAccessXNotify:
            case XkbExtensionDeviceNotify:
                size = 2;
                break;
            case XkbControlsNotify:
            case XkbIndicatorStateNotify:
            case XkbIndicatorMapNotify:
                size = 4;
                break;
            case XkbBellNotify:
            case XkbActionMessage:
            case XkbCompatMapNotify:
                size = 1;
                break;
            default:
                client->errorValue = _XkbErrCode2(0x1, bit);
                return BadValue;
            }
            if (dataLeft < (size * 2))
                return BadLength;
            if (size == 2) {
                swaps(&from.c16[0]);
                swaps(&from.c16[1]);
            }
            else if (size == 4) {
                swapl(&from.c32[0]);
                swapl(&from.c32[1]);
            }
            else {
                size = 2;
            }
            from.c8 += (size * 2);
            dataLeft -= (size * 2);
        }
        if (dataLeft > 2) {
            ErrorF("[xkb] Extra data (%d bytes) after SelectEvents\n",
                   dataLeft);
            return BadLength;
        }
    }
    return ProcXkbSelectEvents(client);
}


SProcXkbGetGeometry(ClientPtr client)
{
    REQUEST(xkbGetGeometryReq);

    swaps(&stuff->length);
    REQUEST_SIZE_MATCH(xkbGetGeometryReq);
    swaps(&stuff->deviceSpec);
    swapl(&stuff->name);
    return ProcXkbGetGeometry(client);
}


SProcXkbSetMap(ClientPtr client)
{
    REQUEST(xkbSetMapReq);

    swaps(&stuff->length);
    REQUEST_AT_LEAST_SIZE(xkbSetMapReq);
    swaps(&stuff->deviceSpec);
    swaps(&stuff->present);
    swaps(&stuff->flags);
    swaps(&stuff->totalSyms);
    swaps(&stuff->totalActs);
    swaps(&stuff->virtualMods);
    return ProcXkbSetMap(client);
}


SProcXkbGetCompatMap(ClientPtr client)
{
    REQUEST(xkbGetCompatMapReq);

    swaps(&stuff->length);
    REQUEST_SIZE_MATCH(xkbGetCompatMapReq);
    swaps(&stuff->deviceSpec);
    swaps(&stuff->firstSI);
    swaps(&stuff->nSI);
    return ProcXkbGetCompatMap(client);
}


SProcXkbSetIndicatorMap(ClientPtr client)
{
    REQUEST(xkbSetIndicatorMapReq);

    swaps(&stuff->length);
    REQUEST_AT_LEAST_SIZE(xkbSetIndicatorMapReq);
    swaps(&stuff->deviceSpec);
    swapl(&stuff->which);
    return ProcXkbSetIndicatorMap(client);
}


SProcXkbGetKbdByName(ClientPtr client)
{
    REQUEST(xkbGetKbdByNameReq);

    swaps(&stuff->length);
    REQUEST_AT_LEAST_SIZE(xkbGetKbdByNameReq);
    swaps(&stuff->deviceSpec);
    swaps(&stuff->want);
    swaps(&stuff->need);
    return ProcXkbGetKbdByName(client);
}


SProcXkbDispatch(ClientPtr client)
{
    REQUEST(xReq);
    switch (stuff->data) {
    case X_kbUseExtension:
        return SProcXkbUseExtension(client);
    case X_kbSelectEvents:
        return SProcXkbSelectEvents(client);
    case X_kbBell:
        return SProcXkbBell(client);
    case X_kbGetState:
        return SProcXkbGetState(client);
    case X_kbLatchLockState:
        return SProcXkbLatchLockState(client);
    case X_kbGetControls:
        return SProcXkbGetControls(client);
    case X_kbSetControls:
        return SProcXkbSetControls(client);
    case X_kbGetMap:
        return SProcXkbGetMap(client);
    case X_kbSetMap:
        return SProcXkbSetMap(client);
    case X_kbGetCompatMap:
        return SProcXkbGetCompatMap(client);
    case X_kbSetCompatMap:
        return SProcXkbSetCompatMap(client);
    case X_kbGetIndicatorState:
        return SProcXkbGetIndicatorState(client);
    case X_kbGetIndicatorMap:
        return SProcXkbGetIndicatorMap(client);
    case X_kbSetIndicatorMap:
        return SProcXkbSetIndicatorMap(client);
    case X_kbGetNamedIndicator:
        return SProcXkbGetNamedIndicator(client);
    case X_kbSetNamedIndicator:
        return SProcXkbSetNamedIndicator(client);
    case X_kbGetNames:
        return SProcXkbGetNames(client);
    case X_kbSetNames:
        return SProcXkbSetNames(client);
    case X_kbGetGeometry:
        return SProcXkbGetGeometry(client);
    case X_kbSetGeometry:
        return SProcXkbSetGeometry(client);
    case X_kbPerClientFlags:
        return SProcXkbPerClientFlags(client);
    case X_kbListComponents:
        return SProcXkbListComponents(client);
    case X_kbGetKbdByName:
        return SProcXkbGetKbdByName(client);
    case X_kbGetDeviceInfo:
        return SProcXkbGetDeviceInfo(client);
    case X_kbSetDeviceInfo:
        return SProcXkbSetDeviceInfo(client);
    case X_kbSetDebuggingFlags:
        return SProcXkbSetDebuggingFlags(client);
    default:
        return BadRequest;
    }
}


SProcXkbGetMap(ClientPtr client)
{
    REQUEST(xkbGetMapReq);

    swaps(&stuff->length);
    REQUEST_SIZE_MATCH(xkbGetMapReq);
    swaps(&stuff->deviceSpec);
    swaps(&stuff->full);
    swaps(&stuff->partial);
    swaps(&stuff->virtualMods);
    return ProcXkbGetMap(client);
}


SProcXkbPerClientFlags(ClientPtr client)
{
    REQUEST(xkbPerClientFlagsReq);

    swaps(&stuff->length);
    REQUEST_SIZE_MATCH(xkbPerClientFlagsReq);
    swaps(&stuff->deviceSpec);
    swapl(&stuff->change);
    swapl(&stuff->value);
    swapl(&stuff->ctrlsToChange);
    swapl(&stuff->autoCtrls);
    swapl(&stuff->autoCtrlValues);
    return ProcXkbPerClientFlags(client);
}


e1000_send_packet(E1000State *s, const uint8_t *buf, int size)
{
    static const int PTCregs[6] = { PTC64, PTC127, PTC255, PTC511,
                                    PTC1023, PTC1522 };

    NetClientState *nc = qemu_get_queue(s->nic);
    if (s->phy_reg[PHY_CTRL] & MII_CR_LOOPBACK) {
        qemu_receive_packet(nc, buf, size);
    } else {
        qemu_send_packet(nc, buf, size);
    }
    inc_tx_bcast_or_mcast_count(s, buf);
    e1000x_increase_size_stats(s->mac_reg, PTCregs, size);
}

e1000: switch to use qemu_receive_packet() for loopback

This patch switches to use qemu_receive_packet() which can detect
reentrancy and return early.

This is intended to address CVE-2021-3416.

Cc: Prasad J Pandit <ppandit@redhat.com>
Cc: qemu-stable@nongnu.org
Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Signed-off-by: Jason Wang <jasowang@redhat.com>


void memslot_info_reset(RedMemSlotInfo *info)
{
        uint32_t i;
        for (i = 0; i < info->num_memslots_groups; ++i) {
            memset(info->mem_slots[i], 0, sizeof(MemSlot) * info->num_memslots);
        }
}

memslot: Fix off-by-one error in group/slot boundary check

RedMemSlotInfo keeps an array of groups, and each group contains an
array of slots. Unfortunately, these checks are off by 1, they check
that the index is greater or equal to the number of elements in the
array, while these arrays are 0 based. The check should only check for
strictly greater than the number of elements.

For the group array, this is not a big issue, as these memslot groups
are created by spice-server users (eg QEMU), and the group ids used to
index that array are also generated by the spice-server user, so it
should not be possible for the guest to set them to arbitrary values.

The slot id is more problematic, as it's calculated from a QXLPHYSICAL
address, and such addresses are usually set by the guest QXL driver, so
the guest can set these to arbitrary values, including malicious values,
which are probably easy to build from the guest PCI configuration.

This patch fixes the arrays bound check, and adds a test case for this.
This fixes CVE-2019-3813.

Signed-off-by: Christophe Fergeau <cfergeau@redhat.com>
Acked-by: Frediano Ziglio <fziglio@redhat.com>


static void test_circular_small_chunks(void)
{
    RedMemSlotInfo mem_info;
    RedCursorCmd *red_cursor_cmd;
    QXLCursorCmd cursor_cmd;
    QXLCursor *cursor;
    QXLDataChunk *chunks[2];

    init_meminfo(&mem_info);
    g_test_expect_message(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
                          "*red_get_data_chunks_ptr: data split in too many chunks, avoiding DoS*");

    /* a circular list of small chunks should not be a problems */
    memset(&cursor_cmd, 0, sizeof(cursor_cmd));
    cursor_cmd.type = QXL_CURSOR_SET;

    cursor = create_chunk(SPICE_OFFSETOF(QXLCursor, chunk), 1, NULL, 0xaa);
    cursor->header.unique = 1;
    cursor->header.width = 128;
    cursor->header.height = 128;
    cursor->data_size = 128 * 128 * 4;

    chunks[0] = create_chunk(0, 1, &cursor->chunk, 0xaa);
    chunks[0]->next_chunk = to_physical(&cursor->chunk);

    cursor_cmd.u.set.shape = to_physical(cursor);

    red_cursor_cmd = red_cursor_cmd_new(NULL, &mem_info, 0, to_physical(&cursor_cmd));
    if (red_cursor_cmd != NULL) {
        /* function does not return errors so there should be no data */
        g_assert_cmpuint(red_cursor_cmd->type, ==, QXL_CURSOR_SET);
        g_assert_cmpuint(red_cursor_cmd->u.set.position.x, ==, 0);
        g_assert_cmpuint(red_cursor_cmd->u.set.position.y, ==, 0);
        g_assert_cmpuint(red_cursor_cmd->u.set.shape.data_size, ==, 0);
        red_cursor_cmd_unref(red_cursor_cmd);
    }
    g_test_assert_expected_messages();

    g_free(cursor);
    g_free(chunks[0]);
    memslot_info_destroy(&mem_info);
}


static void deinit_qxl_surface(QXLSurfaceCmd *qxl)
{
    g_free(from_physical(qxl->u.surface_create.data));
}


void memslot_info_del_slot(RedMemSlotInfo *info, uint32_t slot_group_id, uint32_t slot_id)
{
    spice_return_if_fail(info->num_memslots_groups > slot_group_id);
    spice_return_if_fail(info->num_memslots > slot_id);

    info->mem_slots[slot_group_id][slot_id].virt_start_addr = 0;
    info->mem_slots[slot_group_id][slot_id].virt_end_addr = 0;
}


from_physical(QXLPHYSICAL physical)
{
    return (void *)(uintptr_t) physical;
}


void memslot_info_destroy(RedMemSlotInfo *info)
{
    uint32_t i;

    for (i = 0; i < info->num_memslots_groups; ++i) {
        g_free(info->mem_slots[i]);
    }
    g_free(info->mem_slots);
}


static void test_memslot_invalid_slot_id(void)
{
    RedMemSlotInfo mem_info;
    init_meminfo(&mem_info);

    memslot_get_virt(&mem_info, 1 << mem_info.memslot_id_shift, 16, 0);
}


static void test_memslot_invalid_group_id(void)
{
    RedMemSlotInfo mem_info;
    init_meminfo(&mem_info);

    memslot_get_virt(&mem_info, 0, 16, 1);
}


static unsigned long __get_clean_virt(RedMemSlotInfo *info, QXLPHYSICAL addr)
{
    return addr & info->memslot_clean_virt_mask;
}


void memslot_info_init(RedMemSlotInfo *info,
                       uint32_t num_groups, uint32_t num_slots,
                       uint8_t generation_bits,
                       uint8_t id_bits,
                       uint8_t internal_groupslot_id)
{
    uint32_t i;

    spice_assert(num_slots > 0);
    spice_assert(num_groups > 0);

    info->num_memslots_groups = num_groups;
    info->num_memslots = num_slots;
    info->generation_bits = generation_bits;
    info->mem_slot_bits = id_bits;
    info->internal_groupslot_id = internal_groupslot_id;

    info->mem_slots = g_new(MemSlot *, num_groups);

    for (i = 0; i < num_groups; ++i) {
        info->mem_slots[i] = g_new0(MemSlot, num_slots);
    }

    /* TODO: use QXLPHYSICAL_BITS */
    info->memslot_id_shift = 64 - info->mem_slot_bits;
    info->memslot_gen_shift = 64 - (info->mem_slot_bits + info->generation_bits);
    info->memslot_gen_mask = ~((QXLPHYSICAL)-1 << info->generation_bits);
    info->memslot_clean_virt_mask = (((QXLPHYSICAL)(-1)) >>
                                       (info->mem_slot_bits + info->generation_bits));
}


create_chunk(size_t prefix, uint32_t size, QXLDataChunk* prev, int fill)
{
    uint8_t *ptr = g_malloc0(prefix + sizeof(QXLDataChunk) + size);
    QXLDataChunk *qxl = (QXLDataChunk *) (ptr + prefix);
    memset(&qxl->data[0], fill, size);
    qxl->data_size = size;
    qxl->prev_chunk = to_physical(prev);
    if (prev)
        prev->next_chunk = to_physical(qxl);
    return ptr;
}


static void test_circular_empty_chunks(void)
{
    RedMemSlotInfo mem_info;
    RedCursorCmd *red_cursor_cmd;
    QXLCursorCmd cursor_cmd;
    QXLCursor *cursor;
    QXLDataChunk *chunks[2];

    init_meminfo(&mem_info);
    g_test_expect_message(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
                          "*red_get_data_chunks_ptr: data split in too many chunks, avoiding DoS*");

    /* a circular list of empty chunks should not be a problems */
    memset(&cursor_cmd, 0, sizeof(cursor_cmd));
    cursor_cmd.type = QXL_CURSOR_SET;

    cursor = create_chunk(SPICE_OFFSETOF(QXLCursor, chunk), 0, NULL, 0xaa);
    cursor->header.unique = 1;
    cursor->header.width = 128;
    cursor->header.height = 128;
    cursor->data_size = 128 * 128 * 4;

    chunks[0] = create_chunk(0, 0, &cursor->chunk, 0xaa);
    chunks[0]->next_chunk = to_physical(&cursor->chunk);

    cursor_cmd.u.set.shape = to_physical(cursor);

    red_cursor_cmd = red_cursor_cmd_new(NULL, &mem_info, 0, to_physical(&cursor_cmd));
    if (red_cursor_cmd != NULL) {
        /* function does not return errors so there should be no data */
        g_assert_cmpuint(red_cursor_cmd->type, ==, QXL_CURSOR_SET);
        g_assert_cmpuint(red_cursor_cmd->u.set.position.x, ==, 0);
        g_assert_cmpuint(red_cursor_cmd->u.set.position.y, ==, 0);
        g_assert_cmpuint(red_cursor_cmd->u.set.shape.data_size, ==, 0);
        red_cursor_cmd_unref(red_cursor_cmd);
    }
    g_test_assert_expected_messages();

    g_free(cursor);
    g_free(chunks[0]);
    memslot_info_destroy(&mem_info);
}


static void test_no_issues(void)
{
    RedMemSlotInfo mem_info;
    RedSurfaceCmd *cmd;
    QXLSurfaceCmd qxl;

    init_meminfo(&mem_info);
    init_qxl_surface(&qxl);

    /* try to create a surface with no issues, should succeed */
    cmd = red_surface_cmd_new(NULL, &mem_info, 0, to_physical(&qxl));
    g_assert_nonnull(cmd);
    red_surface_cmd_unref(cmd);

    deinit_qxl_surface(&qxl);
    memslot_info_destroy(&mem_info);
}


to_physical(const void *ptr)
{
    return (uintptr_t) ptr;
}


int main(int argc, char *argv[])
{
    g_test_init(&argc, &argv, NULL);

    /* try to use invalid memslot group/slot */
    g_test_add_func("/server/memslot-invalid-addresses", test_memslot_invalid_addresses);
    g_test_add_func("/server/memslot-invalid-addresses/subprocess/group_id", test_memslot_invalid_group_id);
    g_test_add_func("/server/memslot-invalid-addresses/subprocess/slot_id", test_memslot_invalid_slot_id);

    /* try to create a surface with no issues, should succeed */
    g_test_add_func("/server/qxl-parsing-no-issues", test_no_issues);

    /* try to create a surface with a stride too small to fit
     * the entire width.
     * This can be used to cause buffer overflows so refuse it.
     */
    g_test_add_func("/server/qxl-parsing/stride-too-small", test_stride_too_small);

    /* try to create a surface quite large.
     * The sizes (width and height) were chosen so the multiplication
     * using 32 bit values gives a very small value.
     * These kind of values should be refused as they will cause
     * overflows. Also the total memory for the card is not enough to
     * hold the surface so surely can't be accepted.
     */
    g_test_add_func("/server/qxl-parsing/too-big-image", test_too_big_image);

    /* test base cursor with no problems */
    g_test_add_func("/server/qxl-parsing/base-cursor-command", test_cursor_command);

    /* a circular list of empty chunks should not be a problems */
    g_test_add_func("/server/qxl-parsing/circular-empty-chunks", test_circular_empty_chunks);

    /* a circular list of small chunks should not be a problems */
    g_test_add_func("/server/qxl-parsing/circular-small-chunks", test_circular_small_chunks);

    return g_test_run();
}


static void test_memslot_invalid_addresses(void)
{
    g_test_trap_subprocess("/server/memslot-invalid-addresses/subprocess/group_id", 0, 0);
    g_test_trap_assert_stderr("*group_id too big*");

    g_test_trap_subprocess("/server/memslot-invalid-addresses/subprocess/slot_id", 0, 0);
    g_test_trap_assert_stderr("*slot_id 1 too big*");
}


int memslot_validate_virt(RedMemSlotInfo *info, unsigned long virt, int slot_id,
                          uint32_t add_size, uint32_t group_id)
{
    MemSlot *slot;

    slot = &info->mem_slots[group_id][slot_id];
    if ((virt + add_size) < virt) {
        spice_critical("virtual address overlap");
        return 0;
    }

    if (virt < slot->virt_start_addr || (virt + add_size) > slot->virt_end_addr) {
        print_memslots(info);
        spice_warning("virtual address out of range"
              "    virt=0x%lx+0x%x slot_id=%d group_id=%d\n"
              "    slot=0x%lx-0x%lx delta=0x%lx",
              virt, add_size, slot_id, group_id,
              slot->virt_start_addr, slot->virt_end_addr, slot->address_delta);
        return 0;
    }
    return 1;
}


unsigned long memslot_max_size_virt(RedMemSlotInfo *info,
                                    unsigned long virt, int slot_id,
                                    uint32_t group_id)
{
    MemSlot *slot;

    slot = &info->mem_slots[group_id][slot_id];

    if (virt < slot->virt_start_addr || virt > slot->virt_end_addr) {
        return 0;
    }
    return slot->virt_end_addr - virt;
}


static void print_memslots(RedMemSlotInfo *info)
{
    int i;
    int x;

    for (i = 0; i < info->num_memslots_groups; ++i) {
        for (x = 0; x < info->num_memslots; ++x) {
            if (!info->mem_slots[i][x].virt_start_addr &&
                !info->mem_slots[i][x].virt_end_addr) {
                continue;
            }
            printf("id %d, group %d, virt start %lx, virt end %lx, generation %u, delta %lx\n",
                   x, i, info->mem_slots[i][x].virt_start_addr,
                   info->mem_slots[i][x].virt_end_addr, info->mem_slots[i][x].generation,
                   info->mem_slots[i][x].address_delta);
            }
    }
}


static void test_cursor_command(void)
{
    RedMemSlotInfo mem_info;
    RedCursorCmd *red_cursor_cmd;
    QXLCursorCmd cursor_cmd;
    QXLCursor *cursor;

    init_meminfo(&mem_info);

    /* test base cursor with no problems */
    memset(&cursor_cmd, 0, sizeof(cursor_cmd));
    cursor_cmd.type = QXL_CURSOR_SET;

    cursor = create_chunk(SPICE_OFFSETOF(QXLCursor, chunk), 128 * 128 * 4, NULL, 0xaa);
    cursor->header.unique = 1;
    cursor->header.width = 128;
    cursor->header.height = 128;
    cursor->data_size = 128 * 128 * 4;

    cursor_cmd.u.set.shape = to_physical(cursor);

    red_cursor_cmd = red_cursor_cmd_new(NULL, &mem_info, 0, to_physical(&cursor_cmd));
    g_assert_nonnull(red_cursor_cmd);
    red_cursor_cmd_unref(red_cursor_cmd);
    g_free(cursor);
    memslot_info_destroy(&mem_info);
}


static void init_qxl_surface(QXLSurfaceCmd *qxl)
{
    void *surface_mem;

    memset(qxl, 0, sizeof(*qxl));

    qxl->surface_id = 123;

    qxl->u.surface_create.format = SPICE_SURFACE_FMT_32_xRGB;
    qxl->u.surface_create.width = 128;
    qxl->u.surface_create.stride = 512;
    qxl->u.surface_create.height = 128;
    surface_mem = g_malloc(0x10000);
    qxl->u.surface_create.data = to_physical(surface_mem);
}


static void test_too_big_image(void)
{
    RedMemSlotInfo mem_info;
    RedSurfaceCmd *cmd;
    QXLSurfaceCmd qxl;

    init_meminfo(&mem_info);
    init_qxl_surface(&qxl);

    /* try to create a surface quite large.
     * The sizes (width and height) were chosen so the multiplication
     * using 32 bit values gives a very small value.
     * These kind of values should be refused as they will cause
     * overflows. Also the total memory for the card is not enough to
     * hold the surface so surely can't be accepted.
     */
    qxl.u.surface_create.stride = 0x08000004 * 4;
    qxl.u.surface_create.width = 0x08000004;
    qxl.u.surface_create.height = 0x40000020;
    cmd = red_surface_cmd_new(NULL, &mem_info, 0, to_physical(&qxl));
    g_assert_null(cmd);

    deinit_qxl_surface(&qxl);
    memslot_info_destroy(&mem_info);
}


static void init_meminfo(RedMemSlotInfo *mem_info)
{
    memslot_info_init(mem_info, 1 /* groups */, 1 /* slots */, 1, 1, 0);
    memslot_info_add_slot(mem_info, 0, 0, 0 /* delta */, 0 /* start */, ~0ul /* end */, 0 /* generation */);
}


void *memslot_get_virt(RedMemSlotInfo *info, QXLPHYSICAL addr, uint32_t add_size,
                       int group_id)
{
    int slot_id;
    int generation;
    unsigned long h_virt;

    MemSlot *slot;

    if (group_id >= info->num_memslots_groups) {
        spice_critical("group_id too big");
        return NULL;
    }

    slot_id = memslot_get_id(info, addr);
    if (slot_id >= info->num_memslots) {
        print_memslots(info);
        spice_critical("slot_id %d too big, addr=%" PRIx64, slot_id, addr);
        return NULL;
    }

    slot = &info->mem_slots[group_id][slot_id];

    generation = memslot_get_generation(info, addr);
    if (generation != slot->generation) {
        print_memslots(info);
        spice_critical("address generation is not valid, group_id %d, slot_id %d, "
                       "gen %d, slot_gen %d",
                       group_id, slot_id,
                       generation, slot->generation);
        return NULL;
    }

    h_virt = __get_clean_virt(info, addr);
    h_virt += slot->address_delta;

    if (!memslot_validate_virt(info, h_virt, slot_id, add_size, group_id)) {
        return NULL;
    }

    return (void*)(uintptr_t)h_virt;
}


void memslot_info_add_slot(RedMemSlotInfo *info, uint32_t slot_group_id, uint32_t slot_id,
                           uint64_t addr_delta, unsigned long virt_start, unsigned long virt_end,
                           uint32_t generation)
{
    spice_assert(info->num_memslots_groups > slot_group_id);
    spice_assert(info->num_memslots > slot_id);

    info->mem_slots[slot_group_id][slot_id].address_delta = addr_delta;
    info->mem_slots[slot_group_id][slot_id].virt_start_addr = virt_start;
    info->mem_slots[slot_group_id][slot_id].virt_end_addr = virt_end;
    info->mem_slots[slot_group_id][slot_id].generation = generation;
}


static void test_stride_too_small(void)
{
    RedMemSlotInfo mem_info;
    RedSurfaceCmd *cmd;
    QXLSurfaceCmd qxl;

    init_meminfo(&mem_info);
    init_qxl_surface(&qxl);

    /* try to create a surface with a stride too small to fit
     * the entire width.
     * This can be used to cause buffer overflows so refuse it.
     */
    qxl.u.surface_create.stride = 256;
    cmd = red_surface_cmd_new(NULL, &mem_info, 0, to_physical(&qxl));
    g_assert_null(cmd);

    deinit_qxl_surface(&qxl);
    memslot_info_destroy(&mem_info);
}


static int ftpcccmethod(struct Configurable *config, char *str)
{
  if(curlx_strequal("passive", str))
    return CURLFTPSSL_CCC_PASSIVE;
  if(curlx_strequal("active", str))
    return CURLFTPSSL_CCC_ACTIVE;
  warnf(config, "unrecognized ftp CCC method '%s', using default\n", str);
  return CURLFTPSSL_CCC_PASSIVE;
}

'mytx' in bug report #1723194 (http://curl.haxx.se/bug/view.cgi?id=1723194)
pointed out that the warnf() function in the curl tool didn't properly deal
with the cases when excessively long words were used in the string to chop
up.


static void checkpasswd(const char *kind, /* for what purpose */
                        char **userpwd) /* pointer to allocated string */
{
  char *ptr;
  if(!*userpwd)
    return;

  ptr = strchr(*userpwd, ':');
  if(!ptr) {
    /* no password present, prompt for one */
    char passwd[256]="";
    char prompt[256];
    size_t passwdlen;
    size_t userlen = strlen(*userpwd);
    char *passptr;

    /* build a nice-looking prompt */
    curlx_msnprintf(prompt, sizeof(prompt),
                   "Enter %s password for user '%s':",
                   kind, *userpwd);

    /* get password */
    getpass_r(prompt, passwd, sizeof(passwd));
    passwdlen = strlen(passwd);

    /* extend the allocated memory area to fit the password too */
    passptr = realloc(*userpwd,
                      passwdlen + 1 + /* an extra for the colon */
                      userlen + 1);   /* an extra for the zero */

    if(passptr) {
      /* append the password separated with a colon */
      passptr[userlen]=':';
      memcpy(&passptr[userlen+1], passwd, passwdlen+1);
      *userpwd = passptr;
    }
  }
}


static ParameterError getparameter(char *flag, /* f or -long-flag */
                                   char *nextarg, /* NULL if unset */
                                   bool *usedarg, /* set to TRUE if the arg
                                                     has been used */
                                   struct Configurable *config)
{
  char letter;
  char subletter=0; /* subletters can only occur on long options */
  int rc; /* generic return code variable */
  const char *parse=NULL;
  unsigned int j;
  time_t now;
  int hit=-1;
  bool longopt=FALSE;
  bool singleopt=FALSE; /* when true means '-o foo' used '-ofoo' */
  ParameterError err;

  /* single-letter,
     long-name,
     boolean whether it takes an additional argument
     */
  static const struct LongShort aliases[]= {
    /* all these ones, starting with "*" or "$" as a short-option have *no*
       short option to mention. */
    {"*", "url",         TRUE},
    {"*a", "random-file", TRUE},
    {"*b", "egd-file",   TRUE},
    {"*c", "connect-timeout", TRUE},
    {"*d", "ciphers",    TRUE},
    {"*e", "disable-epsv", FALSE},
#ifdef USE_ENVIRONMENT
    {"*f", "environment", FALSE},
#endif
    {"*g", "trace",      TRUE},
    {"*h", "trace-ascii", TRUE},
    {"*i", "limit-rate", TRUE},
    {"*j", "compressed",  FALSE}, /* might take an arg someday */
    {"*k", "digest",     FALSE},
    {"*l", "negotiate",  FALSE},
    {"*m", "ntlm",       FALSE},
    {"*n", "basic",      FALSE},
    {"*o", "anyauth",    FALSE},
#ifdef MSDOS
    {"*p", "wdebug",     FALSE},
#endif
    {"*q", "ftp-create-dirs", FALSE},
    {"*r", "create-dirs", FALSE},
    {"*s", "max-redirs",   TRUE},
    {"*t", "proxy-ntlm",   FALSE},
    {"*u", "crlf",        FALSE},
    {"*v", "stderr",      TRUE},
    {"*w", "interface",   TRUE},
    {"*x", "krb4",        TRUE},
    {"*y", "max-filesize", TRUE},
    {"*z", "disable-eprt", FALSE},
    {"$a", "ftp-ssl",    FALSE},
    {"$b", "ftp-pasv",   FALSE},
    {"$c", "socks5",     TRUE},
    {"$c", "socks",      TRUE}, /* this is how the option was documented but
                                   we prefer the --socks5 version for explicit
                                   version */
    {"$d", "tcp-nodelay",FALSE},
    {"$e", "proxy-digest", FALSE},
    {"$f", "proxy-basic", FALSE},
    {"$g", "retry",      TRUE},
    {"$h", "retry-delay", TRUE},
    {"$i", "retry-max-time", TRUE},
    {"$m", "ftp-account", TRUE},
    {"$n", "proxy-anyauth", FALSE},
    {"$o", "trace-time", FALSE},
    {"$p", "ignore-content-length", FALSE},
    {"$q", "ftp-skip-pasv-ip", FALSE},
    {"$r", "ftp-method", TRUE},
    {"$s", "local-port", TRUE},
    {"$t", "socks4",     TRUE},
    {"$u", "ftp-alternative-to-user", TRUE},
    {"$v", "ftp-ssl-reqd", FALSE},
    {"$w", "no-sessionid", FALSE},
    {"$x", "ftp-ssl-control", FALSE},
    {"$y", "ftp-ssl-ccc", FALSE},
    {"$j", "ftp-ssl-ccc-mode", TRUE},
    {"$z", "libcurl",    TRUE},
    {"$#", "raw",        FALSE},

    {"0", "http1.0",     FALSE},
    {"1", "tlsv1",       FALSE},
    {"2", "sslv2",       FALSE},
    {"3", "sslv3",       FALSE},
    {"4", "ipv4",       FALSE},
    {"6", "ipv6",       FALSE},
    {"a", "append",      FALSE},
    {"A", "user-agent",  TRUE},
    {"b", "cookie",      TRUE},
    {"B", "use-ascii",   FALSE},
    {"c", "cookie-jar",  TRUE},
    {"C", "continue-at", TRUE},
    {"d", "data",        TRUE},
    {"da", "data-ascii", TRUE},
    {"db", "data-binary", TRUE},
    {"D", "dump-header", TRUE},
    {"e", "referer",     TRUE},
    {"E", "cert",        TRUE},
    {"Ea", "cacert",     TRUE},
    {"Eb","cert-type",   TRUE},
    {"Ec","key",         TRUE},
    {"Ed","key-type",    TRUE},
    {"Ee","pass",        TRUE},
    {"Ef","engine",      TRUE},
    {"Eg","capath ",     TRUE},
    {"Eh","pubkey",      TRUE},
    {"f", "fail",        FALSE},
    {"F", "form",        TRUE},
    {"Fs","form-string", TRUE},
    {"g", "globoff",     FALSE},
    {"G", "get",         FALSE},
    {"h", "help",        FALSE},
    {"H", "header",      TRUE},
    {"i", "include",     FALSE},
    {"I", "head",        FALSE},
    {"j", "junk-session-cookies", FALSE},
    {"k", "insecure",    FALSE},
    {"K", "config",      TRUE},
    {"l", "list-only",   FALSE},
    {"L", "location",    FALSE},
    {"Lt", "location-trusted", FALSE},
    {"m", "max-time",    TRUE},
    {"M", "manual",      FALSE},
    {"n", "netrc",       FALSE},
    {"no", "netrc-optional", FALSE},
    {"N", "no-buffer",   FALSE},
    {"o", "output",      TRUE},
    {"O", "remote-name", FALSE},
    {"p", "proxytunnel", FALSE},
    {"P", "ftpport",     TRUE}, /* older version */
    {"P", "ftp-port",    TRUE},
    {"q", "disable",     FALSE},
    {"Q", "quote",       TRUE},
    {"r", "range",       TRUE},
    {"R", "remote-time", FALSE},
    {"s", "silent",      FALSE},
    {"S", "show-error",  FALSE},
    {"t", "telnet-options", TRUE},
    {"T", "upload-file", TRUE},
    {"u", "user",        TRUE},
    {"U", "proxy-user",  TRUE},
    {"v", "verbose",     FALSE},
    {"V", "version",     FALSE},
    {"w", "write-out",   TRUE},
    {"x", "proxy",       TRUE},
    {"X", "request",     TRUE},
    {"X", "http-request", TRUE}, /* OBSOLETE VERSION */
    {"Y", "speed-limit",  TRUE},
    {"y", "speed-time", TRUE},
    {"z", "time-cond",   TRUE},
    {"#", "progress-bar",FALSE},
  };

  if(('-' != flag[0]) ||
     (('-' == flag[0]) && ('-' == flag[1]))) {
    /* this should be a long name */
    char *word=('-' == flag[0])?flag+2:flag;
    size_t fnam=strlen(word);
    int numhits=0;
    for(j=0; j< sizeof(aliases)/sizeof(aliases[0]); j++) {
      if(curlx_strnequal(aliases[j].lname, word, fnam)) {
        longopt = TRUE;
        numhits++;
        if(curlx_strequal(aliases[j].lname, word)) {
          parse = aliases[j].letter;
          hit = j;
          numhits = 1; /* a single unique hit */
          break;
        }
        parse = aliases[j].letter;
        hit = j;
      }
    }
    if(numhits>1) {
      /* this is at least the second match! */
      return PARAM_OPTION_AMBIGUOUS;
    }
    if(hit < 0) {
      return PARAM_OPTION_UNKNOWN;
    }
  }
  else {
    flag++; /* prefixed with one dash, pass it */
    hit=-1;
    parse = flag;
  }

  do {
    /* we can loop here if we have multiple single-letters */

    if(!longopt) {
      if(NULL != parse) {
        letter = (char)*parse;
      }
      else {
        letter = '\0';
      }
      subletter='\0';
    }
    else {
      letter = parse[0];
      subletter = parse[1];
    }
    *usedarg = FALSE; /* default is that we don't use the arg */

    if(hit < 0) {
      for(j=0; j< sizeof(aliases)/sizeof(aliases[0]); j++) {
        if(letter == aliases[j].letter[0]) {
          hit = j;
          break;
        }
      }
      if(hit < 0) {
        return PARAM_OPTION_UNKNOWN;
      }
    }
    if(hit < 0) {
      return PARAM_OPTION_UNKNOWN;
    }
    if(!longopt && aliases[hit].extraparam && parse[1]) {
      nextarg=(char *)&parse[1]; /* this is the actual extra parameter */
      singleopt=TRUE;   /* don't loop anymore after this */
    }
    else if(!nextarg && aliases[hit].extraparam) {
      return PARAM_REQUIRES_PARAMETER;
    }
    else if(nextarg && aliases[hit].extraparam)
      *usedarg = TRUE; /* mark it as used */

    switch(letter) {
    case '*': /* options without a short option */
      switch(subletter) {
      case 'a': /* random-file */
        GetStr(&config->random_file, nextarg);
        break;
      case 'b': /* egd-file */
        GetStr(&config->egd_file, nextarg);
        break;
      case 'c': /* connect-timeout */
        if(str2num(&config->connecttimeout, nextarg))
          return PARAM_BAD_NUMERIC;
        break;
      case 'd': /* ciphers */
        GetStr(&config->cipher_list, nextarg);
        break;
      case 'e': /* --disable-epsv */
        config->disable_epsv ^= TRUE;
        break;
#ifdef USE_ENVIRONMENT
      case 'f':
        config->writeenv ^= TRUE;
        break;
#endif
      case 'g': /* --trace */
        GetStr(&config->trace_dump, nextarg);
        if(config->tracetype && (config->tracetype != TRACE_BIN))
          warnf(config, "--trace overrides an earlier trace/verbose option\n");
        config->tracetype = TRACE_BIN;
        break;
      case 'h': /* --trace-ascii */
        GetStr(&config->trace_dump, nextarg);
        if(config->tracetype && (config->tracetype != TRACE_ASCII))
          warnf(config,
                "--trace-ascii overrides an earlier trace/verbose option\n");
        config->tracetype = TRACE_ASCII;
        break;
      case 'i': /* --limit-rate */
        {
          /* We support G, M, K too */
          char *unit;
          curl_off_t value = curlx_strtoofft(nextarg, &unit, 0);

          if(!*unit)
            unit=(char *)"b";
          else if(strlen(unit) > 1)
            unit=(char *)"w"; /* unsupported */

          switch(*unit) {
          case 'G':
          case 'g':
            value *= 1024*1024*1024;
            break;
          case 'M':
          case 'm':
            value *= 1024*1024;
            break;
          case 'K':
          case 'k':
            value *= 1024;
            break;
          case 'b':
          case 'B':
            /* for plain bytes, leave as-is */
            break;
          default:
            warnf(config, "unsupported rate unit. Use G, M, K or B!\n");
            return PARAM_BAD_USE;
          }
          config->recvpersecond = value;
          config->sendpersecond = value;
        }
        break;

      case 'j': /* --compressed */
        config->encoding ^= TRUE;
        break;

      case 'k': /* --digest */
        config->authtype = CURLAUTH_DIGEST;
        break;

      case 'l': /* --negotiate */
        if(curlinfo->features & CURL_VERSION_GSSNEGOTIATE)
          config->authtype = CURLAUTH_GSSNEGOTIATE;
        else
          return PARAM_LIBCURL_DOESNT_SUPPORT;
        break;

      case 'm': /* --ntlm */
        if(curlinfo->features & CURL_VERSION_NTLM)
          config->authtype = CURLAUTH_NTLM;
        else
          return PARAM_LIBCURL_DOESNT_SUPPORT;
        break;

      case 'n': /* --basic for completeness */
        config->authtype = CURLAUTH_BASIC;
        break;

      case 'o': /* --anyauth, let libcurl pick it */
        config->authtype = CURLAUTH_ANY;
        break;

#ifdef MSDOS
      case 'p': /* --wdebug */
        dbug_init();
        break;
#endif
      case 'q': /* --ftp-create-dirs */
        config->ftp_create_dirs ^= TRUE;
        break;

      case 'r': /* --create-dirs */
        config->create_dirs = TRUE;
        break;

      case 's': /* --max-redirs */
        /* specified max no of redirects (http(s)) */
        if(str2num(&config->maxredirs, nextarg))
          return PARAM_BAD_NUMERIC;
        break;

      case 't': /* --proxy-ntlm */
        if(curlinfo->features & CURL_VERSION_NTLM)
          config->proxyntlm ^= TRUE;
        else
          return PARAM_LIBCURL_DOESNT_SUPPORT;
        break;

      case 'u': /* --crlf */
        /* LF -> CRLF conversinon? */
        config->crlf = TRUE;
        break;

      case 'v': /* --stderr */
        if(strcmp(nextarg, "-")) {
          FILE *newfile = fopen(nextarg, "wt");
          if(!newfile)
            warnf(config, "Failed to open %s!\n", nextarg);
          else {
            config->errors = newfile;
            config->errors_fopened = TRUE;
          }
        }
        else
          config->errors = stdout;
      break;
      case 'w': /* --interface */
        /* interface */
        GetStr(&config->iface, nextarg);
        break;
      case 'x': /* --krb4 */
        /* krb4 level string */
        if(curlinfo->features & CURL_VERSION_KERBEROS4)
          GetStr(&config->krb4level, nextarg);
        else
          return PARAM_LIBCURL_DOESNT_SUPPORT;
        break;
      case 'y': /* --max-filesize */
        if(str2offset(&config->max_filesize, nextarg))
          return PARAM_BAD_NUMERIC;
        break;
      case 'z': /* --disable-eprt */
        config->disable_eprt ^= TRUE;
        break;

      default: /* the URL! */
        {
          struct getout *url;
          if(config->url_get || (config->url_get=config->url_list)) {
            /* there's a node here, if it already is filled-in continue to find
               an "empty" node */
            while(config->url_get && (config->url_get->flags&GETOUT_URL))
              config->url_get = config->url_get->next;
          }

          /* now there might or might not be an available node to fill in! */

          if(config->url_get)
            /* existing node */
            url = config->url_get;
          else
            /* there was no free node, create one! */
            url=new_getout(config);

          if(url) {
            /* fill in the URL */
            GetStr(&url->url, nextarg);
            url->flags |= GETOUT_URL;
          }
        }
      }
      break;
    case '$': /* more options without a short option */
      switch(subletter) {
      case 'a': /* --ftp-ssl */
        config->ftp_ssl ^= TRUE;
        break;
      case 'b': /* --ftp-pasv */
        if(config->ftpport)
          free(config->ftpport);
        config->ftpport = NULL;
        break;
      case 'c': /* --socks5 specifies a socks5 proxy to use */
        GetStr(&config->socksproxy, nextarg);
        config->socksver = CURLPROXY_SOCKS5;
        break;
      case 't': /* --socks4 specifies a socks4 proxy to use */
        GetStr(&config->socksproxy, nextarg);
        config->socksver = CURLPROXY_SOCKS4;
        break;
      case 'd': /* --tcp-nodelay option */
        config->tcp_nodelay ^= TRUE;
        break;
      case 'e': /* --proxy-digest */
        config->proxydigest ^= TRUE;
        break;
      case 'f': /* --proxy-basic */
        config->proxybasic ^= TRUE;
        break;
      case 'g': /* --retry */
        if(str2num(&config->req_retry, nextarg))
          return PARAM_BAD_NUMERIC;
        break;
      case 'h': /* --retry-delay */
        if(str2num(&config->retry_delay, nextarg))
          return PARAM_BAD_NUMERIC;
        break;
      case 'i': /* --retry-max-time */
        if(str2num(&config->retry_maxtime, nextarg))
          return PARAM_BAD_NUMERIC;
        break;

      case 'm': /* --ftp-account */
        GetStr(&config->ftp_account, nextarg);
        break;
      case 'n': /* --proxy-anyauth */
        config->proxyanyauth ^= TRUE;
        break;
      case 'o': /* --trace-time */
        config->tracetime ^= TRUE;
        break;
      case 'p': /* --ignore-content-length */
        config->ignorecl ^= TRUE;
        break;
      case 'q': /* --ftp-skip-pasv-ip */
        config->ftp_skip_ip ^= TRUE;
        break;
      case 'r': /* --ftp-method (undocumented at this point) */
        config->ftp_filemethod = ftpfilemethod(config, nextarg);
        break;
      case 's': /* --local-port */
        rc = sscanf(nextarg, "%d - %d",
                    &config->localport,
                    &config->localportrange);
        if(!rc)
          return PARAM_BAD_USE;
        else if(rc == 1)
          config->localportrange = 1; /* default number of ports to try */
        else {
          config->localportrange -= config->localport;
          if(config->localportrange < 1) {
            warnf(config, "bad range input\n");
            return PARAM_BAD_USE;
          }
        }
        break;
      case 'u': /* --ftp-alternative-to-user */
        GetStr(&config->ftp_alternative_to_user, nextarg);
        break;
      case 'v': /* --ftp-ssl-reqd */
        config->ftp_ssl_reqd ^= TRUE;
        break;
      case 'w': /* --no-sessionid */
        config->disable_sessionid ^= TRUE;
        break;
      case 'x': /* --ftp-ssl-control */
        config->ftp_ssl_control ^= TRUE;
        break;
      case 'y': /* --ftp-ssl-ccc */
        config->ftp_ssl_ccc ^= TRUE;
        if(!config->ftp_ssl_ccc_mode)
            config->ftp_ssl_ccc_mode = CURLFTPSSL_CCC_PASSIVE;
        break;
      case 'j': /* --ftp-ssl-ccc-mode */
        config->ftp_ssl_ccc = TRUE;
        config->ftp_ssl_ccc_mode = ftpcccmethod(config, nextarg);
        break;
      case 'z': /* --libcurl */
        GetStr(&config->libcurl, nextarg);
        break;
      case '#': /* --raw */
        config->raw ^= TRUE;
        break;
      }
      break;
    case '#': /* --progress-bar */
      config->progressmode ^= CURL_PROGRESS_BAR;
      break;
    case '0':
      /* HTTP version 1.0 */
      config->httpversion = CURL_HTTP_VERSION_1_0;
      break;
    case '1':
      /* TLS version 1 */
      config->ssl_version = CURL_SSLVERSION_TLSv1;
      break;
    case '2':
      /* SSL version 2 */
      config->ssl_version = CURL_SSLVERSION_SSLv2;
      break;
    case '3':
      /* SSL version 3 */
      config->ssl_version = CURL_SSLVERSION_SSLv3;
      break;
    case '4':
      /* IPv4 */
      config->ip_version = 4;
      break;
    case '6':
      /* IPv6 */
      config->ip_version = 6;
      break;
    case 'a':
      /* This makes the FTP sessions use APPE instead of STOR */
      config->conf ^= CONF_FTPAPPEND;
      break;
    case 'A':
      /* This specifies the User-Agent name */
      GetStr(&config->useragent, nextarg);
      break;
    case 'b': /* cookie string coming up: */
      if(nextarg[0] == '@') {
        nextarg++;
      }
      else if(strchr(nextarg, '=')) {
        /* A cookie string must have a =-letter */
        GetStr(&config->cookie, nextarg);
        break;
      }
      /* We have a cookie file to read from! */
      GetStr(&config->cookiefile, nextarg);
      break;
    case 'B':
      /* use ASCII/text when transfering */
      config->conf ^= CONF_GETTEXT;
      break;
    case 'c':
      /* get the file name to dump all cookies in */
      GetStr(&config->cookiejar, nextarg);
      break;
    case 'C':
      /* This makes us continue an ftp transfer at given position */
      if(!curlx_strequal(nextarg, "-")) {
        if(str2offset(&config->resume_from, nextarg))
          return PARAM_BAD_NUMERIC;
        config->resume_from_current = FALSE;
      }
      else {
        config->resume_from_current = TRUE;
        config->resume_from = 0;
      }
      config->use_resume=TRUE;
      break;
    case 'd':
      /* postfield data */
      {
        char *postdata=NULL;

        if('@' == *nextarg) {
          /* the data begins with a '@' letter, it means that a file name
             or - (stdin) follows */
          FILE *file;

          nextarg++; /* pass the @ */

          if(curlx_strequal("-", nextarg)) {
            file = stdin;
            if(subletter == 'b') /* forced binary */
              SET_BINMODE(stdin);
          }
          else {
            file = fopen(nextarg, "rb");
            if(!file)
              warnf(config, "Couldn't read data from file \"%s\", this makes "
                    "an empty POST.\n", nextarg);
          }

          if(subletter == 'b') /* forced binary */
            postdata = file2memory(file, &config->postfieldsize);
          else
            postdata = file2string(file);

          if(file && (file != stdin))
            fclose(file);

          if(!postdata) {
            /* no data from the file, point to a zero byte string to make this
               get sent as a POST anyway */
            postdata=strdup("");
          }
        }
        else {
          GetStr(&postdata, nextarg);
        }

#ifdef CURL_DOES_CONVERSIONS
        if(subletter != 'b') { /* NOT forced binary, convert to ASCII */
          convert_to_network(postdata, strlen(postdata));
        }
#endif

        if(config->postfields) {
          /* we already have a string, we append this one
             with a separating &-letter */
          char *oldpost=config->postfields;
          size_t newlen = strlen(oldpost) + strlen(postdata) + 2;
          config->postfields=malloc(newlen);
          if(!config->postfields) {
            free(postdata);
            return PARAM_NO_MEM;
          }
          /* use ASCII value 0x26 for '&' to accommodate non-ASCII platforms */
          snprintf(config->postfields, newlen, "%s\x26%s", oldpost, postdata);
          free(oldpost);
          free(postdata);
        }
        else
          config->postfields=postdata;
      }
      /*
        We can't set the request type here, as this data might be used in
        a simple GET if -G is used. Already or soon.

        if(SetHTTPrequest(HTTPREQ_SIMPLEPOST, &config->httpreq))
          return PARAM_BAD_USE;
      */
      break;
    case 'D':
      /* dump-header to given file name */
      GetStr(&config->headerfile, nextarg);
      break;
    case 'e':
      {
        char *ptr = strstr(nextarg, ";auto");
        if(ptr) {
          /* Automatic referer requested, this may be combined with a
             set initial one */
          config->conf |= CONF_AUTO_REFERER;
          *ptr = 0; /* zero terminate here */
        }
        GetStr(&config->referer, nextarg);
      }
      break;
    case 'E':
      switch(subletter) {
      case 'a': /* CA info PEM file */
        /* CA info PEM file */
        GetStr(&config->cacert, nextarg);
        break;
      case 'b': /* cert file type */
        GetStr(&config->cert_type, nextarg);
        break;
      case 'c': /* private key file */
        GetStr(&config->key, nextarg);
        break;
      case 'd': /* private key file type */
        GetStr(&config->key_type, nextarg);
        break;
      case 'e': /* private key passphrase */
        GetStr(&config->key_passwd, nextarg);
        cleanarg(nextarg);
        break;
      case 'f': /* crypto engine */
        GetStr(&config->engine, nextarg);
        if (config->engine && curlx_strequal(config->engine,"list"))
           config->list_engines = TRUE;
        break;
      case 'g': /* CA info PEM file */
        /* CA cert directory */
        GetStr(&config->capath, nextarg);
        break;
      case 'h': /* --pubkey public key file */
        GetStr(&config->pubkey, nextarg);
        break;
      default: /* certificate file */
        {
          char *ptr = strchr(nextarg, ':');
          /* Since we live in a world of weirdness and confusion, the win32
             dudes can use : when using drive letters and thus
             c:\file:password needs to work. In order not to break
             compatibility, we still use : as separator, but we try to detect
             when it is used for a file name! On windows. */
#ifdef WIN32
          if(ptr &&
             (ptr == &nextarg[1]) &&
             (nextarg[2] == '\\' || nextarg[2] == '/') &&
             (ISALPHA(nextarg[0])) )
             /* colon in the second column, followed by a backslash, and the
                first character is an alphabetic letter:

                this is a drive letter colon */
            ptr = strchr(&nextarg[3], ':'); /* find the next one instead */
#endif
          if(ptr) {
            /* we have a password too */
            *ptr=0;
            ptr++;
            GetStr(&config->key_passwd, ptr);
          }
          GetStr(&config->cert, nextarg);
          cleanarg(nextarg);
        }
      }
      break;
    case 'f':
      /* fail hard on errors  */
      config->conf ^= CONF_FAILONERROR;
      break;
    case 'F':
      /* "form data" simulation, this is a little advanced so lets do our best
         to sort this out slowly and carefully */
      if(formparse(config,
                   nextarg,
                   &config->httppost,
                   &config->last_post,
                   (bool) (subletter=='s'))) /* 's' means literal string */
        return PARAM_BAD_USE;
      if(SetHTTPrequest(config, HTTPREQ_POST, &config->httpreq))
        return PARAM_BAD_USE;
      break;

    case 'g': /* g disables URLglobbing */
      config->globoff ^= TRUE;
      break;

    case 'G': /* HTTP GET */
      config->use_httpget = TRUE;
      break;

    case 'h': /* h for help */
      help();
      return PARAM_HELP_REQUESTED;
    case 'H':
      /* A custom header to append to a list */
      err = add2list(&config->headers, nextarg);
      if(err)
        return err;
      break;
    case 'i':
      config->conf ^= CONF_HEADER; /* include the HTTP header as well */
      break;
    case 'j':
      config->cookiesession ^= TRUE;
      break;
    case 'I':
      /*
       * This is a bit tricky. We either SET both bits, or we clear both
       * bits. Let's not make any other outcomes from this.
       */
      if((CONF_HEADER|CONF_NOBODY) !=
         (config->conf&(CONF_HEADER|CONF_NOBODY)) ) {
        /* one of them weren't set, set both */
        config->conf |= (CONF_HEADER|CONF_NOBODY);
        if(SetHTTPrequest(config, HTTPREQ_HEAD, &config->httpreq))
          return PARAM_BAD_USE;
      }
      else {
        /* both were set, clear both */
        config->conf &= ~(CONF_HEADER|CONF_NOBODY);
        if(SetHTTPrequest(config, HTTPREQ_GET, &config->httpreq))
          return PARAM_BAD_USE;
      }
      break;
    case 'k': /* allow insecure SSL connects */
      config->insecure_ok ^= TRUE;
      break;
    case 'K': /* parse config file */
      if(parseconfig(nextarg, config))
        warnf(config, "error trying read config from the '%s' file\n",
              nextarg);
      break;
    case 'l':
      config->conf ^= CONF_FTPLISTONLY; /* only list the names of the FTP dir */
      break;
    case 'L':
      config->conf ^= CONF_FOLLOWLOCATION; /* Follow Location: HTTP headers */
      switch (subletter) {
      case 't':
        /* Continue to send authentication (user+password) when following
         * locations, even when hostname changed */
        config->conf ^= CONF_UNRESTRICTED_AUTH;
        break;
      }
      break;
    case 'm':
      /* specified max time */
      if(str2num(&config->timeout, nextarg))
        return PARAM_BAD_NUMERIC;
      break;
    case 'M': /* M for manual, huge help */
#ifdef USE_MANUAL
      hugehelp();
      return PARAM_HELP_REQUESTED;
#else
      warnf(config,
            "built-in manual was disabled at build-time!\n");
      return PARAM_OPTION_UNKNOWN;
#endif
    case 'n':
      switch(subletter) {
      case 'o': /* CA info PEM file */
        /* use .netrc or URL */
        config->conf ^= CONF_NETRC_OPT;
        break;
      default:
        /* pick info from .netrc, if this is used for http, curl will
           automatically enfore user+password with the request */
        config->conf ^= CONF_NETRC;
        break;
      }
      break;
    case 'N':
      /* disable the output I/O buffering */
      config->nobuffer ^= 1;
      break;
    case 'o':
    case 'O':
      /* output file */
      {
        struct getout *url;
        if(config->url_out || (config->url_out=config->url_list)) {
          /* there's a node here, if it already is filled-in continue to find
             an "empty" node */
          while(config->url_out && (config->url_out->flags&GETOUT_OUTFILE))
            config->url_out = config->url_out->next;
        }

        /* now there might or might not be an available node to fill in! */

        if(config->url_out)
          /* existing node */
          url = config->url_out;
        else
          /* there was no free node, create one! */
          url=new_getout(config);

        if(url) {
          /* fill in the outfile */
          if('o' == letter)
            GetStr(&url->outfile, nextarg);
          else {
            url->outfile=NULL; /* leave it */
            url->flags |= GETOUT_USEREMOTE;
          }
          url->flags |= GETOUT_OUTFILE;
        }
      }
      break;
    case 'P':
      /* This makes the FTP sessions use PORT instead of PASV */
      /* use <eth0> or <192.168.10.10> style addresses. Anything except
         this will make us try to get the "default" address.
         NOTE: this is a changed behaviour since the released 4.1!
         */
      GetStr(&config->ftpport, nextarg);
      break;
    case 'p':
      /* proxy tunnel for non-http protocols */
      config->proxytunnel ^= TRUE;
      break;

    case 'q': /* if used first, already taken care of, we do it like
                 this so we don't cause an error! */
      break;
    case 'Q':
      /* QUOTE command to send to FTP server */
      err = PARAM_OK;
      switch(nextarg[0]) {
      case '-':
        /* prefixed with a dash makes it a POST TRANSFER one */
        nextarg++;
        err = add2list(&config->postquote, nextarg);
        break;
      case '+':
        /* prefixed with a plus makes it a just-before-transfer one */
        nextarg++;
        err = add2list(&config->prequote, nextarg);
        break;
      default:
        err = add2list(&config->quote, nextarg);
        break;
      }
      if(err)
        return err;
      break;
    case 'r':
      /* Specifying a range WITHOUT A DASH will create an illegal HTTP range
         (and won't actually be range by definition). The man page previously
         claimed that to be a good way, why this code is added to work-around
         it. */
      if(ISDIGIT(*nextarg) && !strchr(nextarg, '-')) {
        char buffer[32];
        curl_off_t off;
        warnf(config,
              "A specfied range MUST include at least one dash (-). "
              "Appending one for you!\n");
        off = curlx_strtoofft(nextarg, NULL, 10);
        snprintf(buffer, sizeof(buffer), "%Od-", off);
        GetStr(&config->range, buffer);
      }
      {
        /* byte range requested */
        char* tmp_range;
        tmp_range=nextarg;
        while(*tmp_range != '\0') {
          if(!ISDIGIT(*tmp_range)&&*tmp_range!='-'&&*tmp_range!=',') {
            warnf(config,"Invalid character is found in given range. "
                  "A specified range MUST have only digits in "
                  "\'start\'-\'stop\'. The server's response to this "
                  "request is uncertain.\n");
            break;
          }
          tmp_range++;
        }
        /* byte range requested */
        GetStr(&config->range, nextarg);
      }
      break;
    case 'R':
      /* use remote file's time */
      config->remote_time ^= TRUE;
      break;
    case 's':
      /* don't show progress meter, don't show errors : */
      config->conf |= (CONF_MUTE|CONF_NOPROGRESS);
      config->showerror ^= TRUE; /* toggle off */
      break;
    case 'S':
      /* show errors */
      config->showerror ^= TRUE; /* toggle on if used with -s */
      break;
    case 't':
      /* Telnet options */
      err = add2list(&config->telnet_options, nextarg);
      if(err)
        return err;
      break;
    case 'T':
      /* we are uploading */
      {
        struct getout *url;
        if(config->url_out || (config->url_out=config->url_list)) {
          /* there's a node here, if it already is filled-in continue to find
             an "empty" node */
          while(config->url_out && (config->url_out->flags&GETOUT_UPLOAD))
            config->url_out = config->url_out->next;
        }

        /* now there might or might not be an available node to fill in! */

        if(config->url_out)
          /* existing node */
          url = config->url_out;
        else
          /* there was no free node, create one! */
          url=new_getout(config);

        if(url) {
          url->flags |= GETOUT_UPLOAD; /* mark -T used */
          if(!*nextarg)
            url->flags |= GETOUT_NOUPLOAD;
          else {
            /* "-" equals stdin, but keep the string around for now */
            GetStr(&url->infile, nextarg);
          }
        }
      }
      break;
    case 'u':
      /* user:password  */
      GetStr(&config->userpwd, nextarg);
      cleanarg(nextarg);
      checkpasswd("host", &config->userpwd);
      break;
    case 'U':
      /* Proxy user:password  */
      GetStr(&config->proxyuserpwd, nextarg);
      cleanarg(nextarg);
      checkpasswd("proxy", &config->proxyuserpwd);
      break;
    case 'v':
      /* the '%' thing here will cause the trace get sent to stderr */
      GetStr(&config->trace_dump, (char *)"%");
      if(config->tracetype && (config->tracetype != TRACE_PLAIN))
        warnf(config,
              "-v/--verbose overrides an earlier trace/verbose option\n");
      config->tracetype = TRACE_PLAIN;
      break;
    case 'V':
    {
      const char * const *proto;

      printf(CURL_ID "%s\n", curl_version());
      if (curlinfo->protocols) {
        printf("Protocols: ");
        for (proto=curlinfo->protocols; *proto; ++proto) {
          printf("%s ", *proto);
        }
        puts(""); /* newline */
      }
      if(curlinfo->features) {
        unsigned int i;
        struct feat {
          const char *name;
          int bitmask;
        };
        static const struct feat feats[] = {
          {"AsynchDNS", CURL_VERSION_ASYNCHDNS},
          {"Debug", CURL_VERSION_DEBUG},
          {"GSS-Negotiate", CURL_VERSION_GSSNEGOTIATE},
          {"IDN", CURL_VERSION_IDN},
          {"IPv6", CURL_VERSION_IPV6},
          {"Largefile", CURL_VERSION_LARGEFILE},
          {"NTLM", CURL_VERSION_NTLM},
          {"SPNEGO", CURL_VERSION_SPNEGO},
          {"SSL",  CURL_VERSION_SSL},
          {"SSPI",  CURL_VERSION_SSPI},
          {"krb4", CURL_VERSION_KERBEROS4},
          {"libz", CURL_VERSION_LIBZ},
          {"CharConv", CURL_VERSION_CONV}
        };
        printf("Features: ");
        for(i=0; i<sizeof(feats)/sizeof(feats[0]); i++) {
          if(curlinfo->features & feats[i].bitmask)
            printf("%s ", feats[i].name);
        }
        puts(""); /* newline */
      }
    }
    return PARAM_HELP_REQUESTED;
    case 'w':
      /* get the output string */
      if('@' == *nextarg) {
        /* the data begins with a '@' letter, it means that a file name
           or - (stdin) follows */
        FILE *file;
        nextarg++; /* pass the @ */
        if(curlx_strequal("-", nextarg))
          file = stdin;
        else
          file = fopen(nextarg, "r");
        config->writeout = file2string(file);
        if(!config->writeout)
          warnf(config, "Failed to read %s", file);
        if(file && (file != stdin))
          fclose(file);
      }
      else
        GetStr(&config->writeout, nextarg);
      break;
    case 'x':
      /* proxy */
      GetStr(&config->proxy, nextarg);
      break;
    case 'X':
      /* set custom request */
      GetStr(&config->customrequest, nextarg);
      break;
    case 'y':
      /* low speed time */
      if(str2num(&config->low_speed_time, nextarg))
        return PARAM_BAD_NUMERIC;
      if(!config->low_speed_limit)
        config->low_speed_limit = 1;
      break;
    case 'Y':
      /* low speed limit */
      if(str2num(&config->low_speed_limit, nextarg))
        return PARAM_BAD_NUMERIC;
      if(!config->low_speed_time)
        config->low_speed_time=30;
      break;
    case 'z': /* time condition coming up */
      switch(*nextarg) {
      case '+':
        nextarg++;
      default:
        /* If-Modified-Since: (section 14.28 in RFC2068) */
        config->timecond = CURL_TIMECOND_IFMODSINCE;
        break;
      case '-':
        /* If-Unmodified-Since:  (section 14.24 in RFC2068) */
        config->timecond = CURL_TIMECOND_IFUNMODSINCE;
        nextarg++;
        break;
      case '=':
        /* Last-Modified:  (section 14.29 in RFC2068) */
        config->timecond = CURL_TIMECOND_LASTMOD;
        nextarg++;
        break;
      }
      now=time(NULL);
      config->condtime=curl_getdate(nextarg, &now);
      if(-1 == (int)config->condtime) {
        /* now let's see if it is a file name to get the time from instead! */
        struct_stat statbuf;
        if(-1 == stat(nextarg, &statbuf)) {
          /* failed, remove time condition */
          config->timecond = CURL_TIMECOND_NONE;
          warnf(config,
                "Illegal date format for -z/--timecond (and not "
                "a file name). Disabling time condition. "
                "See curl_getdate(3) for valid date syntax.\n");
        }
        else {
          /* pull the time out from the file */
          config->condtime = statbuf.st_mtime;
        }
      }
      break;
    default: /* unknown flag */
      return PARAM_OPTION_UNKNOWN;
    }
    hit = -1;

  } while(!longopt && !singleopt && *++parse && !*usedarg);

  return PARAM_OK;
}