summaryrefslogtreecommitdiffstats
path: root/src/util/dict_nisplus.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 01:46:30 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 01:46:30 +0000
commitb5896ba9f6047e7031e2bdee0622d543e11a6734 (patch)
treefd7b460593a2fee1be579bec5697e6d887ea3421 /src/util/dict_nisplus.c
parentInitial commit. (diff)
downloadpostfix-b5896ba9f6047e7031e2bdee0622d543e11a6734.tar.xz
postfix-b5896ba9f6047e7031e2bdee0622d543e11a6734.zip
Adding upstream version 3.4.23.upstream/3.4.23upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/util/dict_nisplus.c')
-rw-r--r--src/util/dict_nisplus.c304
1 files changed, 304 insertions, 0 deletions
diff --git a/src/util/dict_nisplus.c b/src/util/dict_nisplus.c
new file mode 100644
index 0000000..1f5e126
--- /dev/null
+++ b/src/util/dict_nisplus.c
@@ -0,0 +1,304 @@
+/*++
+/* NAME
+/* dict_nisplus 3
+/* SUMMARY
+/* dictionary manager interface to NIS+ maps
+/* SYNOPSIS
+/* #include <dict_nisplus.h>
+/*
+/* DICT *dict_nisplus_open(map, open_flags, dict_flags)
+/* const char *map;
+/* int dummy;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_nisplus_open() makes the specified NIS+ map accessible via
+/* the generic dictionary operations described in dict_open(3).
+/* The \fIdummy\fR argument is not used.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* DIAGNOSTICS
+/* Fatal errors:
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Geoff Gibbs
+/* UK-HGMP-RC
+/* Hinxton
+/* Cambridge
+/* CB10 1SB, UK
+/*
+/* based on the code for dict_nis.c et al by :-
+/*
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#ifdef HAS_NISPLUS
+#include <rpcsvc/nis.h> /* for nis_list */
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <stringops.h>
+#include <dict.h>
+#include <dict_nisplus.h>
+
+#ifdef HAS_NISPLUS
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* generic members */
+ char *template; /* parsed query template */
+ int column; /* NIS+ field number (start at 1) */
+} DICT_NISPLUS;
+
+ /*
+ * Begin quote from nis+(1):
+ *
+ * The following text represents a context-free grammar that defines the
+ * set of legal NIS+ names. The terminals in this grammar are the
+ * characters `.' (dot), `[' (open bracket), `]' (close bracket), `,'
+ * (comma), `=' (equals) and whitespace. Angle brackets (`<' and `>'),
+ * which delineate non- terminals, are not part of the grammar. The
+ * character `|' (vertical bar) is used to separate alternate productions
+ * and should be read as ``this production OR this production''.
+ *
+ * name ::= . | <simple name> | <indexed name>
+ *
+ * simple name ::= <string>. | <string>.<simple name>
+ *
+ * indexed name ::= <search criterion>,<simple name>
+ *
+ * search criterion ::= [ <attribute list> ]
+ *
+ * attribute list ::= <attribute> | <attribute>,<attribute list>
+ *
+ * attribute ::= <string> = <string>
+ *
+ * string ::= ISO Latin 1 character set except the character
+ * '/' (slash). The initial character may not be a terminal character or
+ * the characters '@' (at), '+' (plus), or (`-') hyphen.
+ *
+ * Terminals that appear in strings must be quoted with `"' (double quote).
+ * The `"' character may be quoted by quoting it with itself `""'.
+ *
+ * End quote fron nis+(1).
+ *
+ * This NIS client always quotes the entire query string (the value part of
+ * [attribute=value],file.domain.) so the issue with initial characters
+ * should not be applicable. One wonders what restrictions are applicable
+ * when a string is quoted, but the manual doesn't specify what can appear
+ * between quotes, and we don't want to get burned.
+ */
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+
+/* dict_nisplus_lookup - find table entry */
+
+static const char *dict_nisplus_lookup(DICT *dict, const char *key)
+{
+ const char *myname = "dict_nisplus_lookup";
+ DICT_NISPLUS *dict_nisplus = (DICT_NISPLUS *) dict;
+ static VSTRING *quoted_key;
+ static VSTRING *query;
+ static VSTRING *retval;
+ nis_result *reply;
+ int count;
+ const char *cp;
+ int last_col;
+ int ch;
+
+ dict->error = 0;
+
+ /*
+ * Initialize.
+ */
+ if (quoted_key == 0) {
+ query = vstring_alloc(100);
+ retval = vstring_alloc(100);
+ quoted_key = vstring_alloc(100);
+ }
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, key);
+ key = lowercase(vstring_str(dict->fold_buf));
+ }
+
+ /*
+ * Check that the lookup key does not contain characters disallowed by
+ * nis+(1).
+ *
+ * XXX Many client implementations don't seem to care about disallowed
+ * characters.
+ */
+ VSTRING_RESET(quoted_key);
+ VSTRING_ADDCH(quoted_key, '"');
+ for (cp = key; (ch = *(unsigned const char *) cp) != 0; cp++) {
+ if ((ISASCII(ch) && !ISPRINT(ch)) || (ch > 126 && ch < 160)) {
+ msg_warn("map %s:%s: lookup key with non-printing character 0x%x:"
+ " ignoring this request",
+ dict->type, dict->name, ch);
+ return (0);
+ } else if (ch == '"') {
+ VSTRING_ADDCH(quoted_key, '"');
+ }
+ VSTRING_ADDCH(quoted_key, ch);
+ }
+ VSTRING_ADDCH(quoted_key, '"');
+ VSTRING_TERMINATE(quoted_key);
+
+ /*
+ * Plug the key into the query template, which typically looks something
+ * like the following: [alias=%s],mail_aliases.org_dir.my.nisplus.domain.
+ *
+ * XXX The nis+ documentation defines a length limit for simple names like
+ * a.b.c., but defines no length limit for (the components of) indexed
+ * names such as [x=y],a.b.c. Our query length is limited because Postfix
+ * addresses (in envelopes or in headers) have a finite length.
+ */
+ vstring_sprintf(query, dict_nisplus->template, STR(quoted_key));
+ reply = nis_list(STR(query), FOLLOW_LINKS | FOLLOW_PATH, NULL, NULL);
+
+ /*
+ * When lookup succeeds, the result may be ambiguous, or the requested
+ * column may not exist.
+ */
+ if (reply->status == NIS_SUCCESS) {
+ if ((count = NIS_RES_NUMOBJ(reply)) != 1) {
+ msg_warn("ambiguous match (%d results) for %s in NIS+ map %s:"
+ " ignoring this request",
+ count, key, dict_nisplus->dict.name);
+ nis_freeresult(reply);
+ return (0);
+ } else {
+ last_col = NIS_RES_OBJECT(reply)->zo_data
+ .objdata_u.en_data.en_cols.en_cols_len - 1;
+ if (dict_nisplus->column > last_col)
+ msg_fatal("requested column %d > max column %d in table %s",
+ dict_nisplus->column, last_col,
+ dict_nisplus->dict.name);
+ vstring_strcpy(retval,
+ NIS_RES_OBJECT(reply)->zo_data.objdata_u
+ .en_data.en_cols.en_cols_val[dict_nisplus->column]
+ .ec_value.ec_value_val);
+ if (msg_verbose)
+ msg_info("%s: %s, column %d -> %s", myname, STR(query),
+ dict_nisplus->column, STR(retval));
+ nis_freeresult(reply);
+ return (STR(retval));
+ }
+ }
+
+ /*
+ * When the NIS+ lookup fails for reasons other than "key not found",
+ * keep logging warnings, and hope that someone will eventually notice
+ * the problem and fix it.
+ */
+ else {
+ if (reply->status != NIS_NOTFOUND
+ && reply->status != NIS_PARTIAL) {
+ msg_warn("lookup %s, NIS+ map %s: %s",
+ key, dict_nisplus->dict.name,
+ nis_sperrno(reply->status));
+ dict->error = DICT_ERR_RETRY;
+ } else {
+ if (msg_verbose)
+ msg_info("%s: not found: query %s", myname, STR(query));
+ }
+ nis_freeresult(reply);
+ return (0);
+ }
+}
+
+/* dict_nisplus_close - close NISPLUS map */
+
+static void dict_nisplus_close(DICT *dict)
+{
+ DICT_NISPLUS *dict_nisplus = (DICT_NISPLUS *) dict;
+
+ myfree(dict_nisplus->template);
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* dict_nisplus_open - open NISPLUS map */
+
+DICT *dict_nisplus_open(const char *map, int open_flags, int dict_flags)
+{
+ const char *myname = "dict_nisplus_open";
+ DICT_NISPLUS *dict_nisplus;
+ char *col_field;
+
+ /*
+ * Sanity check.
+ */
+ if (open_flags != O_RDONLY)
+ return (dict_surrogate(DICT_TYPE_NISPLUS, map, open_flags, dict_flags,
+ "%s:%s map requires O_RDONLY access mode",
+ DICT_TYPE_NISPLUS, map));
+
+ /*
+ * Initialize. This is a read-only map with fixed strings, not with
+ * regular expressions.
+ */
+ dict_nisplus = (DICT_NISPLUS *)
+ dict_alloc(DICT_TYPE_NISPLUS, map, sizeof(*dict_nisplus));
+ dict_nisplus->dict.lookup = dict_nisplus_lookup;
+ dict_nisplus->dict.close = dict_nisplus_close;
+ dict_nisplus->dict.flags = dict_flags | DICT_FLAG_FIXED;
+ if (dict_flags & DICT_FLAG_FOLD_FIX)
+ dict_nisplus->dict.fold_buf = vstring_alloc(10);
+ dict_nisplus->dict.owner.status = DICT_OWNER_TRUSTED;
+
+ /*
+ * Convert the query template into an indexed name and column number. The
+ * query template looks like:
+ *
+ * [attribute=%s;attribute=value...];simple.name.:column
+ *
+ * One instance of %s gets to be replaced by a version of the lookup key;
+ * other attributes must specify fixed values. The reason for using ';'
+ * is that the comma character is special in main.cf. When no column
+ * number is given at the end of the map name, we use a default column.
+ */
+ dict_nisplus->template = mystrdup(map);
+ translit(dict_nisplus->template, ";", ",");
+ if ((col_field = strstr(dict_nisplus->template, ".:")) != 0) {
+ col_field[1] = 0;
+ col_field += 2;
+ if (!alldig(col_field) || (dict_nisplus->column = atoi(col_field)) < 1)
+ msg_fatal("bad column field in NIS+ map name: %s", map);
+ } else {
+ dict_nisplus->column = 1;
+ }
+ if (msg_verbose)
+ msg_info("%s: opened NIS+ table %s for column %d",
+ myname, dict_nisplus->template, dict_nisplus->column);
+ return (DICT_DEBUG (&dict_nisplus->dict));
+}
+
+#endif