From 34b3d8a4025bc1c8ace4b1035597970ddf9d8a8d Mon Sep 17 00:00:00 2001
From: venaas <venaas>
Date: Thu, 29 May 2008 11:02:26 +0000
Subject: rewritten gconfig to not exit on errors

git-svn-id: https://svn.testnett.uninett.no/radsecproxy/trunk@263 e88ac4ed-0b26-0410-9574-a7f39faa03bf
---
 gconfig.c     | 218 ++++++++++++++++++++++++++++++++++++++--------------------
 gconfig.h     |   4 +-
 radsecproxy.c |  68 +++++++++---------
 3 files changed, 183 insertions(+), 107 deletions(-)

diff --git a/gconfig.c b/gconfig.c
index 7a367e7..6f9da4e 100644
--- a/gconfig.c
+++ b/gconfig.c
@@ -18,14 +18,6 @@
 #include "util.h"
 #include "gconfig.h"
 
-char *mystringcopyx(const char *s) {
-    char *t;
-    t = stringcopy(s, 0);
-    if (!t)
-	debugx(1, DBG_ERR, "malloc failed");
-    return t;
-}
-
 /* returns NULL on error, where to continue parsing if token and ok. E.g. "" will return token with empty string */
 char *strtokenquote(char *s, char **token, char *del, char *quote, char *comment) {
     char *t = s, *q, *r;
@@ -60,6 +52,7 @@ char *strtokenquote(char *s, char **token, char *del, char *quote, char *comment
 FILE *pushgconffile(struct gconffile **cf, const char *path) {
     int i;
     struct gconffile *newcf;
+    char *pathcopy;
     FILE *f;
 
     f = fopen(path, "r");
@@ -68,28 +61,39 @@ FILE *pushgconffile(struct gconffile **cf, const char *path) {
 	return NULL;
     }
     debug(DBG_DBG, "opened config file %s", path);
+
+    pathcopy = stringcopy(path, 0);
+    if (!pathcopy)
+	goto errmalloc;
+    
     if (!*cf) {
 	newcf = malloc(sizeof(struct gconffile) * 2);
 	if (!newcf)
-	    debugx(1, DBG_ERR, "malloc failed");
+	    goto errmalloc;
 	newcf[1].file = NULL;
 	newcf[1].path = NULL;
     } else {
 	for (i = 0; (*cf)[i].path; i++);
 	newcf = realloc(*cf, sizeof(struct gconffile) * (i + 2));
 	if (!newcf)
-	    debugx(1, DBG_ERR, "malloc failed");
+	    goto errmalloc;
 	memmove(newcf + 1, newcf, sizeof(struct gconffile) * (i + 1));
     }
     newcf[0].file = f;
-    newcf[0].path = mystringcopyx(path);
+    newcf[0].path = pathcopy;
     *cf = newcf;
     return f;
+    
+ errmalloc:
+    free(pathcopy);
+    fclose(f);
+    debug(DBG_ERR, "malloc failed");
+    return NULL;
 }
 
 FILE *pushgconffiles(struct gconffile **cf, const char *cfgpath) {
     int i;
-    FILE *f;
+    FILE *f = NULL;
     glob_t globbuf;
     char *path, *curfile = NULL, *dir;
     
@@ -98,11 +102,17 @@ FILE *pushgconffiles(struct gconffile **cf, const char *cfgpath) {
 	path = (char *)cfgpath;
     else {
 	/* dirname may modify its argument */
-	curfile = mystringcopyx((*cf)->path);
+	curfile = stringcopy((*cf)->path, 0);
+	if (!curfile) {
+	    debug(DBG_ERR, "malloc failed");
+	    goto exit;
+	}
 	dir = dirname(curfile);
 	path = malloc(strlen(dir) + strlen(cfgpath) + 2);
-	if (!path)
-	    debugx(1, DBG_ERR, "malloc failed");
+	if (!path) {
+	    debug(DBG_ERR, "malloc failed");
+	    goto exit;
+	}
 	strcpy(path, dir);
 	path[strlen(dir)] = '/';
 	strcpy(path + strlen(dir) + 1, cfgpath);
@@ -110,15 +120,17 @@ FILE *pushgconffiles(struct gconffile **cf, const char *cfgpath) {
     memset(&globbuf, 0, sizeof(glob_t));
     if (glob(path, 0, NULL, &globbuf)) {
 	debug(DBG_INFO, "could not glob %s", path);
-	f = NULL;
-    } else {
-	for (i = globbuf.gl_pathc - 1; i >= 0; i--) {
-	    f = pushgconffile(cf, globbuf.gl_pathv[i]);
-	    if (!f)
-		break;
-	}    
-	globfree(&globbuf);
+	goto exit;
     }
+
+    for (i = globbuf.gl_pathc - 1; i >= 0; i--) {
+	f = pushgconffile(cf, globbuf.gl_pathv[i]);
+	if (!f)
+	    break;
+    }    
+    globfree(&globbuf);
+
+ exit:    
     if (curfile) {
 	free(curfile);
 	free(path);
@@ -149,8 +161,10 @@ FILE *popgconffile(struct gconffile **cf) {
 struct gconffile *openconfigfile(const char *file) {
     struct gconffile *cf = NULL;
 
-    if (!pushgconffile(&cf, file))
-	debugx(1, DBG_ERR, "could not read config file %s\n%s", file, strerror(errno));
+    if (!pushgconffile(&cf, file)) {
+	debug(DBG_ERR, "could not read config file %s\n%s", file, strerror(errno));
+	return NULL;
+    }
     debug(DBG_DBG, "reading config file %s", file);
     return cf;
 }
@@ -166,7 +180,7 @@ struct gconffile *openconfigfile(const char *file) {
  * }
  */
 
-void getconfigline(struct gconffile **cf, char *block, char **opt, char **val, int *conftype) {
+int getconfigline(struct gconffile **cf, char *block, char **opt, char **val, int *conftype) {
     char line[1024];
     char *tokens[3], *s;
     int tcount;
@@ -176,19 +190,21 @@ void getconfigline(struct gconffile **cf, char *block, char **opt, char **val, i
     *conftype = 0;
     
     if (!cf || !*cf || !(*cf)->file)
-	return;
+	return 1;
 
     for (;;) {
 	if (!fgets(line, 1024, (*cf)->file)) {
 	    if (popgconffile(cf))
 		continue;
-	    return;
+	    return 1;
 	}
 	s = line;
 	for (tcount = 0; tcount < 3; tcount++) {
 	    s = strtokenquote(s, &tokens[tcount], " \t\r\n", "\"'", tcount ? NULL : "#");
-	    if (!s)
-		debugx(1, DBG_ERR, "Syntax error in line starting with: %s", line);
+	    if (!s) {
+		debug(DBG_ERR, "Syntax error in line starting with: %s", line);
+		return 0;
+	    }
 	    if (!tokens[tcount])
 		break;
 	}
@@ -197,59 +213,91 @@ void getconfigline(struct gconffile **cf, char *block, char **opt, char **val, i
 
 	if (**tokens == '}') {
 	    if (block)
-		return;
-	    debugx(1, DBG_ERR, "configuration error, found } with no matching {");
+		return 1;
+	    debug(DBG_ERR, "configuration error, found } with no matching {");
+	    return 0;
 	}
 	break;
     }
     
     switch (tcount) {
     case 2:
-	*opt = mystringcopyx(tokens[0]);
-	*val = mystringcopyx(tokens[1]);
+	*opt = stringcopy(tokens[0], 0);
+	if (!*opt)
+	    goto errmalloc;
+	*val = stringcopy(tokens[1], 0);
+	if (!*val)
+	    goto errmalloc;
 	*conftype = CONF_STR;
 	break;
     case 3:
 	if (tokens[1][0] == '=' && tokens[1][1] == '\0') {
-	    *opt = mystringcopyx(tokens[0]);
-	    *val = mystringcopyx(tokens[2]);
+	    *opt = stringcopy(tokens[0], 0);
+	    if (!*opt)
+		goto errmalloc;
+	    *val = stringcopy(tokens[2], 0);
+	    if (!*val)
+		goto errmalloc;
 	    *conftype = CONF_STR;
 	    break;
 	}
 	if (tokens[2][0] == '{' && tokens[2][1] == '\0') {
-	    *opt = mystringcopyx(tokens[0]);
-	    *val = mystringcopyx(tokens[1]);
+	    *opt = stringcopy(tokens[0], 0);
+	    if (!*opt)
+		goto errmalloc;
+	    *val = stringcopy(tokens[1], 0);
+	    if (!*val)
+		goto errmalloc;
 	    *conftype = CONF_CBK;
 	    break;
 	}
 	/* fall through */
     default:
 	if (block)
-	    debugx(1, DBG_ERR, "configuration error in block %s, line starting with %s", block, tokens[0]);
-	debugx(1, DBG_ERR, "configuration error, syntax error in line starting with %s", tokens[0]);
+	    debug(DBG_ERR, "configuration error in block %s, line starting with %s", block, tokens[0]);
+	else
+	    debug(DBG_ERR, "configuration error, syntax error in line starting with %s", tokens[0]);
+	return 0;
     }
 
-    if (!**val)
-	debugx(1, DBG_ERR, "configuration error, option %s needs a non-empty value", *opt);
-    return;
+    if (**val)
+	return 1;
+    
+    debug(DBG_ERR, "configuration error, option %s needs a non-empty value", *opt);
+    goto errexit;
+
+ errmalloc:
+    debug(DBG_ERR, "malloc failed");
+ errexit:    
+    free(*opt);
+    *opt = NULL;
+    free(*val);
+    *val = NULL;
+    return 0;
 }
 
-void getgenericconfig(struct gconffile **cf, char *block, ...) {
+/* returns 1 if ok, 0 on error */
+/* caller must free returned values also on error */
+int getgenericconfig(struct gconffile **cf, char *block, ...) {
     va_list ap;
-    char *opt = NULL, *val, *word, *optval, **str = NULL, ***mstr = NULL;
+    char *opt = NULL, *val, *word, *optval, **str = NULL, ***mstr = NULL, **newmstr;
     uint8_t *bln;
     int type = 0, conftype = 0, n;
-    void (*cbk)(struct gconffile **, char *, char *, char *) = NULL;
+    void (*cbk)(struct gconffile **, void *, char *, char *, char *) = NULL;
+    void *cbkarg = NULL;
 
     for (;;) {
 	free(opt);
-	getconfigline(cf, block, &opt, &val, &conftype);
+	if (!getconfigline(cf, block, &opt, &val, &conftype))
+	    return 0;
 	if (!opt)
-	    return;
+	    return 1;
 
 	if (conftype == CONF_STR && !strcasecmp(opt, "include")) {
-	    if (!pushgconffiles(cf, val))
-		debugx(1, DBG_ERR, "failed to include config file %s", val);
+	    if (!pushgconffiles(cf, val)) {
+		debug(DBG_ERR, "failed to include config file %s", val);
+		goto errexit;
+	    }
 	    free(val);
 	    continue;
 	}
@@ -261,23 +309,26 @@ void getgenericconfig(struct gconffile **cf, char *block, ...) {
 	    case CONF_STR:
 		str = va_arg(ap, char **);
 		if (!str)
-		    debugx(1, DBG_ERR, "getgenericconfig: internal parameter error");
+		    goto errparam;
 		break;
 	    case CONF_MSTR:
 		mstr = va_arg(ap, char ***);
 		if (!mstr)
-		    debugx(1, DBG_ERR, "getgenericconfig: internal parameter error");
+		    goto errparam;
 		break;
 	    case CONF_BLN:
 		bln = va_arg(ap, uint8_t *);
 		if (!bln)
-		    debugx(1, DBG_ERR, "getgenericconfig: internal parameter error");
+		    goto errparam;
 		break;
 	    case CONF_CBK:
-		cbk = va_arg(ap, void (*)(struct gconffile **, char *, char *, char *));
+		cbk = va_arg(ap, void (*)(struct gconffile **, void *, char *, char *, char *));
+		if (!cbk)
+		    goto errparam;
+		cbkarg = va_arg(ap, void *);
 		break;
 	    default:
-		debugx(1, DBG_ERR, "getgenericconfig: internal parameter error");
+		goto errparam;
 	    }
 	    if (!strcasecmp(opt, word))
 		break;
@@ -286,21 +337,25 @@ void getgenericconfig(struct gconffile **cf, char *block, ...) {
 	
 	if (!word) {
 	    if (block)
-		debugx(1, DBG_ERR, "configuration error in block %s, unknown option %s", block, opt);
-	    debugx(1, DBG_ERR, "configuration error, unknown option %s", opt);
+		debug(DBG_ERR, "configuration error in block %s, unknown option %s", block, opt);
+	    debug(DBG_ERR, "configuration error, unknown option %s", opt);
+	    goto errexit;
 	}
 
 	if (((type == CONF_STR || type == CONF_MSTR || type == CONF_BLN) && conftype != CONF_STR) ||
 	    (type == CONF_CBK && conftype != CONF_CBK)) {
 	    if (block)
-		debugx(1, DBG_ERR, "configuration error in block %s, wrong syntax for option %s", block, opt);
-	    debugx(1, DBG_ERR, "configuration error, wrong syntax for option %s", opt);
+		debug(DBG_ERR, "configuration error in block %s, wrong syntax for option %s", block, opt);
+	    debug(DBG_ERR, "configuration error, wrong syntax for option %s", opt);
+	    goto errexit;
 	}
 
 	switch (type) {
 	case CONF_STR:
-	    if (*str)
-		debugx(1, DBG_ERR, "configuration error, option %s already set to %s", opt, *str);
+	    if (*str) {
+		debug(DBG_ERR, "configuration error, option %s already set to %s", opt, *str);
+		goto errexit;
+	    }
 	    *str = val;
 	    break;
 	case CONF_MSTR:
@@ -308,33 +363,41 @@ void getgenericconfig(struct gconffile **cf, char *block, ...) {
 		for (n = 0; (*mstr)[n]; n++);
 	    else
 		n = 0;
-	    *mstr = realloc(*mstr, sizeof(char *) * (n + 2));
-	    if (!*mstr)
-		debugx(1, DBG_ERR, "malloc failed");
-	    (*mstr)[n] = val;
-	    (*mstr)[n + 1] = NULL;
+	    newmstr = realloc(*mstr, sizeof(char *) * (n + 2));
+	    if (!newmstr) {
+		debug(DBG_ERR, "malloc failed");
+		goto errexit;
+	    }
+	    newmstr[n] = val;
+	    newmstr[n + 1] = NULL;
+	    *mstr = newmstr;
 	    break;
 	case CONF_BLN:
 	    if (!strcasecmp(val, "on"))
 		*bln = 1;
 	    else if (!strcasecmp(val, "off"))
 		*bln = 0;
-	    else if (block)
-		debugx(1, DBG_ERR, "configuration error in block %s, value for option %s must be on or off, not %s", block, opt, val);
-	    else
-		debugx(1, DBG_ERR, "configuration error, value for option %s must be on or off, not %s", opt, val);
+	    else {
+		if (block)
+		    debug(DBG_ERR, "configuration error in block %s, value for option %s must be on or off, not %s", block, opt, val);
+		else
+		    debug(DBG_ERR, "configuration error, value for option %s must be on or off, not %s", opt, val);
+		goto errexit;
+	    }
 	    break;
 	case CONF_CBK:
 	    optval = malloc(strlen(opt) + strlen(val) + 2);
-	    if (!optval)
-		debugx(1, DBG_ERR, "malloc failed");
+	    if (!optval) {
+		debug(DBG_ERR, "malloc failed");
+		goto errexit;
+	    }
 	    sprintf(optval, "%s %s", opt, val);
-	    cbk(cf, optval, opt, val);
+	    cbk(cf, cbkarg, optval, opt, val);
 	    free(val);
 	    free(optval);
 	    continue;
 	default:
-	    debugx(1, DBG_ERR, "getgenericconfig: internal parameter error");
+	    goto errparam;
 	}
 	if (block)
 	    debug(DBG_DBG, "getgenericconfig: block %s: %s = %s", block, opt, val);
@@ -343,4 +406,11 @@ void getgenericconfig(struct gconffile **cf, char *block, ...) {
 	if (type == CONF_BLN)
 	    free(val);
     }
+
+ errparam:
+    debug(DBG_ERR, "getgenericconfig: internal parameter error");
+ errexit:
+    free(opt);
+    free(val);
+    return 0;
 }
diff --git a/gconfig.h b/gconfig.h
index af44dac..55e8efe 100644
--- a/gconfig.h
+++ b/gconfig.h
@@ -8,8 +8,8 @@ struct gconffile {
     FILE *file;
 };
 
-void getconfigline(struct gconffile **cf, char *block, char **opt, char **val, int *conftype);
-void getgenericconfig(struct gconffile **cf, char *block, ...);
+int getconfigline(struct gconffile **cf, char *block, char **opt, char **val, int *conftype);
+int getgenericconfig(struct gconffile **cf, char *block, ...);
 FILE *pushgconffile(struct gconffile **cf, const char *path);
 FILE *pushgconffiles(struct gconffile **cf, const char *path);
 FILE *popgconffile(struct gconffile **cf);
diff --git a/radsecproxy.c b/radsecproxy.c
index 5f18086..4833897 100644
--- a/radsecproxy.c
+++ b/radsecproxy.c
@@ -3159,7 +3159,7 @@ void addrewrite(char *value, char **attrs, char **vattrs) {
     debug(DBG_DBG, "addrewrite: added rewrite block %s", value);
 }
 
-void confclient_cb(struct gconffile **cf, char *block, char *opt, char *val) {
+void confclient_cb(struct gconffile **cf, void *arg, char *block, char *opt, char *val) {
     char *type = NULL, *tls = NULL, *matchcertattr = NULL, *rewrite = NULL, *rewriteattr = NULL;
     struct clsrvconf *conf;
     
@@ -3171,7 +3171,7 @@ void confclient_cb(struct gconffile **cf, char *block, char *opt, char *val) {
     memset(conf, 0, sizeof(struct clsrvconf));
     conf->certnamecheck = 1;
     
-    getgenericconfig(cf, block,
+    if (!getgenericconfig(cf, block,
 		     "type", CONF_STR, &type,
 		     "host", CONF_STR, &conf->host,
 		     "secret", CONF_STR, &conf->secret,
@@ -3181,8 +3181,9 @@ void confclient_cb(struct gconffile **cf, char *block, char *opt, char *val) {
 		     "rewrite", CONF_STR, &rewrite,
 		     "rewriteattribute", CONF_STR, &rewriteattr,
 		     NULL
-		     );
-
+			  ))
+	debugx(1, DBG_ERR, "configuration error");
+    
     conf->name = stringcopy(val, 0);
     if (!conf->host)
 	conf->host = stringcopy(val, 0);
@@ -3224,7 +3225,7 @@ void confclient_cb(struct gconffile **cf, char *block, char *opt, char *val) {
     }
 }
 
-void confserver_cb(struct gconffile **cf, char *block, char *opt, char *val) {
+void confserver_cb(struct gconffile **cf, void *arg, char *block, char *opt, char *val) {
     char *type = NULL, *tls = NULL, *matchcertattr = NULL, *rewrite = NULL;
     struct clsrvconf *conf;
     
@@ -3236,7 +3237,7 @@ void confserver_cb(struct gconffile **cf, char *block, char *opt, char *val) {
     memset(conf, 0, sizeof(struct clsrvconf));
     conf->certnamecheck = 1;
     
-    getgenericconfig(cf, block,
+    if (!getgenericconfig(cf, block,
 		     "type", CONF_STR, &type,
 		     "host", CONF_STR, &conf->host,
 		     "port", CONF_STR, &conf->port,
@@ -3248,7 +3249,8 @@ void confserver_cb(struct gconffile **cf, char *block, char *opt, char *val) {
 		     "CertificateNameCheck", CONF_BLN, &conf->certnamecheck,
 		     "DynamicLookupCommand", CONF_STR, &conf->dynamiclookupcommand,
 		     NULL
-		     );
+			  ))
+	debugx(1, DBG_ERR, "configuration error");
 
     conf->name = stringcopy(val, 0);
     if (!conf->host)
@@ -3289,34 +3291,36 @@ void confserver_cb(struct gconffile **cf, char *block, char *opt, char *val) {
     }
 }
 
-void confrealm_cb(struct gconffile **cf, char *block, char *opt, char *val) {
+void confrealm_cb(struct gconffile **cf, void *arg, char *block, char *opt, char *val) {
     char **servers = NULL, **accservers = NULL, *msg = NULL;
     
     debug(DBG_DBG, "confrealm_cb called for %s", block);
     
-    getgenericconfig(cf, block,
+    if (!getgenericconfig(cf, block,
 		     "server", CONF_MSTR, &servers,
 		     "accountingServer", CONF_MSTR, &accservers,
 		     "ReplyMessage", CONF_STR, &msg,
 		     NULL
-		     );
+			  ))
+	debugx(1, DBG_ERR, "configuration error");
 
     addrealm(realms, val, servers, accservers, msg);
 }
 
-void conftls_cb(struct gconffile **cf, char *block, char *opt, char *val) {
+void conftls_cb(struct gconffile **cf, void *arg, char *block, char *opt, char *val) {
     char *cacertfile = NULL, *cacertpath = NULL, *certfile = NULL, *certkeyfile = NULL, *certkeypwd = NULL;
     
     debug(DBG_DBG, "conftls_cb called for %s", block);
     
-    getgenericconfig(cf, block,
+    if (!getgenericconfig(cf, block,
 		     "CACertificateFile", CONF_STR, &cacertfile,
 		     "CACertificatePath", CONF_STR, &cacertpath,
 		     "CertificateFile", CONF_STR, &certfile,
 		     "CertificateKeyFile", CONF_STR, &certkeyfile,
 		     "CertificateKeyPassword", CONF_STR, &certkeypwd,
 		     NULL
-		     );
+			  ))
+	debugx(1, DBG_ERR, "configuration error");
     
     tlsadd(val, cacertfile, cacertpath, certfile, certkeyfile, certkeypwd);
     free(cacertfile);
@@ -3326,16 +3330,17 @@ void conftls_cb(struct gconffile **cf, char *block, char *opt, char *val) {
     free(certkeypwd);
 }
 
-void confrewrite_cb(struct gconffile **cf, char *block, char *opt, char *val) {
+void confrewrite_cb(struct gconffile **cf, void *arg, char *block, char *opt, char *val) {
     char **attrs = NULL, **vattrs = NULL;
     
     debug(DBG_DBG, "confrewrite_cb called for %s", block);
     
-    getgenericconfig(cf, block,
+    if (!getgenericconfig(cf, block,
 		     "removeAttribute", CONF_MSTR, &attrs,
 		     "removeVendorAttribute", CONF_MSTR, &vattrs,
 		     NULL
-		     );
+			  ))
+	debugx(1, DBG_ERR, "configuration error");
     addrewrite(val, attrs, vattrs);
 }
 
@@ -3366,21 +3371,22 @@ void getmainconfig(const char *configfile) {
     if (!rewriteconfs)
 	debugx(1, DBG_ERR, "malloc failed");    
  
-    getgenericconfig(&cfs, NULL,
-		     "ListenUDP", CONF_STR, &options.listenudp,
-		     "ListenTCP", CONF_STR, &options.listentcp,
-		     "ListenAccountingUDP", CONF_STR, &options.listenaccudp,
-		     "SourceUDP", CONF_STR, &options.sourceudp,
-		     "SourceTCP", CONF_STR, &options.sourcetcp,
-		     "LogLevel", CONF_STR, &loglevel,
-		     "LogDestination", CONF_STR, &options.logdestination,
-		     "Client", CONF_CBK, confclient_cb,
-		     "Server", CONF_CBK, confserver_cb,
-		     "Realm", CONF_CBK, confrealm_cb,
-		     "TLS", CONF_CBK, conftls_cb,
-		     "Rewrite", CONF_CBK, confrewrite_cb,
-		     NULL
-		     );
+    if (!getgenericconfig(&cfs, NULL,
+			  "ListenUDP", CONF_STR, &options.listenudp,
+			  "ListenTCP", CONF_STR, &options.listentcp,
+			  "ListenAccountingUDP", CONF_STR, &options.listenaccudp,
+			  "SourceUDP", CONF_STR, &options.sourceudp,
+			  "SourceTCP", CONF_STR, &options.sourcetcp,
+			  "LogLevel", CONF_STR, &loglevel,
+			  "LogDestination", CONF_STR, &options.logdestination,
+			  "Client", CONF_CBK, confclient_cb, NULL,
+			  "Server", CONF_CBK, confserver_cb, NULL,
+			  "Realm", CONF_CBK, confrealm_cb, NULL,
+			  "TLS", CONF_CBK, conftls_cb, NULL,
+			  "Rewrite", CONF_CBK, confrewrite_cb, NULL,
+			  NULL
+			  ))
+	debugx(1, DBG_ERR, "configuration error");
     tlsfree();
     rewritefree();
     
-- 
cgit v1.1