summaryrefslogtreecommitdiffabout
authorSergey Poznyakoff <gray@gnu.org.ua>2010-06-28 21:08:32 (GMT)
committer Sergey Poznyakoff <gray@gnu.org.ua>2010-06-28 21:09:40 (GMT)
commit2af5a2255dfcf46c06cb39010768863c5a2837aa (patch) (side-by-side diff)
treedc354959ed5f909b560b2eb46fa002dc99c67aa7
parente696e8668cd2700825c42bf50c8a74a7292dcbf6 (diff)
downloadsmap-2af5a2255dfcf46c06cb39010768863c5a2837aa.tar.gz
smap-2af5a2255dfcf46c06cb39010768863c5a2837aa.tar.bz2
Implement user-defined variables in configs.
* src/cfg.c (asgn_p, asgn, find_env_var): New functions. (parse_config_loop): Handle user-defined variable assignments and expansions. * modules/mailutils/mailutils.c: Use wordsplit instead of the MU vartabs. * doc/ex-meta1.texi: Rewrite.
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--doc/ex-meta1.texi145
-rw-r--r--modules/mailutils/mailutils.c180
-rw-r--r--src/cfg.c83
3 files changed, 248 insertions, 160 deletions
diff --git a/doc/ex-meta1.texi b/doc/ex-meta1.texi
index e558008..d74b49c 100644
--- a/doc/ex-meta1.texi
+++ b/doc/ex-meta1.texi
@@ -3,13 +3,36 @@
@c See file smap.texi for copying conditions.
@c *******************************************************************
@cindex MeTA1
- In this appendix we will show how to use the @samp{mailutils}
-module (@pxref{mailutils,mailutils module}) to configure local user
+ In this appendix we will show how to use the @samp{mysql}
+module (@pxref{mysql,mysql module}) to configure local user
and alias maps for @acronym{MeTA1}. For this purpose, we will assume
that the actual data is stored in two tables in a @acronym{MySQL}
database. The two maps will be served by two separate databases, each
of which uses a separate configuration file.
+ To reduce the number of connections to the @acronym{MySQL} server,
+the @acronym{MySQL} database will be opened at the module level and
+shared between the two smap databases. Thus, the module
+initialization in @file{smapd.conf} looks like:
+
+@example
+module mysql mysql open config-group=smap
+@end example
+
+The @samp{open} parameter instructs the module to open the requested
+databases. The @samp{config-group} parameter refers to a group
+name in the default @file{/etc/my.cnf} file that contains information
+about the @acronym{MySQL} database and credentials for accessing it.
+The following is a sample snippet from @file{/etc/my.cnf}:
+
+@example
+[smap]
+database = Mail
+user = smap
+password = guessme
+socket = /tmp/mysql.sock
+@end example
+
@menu
* userdb-meta1:: Configure local_user_map.
* aliases-meta1:: Configure aliases.
@@ -33,43 +56,25 @@ CREATE TABLE userdb (
@end group
@end example
-Module configuration file @file{/etc/mailutils.d/meta1-userdb}
-begins with the following stanza:
+The smap database is defined as follows:
@example
@group
-auth @{
- authentication clear;
- authentication sql;
- authorization clear;
- authorization sql;
-@}
+database userdb mysql \
+ defaultdb
+ query="SELECT user FROM userdb WHERE user='$key'"
+ positive-reply=OK
@end group
@end example
-This clears any previous settings that the authorization engine might
-have read from the main configuration file, and requests that only
-@samp{sql} method be used for both authentication and authorization.
-
-Now, we need to supply a @samp{sql} statement. Mailutils requires
-that the @code{getpwnam} query return at least six fields, whereas the
-@samp{userdb} table contains only two columns. So we will need to supply
-defaults for the remaining four:
-
-@example
-sql @{
- interface mysql;
- host sql.host.name
- user smap;
- passwd guessme;
- db mail;
- getpwnam "SELECT user as name, 'x' as passwd,10000 as uid, 10000 as gid, "
- "'/nonexistent' as dir, '/sbin/nologin' as shell "
- "FROM userdb WHERE user='$@{user@}'";
-@};
-@end example
-
- That's all we need to have in @file{/etc/mailutils.d/meta1-userdb}.
+The @samp{defaultdb} parameter tells it to use the default SQL
+database opened in the module initialization instruction. The
+@samp{query} parameter supplies the SQL query to run (the
+@samp{$@{key@}} variable will be expanded to the value of the actual
+lookup key, prior to executing the query). Finally,
+@samp{positive-reply} defines the reply to give if the query returns
+some tuples. The database only verifies whether the user is present
+or not, so no additional result is supplied in the reply.
@node aliases-meta1
@appendixsec Configure aliases
@@ -87,74 +92,26 @@ CREATE TABLE userdb (
@end group
@end example
-It will be served by @samp{alias} database, which will read
-the configuration for Mailutils from the file
-@file{/etc/mailutils.d/meta1-alias}. This file is similar to
-@file{meta1-userdb}, but uses a different query in its @samp{sql}
-section:
+It will be served by @samp{alias} database, defined as follows:
@example
-auth @{
- authentication clear;
- authentication sql;
- authorization clear;
- authorization sql;
-@}
-
-sql @{
- interface mysql;
- host sql.host.name
- user smap;
- passwd guessme;
- db mail;
- getpwnam "SELECT alias as name, 'x' as passwd,1 as uid, 1 as gid, "
- "'/nonexistent' as dir, '/sbin/nologin' as shell "
- "FROM aliases WHERE name='$@{user@}'";
-@}
-@end example
-
-@node smapd-meta1
-@appendixsec Smapd configuration
-
- Let's now configure @file{smapd.conf}. Suppose it will run a single
-server, which we will call @samp{local}. The server will listen on a
-UNIX socket @file{/var/spool/meta1/smap/userdb}. It is important that
-@samp{meta1} be able to read from and write to that socket, so we will make
-it owned by user @samp{meta1m}:
-
-@example
-server local unix:///var/spool/meta1/smap/userdb begin
- user meta1m
-end
-@end example
-
- Next task is to configure the databases. The @samp{userdb} database is
-pretty simple:
-
-@example
-database userdb mailutils mode=auth \
- config-file=/etc/mailutils.d/meta1-userdb
+@group
+database alias mysql \
+ defaultdb \
+ query="SELECT alias FROM aliases WHERE user='$key'" \
+ positive-reply="OK $alias"
+@end group
@end example
- It will return @samp{OK} if the user is found in the database and
-@samp{NOTFOUND} otherwise, which is exactly what the @acronym{MTA} needs.
+It differs from the @samp{userdb} database only in that it returns
+a @dfn{result section} with its positive reply.
- The @samp{aliasdb} database is a bit different. In case of a
-positive reply, it must return the expanded alias value, so we need to
-supply a new @samp{positive-reply} template:
-@example
-database aliasdb mailutils mode=auth \
- config-file=/usr/local/etc/mailutils.d/meta1-alias \
- positive-reply="OK $@{name@}"
-@end example
-
- The @samp{$@{name@}} will be replaced with the value of the first
-column in the tuple returned by the @acronym{SQL} database
-(@pxref{aliases-meta1, getpwnam}).
+@node smapd-meta1
+@appendixsec Dispatch Rules
- To dispatch queries to these databases, the following rules will
-suffice:
+ The following rules dispatch queries based on their map names to
+the two databases:
@example
dispatch map alias database aliasdb
diff --git a/modules/mailutils/mailutils.c b/modules/mailutils/mailutils.c
index 10018c4..03966c1 100644
--- a/modules/mailutils/mailutils.c
+++ b/modules/mailutils/mailutils.c
@@ -25,6 +25,7 @@
#include <smap/diag.h>
#include <smap/module.h>
#include <smap/parseopt.h>
+#include <smap/wordsplit.h>
static char *dfl_positive_reply = "OK";
static char *dfl_negative_reply = "NOTFOUND";
@@ -66,58 +67,97 @@ _mu_smap_db_free(struct _mu_smap_db *db)
free(db);
}
+static void
+free_env(char **env)
+{
+ int i;
+ for (i = 0; env[i]; i++)
+ free(env[i]);
+}
+
static char *
-expand_reply_text(const char *arg, struct _mu_smap_result *res)
+mkvar(const char *name, const char *val)
{
- int rc;
- mu_vartab_t vtab;
- char *reply = NULL;
- char buf[512];
- struct mu_auth_data *auth = res->auth;
+ char *ptr = malloc(strlen(name) + strlen(val) + 2);
+ if (ptr) {
+ strcpy(ptr, name);
+ strcat(ptr, "=");
+ strcat(ptr, val);
+ }
+ return ptr;
+}
- if (!arg)
- return NULL;
- mu_vartab_create(&vtab);
- mu_vartab_define(vtab, "db", res->db, 0);
- mu_vartab_define(vtab, "key", res->key, 0);
- mu_vartab_define(vtab, "map", res->map, 0);
- mu_vartab_define(vtab, MU_AUTH_NAME, auth ? auth->name : "", 0);
- mu_vartab_define(vtab, MU_AUTH_PASSWD, auth ? auth->passwd : "", 0);
- if (!auth)
- strcpy(buf, "-1");
- else
+static int
+mkenv(char **env, struct _mu_smap_result *res)
+{
+ int i = 0;
+ struct mu_auth_data *auth = res->auth;
+ char buf[512];
+
+#define MKVAR(n, v) \
+ do { \
+ if (!(env[i++] = mkvar(n, v))) \
+ return 1; \
+ } while (0)
+
+ MKVAR("db", res->db);
+ MKVAR("key", res->key);
+ MKVAR("map", res->map);
+ if (auth) {
+ MKVAR(MU_AUTH_NAME, auth->name);
+ MKVAR(MU_AUTH_PASSWD, auth->passwd);
snprintf(buf, sizeof buf, "%lu", (unsigned long) auth->uid);
- mu_vartab_define(vtab, MU_AUTH_UID, buf, 0);
- if (!auth)
- strcpy(buf, "-1");
- else
+ MKVAR(MU_AUTH_UID, buf);
snprintf(buf, sizeof buf, "%lu", (unsigned long) auth->gid);
- mu_vartab_define(vtab, MU_AUTH_GID, buf, 0);
- mu_vartab_define(vtab, MU_AUTH_GECOS,
- auth ? auth->gecos : "", 0);
- mu_vartab_define(vtab, MU_AUTH_DIR, auth ? auth->dir : "", 0);
- mu_vartab_define(vtab, MU_AUTH_SHELL, auth ? auth->shell : "", 0);
- mu_vartab_define(vtab, MU_AUTH_MAILBOX,
- (auth && auth->mailbox) ? auth->mailbox :
- res->url ? mu_url_to_string(res->url) : "", 0);
- if (!auth)
- strcpy(buf, "NONE");
- else
+ MKVAR(MU_AUTH_GID, buf);
+ MKVAR(MU_AUTH_GECOS, auth->gecos);
+ MKVAR(MU_AUTH_DIR, auth->dir);
+ MKVAR(MU_AUTH_SHELL, auth->shell);
+ MKVAR(MU_AUTH_MAILBOX,
+ auth->mailbox ? auth->mailbox :
+ res->url ? mu_url_to_string(res->url) : "");
snprintf(buf, sizeof buf, "%lu", (unsigned long) auth->quota);
- mu_vartab_define(vtab, MU_AUTH_QUOTA, buf, 0);
- snprintf(buf, sizeof buf, "%lu", (unsigned long) res->mbsize);
- mu_vartab_define(vtab, "mbsize", buf, 0);
- snprintf(buf, sizeof buf, "%lu", (unsigned long) res->msgsize);
- mu_vartab_define(vtab, "msgsize", buf, 0);
-
+ MKVAR(MU_AUTH_QUOTA, buf);
+ snprintf(buf, sizeof buf, "%lu", (unsigned long) res->mbsize);
+ MKVAR("mbsize", buf);
+ snprintf(buf, sizeof buf, "%lu", (unsigned long) res->msgsize);
+ MKVAR("msgsize", buf);
+ }
if (res->diag)
- mu_vartab_define(vtab, "diag", res->diag, 1);
- rc = mu_vartab_expand(vtab, arg, &reply);
+ MKVAR("diag", res->diag);
+
+ env[i] = NULL;
+ return 0;
+}
+
+static int
+expand_reply_text(const char *arg, struct _mu_smap_result *res, char **repl)
+{
+ int rc;
+ char *env[16];
+ struct wordsplit ws;
+
+ if (mkenv(env, res)) {
+ mu_error("not enough memory");
+ free_env(env);
+ return 1;
+ }
+
+ ws.ws_env = (const char **) env;
+ ws.ws_error = smap_error;
+ rc = wordsplit(arg, &ws,
+ WRDSF_NOSPLIT |
+ WRDSF_NOCMD |
+ WRDSF_ENV |
+ WRDSF_ERROR |
+ WRDSF_SHOWERR);
+ free_env(env);
if (rc)
- mu_error("cannot expand string `%s': %s",
- arg, mu_strerror (rc));
- mu_vartab_destroy(&vtab);
- return reply;
+ return 1;
+ *repl = ws.ws_wordv[0];
+ ws.ws_wordv = NULL;
+ wordsplit_free(&ws);
+ return 0;
}
static int
@@ -130,7 +170,8 @@ _mu_auth_query(smap_database_t dbp,
struct mu_auth_data *auth = mu_get_auth_by_name(key);
struct _mu_smap_result res;
char *reply;
-
+ int rc;
+
res.db = mdb->id;
res.map = map;
res.key = key;
@@ -140,14 +181,16 @@ _mu_auth_query(smap_database_t dbp,
res.diag = NULL;
res.url = NULL;
if (!auth)
- reply = expand_reply_text(mdb->negative_reply, &res);
+ rc = expand_reply_text(mdb->negative_reply, &res, &reply);
else {
- reply = expand_reply_text(mdb->positive_reply, &res);
+ rc = expand_reply_text(mdb->positive_reply, &res, &reply);
mu_auth_data_free(auth);
}
- smap_stream_printf(ostr, "%s\n", reply);
- free(reply);
- return 0;
+ if (rc == 0) {
+ smap_stream_printf(ostr, "%s\n", reply);
+ free(reply);
+ }
+ return rc;
}
static int
@@ -181,32 +224,34 @@ switch_user_id(struct mu_auth_data *auth, int user)
return rc;
}
-static char *
+static int
checksize(struct _mu_smap_db *mdb, smap_stream_t ostr,
- const char *user, struct _mu_smap_result *res)
+ const char *user, struct _mu_smap_result *res,
+ char **preply)
{
struct mu_auth_data *auth;
mu_mailbox_t mbox;
int status;
- char *reply_txt = NULL;
+
+ *preply = NULL;
auth = mu_get_auth_by_name(user);
res->auth = auth;
if (!auth) {
res->diag = "user not found";
smap_debug(dbgid, 1, ("%s: user not found", user));
- return NULL;
+ return 0;
}
if (switch_user_id(auth, 1)) {
res->diag = "local system error";
- return NULL;
+ return 0;
}
status = mu_mailbox_create_default(&mbox, auth->mailbox);
if (status) {
res->diag = "local system error";
mu_error("could not create mailbox `%s': %s",
auth->mailbox, mu_strerror(status));
- return NULL;
+ return 0;
}
mu_mailbox_get_url(mbox, &res->url);
@@ -245,14 +290,15 @@ checksize(struct _mu_smap_db *mdb, smap_stream_t ostr,
stat = mdb->positive_reply;
res->diag = "QUOTAOK";
}
- reply_txt = expand_reply_text(stat, res);
+ if (expand_reply_text(stat, res, preply))
+ return 1;
}
switch_user_id(auth, 0);
mu_mailbox_close(mbox);
}
mu_mailbox_destroy(&mbox);
mu_auth_data_free(auth);
- return reply_txt;
+ return 0;
}
static int
@@ -267,7 +313,8 @@ _mu_mbq_query(smap_database_t dbp,
size_t len;
struct _mu_smap_result res;
char *reply;
-
+ int rc;
+
memset(&res, 0, sizeof(res));
res.db = mdb->id;
res.map = map;
@@ -290,13 +337,16 @@ _mu_mbq_query(smap_database_t dbp,
("ignoring junk after %s", user + len));
}
- reply = checksize(mdb, ostr, user, &res);
- if (!reply)
- reply = expand_reply_text(mdb->onerror_reply, &res);
- smap_stream_printf(ostr, "%s\n", reply);
- free(reply);
+ rc = checksize(mdb, ostr, user, &res, &reply);
+
+ if (!rc && !reply)
+ rc = expand_reply_text(mdb->onerror_reply, &res, &reply);
+ if (rc == 0) {
+ smap_stream_printf(ostr, "%s\n", reply);
+ free(reply);
+ }
free(user);
- return 0;
+ return rc;
}
diff --git a/src/cfg.c b/src/cfg.c
index 0809504..e0eea62 100644
--- a/src/cfg.c
+++ b/src/cfg.c
@@ -190,13 +190,78 @@ wrdse_to_ex(int code)
return EX_UNAVAILABLE;
}
+static int
+asgn_p(char *buf)
+{
+ unsigned char *p = (unsigned char*)buf;
+ while (*p && (*p == ' ' || *p == '\t'))
+ p++;
+ if (!*p)
+ return 0;
+ if (*p < 127 && (isalpha(*p) || *p == '_')) {
+ while (*p && *p < 127 && (isalnum(*p) || *p == '_'))
+ p++;
+ return *p == '=';
+ }
+ return 0;
+}
+
+static size_t
+find_env_var(const char **env, char *name)
+{
+ size_t i;
+
+ for (i = 0; env[i]; i++) {
+ size_t j;
+ const char *var = env[i];
+
+ for (j = 0; name[j]; j++) {
+ if (name[j] != var[j])
+ break;
+ if (name[j] == '=')
+ return i;
+ }
+ }
+ return i;
+}
+
+static void
+asgn(struct wordsplit *ws, size_t *psize)
+{
+ size_t size = *psize;
+
+ if (size == 0) {
+ size = 16;
+ ws->ws_env = ecalloc(size, sizeof(ws->ws_env[0]));
+ ws->ws_env[0] = ws->ws_wordv[0];
+ } else {
+ size_t n = find_env_var(ws->ws_env, ws->ws_wordv[0]);
+ if (ws->ws_env[n])
+ free((char*)ws->ws_env[n]);
+ else if (n == size - 1) {
+ size *= 2;
+ ws->ws_env = erealloc(ws->ws_env,
+ size * sizeof(ws->ws_env[0]));
+ }
+ ws->ws_env[n] = ws->ws_wordv[0];
+ ws->ws_env[n++] = NULL;
+ }
+ ws->ws_wordv[0] = NULL;
+ *psize = size;
+}
+
+
void
parse_config_loop(struct cfg_kw *kwtab, void *data)
{
char *buf = NULL;
size_t size = 0;
struct wordsplit ws;
- int wsflags = WRDSF_DEFFLAGS|WRDSF_ERROR|WRDSF_COMMENT|WRDSF_ENOMEMABRT;
+ int wsflags = WRDSF_DEFFLAGS |
+ WRDSF_ERROR |
+ WRDSF_COMMENT |
+ WRDSF_ENOMEMABRT;
+ size_t envsize = 0;
int eof = 0;
ws.ws_error = smap_error;
@@ -216,6 +281,20 @@ parse_config_loop(struct cfg_kw *kwtab, void *data)
if (ws.ws_wordc == 0)
continue;
+ if (asgn_p(ws.ws_wordv[0])) {
+ if (ws.ws_wordc > 1) {
+ smap_error("%s:%u: too many arguments "
+ "(unquoted assignment?)",
+ cfg_file_name, cfg_line);
+ cfg_errors = 1;
+ } else {
+ asgn(&ws, &envsize);
+ wsflags &= ~WRDSF_NOVAR;
+ wsflags |= WRDSF_ENV | WRDSF_KEEPUNDEF;
+ }
+ continue;
+ }
+
kwp = find_cfg_kw(kwtab, ws.ws_wordv[0]);
if (!kwp) {
smap_error("%s:%u: unrecognized line",
@@ -296,6 +375,8 @@ parse_config_loop(struct cfg_kw *kwtab, void *data)
}
if (wsflags & WRDSF_REUSE)
wordsplit_free(&ws);
+ if (envsize)
+ free(ws.ws_env);
free(buf);
}

Return to:

Send suggestions and report system problems to the System administrator.