LCOV - code coverage report
Current view: top level - source4/utils/oLschema2ldif - lib.c (source / functions) Hit Total Coverage
Test: coverage report for master 2f515e9b Lines: 227 349 65.0 %
Date: 2024-04-21 15:09:00 Functions: 5 6 83.3 %

          Line data    Source code
       1             : /*
       2             :    ldb database library
       3             : 
       4             :    Copyright (C) Simo Sorce 2005
       5             : 
       6             :      ** NOTE! The following LGPL license applies to the ldb
       7             :      ** library. This does NOT imply that all of Samba is released
       8             :      ** under the LGPL
       9             : 
      10             :    This library is free software; you can redistribute it and/or
      11             :    modify it under the terms of the GNU Lesser General Public
      12             :    License as published by the Free Software Foundation; either
      13             :    version 3 of the License, or (at your option) any later version.
      14             : 
      15             :    This library is distributed in the hope that it will be useful,
      16             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
      17             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      18             :    Lesser General Public License for more details.
      19             : 
      20             :    You should have received a copy of the GNU Lesser General Public
      21             :    License along with this library; if not, see <http://www.gnu.org/licenses/>.
      22             : */
      23             : 
      24             : /*
      25             :  *  Name: ldb
      26             :  *
      27             :  *  Component: oLschema2ldif
      28             :  *
      29             :  *  Description: utility to convert an OpenLDAP schema into AD LDIF
      30             :  *
      31             :  *  Author: Simo Sorce
      32             :  */
      33             : 
      34             : #include "includes.h"
      35             : #include "./lib.h"
      36             : #include "ldb.h"
      37             : #include "../librpc/gen_ndr/ndr_misc.h"
      38             : 
      39             : #undef strcasecmp
      40             : 
      41             : #include <gnutls/gnutls.h>
      42             : #include <gnutls/crypto.h>
      43             : 
      44             : #define SCHEMA_UNKNOWN 0
      45             : #define SCHEMA_NAME 1
      46             : #define SCHEMA_SUP 2
      47             : #define SCHEMA_STRUCTURAL 3
      48             : #define SCHEMA_ABSTRACT 4
      49             : #define SCHEMA_AUXILIARY 5
      50             : #define SCHEMA_MUST 6
      51             : #define SCHEMA_MAY 7
      52             : #define SCHEMA_SINGLE_VALUE 8
      53             : #define SCHEMA_EQUALITY 9
      54             : #define SCHEMA_ORDERING 10
      55             : #define SCHEMA_SUBSTR 11
      56             : #define SCHEMA_SYNTAX 12
      57             : #define SCHEMA_DESC 13
      58             : 
      59             : struct schema_token {
      60             :         int type;
      61             :         char *value;
      62             : };
      63             : 
      64          18 : static int check_braces(const char *string)
      65             : {
      66          18 :         size_t b;
      67          18 :         char *c;
      68             : 
      69          18 :         b = 0;
      70          18 :         if ((c = strchr(string, '(')) == NULL) {
      71             :                 return -1;
      72             :         }
      73          18 :         b++;
      74          18 :         c++;
      75          25 :         while (b) {
      76          18 :                 c = strpbrk(c, "()");
      77          18 :                 if (c == NULL) return 1;
      78           7 :                 if (*c == '(') b++;
      79           7 :                 if (*c == ')') {
      80           7 :                         b--;
      81           7 :                         if (*(c - 1) != ' ' && c && (*(c + 1) == '\0')) {
      82             :                                 return 2;
      83             :                         }
      84             :                 }
      85           7 :                 c++;
      86             :         }
      87             :         return 0;
      88             : }
      89             : 
      90          48 : static char *skip_spaces(char *string) {
      91          48 :         return (string + strspn(string, " \t\n"));
      92             : }
      93             : 
      94           0 : static int add_multi_string(struct ldb_message *msg, const char *attr, char *values)
      95             : {
      96           0 :         char *c;
      97           0 :         char *s;
      98           0 :         int n;
      99             : 
     100           0 :         c = skip_spaces(values);
     101           0 :         while (*c) {
     102           0 :                 n = strcspn(c, " \t$");
     103           0 :                 s = talloc_strndup(msg, c, n);
     104           0 :                 if (ldb_msg_add_string(msg, attr, s) != 0) {
     105             :                         return -1;
     106             :                 }
     107           0 :                 c += n;
     108           0 :                 c += strspn(c, " \t$");
     109             :         }
     110             : 
     111             :         return 0;
     112             : }
     113             : 
     114             : #define MSG_ADD_STRING(a, v) do { if (ldb_msg_add_string(msg, a, v) != 0) goto failed; } while(0)
     115             : #define MSG_ADD_M_STRING(a, v) do { if (add_multi_string(msg, a, v) != 0) goto failed; } while(0)
     116             : 
     117           4 : static char *get_def_value(TALLOC_CTX *ctx, char **string)
     118             : {
     119           4 :         char *c = *string;
     120           4 :         char *value;
     121           4 :         int n;
     122             : 
     123           4 :         if (*c == '\'') {
     124           1 :                 c++;
     125           1 :                 n = strcspn(c, "\'");
     126           1 :                 value = talloc_strndup(ctx, c, n);
     127           1 :                 c += n;
     128           1 :                 if (*c != '\0') {
     129           0 :                         c++; /* skip closing \' */
     130             :                 }
     131             :         } else {
     132           3 :                 n = strcspn(c, " \t\n");
     133           3 :                 value = talloc_strndup(ctx, c, n);
     134           3 :                 c += n;
     135             :         }
     136           4 :         *string = c;
     137             : 
     138           4 :         return value;
     139             : }
     140             : 
     141          10 : static struct schema_token *get_next_schema_token(TALLOC_CTX *ctx, char **string)
     142             : {
     143          10 :         char *c = skip_spaces(*string);
     144          10 :         char *type;
     145          10 :         struct schema_token *token;
     146          10 :         int n;
     147             : 
     148          10 :         token = talloc(ctx, struct schema_token);
     149             : 
     150          10 :         n = strcspn(c, " \t\n");
     151          10 :         type = talloc_strndup(token, c, n);
     152          10 :         c += n;
     153          10 :         c = skip_spaces(c);
     154             : 
     155          10 :         if (strcasecmp("NAME", type) == 0) {
     156           1 :                 talloc_free(type);
     157           1 :                 token->type = SCHEMA_NAME;
     158             :                 /* we do not support aliases so we get only the first name given and skip others */
     159           1 :                 if (*c == '(') {
     160           0 :                         char *s = strchr(c, ')');
     161           0 :                         if (s == NULL) return NULL;
     162           0 :                         s = skip_spaces(s);
     163           0 :                         *string = s;
     164             : 
     165           0 :                         c++;
     166           0 :                         c = skip_spaces(c);
     167             :                 }
     168             : 
     169           1 :                 token->value = get_def_value(ctx, &c);
     170             : 
     171           1 :                 if (*string < c) { /* single name */
     172           1 :                         c = skip_spaces(c);
     173           1 :                         *string = c;
     174             :                 }
     175           1 :                 return token;
     176             :         }
     177           9 :         if (strcasecmp("SUP", type) == 0) {
     178           1 :                 talloc_free(type);
     179           1 :                 token->type = SCHEMA_SUP;
     180             : 
     181           1 :                 if (*c == '(') {
     182           1 :                         c++;
     183           1 :                         n = strcspn(c, ")");
     184           1 :                         token->value = talloc_strndup(ctx, c, n);
     185           1 :                         c += n;
     186           1 :                         if (*c == '\0') {
     187           1 :                                 talloc_free(token->value);
     188           1 :                                 return NULL;
     189             :                         }
     190           0 :                         c++;
     191             :                 } else {
     192           0 :                         token->value = get_def_value(ctx, &c);
     193             :                 }
     194             : 
     195           0 :                 c = skip_spaces(c);
     196           0 :                 *string = c;
     197           0 :                 return token;
     198             :         }
     199             : 
     200           8 :         if (strcasecmp("STRUCTURAL", type) == 0) {
     201           0 :                 talloc_free(type);
     202           0 :                 token->type = SCHEMA_STRUCTURAL;
     203           0 :                 *string = c;
     204           0 :                 return token;
     205             :         }
     206             : 
     207           8 :         if (strcasecmp("ABSTRACT", type) == 0) {
     208           0 :                 talloc_free(type);
     209           0 :                 token->type = SCHEMA_ABSTRACT;
     210           0 :                 *string = c;
     211           0 :                 return token;
     212             :         }
     213             : 
     214           8 :         if (strcasecmp("AUXILIARY", type) == 0) {
     215           0 :                 talloc_free(type);
     216           0 :                 token->type = SCHEMA_AUXILIARY;
     217           0 :                 *string = c;
     218           0 :                 return token;
     219             :         }
     220             : 
     221           8 :         if (strcasecmp("MUST", type) == 0) {
     222           1 :                 talloc_free(type);
     223           1 :                 token->type = SCHEMA_MUST;
     224             : 
     225           1 :                 if (*c == '(') {
     226           1 :                         c++;
     227           1 :                         n = strcspn(c, ")");
     228           1 :                         token->value = talloc_strndup(ctx, c, n);
     229           1 :                         c += n;
     230           1 :                         if (*c == '\0') {
     231           1 :                                 talloc_free(token->value);
     232           1 :                                 return NULL;
     233             :                         }
     234           0 :                         c++;
     235             :                 } else {
     236           0 :                         token->value = get_def_value(ctx, &c);
     237             :                 }
     238             : 
     239           0 :                 c = skip_spaces(c);
     240           0 :                 *string = c;
     241           0 :                 return token;
     242             :         }
     243             : 
     244           7 :         if (strcasecmp("MAY", type) == 0) {
     245           1 :                 talloc_free(type);
     246           1 :                 token->type = SCHEMA_MAY;
     247             : 
     248           1 :                 if (*c == '(') {
     249           1 :                         c++;
     250           1 :                         n = strcspn(c, ")");
     251           1 :                         token->value = talloc_strndup(ctx, c, n);
     252           1 :                         c += n;
     253           1 :                         if (*c == '\0') {
     254           1 :                                 talloc_free(token->value);
     255           1 :                                 return NULL;
     256             :                         }
     257           0 :                         c++;
     258             :                 } else {
     259           0 :                         token->value = get_def_value(ctx, &c);
     260             :                 }
     261             : 
     262           0 :                 c = skip_spaces(c);
     263           0 :                 *string = c;
     264           0 :                 return token;
     265             :         }
     266             : 
     267           6 :         if (strcasecmp("SINGLE-VALUE", type) == 0) {
     268           0 :                 talloc_free(type);
     269           0 :                 token->type = SCHEMA_SINGLE_VALUE;
     270           0 :                 *string = c;
     271           0 :                 return token;
     272             :         }
     273             : 
     274           6 :         if (strcasecmp("EQUALITY", type) == 0) {
     275           0 :                 talloc_free(type);
     276           0 :                 token->type = SCHEMA_EQUALITY;
     277             : 
     278           0 :                 token->value = get_def_value(ctx, &c);
     279             : 
     280           0 :                 c = skip_spaces(c);
     281           0 :                 *string = c;
     282           0 :                 return token;
     283             :         }
     284             : 
     285           6 :         if (strcasecmp("ORDERING", type) == 0) {
     286           0 :                 talloc_free(type);
     287           0 :                 token->type = SCHEMA_ORDERING;
     288             : 
     289           0 :                 token->value = get_def_value(ctx, &c);
     290             : 
     291           0 :                 c = skip_spaces(c);
     292           0 :                 *string = c;
     293           0 :                 return token;
     294             :         }
     295             : 
     296           6 :         if (strcasecmp("SUBSTR", type) == 0) {
     297           0 :                 talloc_free(type);
     298           0 :                 token->type = SCHEMA_SUBSTR;
     299             : 
     300           0 :                 token->value = get_def_value(ctx, &c);
     301             : 
     302           0 :                 c = skip_spaces(c);
     303           0 :                 *string = c;
     304           0 :                 return token;
     305             :         }
     306             : 
     307           6 :         if (strcasecmp("SYNTAX", type) == 0) {
     308           3 :                 talloc_free(type);
     309           3 :                 token->type = SCHEMA_SYNTAX;
     310             : 
     311           3 :                 token->value = get_def_value(ctx, &c);
     312             : 
     313           3 :                 c = skip_spaces(c);
     314           3 :                 *string = c;
     315           3 :                 return token;
     316             :         }
     317             : 
     318           3 :         if (strcasecmp("DESC", type) == 0) {
     319           0 :                 talloc_free(type);
     320           0 :                 token->type = SCHEMA_DESC;
     321             : 
     322           0 :                 token->value = get_def_value(ctx, &c);
     323             : 
     324           0 :                 c = skip_spaces(c);
     325           0 :                 *string = c;
     326           0 :                 return token;
     327             :         }
     328             : 
     329           3 :         token->type = SCHEMA_UNKNOWN;
     330           3 :         token->value = type;
     331           3 :         if (*c == ')') {
     332           0 :                 *string = c;
     333           0 :                 return token;
     334             :         }
     335           3 :         if (*c == '\'') {
     336           1 :                 c = strchr(++c, '\'');
     337           1 :                 if (c == NULL || *c == '\0') {
     338             :                         return NULL;
     339             :                 }
     340           1 :                 c++;
     341             :         } else {
     342           2 :                 c += strcspn(c, " \t\n");
     343             :         }
     344           3 :         c = skip_spaces(c);
     345           3 :         *string = c;
     346             : 
     347           3 :         return token;
     348             : }
     349             : 
     350           7 : static struct ldb_message *process_entry(TALLOC_CTX *mem_ctx, struct conv_options *opt, const char *entry)
     351           7 : {
     352           7 :         TALLOC_CTX *ctx;
     353           7 :         struct ldb_message *msg;
     354           7 :         struct schema_token *token;
     355           7 :         char *c, *s;
     356           7 :         int n;
     357             : 
     358           7 :         uint8_t digest[gnutls_hash_get_len(GNUTLS_DIG_SHA256)];
     359           7 :         int rc;
     360             : 
     361           7 :         struct GUID guid;
     362             : 
     363           7 :         bool isAttribute = false;
     364           7 :         bool single_valued = false;
     365             : 
     366           7 :         ctx = talloc_new(mem_ctx);
     367           7 :         if (ctx == NULL) {
     368             :                 return NULL;
     369             :         }
     370           7 :         msg = ldb_msg_new(ctx);
     371           7 :         if (msg == NULL) {
     372           0 :                 goto failed;
     373             :         }
     374             : 
     375           7 :         ldb_msg_add_string(msg, "objectClass", "top");
     376             : 
     377           7 :         c = talloc_strdup(ctx, entry);
     378           7 :         if (!c) return NULL;
     379             : 
     380           7 :         c = skip_spaces(c);
     381             : 
     382           7 :         switch (*c) {
     383           6 :         case 'a':
     384           6 :                 if (strncmp(c, "attributetype", 13) == 0) {
     385           6 :                         c += 13;
     386           6 :                         MSG_ADD_STRING("objectClass", "attributeSchema");
     387             :                         isAttribute = true;
     388             :                         break;
     389             :                 }
     390           0 :                 goto failed;
     391           1 :         case 'o':
     392           1 :                 if (strncmp(c, "objectclass", 11) == 0) {
     393           1 :                         c += 11;
     394           1 :                         MSG_ADD_STRING("objectClass", "classSchema");
     395             :                         break;
     396             :                 }
     397           0 :                 goto failed;
     398           0 :         default:
     399           0 :                 goto failed;
     400             :         }
     401             : 
     402           7 :         c = strchr(c, '(');
     403           7 :         if (c == NULL) goto failed;
     404           7 :         c++;
     405             : 
     406           7 :         c = skip_spaces(c);
     407             : 
     408             :         /* get attributeID */
     409           7 :         n = strcspn(c, " \t");
     410           7 :         s = talloc_strndup(msg, c, n);
     411           7 :         if (isAttribute) {
     412           6 :                 MSG_ADD_STRING("attributeID", s);
     413             :         } else {
     414           1 :                 MSG_ADD_STRING("governsID", s);
     415             :         }
     416             : 
     417           7 :         rc = gnutls_hash_fast(GNUTLS_DIG_SHA256,
     418             :                               s,
     419             :                               strlen(s),
     420             :                               digest);
     421           7 :         if (rc < 0) {
     422           0 :                 goto failed;
     423             :         }
     424             : 
     425           7 :         memcpy(&guid, digest, sizeof(struct GUID));
     426             : 
     427           7 :         if (dsdb_msg_add_guid(msg, &guid, "schemaIdGuid") != 0) {
     428           0 :                 goto failed;
     429             :         }
     430             : 
     431           7 :         c += n;
     432           7 :         c = skip_spaces(c);
     433             : 
     434          11 :         while (*c != ')') {
     435          10 :                 token = get_next_schema_token(msg, &c);
     436          10 :                 if (!token) goto failed;
     437             : 
     438           7 :                 switch (token->type) {
     439           1 :                 case SCHEMA_NAME:
     440           1 :                         MSG_ADD_STRING("cn", token->value);
     441           1 :                         MSG_ADD_STRING("name", token->value);
     442           1 :                         MSG_ADD_STRING("lDAPDisplayName", token->value);
     443           1 :                         msg->dn = ldb_dn_copy(msg, opt->basedn);
     444           1 :                         ldb_dn_add_child_fmt(msg->dn, "CN=%s,CN=Schema,CN=Configuration", token->value);
     445           1 :                         break;
     446             : 
     447           0 :                 case SCHEMA_SUP:
     448           0 :                         MSG_ADD_M_STRING("subClassOf", token->value);
     449             :                         break;
     450             : 
     451           0 :                 case SCHEMA_STRUCTURAL:
     452           0 :                         MSG_ADD_STRING("objectClassCategory", "1");
     453             :                         break;
     454             : 
     455           0 :                 case SCHEMA_ABSTRACT:
     456           0 :                         MSG_ADD_STRING("objectClassCategory", "2");
     457             :                         break;
     458             : 
     459           0 :                 case SCHEMA_AUXILIARY:
     460           0 :                         MSG_ADD_STRING("objectClassCategory", "3");
     461             :                         break;
     462             : 
     463           0 :                 case SCHEMA_MUST:
     464           0 :                         MSG_ADD_M_STRING("mustContain", token->value);
     465             :                         break;
     466             : 
     467           0 :                 case SCHEMA_MAY:
     468           0 :                         MSG_ADD_M_STRING("mayContain", token->value);
     469             :                         break;
     470             : 
     471           0 :                 case SCHEMA_SINGLE_VALUE:
     472           0 :                         single_valued = true;
     473           0 :                         break;
     474             : 
     475             :                 case SCHEMA_EQUALITY:
     476             :                         /* TODO */
     477             :                         break;
     478             : 
     479             :                 case SCHEMA_ORDERING:
     480             :                         /* TODO */
     481             :                         break;
     482             : 
     483             :                 case SCHEMA_SUBSTR:
     484             :                         /* TODO */
     485             :                         break;
     486             : 
     487           3 :                 case SCHEMA_SYNTAX:
     488             :                 {
     489           3 :                         char *syntax_oid;
     490           3 :                         const struct dsdb_syntax *map;
     491           3 :                         char *oMSyntax;
     492             : 
     493           3 :                         n = strcspn(token->value, "{");
     494           3 :                         syntax_oid = talloc_strndup(ctx, token->value, n);
     495             : 
     496           3 :                         map = find_syntax_map_by_standard_oid(syntax_oid);
     497           3 :                         if (!map) {
     498             :                                 break;
     499             :                         }
     500             : 
     501           0 :                         MSG_ADD_STRING("attributeSyntax", map->attributeSyntax_oid);
     502             : 
     503           0 :                         oMSyntax = talloc_asprintf(msg, "%d", map->oMSyntax);
     504           0 :                         MSG_ADD_STRING("oMSyntax", oMSyntax);
     505             : 
     506             :                         break;
     507             :                 }
     508           0 :                 case SCHEMA_DESC:
     509           0 :                         MSG_ADD_STRING("description", token->value);
     510             :                         break;
     511             : 
     512           3 :                 default:
     513           3 :                         fprintf(stderr, "Unknown Definition: %s\n", token->value);
     514           3 :                         goto failed;
     515             :                 }
     516             :         }
     517             : 
     518           1 :         if (isAttribute) {
     519           0 :                 MSG_ADD_STRING("isSingleValued", single_valued ? "TRUE" : "FALSE");
     520             :         } else {
     521           1 :                 if (msg->dn == NULL) {
     522           1 :                         goto failed;
     523             :                 }
     524           0 :                 MSG_ADD_STRING("defaultObjectCategory", ldb_dn_get_linearized(msg->dn));
     525             :         }
     526             : 
     527           0 :         talloc_steal(mem_ctx, msg);
     528           0 :         talloc_free(ctx);
     529           0 :         return msg;
     530             : 
     531           7 : failed:
     532           7 :         talloc_free(ctx);
     533           7 :         return NULL;
     534             : }
     535             : 
     536           7 : struct schema_conv process_file(TALLOC_CTX *mem_ctx, struct conv_options *opt)
     537             : {
     538           7 :         struct schema_conv ret;
     539           7 :         char *entry;
     540           7 :         int c, t, line;
     541           7 :         struct ldb_ldif ldif;
     542           7 :         FILE *in = opt->in;
     543           7 :         FILE *out = opt->out;
     544             : 
     545           7 :         ldif.changetype = LDB_CHANGETYPE_NONE;
     546             : 
     547           7 :         ret.count = 0;
     548           7 :         ret.failures = 0;
     549           7 :         line = 0;
     550             : 
     551          13 :         while ((c = fgetc(in)) != EOF) {
     552           7 :                 line++;
     553             :                 /* fprintf(stderr, "Parsing line %d\n", line); */
     554           7 :                 if (c == '#') {
     555           0 :                         do {
     556           0 :                                 c = fgetc(in);
     557           0 :                         } while (c != EOF && c != '\n');
     558           0 :                         continue;
     559             :                 }
     560           7 :                 if (c == '\n') {
     561           0 :                         continue;
     562             :                 }
     563             : 
     564           7 :                 t = 0;
     565           7 :                 entry = talloc_array(mem_ctx, char, 1024);
     566           7 :                 if (entry == NULL) exit(-1);
     567             : 
     568         543 :                 do {
     569         543 :                         if (c == '\n') {
     570          17 :                                 int ret2 = 0;
     571          17 :                                 entry[t] = '\0';
     572          17 :                                 ret2 = check_braces(entry);
     573          17 :                                 if (ret2 == 0) {
     574           6 :                                         ret.count++;
     575           6 :                                         ldif.msg = process_entry(mem_ctx, opt, entry);
     576           6 :                                         if (ldif.msg == NULL) {
     577           6 :                                                 ret.failures++;
     578           6 :                                                 fprintf(stderr, "No valid msg from entry \n[%s]\n at line %d\n", entry, line);
     579             :                                                 break;
     580             :                                         }
     581           0 :                                         ldb_ldif_write_file(opt->ldb_ctx, out, &ldif);
     582           0 :                                         break;
     583             :                                 }
     584          11 :                                 if (ret2 == 2) {
     585           0 :                                         fprintf(stderr, "Invalid entry %s, closing braces need to be preceded by a space\n", entry);
     586           0 :                                         ret.failures++;
     587           0 :                                         break;
     588             :                                 }
     589          11 :                                 line++;
     590             :                         } else {
     591         526 :                                 entry[t] = c;
     592         526 :                                 t++;
     593             :                         }
     594         537 :                         if ((t % 1023) == 0) {
     595           0 :                                 entry = talloc_realloc(mem_ctx, entry, char, t + 1024);
     596           0 :                                 if (entry == NULL) exit(-1);
     597             :                         }
     598         537 :                 } while ((c = fgetc(in)) != EOF);
     599             : 
     600           7 :                 if (c != '\n') {
     601           1 :                         entry[t] = '\0';
     602           1 :                         if (check_braces(entry) == 0) {
     603           1 :                                 ret.count++;
     604           1 :                                 ldif.msg = process_entry(mem_ctx, opt, entry);
     605           1 :                                 if (ldif.msg == NULL) {
     606           1 :                                         ret.failures++;
     607           1 :                                         fprintf(stderr, "No valid msg from entry \n[%s]\n at line %d\n", entry, line);
     608             :                                         break;
     609             :                                 }
     610           0 :                                 ldb_ldif_write_file(opt->ldb_ctx, out, &ldif);
     611             :                         } else {
     612           0 :                                 fprintf(stderr, "malformed entry on line %d\n", line);
     613           0 :                                 ret.failures++;
     614             :                         }
     615             :                 }
     616             : 
     617           6 :                 if (c == EOF) break;
     618             :         }
     619             : 
     620           7 :         return ret;
     621             : }

Generated by: LCOV version 1.14