/*++ /* NAME /* dict_nisplus 3 /* SUMMARY /* dictionary manager interface to NIS+ maps /* SYNOPSIS /* #include /* /* 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 #include #include #include #include #ifdef HAS_NISPLUS #include /* for nis_list */ #endif /* Utility library. */ #include #include #include #include #include #include #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 ::= , * * search criterion ::= [ ] * * attribute list ::= | , * * attribute ::= = * * 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