Merge remote-tracking branch 'origin'

This commit is contained in:
psyc://psyced.org/~lynX 2013-09-04 19:32:38 +02:00
commit 658f67290e
9 changed files with 143 additions and 104 deletions

8
FAQ
View File

@ -20,3 +20,11 @@ Why our own derivate of the LDMud?
It should still be possible to run psyced with an off-the-mill ldmud and
counterwise run a MUD installation with a psyclpc.
===============================================================================
Why is ERQ crashing?
- erq doesn't conform to fortify code safety standards. this is ugly and
should be fixed but it is okay to just turn off fortify because erq is
fed exclusively with sanitized data from psyced, so buffer overruns are
possible but only theoretical.

15
INSTALL
View File

@ -95,20 +95,13 @@ Unix or Unix-like system
install-headers: install the driver header files in ${includedir}.
install-all: compile and install everything.
To actually run this, you need either psyced or a mudlib.
To actually use psyclpc, you need either psyced or a mudlib.
pthreads:
If your systems supports pthreads, --enable-use-pthreads will allow to
compile the driver with pthread support. Currently this means that
background threads will be used to write data to the network.
WARNING: pthreads support is still experimental and might crash
your driver!
Debian/Ubuntu Linux:
apt-get install libssl-dev libidn11-dev libpcre3 bison autoconf
FreeBSD:
When using gcc, it could happen that the compiler aborts with signal 10
or 11. The reasons are unknown, but you can restart the compilation
process by typing "make" again (and again...).
...
AIX 3.4:
The native compiler comes in several forms, of which only the

13
TODO
View File

@ -1,8 +1,16 @@
FROM LDMUD
+ we are glad to find out that Lars is back and working on ldmud! :D
- examine & apply: 2320, 2334, 2335, 2336, 2340, 2341
- ldmud has made quite some progress, although not exactly in the areas
that we are active in. syncing with ldmud is a good idea.
BUGS
- erq doesn't conform to fortify code safety standards. this is ugly and
should be fixed but it is okay to just turn off fortify because erq is
fed exclusively with sanitized data from psyced, so buffer overruns are
possible but only theoretical.
- configure should warn more vehemently when libidn is missing
- libpsyc isnt recognized even if properly installed
- x86_64 seems to require -ldl explicitly at the end of libs
- sometimes -lpsyc and -lpcre are added twice to $LIBS !?
- should autoconf to sysmalloc also for osol (OpenSolaris)
- #define USE_EXPAT und JSON werden trotzdem gesetzt in config.h
auch wenn configure keine libs gefunden hat (egal, wir verwenden beide nicht)
@ -75,6 +83,7 @@ NETWORKING
- add_message("%s", "\n*** Text lost in transmission ***\n");
should be passed to master instead.
+ _length: <eL> somebody implement a new input_to(#'get_data, length).
? replace ERQ with c-ares http://c-ares.haxx.se/ for async dns lookups
? MAYBE the whole networking should be replaced by libevent!??
+ support epoll() / kqueue (or just libevent?)

View File

@ -84,9 +84,11 @@ SFMT_FLAGS = -fno-strict-aliasing
#
MPATH=-DMUD_LIB='"$(MUD_LIB)"' -DBINDIR='"$(BINDIR)"' -DERQ_DIR='"$(ERQ_DIR)"'
#
# would be nicer to have this idn include in autoconf but it shouldn't hurt
# here either.. it makes this compile on OpenSolaris.. --lynX via tg 2010
CFLAGS=-DPROGNAME='"@PROGNAME@"' @EXTRA_CFLAGS@ $(OPTIMIZE) $(DEBUG) $(WARN) $(MPATH) $(PROFIL) -I/usr/include/idn -I/usr/local/include
# tg says for OpenSolaris we need -I/usr/include/idn here but since that path
# may exist when idnkit is installed and cause errors because of its 'wrong'
# assert.h, we can't include that path here by default. configure should
# learn when to add this path here, but that's tricky. --lynX
CFLAGS=-DPROGNAME='"@PROGNAME@"' @EXTRA_CFLAGS@ $(OPTIMIZE) $(DEBUG) $(WARN) $(MPATH) $(PROFIL) -I/usr/local/include
#
LIBS= @PKGLIBS@ @LIBS@ -lm
#
@ -475,6 +477,9 @@ pkg-pgsql.o : ../mudlib/sys/pgsql.h xalloc.h stdstrings.h simulate.h \
strfuns.h sent.h bytecode.h hash.h backend.h exec.h port.h config.h \
hosts/unix.h hosts/be/be.h machine.h
pkg-psyc.o : xalloc.h simulate.h pkg-psyc.h object.h mstrings.h mapping.h \
machine.h interpret.h efuns.h array.h
pkg-sqlite.o : xalloc.h stdstrings.h object.h svalue.h simulate.h \
mstrings.h interpret.h array.h my-alloca.h typedefs.h driver.h \
strfuns.h sent.h bytecode.h hash.h backend.h port.h config.h main.h \

View File

@ -1390,27 +1390,27 @@ int main(void)
lp_cv_need_lib_iconv=no,
lp_cv_need_lib_iconv=yes
))
if test "$lp_cv_need_lib_iconv" = "yes"; then
# Search the libraries
# if test "$lp_cv_need_lib_iconv" = "yes"; then
# # Search the libraries
tmp=""
# tmp=""
AC_CHECK_LIB(iconv,libiconv_close, tmp="$PKGLIBS -liconv")
# AC_CHECK_LIB(iconv,libiconv_close, tmp="$PKGLIBS -liconv")
if test "x$tmp" = "x"; then
CFLAGS="$saveflags -L/usr/local/lib"
AC_CHECK_LIB(iconv,libiconv_close, tmp="$PKGLIBS -L/usr/local/lib -liconv")
fi
# if test "x$tmp" = "x"; then
# CFLAGS="$saveflags -L/usr/local/lib"
# AC_CHECK_LIB(iconv,libiconv_close, tmp="$PKGLIBS -L/usr/local/lib -liconv")
# fi
if test "x$tmp" = "x"; then
echo "iconv library not found."
lp_cv_has_iconv="no"
else
PKGLIBS="$tmp"
fi
# if test "x$tmp" = "x"; then
# echo "iconv library not found."
# lp_cv_has_iconv="no"
# else
# PKGLIBS="$tmp"
# fi
CFLAGS="$saveflags"
fi
# CFLAGS="$saveflags"
# fi
fi
if test "x$lp_cv_has_iconv" = "xyes"; then

