1 /* $NetBSD: dict_nisplus.c,v 1.1.1.2 2013/01/02 18:59:12 tron Exp $ */
2
3 /*++
4 /* NAME
5 /* dict_nisplus 3
6 /* SUMMARY
7 /* dictionary manager interface to NIS+ maps
8 /* SYNOPSIS
9 /* #include <dict_nisplus.h>
10 /*
11 /* DICT *dict_nisplus_open(map, open_flags, dict_flags)
12 /* const char *map;
13 /* int dummy;
14 /* int dict_flags;
15 /* DESCRIPTION
16 /* dict_nisplus_open() makes the specified NIS+ map accessible via
17 /* the generic dictionary operations described in dict_open(3).
18 /* The \fIdummy\fR argument is not used.
19 /* SEE ALSO
20 /* dict(3) generic dictionary manager
21 /* DIAGNOSTICS
22 /* Fatal errors:
23 /* LICENSE
24 /* .ad
25 /* .fi
26 /* The Secure Mailer license must be distributed with this software.
27 /* AUTHOR(S)
28 /* Geoff Gibbs
29 /* UK-HGMP-RC
30 /* Hinxton
31 /* Cambridge
32 /* CB10 1SB, UK
33 /*
34 /* based on the code for dict_nis.c et al by :-
35 /*
36 /* Wietse Venema
37 /* IBM T.J. Watson Research
38 /* P.O. Box 704
39 /* Yorktown Heights, NY 10598, USA
40 /*--*/
41
42 /* System library. */
43
44 #include <sys_defs.h>
45 #include <stdio.h>
46 #include <string.h>
47 #include <ctype.h>
48 #include <stdlib.h>
49 #ifdef HAS_NISPLUS
50 #include <rpcsvc/nis.h> /* for nis_list */
51 #endif
52
53 /* Utility library. */
54
55 #include <msg.h>
56 #include <mymalloc.h>
57 #include <vstring.h>
58 #include <stringops.h>
59 #include <dict.h>
60 #include <dict_nisplus.h>
61
62 #ifdef HAS_NISPLUS
63
64 /* Application-specific. */
65
66 typedef struct {
67 DICT dict; /* generic members */
68 char *template; /* parsed query template */
69 int column; /* NIS+ field number (start at 1) */
70 } DICT_NISPLUS;
71
72 /*
73 * Begin quote from nis+(1):
74 *
75 * The following text represents a context-free grammar that defines the
76 * set of legal NIS+ names. The terminals in this grammar are the
77 * characters `.' (dot), `[' (open bracket), `]' (close bracket), `,'
78 * (comma), `=' (equals) and whitespace. Angle brackets (`<' and `>'),
79 * which delineate non- terminals, are not part of the grammar. The
80 * character `|' (vertical bar) is used to separate alternate productions
81 * and should be read as ``this production OR this production''.
82 *
83 * name ::= . | <simple name> | <indexed name>
84 *
85 * simple name ::= <string>. | <string>.<simple name>
86 *
87 * indexed name ::= <search criterion>,<simple name>
88 *
89 * search criterion ::= [ <attribute list> ]
90 *
91 * attribute list ::= <attribute> | <attribute>,<attribute list>
92 *
93 * attribute ::= <string> = <string>
94 *
95 * string ::= ISO Latin 1 character set except the character
96 * '/' (slash). The initial character may not be a terminal character or
97 * the characters '@' (at), '+' (plus), or (`-') hyphen.
98 *
99 * Terminals that appear in strings must be quoted with `"' (double quote).
100 * The `"' character may be quoted by quoting it with itself `""'.
101 *
102 * End quote fron nis+(1).
103 *
104 * This NIS client always quotes the entire query string (the value part of
105 * [attribute=value],file.domain.) so the issue with initial characters
106 * should not be applicable. One wonders what restrictions are applicable
107 * when a string is quoted, but the manual doesn't specify what can appear
108 * between quotes, and we don't want to get burned.
109 */
110
111 /*
112 * SLMs.
113 */
114 #define STR(x) vstring_str(x)
115
116 /* dict_nisplus_lookup - find table entry */
117
dict_nisplus_lookup(DICT * dict,const char * key)118 static const char *dict_nisplus_lookup(DICT *dict, const char *key)
119 {
120 const char *myname = "dict_nisplus_lookup";
121 DICT_NISPLUS *dict_nisplus = (DICT_NISPLUS *) dict;
122 static VSTRING *quoted_key;
123 static VSTRING *query;
124 static VSTRING *retval;
125 nis_result *reply;
126 int count;
127 const char *cp;
128 int last_col;
129 int ch;
130
131 dict->error = 0;
132
133 /*
134 * Initialize.
135 */
136 if (quoted_key == 0) {
137 query = vstring_alloc(100);
138 retval = vstring_alloc(100);
139 quoted_key = vstring_alloc(100);
140 }
141
142 /*
143 * Optionally fold the key.
144 */
145 if (dict->flags & DICT_FLAG_FOLD_FIX) {
146 if (dict->fold_buf == 0)
147 dict->fold_buf = vstring_alloc(10);
148 vstring_strcpy(dict->fold_buf, key);
149 key = lowercase(vstring_str(dict->fold_buf));
150 }
151
152 /*
153 * Check that the lookup key does not contain characters disallowed by
154 * nis+(1).
155 *
156 * XXX Many client implementations don't seem to care about disallowed
157 * characters.
158 */
159 VSTRING_RESET(quoted_key);
160 VSTRING_ADDCH(quoted_key, '"');
161 for (cp = key; (ch = *(unsigned const char *) cp) != 0; cp++) {
162 if ((ISASCII(ch) && !ISPRINT(ch)) || (ch > 126 && ch < 160)) {
163 msg_warn("map %s:%s: lookup key with non-printing character 0x%x:"
164 " ignoring this request",
165 dict->type, dict->name, ch);
166 return (0);
167 } else if (ch == '"') {
168 VSTRING_ADDCH(quoted_key, '"');
169 }
170 VSTRING_ADDCH(quoted_key, ch);
171 }
172 VSTRING_ADDCH(quoted_key, '"');
173 VSTRING_TERMINATE(quoted_key);
174
175 /*
176 * Plug the key into the query template, which typically looks something
177 * like the following: [alias=%s],mail_aliases.org_dir.my.nisplus.domain.
178 *
179 * XXX The nis+ documentation defines a length limit for simple names like
180 * a.b.c., but defines no length limit for (the components of) indexed
181 * names such as [x=y],a.b.c. Our query length is limited because Postfix
182 * addresses (in envelopes or in headers) have a finite length.
183 */
184 vstring_sprintf(query, dict_nisplus->template, STR(quoted_key));
185 reply = nis_list(STR(query), FOLLOW_LINKS | FOLLOW_PATH, NULL, NULL);
186
187 /*
188 * When lookup succeeds, the result may be ambiguous, or the requested
189 * column may not exist.
190 */
191 if (reply->status == NIS_SUCCESS) {
192 if ((count = NIS_RES_NUMOBJ(reply)) != 1) {
193 msg_warn("ambiguous match (%d results) for %s in NIS+ map %s:"
194 " ignoring this request",
195 count, key, dict_nisplus->dict.name);
196 nis_freeresult(reply);
197 return (0);
198 } else {
199 last_col = NIS_RES_OBJECT(reply)->zo_data
200 .objdata_u.en_data.en_cols.en_cols_len - 1;
201 if (dict_nisplus->column > last_col)
202 msg_fatal("requested column %d > max column %d in table %s",
203 dict_nisplus->column, last_col,
204 dict_nisplus->dict.name);
205 vstring_strcpy(retval,
206 NIS_RES_OBJECT(reply)->zo_data.objdata_u
207 .en_data.en_cols.en_cols_val[dict_nisplus->column]
208 .ec_value.ec_value_val);
209 if (msg_verbose)
210 msg_info("%s: %s, column %d -> %s", myname, STR(query),
211 dict_nisplus->column, STR(retval));
212 nis_freeresult(reply);
213 return (STR(retval));
214 }
215 }
216
217 /*
218 * When the NIS+ lookup fails for reasons other than "key not found",
219 * keep logging warnings, and hope that someone will eventually notice
220 * the problem and fix it.
221 */
222 else {
223 if (reply->status != NIS_NOTFOUND
224 && reply->status != NIS_PARTIAL) {
225 msg_warn("lookup %s, NIS+ map %s: %s",
226 key, dict_nisplus->dict.name,
227 nis_sperrno(reply->status));
228 dict->error = DICT_ERR_RETRY;
229 } else {
230 if (msg_verbose)
231 msg_info("%s: not found: query %s", myname, STR(query));
232 }
233 nis_freeresult(reply);
234 return (0);
235 }
236 }
237
238 /* dict_nisplus_close - close NISPLUS map */
239
dict_nisplus_close(DICT * dict)240 static void dict_nisplus_close(DICT *dict)
241 {
242 DICT_NISPLUS *dict_nisplus = (DICT_NISPLUS *) dict;
243
244 myfree(dict_nisplus->template);
245 if (dict->fold_buf)
246 vstring_free(dict->fold_buf);
247 dict_free(dict);
248 }
249
250 /* dict_nisplus_open - open NISPLUS map */
251
dict_nisplus_open(const char * map,int open_flags,int dict_flags)252 DICT *dict_nisplus_open(const char *map, int open_flags, int dict_flags)
253 {
254 const char *myname = "dict_nisplus_open";
255 DICT_NISPLUS *dict_nisplus;
256 char *col_field;
257
258 /*
259 * Sanity check.
260 */
261 if (open_flags != O_RDONLY)
262 return (dict_surrogate(DICT_TYPE_NISPLUS, map, open_flags, dict_flags,
263 "%s:%s map requires O_RDONLY access mode",
264 DICT_TYPE_NISPLUS, map));
265
266 /*
267 * Initialize. This is a read-only map with fixed strings, not with
268 * regular expressions.
269 */
270 dict_nisplus = (DICT_NISPLUS *)
271 dict_alloc(DICT_TYPE_NISPLUS, map, sizeof(*dict_nisplus));
272 dict_nisplus->dict.lookup = dict_nisplus_lookup;
273 dict_nisplus->dict.close = dict_nisplus_close;
274 dict_nisplus->dict.flags = dict_flags | DICT_FLAG_FIXED;
275 if (dict_flags & DICT_FLAG_FOLD_FIX)
276 dict_nisplus->dict.fold_buf = vstring_alloc(10);
277 dict_nisplus->dict.owner.status = DICT_OWNER_TRUSTED;
278
279 /*
280 * Convert the query template into an indexed name and column number. The
281 * query template looks like:
282 *
283 * [attribute=%s;attribute=value...];simple.name.:column
284 *
285 * One instance of %s gets to be replaced by a version of the lookup key;
286 * other attributes must specify fixed values. The reason for using ';'
287 * is that the comma character is special in main.cf. When no column
288 * number is given at the end of the map name, we use a default column.
289 */
290 dict_nisplus->template = mystrdup(map);
291 translit(dict_nisplus->template, ";", ",");
292 if ((col_field = strstr(dict_nisplus->template, ".:")) != 0) {
293 col_field[1] = 0;
294 col_field += 2;
295 if (!alldig(col_field) || (dict_nisplus->column = atoi(col_field)) < 1)
296 msg_fatal("bad column field in NIS+ map name: %s", map);
297 } else {
298 dict_nisplus->column = 1;
299 }
300 if (msg_verbose)
301 msg_info("%s: opened NIS+ table %s for column %d",
302 myname, dict_nisplus->template, dict_nisplus->column);
303 return (DICT_DEBUG (&dict_nisplus->dict));
304 }
305
306 #endif
307