/*
* Copyright 2009--2013 Red Hat Inc., Durham, North Carolina.
* All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Authors:
* "Peter Vrabec" <pvrabec@redhat.com>
* Šimon Lukašík
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <ctype.h>
#include "oval_cmp_evr_string_impl.h"
#include "oval_definitions.h"
#include "oval_types.h"
#include "common/_error.h"
#ifdef HAVE_RPMVERCMP
#include <rpm/rpmlib.h>
#else
#ifdef OS_WINDOWS
#include <malloc.h>
#elif !defined(OS_FREEBSD)
#include <alloca.h>
#endif
static int rpmvercmp(const char *a, const char *b);
static int risdigit(int c) {
// locale independent
return (c >= '0' && c <= '9');
}
#endif
static inline int rpmevrcmp(const char *a, const char *b);
static int compare_values(const char *str1, const char *str2);
static void parseEVR(char *evr, const char **ep, const char **vp, const char **rp);
oval_result_t oval_evr_string_cmp(const char *state, const char *sys, oval_operation_t operation)
{
int result = rpmevrcmp(sys, state);
if (operation == OVAL_OPERATION_EQUALS) {
return ((result == 0) ? OVAL_RESULT_TRUE : OVAL_RESULT_FALSE);
} else if (operation == OVAL_OPERATION_NOT_EQUAL) {
return ((result != 0) ? OVAL_RESULT_TRUE : OVAL_RESULT_FALSE);
} else if (operation == OVAL_OPERATION_GREATER_THAN) {
return ((result == 1) ? OVAL_RESULT_TRUE : OVAL_RESULT_FALSE);
} else if (operation == OVAL_OPERATION_GREATER_THAN_OR_EQUAL) {
return ((result != -1) ? OVAL_RESULT_TRUE : OVAL_RESULT_FALSE);
} else if (operation == OVAL_OPERATION_LESS_THAN) {
return ((result == -1) ? OVAL_RESULT_TRUE : OVAL_RESULT_FALSE);
} else if (operation == OVAL_OPERATION_LESS_THAN_OR_EQUAL) {
return ((result != 1) ? OVAL_RESULT_TRUE : OVAL_RESULT_FALSE);
}
oscap_seterr(OSCAP_EFAMILY_OVAL, "Invalid type of operation in rpm version comparison: %d.", operation);
return OVAL_RESULT_ERROR;
}
static inline int rpmevrcmp(const char *a, const char *b)
{
/* This mimics rpmevrcmp which is not exported by rpmlib version 4.
* Code inspired by rpm.labelCompare() from rpm4/python/header-py.c
*/
const char *a_epoch, *a_version, *a_release;
const char *b_epoch, *b_version, *b_release;
char *a_copy, *b_copy;
int result;
a_copy = oscap_strdup(a);
b_copy = oscap_strdup(b);
parseEVR(a_copy, &a_epoch, &a_version, &a_release);
parseEVR(b_copy, &b_epoch, &b_version, &b_release);
result = compare_values(a_epoch, b_epoch);
if (!result) {
result = compare_values(a_version, b_version);
if (!result)
result = compare_values(a_release, b_release);
}
free(a_copy);
free(b_copy);
return result;
}
static int compare_values(const char *str1, const char *str2)
{
/*
* Code copied from rpm4/python/header-py.c
*/
if (!str1 && !str2)
return 0;
else if (str1 && !str2)
return 1;
else if (!str1 && str2)
return -1;
return rpmvercmp(str1, str2);
}
static void parseEVR(char *evr, const char **ep, const char **vp, const char **rp)
{
/*
* Code copied from rpm4/lib/rpmds.c
*/
const char *epoch;
const char *version; /* assume only version is present */
const char *release;
char *s, *se;
s = evr;
while (*s && risdigit(*s)) s++; /* s points to epoch terminator */
se = strrchr(s, '-'); /* se points to version terminator */
if (*s == ':') {
epoch = evr;
*s++ = '\0';
version = s;
if (*epoch == '\0')
epoch = "0";
} else {
epoch = NULL; /* XXX disable epoch compare if missing */
version = evr;
}
if (se) {
*se++ = '\0';
release = se;
} else {
release = NULL;
}
if (ep) *ep = epoch;
if (vp) *vp = version;
if (rp) *rp = release;
}
#ifndef HAVE_RPMVERCMP
/*
* code from http://rpm.org/api/4.4.2.2/rpmvercmp_8c-source.html
*/
/* compare alpha and numeric segments of two versions */
/* return 1: a is newer than b */
/* 0: a and b are the same version */
/* -1: b is newer than a */
static int rpmvercmp(const char *a, const char *b)
{
char oldch1, oldch2;
char *str1, *str2;
char *one, *two;
int rc;
int isnum;
/* easy comparison to see if versions are identical */
if (!strcmp(a, b))
return 0;
/* TODO: make new malloca */
str1 = alloca(strlen(a) + 1);
str2 = alloca(strlen(b) + 1);
strcpy(str1, a);
strcpy(str2, b);
one = str1;
two = str2;
/* loop through each version segment of str1 and str2 and compare them */
while (*one && *two) {
while (*one && !isalnum(*one))
one++;
while (*two && !isalnum(*two))
two++;
/* If we ran to the end of either, we are finished with the loop */
if (!(*one && *two))
break;
str1 = one;
str2 = two;
/* grab first completely alpha or completely numeric segment */
/* leave one and two pointing to the start of the alpha or numeric */
/* segment and walk str1 and str2 to end of segment */
if (isdigit(*str1)) {
while (*str1 && isdigit(*str1))
str1++;
while (*str2 && isdigit(*str2))
str2++;
isnum = 1;
} else {
while (*str1 && isalpha(*str1))
str1++;
while (*str2 && isalpha(*str2))
str2++;
isnum = 0;
}
/* save character at the end of the alpha or numeric segment */
/* so that they can be restored after the comparison */
oldch1 = *str1;
*str1 = '\0';
oldch2 = *str2;
*str2 = '\0';
/* this cannot happen, as we previously tested to make sure that */
/* the first string has a non-null segment */
if (one == str1)
return -1; /* arbitrary */
/* take care of the case where the two version segments are */
/* different types: one numeric, the other alpha (i.e. empty) */
/* numeric segments are always newer than alpha segments */
/* result_test See patch #60884 (and details) from bugzilla #50977. */
if (two == str2)
return (isnum ? 1 : -1);
if (isnum) {
/* this used to be done by converting the digit segments */
/* to ints using atoi() - it's changed because long */
/* digit segments can overflow an int - this should fix that. */
/* throw away any leading zeros - it's a number, right? */
while (*one == '0')
one++;
while (*two == '0')
two++;
/* whichever number has more digits wins */
if (strlen(one) > strlen(two))
return 1;
if (strlen(two) > strlen(one))
return -1;
}
/* strcmp will return which one is greater - even if the two */
/* segments are alpha or if they are numeric. don't return */
/* if they are equal because there might be more segments to */
/* compare */
rc = strcmp(one, two);
if (rc)
return (rc < 1 ? -1 : 1);
/* restore character that was replaced by null above */
*str1 = oldch1;
one = str1;
*str2 = oldch2;
two = str2;
}
/* this catches the case where all numeric and alpha segments have */
/* compared identically but the segment sepparating characters were */
/* different */
if ((!*one) && (!*two))
return 0;
/* whichever version still has characters left over wins */
if (!*one)
return -1;
else
return 1;
}
#endif
/*
* based on code from dpkg: lib/dpkg/version.c
* Mino changes to use isdigit() and isalpha()
*/
/**
* Give a weight to the character to order in the version comparison.
*
* @param c An ASCII character.
*/
static int order(int c)
{
if (isdigit(c))
return 0;
else if (isalpha(c))
return c;
else if (c == '~')
return -1;
else if (c)
return c + 256;
else
return 0;
}
/*
* based on code from dpkg: lib/dpkg/version.c
* Minor changes to use isdigit()
*/
static int verrevcmp(const char *a, const char *b)
{
if (a == NULL)
a = "";
if (b == NULL)
b = "";
while (*a || *b) {
int first_diff = 0;
while ((*a && !isdigit(*a)) || (*b && !isdigit(*b))) {
int ac = order(*a);
int bc = order(*b);
if (ac != bc)
return ac - bc;
a++;
b++;
}
while (*a == '0')
a++;
while (*b == '0')
b++;
while (isdigit(*a) && isdigit(*b)) {
if (!first_diff)
first_diff = *a - *b;
a++;
b++;
}
if (isdigit(*a))
return 1;
if (isdigit(*b))
return -1;
if (first_diff)
return first_diff;
}
return 0;
}
/*
* Code copied from lib/dpkg/version.c
*/
/**
* Compares two Debian versions.
*
* This function follows the convention of the comparator functions used by
* qsort().
*
* @see deb-version(5)
*
* @param a The first version.
* @param b The second version.
*
* @retval 0 If a and b are equal.
* @retval <0 If a is smaller than b.
* @retval >0 If a is greater than b.
*/
static int dpkg_version_compare(struct dpkg_version *a, struct dpkg_version *b)
{
int rc;
if (a->epoch > b->epoch)
return 1;
if (a->epoch < b->epoch)
return -1;
rc = verrevcmp(a->version, b->version);
if (rc)
return rc;
return verrevcmp(a->revision, b->revision);
}
oval_result_t oval_debian_evr_string_cmp(const char *state, const char *sys, oval_operation_t operation)
{
struct dpkg_version a, b;
const char *a_epoch, *a_version, *a_release;
const char *b_epoch, *b_version, *b_release;
char *a_copy, *b_copy;
long aux;
a_copy = oscap_strdup(sys);
b_copy = oscap_strdup(state);
parseEVR(a_copy, &a_epoch, &a_version, &a_release);
parseEVR(b_copy, &b_epoch, &b_version, &b_release);
if (!a_epoch || !b_epoch) {
oscap_seterr(OSCAP_EFAMILY_OVAL, "Invalid epoch.");
free(a_copy);
free(b_copy);
return OVAL_RESULT_ERROR;
}
aux = strtol(a_epoch, NULL, 10);
if (aux < INT_MIN || aux > INT_MAX) {
free(a_copy);
free(b_copy);
return OVAL_RESULT_ERROR; // Outside int range
}
a.epoch = (int) aux;
aux = strtol(b_epoch, NULL, 10);
if (aux < INT_MIN || aux > INT_MAX) {
free(a_copy);
free(b_copy);
return OVAL_RESULT_ERROR; // Outside int range
}
b.epoch = (int) aux;
a.version = a_version;
a.revision = a_release;
b.version = b_version;
b.revision = b_release;
int result = dpkg_version_compare(&a, &b);
free(a_copy);
free(b_copy);
switch (operation) {
case OVAL_OPERATION_EQUALS:
return ((result == 0) ? OVAL_RESULT_TRUE : OVAL_RESULT_FALSE);
case OVAL_OPERATION_NOT_EQUAL:
return ((result != 0) ? OVAL_RESULT_TRUE : OVAL_RESULT_FALSE);
case OVAL_OPERATION_GREATER_THAN:
return ((result > 0) ? OVAL_RESULT_TRUE : OVAL_RESULT_FALSE);
case OVAL_OPERATION_GREATER_THAN_OR_EQUAL:
return ((result >= 0) ? OVAL_RESULT_TRUE : OVAL_RESULT_FALSE);
case OVAL_OPERATION_LESS_THAN:
return ((result < 0) ? OVAL_RESULT_TRUE : OVAL_RESULT_FALSE);
case OVAL_OPERATION_LESS_THAN_OR_EQUAL:
return ((result <= 0) ? OVAL_RESULT_TRUE : OVAL_RESULT_FALSE);
default:
oscap_seterr(OSCAP_EFAMILY_OVAL, "Invalid type of operation in rpm version comparison: %d.", operation);
}
return OVAL_RESULT_ERROR;
}
oval_result_t oval_versiontype_cmp(const char *state, const char *syschar, oval_operation_t operation)
{
int state_idx = 0;
int sys_idx = 0;
int result = -1;
/* int is_equal = 1; */
for (state_idx = 0, sys_idx = 0; (((state[state_idx]) || (syschar[sys_idx])) && (result == -1));) { // keep going as long as there is data in either the state or sysitem
int tmp_state_int, tmp_sys_int;
tmp_state_int = atoi(&state[state_idx]); // look at the current data field (if we're at the end, atoi should return 0)
tmp_sys_int = atoi(&syschar[sys_idx]);
/* o rly?
if (tmp_state_int != tmp_sys_int)
is_equal = 0; // we might need this later (if we don't terminate early)
*/
if (operation == OVAL_OPERATION_EQUALS) {
if (tmp_state_int != tmp_sys_int)
return (OVAL_RESULT_FALSE);
} else if (operation == OVAL_OPERATION_NOT_EQUAL) {
if (tmp_state_int != tmp_sys_int)
return (OVAL_RESULT_TRUE);
} else if ((operation == OVAL_OPERATION_GREATER_THAN)
|| (operation == OVAL_OPERATION_GREATER_THAN_OR_EQUAL)) {
if (tmp_sys_int > tmp_state_int)
return (OVAL_RESULT_TRUE);
if (tmp_sys_int < tmp_state_int)
return (OVAL_RESULT_FALSE);
} else if ((operation == OVAL_OPERATION_LESS_THAN)
|| (operation == OVAL_OPERATION_LESS_THAN_OR_EQUAL)) {
if (tmp_sys_int < tmp_state_int)
return (OVAL_RESULT_TRUE);
if (tmp_sys_int > tmp_state_int)
return (OVAL_RESULT_FALSE);
} else {
oscap_seterr(OSCAP_EFAMILY_OVAL, "Invalid type of operation in version comparison: %d.", operation);
return OVAL_RESULT_ERROR;
}
if (state[state_idx])
++state_idx;
/* move to the next field within the version string (if there is one) */
while ((state[state_idx]) && (isdigit(state[state_idx])))
++state_idx;
if ((state[state_idx]) && (!isdigit(state[state_idx])))
++state_idx;
if (syschar[sys_idx])
++sys_idx;
/* move to the next field within the version string (if there is one) */
while ((syschar[sys_idx]) && (isdigit(syschar[sys_idx])))
++sys_idx;
if ((syschar[sys_idx]) && (!isdigit(syschar[sys_idx])))
++sys_idx;
}
// OK, we did not terminate early, and we're out of data, so we now know what to return
if (operation == OVAL_OPERATION_EQUALS) {
return (OVAL_RESULT_TRUE);
} else if (operation == OVAL_OPERATION_NOT_EQUAL) {
return (OVAL_RESULT_FALSE);
} else if (operation == OVAL_OPERATION_GREATER_THAN) {
return (OVAL_RESULT_FALSE);
} else if (operation == OVAL_OPERATION_GREATER_THAN_OR_EQUAL) {
return (OVAL_RESULT_TRUE);
} else if (operation == OVAL_OPERATION_LESS_THAN) {
return (OVAL_RESULT_FALSE);
} else if (operation == OVAL_OPERATION_LESS_THAN_OR_EQUAL) {
return (OVAL_RESULT_TRUE);
} // we have already filtered out the invalid ones
assert(0);
return OVAL_RESULT_ERROR;
}