View File

@ -66,19 +66,19 @@ psyc_dispatch(mixed p) {
*/
#include "array.h"
#include "interpret.h"
#include "mapping.h"
#include "mstrings.h"
#include "object.h"
#include "pkg-psyc.h"
#include "simulate.h"
#include "xalloc.h"
#include "efuns.h"
#include "machine.h"
#ifdef HAS_PSYC
# include "array.h"
# include "efuns.h"
# include "interpret.h"
# include "mapping.h"
# include "mstrings.h"
# include "object.h"
# include "pkg-psyc.h"
# include "simulate.h"
# include "xalloc.h"
# include <stdio.h>
# include <unistd.h>
@ -97,8 +97,8 @@ fill_header_from_mapping (svalue_t *key, svalue_t *val, void *extra) {
uint8_t type;
svalue_t vsp, *lval;
psycList list;
psycString *elems = NULL;
PsycList list;
PsycString *elems = NULL;
if (key->type != T_STRING) {
errorf("fill_header_from_mapping: key type %d not supported\n", key->type);
@ -107,12 +107,12 @@ fill_header_from_mapping (svalue_t *key, svalue_t *val, void *extra) {
name = get_txt(key->u.str);
namelen = mstrsize(key->u.str);
type = psyc_getVarType2(name, namelen);
type = psyc_var_type(name, namelen);
if (m->num_values > 1)
oper = val[1].u.number;
if (!oper)
oper = C_GLYPH_OPERATOR_SET;
oper = PSYC_OPERATOR_SET;
switch (val->type) {
case T_STRING:
@ -142,7 +142,7 @@ fill_header_from_mapping (svalue_t *key, svalue_t *val, void *extra) {
case T_POINTER:
if (VEC_SIZE(val->u.vec)) {
elems = pxalloc(sizeof(psycString) * VEC_SIZE(val->u.vec));
elems = pxalloc(sizeof(PsycString) * VEC_SIZE(val->u.vec));
if (!elems) {
errorf("Out of memory in fill_header_from_mapping for elems\n");
return; // not reached
@ -152,7 +152,7 @@ fill_header_from_mapping (svalue_t *key, svalue_t *val, void *extra) {
lval = &(val->u.vec->item[i]);
switch (lval->type) {
case T_STRING:
elems[i] = (psycString){mstrsize(lval->u.str), get_txt(lval->u.str)};
elems[i] = (PsycString){mstrsize(lval->u.str), get_txt(lval->u.str)};
break;
case T_NUMBER:
case T_OBJECT:
@ -167,7 +167,7 @@ fill_header_from_mapping (svalue_t *key, svalue_t *val, void *extra) {
}
f_to_string(&vsp);
elems[i] = (psycString){mstrsize(vsp.u.str), get_txt(vsp.u.str)};
elems[i] = (PsycString){mstrsize(vsp.u.str), get_txt(vsp.u.str)};
break;
default:
errorf("fill_header_from_mapping: list value type %d not supported\n", lval->type);
@ -176,7 +176,7 @@ fill_header_from_mapping (svalue_t *key, svalue_t *val, void *extra) {
}
}
list = psyc_newList(elems, VEC_SIZE(val->u.vec), PSYC_LIST_CHECK_LENGTH);
psyc_list_init(&list, elems, VEC_SIZE(val->u.vec));
valuelen = list.length;
value = pxalloc(valuelen);
if (!value) {
@ -184,7 +184,7 @@ fill_header_from_mapping (svalue_t *key, svalue_t *val, void *extra) {
return; // not reached
}
psyc_renderList(&list, value, valuelen);
psyc_render_list(&list, value, valuelen);
break;
default:
@ -192,8 +192,8 @@ fill_header_from_mapping (svalue_t *key, svalue_t *val, void *extra) {
return; // not reached
}
m->header->modifiers[m->header->lines++] =
psyc_newModifier2(oper, name, namelen, value, valuelen, m->flag);
psyc_modifier_init(&m->header->modifiers[m->header->lines++],
oper, name, namelen, value, valuelen, m->flag);
}
/*-------------------------------------------------------------------------*/
@ -209,8 +209,8 @@ f_psyc_render(svalue_t *sp) {
size_t mlen, blen;
mapping_t *map;
psycPacket packet;
psycHeader headers[2];
PsycPacket packet;
PsycHeader headers[2];
// unless (sp->type == T_POINTER) return sp;
v = sp->u.vec;
@ -221,7 +221,7 @@ f_psyc_render(svalue_t *sp) {
map = v->item[i].u.map;
if (!MAP_SIZE(map)) continue;
headers[i].modifiers = malloc(sizeof(psycModifier) * MAP_SIZE(v->item[i].u.map));
headers[i].modifiers = malloc(sizeof(PsycModifier) * MAP_SIZE(v->item[i].u.map));
if (!headers[i].modifiers) {
errorf("Out of memory in psyc_render for modifier table.\n");
return sp; // not reached
@ -259,12 +259,13 @@ f_psyc_render(svalue_t *sp) {
blen = 0;
}
packet = psyc_newPacket2(headers[PACKET_ROUTING].modifiers,
headers[PACKET_ROUTING].lines,
headers[PACKET_ENTITY].modifiers,
headers[PACKET_ENTITY].lines,
meth, mlen, body, blen,
PSYC_PACKET_CHECK_LENGTH);
psyc_packet_init(&packet, headers[PACKET_ROUTING].modifiers,
headers[PACKET_ROUTING].lines,
headers[PACKET_ENTITY].modifiers,
headers[PACKET_ENTITY].lines,
meth, mlen, body, blen,
PSYC_STATE_NOOP, // TODO: add support for state ops
PSYC_PACKET_CHECK_LENGTH);
#ifdef DEBUG
printf("rendering... packet.length = %ld\n", packet.length);
@ -293,8 +294,8 @@ f_psyc_parse (svalue_t *sp) {
vector_t *v, *list;
mapping_t *map;
char oper = 0;
psycString name = {0,0}, value = {0,0}, elems[MAX_LIST_SIZE], elem;
psycParseListState listState;
PsycString name = {0,0}, value = {0,0}, elems[MAX_LIST_SIZE], elem, elem_type;
PsycParseListState listState;
int ret, retl, type = -1, error = 0;
size_t size, i;
ssize_t n;
@ -317,12 +318,12 @@ f_psyc_parse (svalue_t *sp) {
O_GET_PSYC_STATE(current_object) = state;
memset(state, 0, sizeof(psyc_state_t));
state->parser = pxalloc(sizeof(psycParseState));
state->parser = pxalloc(sizeof(PsycParseState));
if (!state->parser) {
errorf("Out of memory for psyc parse state struct.\n");
return sp; // not reached
}
psyc_initParseState(state->parser);
psyc_parse_state_init(state->parser, PSYC_PARSE_ALL);
}
v = state->packet;
@ -345,14 +346,14 @@ f_psyc_parse (svalue_t *sp) {
memcpy(buffer, state->remaining, state->remaining_len);
memcpy(buffer + state->remaining_len, get_txt(sp->u.str),
mstrsize(sp->u.str));
psyc_setParseBuffer2(state->parser, buffer,
state->remaining_len + mstrsize(sp->u.str));
psyc_parse_buffer_set(state->parser, buffer,
state->remaining_len + mstrsize(sp->u.str));
pfree(state->remaining);
state->remaining = NULL;
state->remaining_len = 0;
} else {
psyc_setParseBuffer2(state->parser, get_txt(sp->u.str),
mstrsize(sp->u.str));
psyc_parse_buffer_set(state->parser, get_txt(sp->u.str),
mstrsize(sp->u.str));
}
} else {
errorf("\npsyc_parse got type %d, not supported\n", sp->type);
@ -363,7 +364,7 @@ f_psyc_parse (svalue_t *sp) {
ret = psyc_parse(state->parser, &oper, &name, &value);
#ifdef DEBUG
printf("#%2d %c%.*s = %.*s\n", ret, oper ? oper : ' ',
(int)name.length, name.ptr, (int)value.length, value.ptr);
(int)name.length, name.data, (int)value.length, value.data);
#endif
if (!state->packet) {
state->packet = allocate_array(4);
@ -393,7 +394,7 @@ f_psyc_parse (svalue_t *sp) {
// incomplete entity or body
state->oper = oper;
state->name = mstring_alloc_string(name.length);
memcpy(get_txt(state->name), name.ptr, name.length);
memcpy(get_txt(state->name), name.data, name.length);
if (!state->name) {
errorf("Out of memory for name.\n");
return sp; // not reached
@ -401,7 +402,7 @@ f_psyc_parse (svalue_t *sp) {
// allocate memory for the total length of the value
state->value_len = 0;
state->value = mstring_alloc_string(psyc_getParseValueLength(state->parser));
state->value = mstring_alloc_string(psyc_parse_value_length(state->parser));
if (!state->value) {
errorf("Out of memory for value.\n");
return sp; // not reached
@ -411,7 +412,7 @@ f_psyc_parse (svalue_t *sp) {
case PSYC_PARSE_ENTITY_CONT: case PSYC_PARSE_BODY_CONT:
case PSYC_PARSE_ENTITY_END: case PSYC_PARSE_BODY_END:
// append value to tmp buffer in state
memcpy(get_txt(state->value) + state->value_len, value.ptr, value.length);
memcpy(get_txt(state->value) + state->value_len, value.data, value.length);
state->value_len += value.length;
}
@ -419,9 +420,9 @@ f_psyc_parse (svalue_t *sp) {
// incomplete entity or body parsing done,
// set oper/name/value to the ones saved in state
oper = state->oper;
name.ptr = get_txt(state->name);
name.data = get_txt(state->name);
name.length = mstrsize(state->name);
value.ptr = get_txt(state->value);
value.data = get_txt(state->value);
value.length = mstrsize(state->value);
}
@ -431,13 +432,13 @@ f_psyc_parse (svalue_t *sp) {
// new_n_tabled fetches a reference of a probably existing
// shared string
put_string(sv, new_n_tabled(name.ptr, name.length));
put_string(sv, new_n_tabled(name.data, name.length));
sv = get_map_lvalue(v->item[PACKET_ROUTING].u.map, sv);
put_number(&sv[1], oper);
// strings are capable of containing 0 so we can do this
// for binary data too. let's use a tabled string even
// for values of routing variables as they repeat a lot
put_string(sv, new_n_tabled(value.ptr, value.length));
put_string(sv, new_n_tabled(value.data, value.length));
break;
case PSYC_PARSE_ENTITY_START:
@ -449,54 +450,55 @@ f_psyc_parse (svalue_t *sp) {
sv = pxalloc(sizeof(svalue_t));
if (ret == PSYC_PARSE_ENTITY)
put_string(sv, new_n_tabled(name.ptr, name.length));
put_string(sv, new_n_tabled(name.data, name.length));
else // PSYC_PARSE_ENTITY_END
put_string(sv, make_tabled(state->name));
sv = get_map_lvalue(v->item[PACKET_ENTITY].u.map, sv);
put_number(&sv[1], oper);
type = psyc_getVarType(&name);
type = psyc_var_type(PSYC_S2ARG(name));
switch (type) {
case PSYC_TYPE_DATE: // number + PSYC_EPOCH
if (psyc_parseDate(&value, &timmy))
if (psyc_parse_uint(PSYC_S2ARG(value), &timmy))
put_number(sv, timmy);
else
error = PSYC_PARSE_ERROR_DATE;
break;
case PSYC_TYPE_TIME: // number
if (psyc_parseTime(&value, &timmy))
if (psyc_parse_uint(PSYC_S2ARG(value), &timmy))
put_number(sv, timmy);
else
error = PSYC_PARSE_ERROR_TIME;
break;
case PSYC_TYPE_AMOUNT: // number
if (psyc_parseNumber(&value, &n))
if (psyc_parse_uint(PSYC_S2ARG(value), &n))
put_number(sv, n);
else
error = PSYC_PARSE_ERROR_AMOUNT;
break;
case PSYC_TYPE_DEGREE: // first digit
if (value.length && value.ptr[0] >= '0' && value.ptr[0] <= '9')
put_number(sv, value.ptr[0] - '0');
if (value.length && value.data[0] >= '0' && value.data[0] <= '9')
put_number(sv, value.data[0] - '0');
else
error = PSYC_PARSE_ERROR_DEGREE;
break;
case PSYC_TYPE_FLAG: // 0 or 1
if (value.length && value.ptr[0] >= '0' && value.ptr[0] <= '1')
put_number(sv, value.ptr[0] - '0');
if (value.length && value.data[0] >= '0' && value.data[0] <= '1')
put_number(sv, value.data[0] - '0');
else
error = PSYC_PARSE_ERROR_FLAG;
break;
case PSYC_TYPE_LIST: // array
size = 0;
if (value.length) {
psyc_initParseListState(&listState);
psyc_setParseListBuffer(&listState, value);
elem = (psycString){0, 0};
psyc_parse_list_state_init(&listState);
psyc_parse_list_buffer_set(&listState, PSYC_S2ARG(value));
elem = (PsycString){0, 0};
elem_type = (PsycString){0, 0};
do {
retl = psyc_parseList(&listState, &elem);
retl = psyc_parse_list(&listState, &elem_type, &elem);
switch (retl) {
case PSYC_PARSE_LIST_END:
retl = 0;
@ -516,7 +518,7 @@ f_psyc_parse (svalue_t *sp) {
list = allocate_array(size);
for (i = 0; i < size; i++)
put_string(&list->item[i], new_n_tabled(elems[i].ptr,
put_string(&list->item[i], new_n_tabled(elems[i].data,
elems[i].length));
put_array(sv, list);
@ -525,8 +527,8 @@ f_psyc_parse (svalue_t *sp) {
if (ret == PSYC_PARSE_ENTITY)
// is it good to put entity variable values into the
// shared string table? probably yes.. but it's a guess
//t_string(sv, new_n_mstring(value.ptr, value.length));
put_string(sv, new_n_tabled(value.ptr, value.length));
//t_string(sv, new_n_mstring(value.data, value.length));
put_string(sv, new_n_tabled(value.data, value.length));
else // PSYC_PARSE_ENTITY_END
put_string(sv, state->value);
}
@ -544,11 +546,11 @@ f_psyc_parse (svalue_t *sp) {
case PSYC_PARSE_BODY:
// new_n_tabled gets the shared string for the method
put_string(&v->item[PACKET_METHOD],
new_n_tabled(name.ptr, name.length));
new_n_tabled(name.data, name.length));
// allocate an untabled string for the packet body
put_string(&v->item[PACKET_BODY],
new_n_mstring(value.ptr, value.length));
new_n_mstring(value.data, value.length));
break;
case PSYC_PARSE_COMPLETE:
@ -559,11 +561,11 @@ f_psyc_parse (svalue_t *sp) {
case PSYC_PARSE_INSUFFICIENT:
// insufficient data, save remaining bytes
state->remaining_len = psyc_getParseRemainingLength(state->parser);
state->remaining_len = psyc_parse_remaining_length(state->parser);
if (state->remaining_len) {
state->remaining = pxalloc(state->remaining_len);
memcpy(state->remaining,
psyc_getParseRemainingBuffer(state->parser),
psyc_parse_remaining_buffer(state->parser),
state->remaining_len);
} else
state->remaining = NULL;

View File

@ -6,6 +6,7 @@
* or int* where necessary.
*/
# include <psyc.h>
# include <psyc/packet.h>
# include <psyc/parse.h>
@ -28,7 +29,7 @@
# define PSYC_PARSE_ERROR_LIST_TOO_LARGE 7
typedef struct psyc_state_s {
psycParseState *parser;
PsycParseState *parser;
vector_t *packet;
// tmp storage for incomplete modifier/body
char oper;
@ -41,9 +42,9 @@ typedef struct psyc_state_s {
} psyc_state_t;
typedef struct psyc_modifier_s {
psycHeader *header;
PsycHeader *header;
p_int num_values;
psycModifierFlag flag;
PsycModifierFlag flag;
} psyc_modifier_t;
static inline void

View File

@ -518,12 +518,33 @@ tls_global_init (void)
, time_stamp());
goto ssl_init_err;
}
#ifdef SSL_CTRL_SET_TMP_ECDH
do {
EC_KEY *ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
if (ecdh == NULL) {
debug_message("%s TLS: Error setting ECDHE parameters:\n"
, time_stamp());
goto ssl_init_err;
} else {
debug_message("%s: TLS: using ECDHE, yai\n"
, time_stamp());
}
SSL_CTX_set_tmp_ecdh(context,ecdh);
EC_KEY_free(ecdh);
} while (0);
#endif
/* Avoid small subgroup attacks */
/* do not do SSLv2 */
SSL_CTX_set_options(context, SSL_OP_SINGLE_DH_USE);
SSL_CTX_set_options(context, SSL_OP_NO_SSLv2);
if (SSL_CTX_set_cipher_list(context, "HIGH:!DSS:!aNULL@STRENGTH") != 1) {
debug_message("SSL_CTX_set_cipher_list failed."
, time_stamp());
goto ssl_init_err;
}
/* OpenSSL successfully initialised */
tls_available = MY_TRUE;
return;
@ -1548,14 +1569,14 @@ f_tls_query_connection_info (svalue_t *sp)
#ifdef HAS_OPENSSL
put_c_string(&(rc->item[TLS_CIPHER])
, SSL_get_cipher(ip->tls_session));
put_number(&(rc->item[TLS_COMP]), 0);
put_number(&(rc->item[TLS_COMP]), ip->tls_session->session->compress_meth);
put_number(&(rc->item[TLS_KX]), 0);
put_number(&(rc->item[TLS_MAC]), 0);
put_c_string(&(rc->item[TLS_PROT])
, SSL_get_version(ip->tls_session));
/* warning: this session id is binary .. maybe fix it someday */
put_c_string(&(rc->item[TLS_SESSION])
, (char*) ip->tls_session->session->session_id);
put_c_n_string(&(rc->item[TLS_SESSION])
, (char*) ip->tls_session->session->session_id, ip->tls_session->session->session_id_length);
#elif defined(HAS_GNUTLS)
put_number(&(rc->item[TLS_CIPHER])
, gnutls_cipher_get(ip->tls_session));

View File

@ -17,7 +17,7 @@ version_longtype="stable"
# A timestamp, to be used by bumpversion and other scripts.
# It can be used, for example, to 'touch' this file on every build, thus
# forcing revision control systems to add it on every checkin automatically.
version_stamp="Tue May 24 18:47:17 CEST 2011"
version_stamp="Mon Aug 29 16:41:20 CEST 2011"
# Okay, LDMUD is using 3.x.x so to avoid conflicts let's just use 4.x.x
version_major=4