summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNoriko Hosoi <nhosoi@redhat.com>2006-01-10 23:53:08 +0000
committerNoriko Hosoi <nhosoi@redhat.com>2006-01-10 23:53:08 +0000
commit67385d898398f60886047b9239d027cf997f9433 (patch)
treec7f9daeed093bdef2afa6f809c13cdfb457a94c7
parent4120eda907e600d7d4b7c3b54e747db8284b4037 (diff)
downloadds-67385d898398f60886047b9239d027cf997f9433.tar.gz
ds-67385d898398f60886047b9239d027cf997f9433.tar.xz
ds-67385d898398f60886047b9239d027cf997f9433.zip
[174776] Multiple restores from a non-existant directory could wipe out database
If the specified backup dir does not exist, does not contain necessary files (including the directory is not accessible), is not a directory, or is identical to the path to the db dir, issues an error and stops restoring before wiping out the database.
-rw-r--r--ldap/servers/slapd/back-ldbm/dblayer.c219
-rw-r--r--ldap/servers/slapd/proto-slap.h2
-rw-r--r--ldap/servers/slapd/util.c159
3 files changed, 248 insertions, 132 deletions
diff --git a/ldap/servers/slapd/back-ldbm/dblayer.c b/ldap/servers/slapd/back-ldbm/dblayer.c
index 0d7cc703..da3f988f 100644
--- a/ldap/servers/slapd/back-ldbm/dblayer.c
+++ b/ldap/servers/slapd/back-ldbm/dblayer.c
@@ -799,16 +799,12 @@ void dblayer_sys_pages(size_t *pagesize, size_t *pages, size_t *procpages, size_
#if defined ( hpux )
{
struct pst_static pst;
- struct pst_dynamic pst_dyn;
int rval = pstat_getstatic(&pst, sizeof(pst), (size_t)1, 0);
if (rval < 0) /* pstat_getstatic failed */
return;
*pagesize = pst.page_size;
*pages = pst.physical_memory;
*availpages = dblayer_getvirtualmemsize() / *pagesize;
- rval = pstat_getdynamic(&pst_dyn, sizeof(pst_dyn), (size_t)1, 0);
- if (rval < 0) /* pstat_getdynamic failed */
- return;
if (procpages)
{
#define BURST (size_t)32 /* get BURST proc info at one time... */
@@ -2386,6 +2382,7 @@ int dblayer_post_close(struct ldbminfo *li, int dbmode)
priv->dblayer_env = NULL; /* pEnv is now garbage */
+#if 0 /* DBDB do NOT remove the environment: bad, bad idea */
if (return_value == 0) {
DB_ENV *env = 0;
return_value = db_env_create(&env, 0);
@@ -2397,9 +2394,7 @@ int dblayer_post_close(struct ldbminfo *li, int dbmode)
if (return_value == 0) {
char *home_dir = dblayer_get_home_dir(li, NULL);
if (home_dir)
- /* DBDB do NOT remove the environment: bad, bad idea
- * return_value = env->remove(env, home_dir, 0);
- */
+ return_value = env->remove(env, home_dir, 0);
if (0 == return_value
&& !((DBLAYER_ARCHIVE_MODE|DBLAYER_EXPORT_MODE) & dbmode)
&& !priv->dblayer_bad_stuff_happened) {
@@ -2415,6 +2410,7 @@ int dblayer_post_close(struct ldbminfo *li, int dbmode)
}
}
}
+#endif
return return_value;
}
@@ -4451,7 +4447,7 @@ int dblayer_copy_directory(struct ldbminfo *li,
int *cnt,
int instance_dir_flag,
int indexonly,
- int resetlsns)
+ int resetlsns)
{
dblayer_private *priv = NULL;
char *new_src_dir = NULL;
@@ -4593,14 +4589,14 @@ int dblayer_copy_directory(struct ldbminfo *li,
}
/* copy filename1 to filename2 */
- /* If the file is a database file, and resetlsns is set, then we need to do a key by key copy */
- if (strcmp(LDBM_FILENAME_SUFFIX, last_four_chars(filename1)) == 0 && resetlsns) {
- return_value = dblayer_copy_file_resetlsns(src_dir, filename1, filename2,
+ /* If the file is a database file, and resetlsns is set, then we need to do a key by key copy */
+ if (strcmp(LDBM_FILENAME_SUFFIX, last_four_chars(filename1)) == 0 && resetlsns) {
+ return_value = dblayer_copy_file_resetlsns(src_dir, filename1, filename2,
0, priv);
- } else {
- return_value = dblayer_copyfile(filename1, filename2,
+ } else {
+ return_value = dblayer_copyfile(filename1, filename2,
0, priv->dblayer_file_mode);
- }
+ }
slapi_ch_free((void**)&filename1);
slapi_ch_free((void**)&filename2);
if (0 > return_value)
@@ -4751,11 +4747,13 @@ int dblayer_backup(struct ldbminfo *li, char *dest_dir, Slapi_Task *task)
/* compare: make sure everything in list A is still in list B */
ok = 1;
- for (listi = listA; *listi && ok; listi++) {
+ for (listi = listA; listi && *listi && ok; listi++) {
int found = 0;
- for (listj = listB; *listj && !found; listj++) {
- if (strcmp(*listi, *listj) == 0)
+ for (listj = listB; listj && *listj && !found; listj++) {
+ if (strcmp(*listi, *listj) == 0) {
found = 1;
+ break;
+ }
}
if (! found) {
ok = 0; /* missing log: start over */
@@ -4775,7 +4773,7 @@ int dblayer_backup(struct ldbminfo *li, char *dest_dir, Slapi_Task *task)
}
if (ok) {
- size_t p1len, p2len;
+ size_t p1len, p2len;
char **listptr;
prefix = NULL;
@@ -4786,12 +4784,12 @@ int dblayer_backup(struct ldbminfo *li, char *dest_dir, Slapi_Task *task)
prefix = home_dir;
}
/* log files have the same filename len(100 is a safety net:) */
- p1len = strlen(prefix) + strlen(*listB) + 100;
+ p1len = strlen(prefix) + strlen(*listB) + 100;
pathname1 = (char *)slapi_ch_malloc(p1len);
- p2len = strlen(dest_dir) + strlen(*listB) + 100;
+ p2len = strlen(dest_dir) + strlen(*listB) + 100;
pathname2 = (char *)slapi_ch_malloc(p2len);
/* We copy those over */
- for (listptr = listB; (*listptr) && ok; ++listptr) {
+ for (listptr = listB; listptr && *listptr && ok; ++listptr) {
PR_snprintf(pathname1, p1len, "%s/%s", prefix, *listptr);
PR_snprintf(pathname2, p2len, "%s/%s", dest_dir, *listptr);
LDAPDebug(LDAP_DEBUG_ANY, "Backing up file %d (%s)\n",
@@ -5172,8 +5170,9 @@ int dblayer_restore(struct ldbminfo *li, char *src_dir, Slapi_Task *task, char *
int dbmode;
int action = 0;
char *home_dir = NULL;
- char *real_src_dir = NULL;
- int frirestore = 0; /* Is a an FRI/single instance restore. 0 for no, 1 for yes */
+ char *real_src_dir = NULL;
+ int frirestore = 0; /* Is a an FRI/single instance restore. 0 for no, 1 for yes */
+ struct stat sbuf;
PR_ASSERT(NULL != li);
priv = (dblayer_private*)li->li_dblayer_private;
@@ -5188,7 +5187,7 @@ int dblayer_restore(struct ldbminfo *li, char *src_dir, Slapi_Task *task, char *
PR_Unlock(li->li_config_mutex);
home_dir = dblayer_get_home_dir(li, NULL);
-
+
if (NULL == home_dir)
{
LDAPDebug(LDAP_DEBUG_ANY,
@@ -5200,54 +5199,82 @@ int dblayer_restore(struct ldbminfo *li, char *src_dir, Slapi_Task *task, char *
/* If it is, we fail */
/* We check on the source staging area, no point in going further if it
* isn't there */
+ if (stat(src_dir, &sbuf) < 0) {
+ LDAPDebug(LDAP_DEBUG_ANY, "restore: backup directory %s does not "
+ "exist.\n", src_dir, 0, 0);
+ if (task) {
+ slapi_task_log_notice(task,
+ "Backup directory %s does not exist.\n", src_dir);
+ }
+ return LDAP_UNWILLING_TO_PERFORM;
+ } else if (!S_ISDIR(sbuf.st_mode)) {
+ LDAPDebug(LDAP_DEBUG_ANY, "restore: backup directory %s is not "
+ "a directory.\n", src_dir, 0, 0);
+ if (task) {
+ slapi_task_log_notice(task,
+ "Backup directory %s is not a directory.\n", src_dir);
+ }
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
if (!dbversion_exists(li, src_dir)) {
- LDAPDebug(LDAP_DEBUG_ANY, "restore: source directory %s does not "
+ LDAPDebug(LDAP_DEBUG_ANY, "restore: backup directory %s does not "
"contain a complete backup\n", src_dir, 0, 0);
-
-
if (task) {
- slapi_task_log_notice(task, "Source directory %s does not "
+ slapi_task_log_notice(task, "Backup directory %s does not "
"contain a complete backup", src_dir );
}
+ return LDAP_UNWILLING_TO_PERFORM;
}
- /* If this is a FRI restore, the bename will be non-NULL */
- if (bename != NULL)
- frirestore = 1;
+ /* If this is a FRI restore, the bename will be non-NULL */
+ if (bename != NULL)
+ frirestore = 1;
- /*
- * Check if the target is a superset of the backup.
- * If not don't restore any db at all, otherwise
- * the target will be crippled.
- */
- dirhandle = PR_OpenDir(src_dir);
- if (NULL != dirhandle)
- {
- while ((direntry = PR_ReadDir(dirhandle, PR_SKIP_DOT | PR_SKIP_DOT_DOT))
- && direntry->name)
- {
+ /*
+ * Check if the target is a superset of the backup.
+ * If not don't restore any db at all, otherwise
+ * the target will be crippled.
+ */
+ dirhandle = PR_OpenDir(src_dir);
+ if (NULL != dirhandle)
+ {
+ while ((direntry = PR_ReadDir(dirhandle, PR_SKIP_DOT | PR_SKIP_DOT_DOT))
+ && direntry->name)
+ {
PR_snprintf(filename1, MAXPATHLEN, "%s/%s", src_dir, direntry->name);
- if(!frirestore || strcmp(direntry->name,bename)==0)
- {
+ if(!frirestore || strcmp(direntry->name,bename)==0)
+ {
tmp_rval = PR_GetFileInfo(filename1, &info);
if (tmp_rval == PR_SUCCESS && PR_FILE_DIRECTORY == info.type) {
- inst = ldbm_instance_find_by_name(li, (char *)direntry->name);
- if ( inst == NULL)
- {
- LDAPDebug(LDAP_DEBUG_ANY,
- "ERROR: target server has no %s configured\n", direntry->name, 0, 0);
- if (task) {
- slapi_task_log_notice(task,
- "ERROR: target server has no %s configured\n", direntry->name);
- }
- PR_CloseDir(dirhandle);
- return LDAP_UNWILLING_TO_PERFORM;
- }
- }
- }
- }
- PR_CloseDir(dirhandle);
- }
+ inst = ldbm_instance_find_by_name(li, (char *)direntry->name);
+ if ( inst == NULL)
+ {
+ LDAPDebug(LDAP_DEBUG_ANY,
+ "ERROR: target server has no %s configured\n", direntry->name, 0, 0);
+ if (task) {
+ slapi_task_log_notice(task,
+ "ERROR: target server has no %s configured\n", direntry->name);
+ }
+ PR_CloseDir(dirhandle);
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
+
+ if (slapd_comp_path(src_dir, inst->inst_parent_dir_name)
+ == 0) {
+ LDAPDebug(LDAP_DEBUG_ANY,
+ "ERROR: backup dir %s and target dir %s are identical\n", src_dir, inst->inst_parent_dir_name, 0);
+ if (task) {
+ slapi_task_log_notice(task,
+ "ERROR: backup dir %s and target dir %s are identical\n", src_dir, inst->inst_parent_dir_name);
+ }
+ PR_CloseDir(dirhandle);
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
+ }
+ }
+ }
+ PR_CloseDir(dirhandle);
+ }
/* We delete the existing database */
@@ -5256,20 +5283,20 @@ int dblayer_restore(struct ldbminfo *li, char *src_dir, Slapi_Task *task, char *
return return_value;
}
- if (frirestore) /*if we are restoring a single backend*/
- {
- char *new_src_dir = NULL;
- return_value = dblayer_fri_restore(home_dir,src_dir,priv,task,&new_src_dir,bename);
- if (return_value) {
- return return_value;
- }
- /* Now modify the src_dir to point to our recovery area and carry on as if nothing had happened... */
- real_src_dir = new_src_dir;
- } else
- {
- /* Otherwise use the src_dir from the caller */
- real_src_dir = src_dir;
- }
+ if (frirestore) /*if we are restoring a single backend*/
+ {
+ char *new_src_dir = NULL;
+ return_value = dblayer_fri_restore(home_dir,src_dir,priv,task,&new_src_dir,bename);
+ if (return_value) {
+ return return_value;
+ }
+ /* Now modify the src_dir to point to our recovery area and carry on as if nothing had happened... */
+ real_src_dir = new_src_dir;
+ } else
+ {
+ /* Otherwise use the src_dir from the caller */
+ real_src_dir = src_dir;
+ }
/* We copy the files over from the staging area */
/* We want to treat the logfiles specially: if there's
@@ -5300,14 +5327,14 @@ int dblayer_restore(struct ldbminfo *li, char *src_dir, Slapi_Task *task, char *
* directory is located.
*/
- inst = ldbm_instance_find_by_name(li, (char *)direntry->name);
- if (inst == NULL)
- continue;
+ inst = ldbm_instance_find_by_name(li, (char *)direntry->name);
+ if (inst == NULL)
+ continue;
restore_dir = inst->inst_parent_dir_name;
- /* If we're doing a partial restore, we need to reset the LSNs on the data files */
+ /* If we're doing a partial restore, we need to reset the LSNs on the data files */
if (dblayer_copy_directory(li, task, filename1,
- restore_dir, 1 /* restore */, &cnt, 0, 0, (bename) ? 1 : 0) == 0)
+ restore_dir, 1 /* restore */, &cnt, 0, 0, (bename) ? 1 : 0) == 0)
continue;
else
{
@@ -5418,7 +5445,7 @@ int dblayer_restore(struct ldbminfo *li, char *src_dir, Slapi_Task *task, char *
slapi_task_log_notice(task, "Failed to init database");
}
return_value = tmp_rval;
- goto error_out;
+ goto error_out;
}
if (0 == return_value) { /* only when the copyfile succeeded */
@@ -5443,21 +5470,21 @@ int dblayer_restore(struct ldbminfo *li, char *src_dir, Slapi_Task *task, char *
return_value = tmp_rval?tmp_rval:return_value;
error_out:
- /* Free the restore src dir, but only if we allocated it above */
- if (real_src_dir != src_dir) {
- /* If this was an FRI restore and the staging area exists, go ahead and remove it */
- if (frirestore && PR_Access(real_src_dir, PR_ACCESS_EXISTS) == PR_SUCCESS)
- {
- int ret1 = 0;
- LDAPDebug(LDAP_DEBUG_ANY, "dblayer_restore: Removing staging area %s.\n",real_src_dir, 0, 0);
- ret1 = ldbm_delete_dirs(real_src_dir);
- if (ret1)
- {
- LDAPDebug(LDAP_DEBUG_ANY, "dblayer_restore: Removal of staging area %s failed!\n", real_src_dir, 0, 0);
- }
- }
- slapi_ch_free((void**)&real_src_dir);
- }
+ /* Free the restore src dir, but only if we allocated it above */
+ if (real_src_dir != src_dir) {
+ /* If this was an FRI restore and the staging area exists, go ahead and remove it */
+ if (frirestore && PR_Access(real_src_dir, PR_ACCESS_EXISTS) == PR_SUCCESS)
+ {
+ int ret1 = 0;
+ LDAPDebug(LDAP_DEBUG_ANY, "dblayer_restore: Removing staging area %s.\n",real_src_dir, 0, 0);
+ ret1 = ldbm_delete_dirs(real_src_dir);
+ if (ret1)
+ {
+ LDAPDebug(LDAP_DEBUG_ANY, "dblayer_restore: Removal of staging area %s failed!\n", real_src_dir, 0, 0);
+ }
+ }
+ slapi_ch_free((void**)&real_src_dir);
+ }
return return_value;
}
diff --git a/ldap/servers/slapd/proto-slap.h b/ldap/servers/slapd/proto-slap.h
index d1fdd406..1b1eaa5f 100644
--- a/ldap/servers/slapd/proto-slap.h
+++ b/ldap/servers/slapd/proto-slap.h
@@ -589,6 +589,8 @@ void g_set_accesslog_level(int val);
void slapd_nasty(char* str, int c, int err);
int strarray2str( char **a, char *buf, size_t buflen, int include_quotes );
int slapd_chown_if_not_owner(const char *filename, uid_t uid, gid_t gid);
+int slapd_comp_path(char *p0, char *p1);
+
/*
* modify.c
diff --git a/ldap/servers/slapd/util.c b/ldap/servers/slapd/util.c
index a129c6cc..a531e94c 100644
--- a/ldap/servers/slapd/util.c
+++ b/ldap/servers/slapd/util.c
@@ -44,6 +44,7 @@
#include <unistd.h>
#include <pwd.h>
#endif
+#include <libgen.h>
#include <pk11func.h>
#include "slap.h"
#include "prtime.h"
@@ -53,6 +54,14 @@
#define UTIL_ESCAPE_HEX 1
#define UTIL_ESCAPE_BACKSLASH 2
+#if defined( _WIN32 )
+#define _PSEP "\\"
+#define _CSEP '\\'
+#else
+#define _PSEP "/"
+#define _CSEP '/'
+#endif
+
static int special_np(unsigned char c)
{
if(c < 32 || c > 126) {
@@ -383,6 +392,60 @@ is_abspath(const char *path)
return 0; /* not an abs path */
}
+static void
+clean_path(char **norm_path)
+{
+ char **np;
+
+ for (np = norm_path; np && *np; np++)
+ slapi_ch_free((void **)np);
+ slapi_ch_free((void **)&norm_path);
+}
+
+static char **
+normalize_path(char *path)
+{
+ char *dname = slapi_ch_strdup(path);
+ char *dnamep = dname;
+ char *bnamep = NULL;
+ char **dirs = (char **)slapi_ch_calloc(strlen(path), 1);
+ char **rdirs = (char **)slapi_ch_calloc(strlen(path), 1);
+ char **dp = dirs;
+ char **rdp;
+ do {
+ bnamep = basename(dnamep);
+ if (0 != strcmp(bnamep, ".")) {
+ *dp++ = slapi_ch_strdup(bnamep); /* remove "/./" in the path */
+ }
+ dnamep = dirname(dnamep);
+ } while (strcmp(dnamep, _PSEP) &&
+ !(0 == strcmp(dnamep, ".") && 0 == strcmp(bnamep, ".")));
+
+ /* remove "xxx/.." in the path */
+ for (dp = dirs, rdp = rdirs; dp && *dp; dp++) {
+ while (*dp && 0 == strcmp(*dp, "..")) {
+ dp++;
+ if (rdp > rdirs)
+ rdp--;
+ }
+ if (*dp)
+ *rdp++ = slapi_ch_strdup(*dp);
+ }
+ for (--dp, rdp = rdirs; dp >= dirs; dp--) {
+ while (*dp && 0 == strcmp(*dp, "..")) {
+ dp--;
+ if (rdp > rdirs)
+ rdp--;
+ }
+ if (*dp)
+ *rdp++ = slapi_ch_strdup(*dp);
+ }
+
+ clean_path(dirs);
+ slapi_ch_free_string(&dname);
+
+ return rdirs;
+}
/*
* Take "relpath" and prepend the current working directory to it
@@ -399,13 +462,10 @@ rel2abspath( char *relpath )
CHAR szDir[_MAX_DIR];
CHAR szFname[_MAX_FNAME];
CHAR szExt[_MAX_EXT];
-#define _PSEP "\\"
-#else
-#define _PSEP "/"
#endif
if ( relpath == NULL ) {
- return NULL;
+ return NULL;
}
#if defined( _WIN32 )
@@ -413,41 +473,51 @@ rel2abspath( char *relpath )
memset (&szDir, 0, sizeof (szDir));
memset (&szFname, 0, sizeof (szFname));
memset (&szExt, 0, sizeof (szExt));
- _splitpath( relpath, szDrive, szDir, szFname, szExt );
- if( szDrive[0] && szDir[0] )
- return( slapi_ch_strdup( relpath ));
- if ( relpath[ 0 ] == '/' || relpath[ 0 ] == '\\' ) {
- return( slapi_ch_strdup( relpath ));
-#else
- if ( relpath[ 0 ] == '/' ) {
- return( slapi_ch_strdup( relpath ));
- }
+ _splitpath( relpath, szDrive, szDir, szFname, szExt );
+ if( szDrive[0] && szDir[0] )
+ return( slapi_ch_strdup( relpath ));
#endif
-
- if ( getcwd( abspath, MAXPATHLEN ) == NULL ) {
- perror( "getcwd" );
- LDAPDebug( LDAP_DEBUG_ANY, "Cannot determine current directory\n",
- 0, 0, 0 );
- exit( 1 );
- }
-
- if ( strlen( relpath ) + strlen( abspath ) + 1 > MAXPATHLEN ) {
- LDAPDebug( LDAP_DEBUG_ANY, "Pathname \"%s" _PSEP "%s\" too long\n",
- abspath, relpath, 0 );
- exit( 1 );
+ if ( relpath[ 0 ] == _CSEP ) { /* absolute path */
+ PR_snprintf(abspath, sizeof(abspath), "%s", relpath);
+ } else { /* relative path */
+ if ( getcwd( abspath, MAXPATHLEN ) == NULL ) {
+ perror( "getcwd" );
+ LDAPDebug( LDAP_DEBUG_ANY, "Cannot determine current directory\n",
+ 0, 0, 0 );
+ exit( 1 );
+ }
+
+ if ( strlen( relpath ) + strlen( abspath ) + 1 > MAXPATHLEN ) {
+ LDAPDebug( LDAP_DEBUG_ANY, "Pathname \"%s" _PSEP "%s\" too long\n",
+ abspath, relpath, 0 );
+ exit( 1 );
+ }
+
+ if ( strcmp( relpath, "." )) {
+ if ( abspath[ 0 ] != '\0' &&
+ abspath[ strlen( abspath ) - 1 ] != _CSEP )
+ {
+ PL_strcatn( abspath, sizeof(abspath), "/" );
+ }
+ PL_strcatn( abspath, sizeof(abspath), relpath );
+ }
}
-
- if ( strcmp( relpath, "." )) {
-#if defined( _WIN32 )
- if ( abspath[ 0 ] != '\0' && abspath[ strlen( abspath ) - 1 ] != '/' && abspath[ strlen( abspath ) - 1 ] != '\\' )
-#else
- if ( abspath[ 0 ] != '\0' && abspath[ strlen( abspath ) - 1 ] != '/' ) {
-#endif
- PL_strcatn( abspath, sizeof(abspath), "/" );
- }
- PL_strcatn( abspath, sizeof(abspath), relpath );
+ {
+ char **norm_path = normalize_path(abspath);
+ char *retpath = slapi_ch_strdup(abspath); /* size is long enough */
+ char **np, *rp;
+ int pathlen = strlen(abspath) + 1;
+ int usedlen = 0;
+ for (np = norm_path, rp = retpath; np && *np; np++) {
+ int thislen = strlen(*np) + 1;
+ if (0 != strcmp(*np, _PSEP))
+ PR_snprintf(rp, pathlen - usedlen, "%c%s", _CSEP, *np);
+ rp += thislen;
+ usedlen += thislen;
+ }
+ clean_path(norm_path);
+ return retpath;
}
- return( slapi_ch_strdup( abspath ));
}
@@ -658,3 +728,20 @@ slapd_chown_if_not_owner(const char *filename, uid_t uid, gid_t gid)
return result;
}
+/*
+ * Compare 2 pathes
+ * Paths could contain ".", "..", "//" in the path, thus normalize them first.
+ * One or two of the paths could be a relative path.
+ */
+int
+slapd_comp_path(char *p0, char *p1)
+{
+ int rval = 0;
+ char *norm_p0 = rel2abspath(p0);
+ char *norm_p1 = rel2abspath(p1);
+
+ rval = strcmp(norm_p0, norm_p1);
+ slapi_ch_free_string(&norm_p0);
+ slapi_ch_free_string(&norm_p1);
+ return rval;
+}