xref: /netbsd-src/external/ibm-public/postfix/dist/src/util/dict_nisplus.c (revision ca453df649ce9db45b64d73678ba06cbccf9aa11)
1 /*	$NetBSD: dict_nisplus.c,v 1.1.1.1 2009/06/23 10:08:59 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 
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     /*
132      * Initialize.
133      */
134     dict_errno = 0;
135     if (quoted_key == 0) {
136 	query = vstring_alloc(100);
137 	retval = vstring_alloc(100);
138 	quoted_key = vstring_alloc(100);
139     }
140 
141     /*
142      * Optionally fold the key.
143      */
144     if (dict->flags & DICT_FLAG_FOLD_FIX) {
145 	if (dict->fold_buf == 0)
146 	    dict->fold_buf = vstring_alloc(10);
147 	vstring_strcpy(dict->fold_buf, key);
148 	key = lowercase(vstring_str(dict->fold_buf));
149     }
150 
151     /*
152      * Check that the lookup key does not contain characters disallowed by
153      * nis+(1).
154      *
155      * XXX Many client implementations don't seem to care about disallowed
156      * characters.
157      */
158     VSTRING_RESET(quoted_key);
159     VSTRING_ADDCH(quoted_key, '"');
160     for (cp = key; (ch = *(unsigned const char *) cp) != 0; cp++) {
161 	if ((ISASCII(ch) && !ISPRINT(ch)) || (ch > 126 && ch < 160)) {
162 	    msg_warn("map %s:%s: lookup key with non-printing character 0x%x:"
163 		     " ignoring this request",
164 		     dict->type, dict->name, ch);
165 	    return (0);
166 	} else if (ch == '"') {
167 	    VSTRING_ADDCH(quoted_key, '"');
168 	}
169 	VSTRING_ADDCH(quoted_key, ch);
170     }
171     VSTRING_ADDCH(quoted_key, '"');
172     VSTRING_TERMINATE(quoted_key);
173 
174     /*
175      * Plug the key into the query template, which typically looks something
176      * like the following: [alias=%s],mail_aliases.org_dir.my.nisplus.domain.
177      *
178      * XXX The nis+ documentation defines a length limit for simple names like
179      * a.b.c., but defines no length limit for (the components of) indexed
180      * names such as [x=y],a.b.c. Our query length is limited because Postfix
181      * addresses (in envelopes or in headers) have a finite length.
182      */
183     vstring_sprintf(query, dict_nisplus->template, STR(quoted_key));
184     reply = nis_list(STR(query), FOLLOW_LINKS | FOLLOW_PATH, NULL, NULL);
185 
186     /*
187      * When lookup succeeds, the result may be ambiguous, or the requested
188      * column may not exist.
189      */
190     if (reply->status == NIS_SUCCESS) {
191 	if ((count = NIS_RES_NUMOBJ(reply)) != 1) {
192 	    msg_warn("ambiguous match (%d results) for %s in NIS+ map %s:"
193 		     " ignoring this request",
194 		     count, key, dict_nisplus->dict.name);
195 	    nis_freeresult(reply);
196 	    return (0);
197 	} else {
198 	    last_col = NIS_RES_OBJECT(reply)->zo_data
199 		.objdata_u.en_data.en_cols.en_cols_len - 1;
200 	    if (dict_nisplus->column > last_col)
201 		msg_fatal("requested column %d > max column %d in table %s",
202 			  dict_nisplus->column, last_col,
203 			  dict_nisplus->dict.name);
204 	    vstring_strcpy(retval,
205 			   NIS_RES_OBJECT(reply)->zo_data.objdata_u
206 			   .en_data.en_cols.en_cols_val[dict_nisplus->column]
207 			   .ec_value.ec_value_val);
208 	    if (msg_verbose)
209 		msg_info("%s: %s, column %d -> %s", myname, STR(query),
210 			 dict_nisplus->column, STR(retval));
211 	    nis_freeresult(reply);
212 	    return (STR(retval));
213 	}
214     }
215 
216     /*
217      * When the NIS+ lookup fails for reasons other than "key not found",
218      * keep logging warnings, and hope that someone will eventually notice
219      * the problem and fix it.
220      */
221     else {
222 	if (reply->status != NIS_NOTFOUND
223 	    && reply->status != NIS_PARTIAL) {
224 	    msg_warn("lookup %s, NIS+ map %s: %s",
225 		     key, dict_nisplus->dict.name,
226 		     nis_sperrno(reply->status));
227 	    dict_errno = DICT_ERR_RETRY;
228 	} else {
229 	    if (msg_verbose)
230 		msg_info("%s: not found: query %s", myname, STR(query));
231 	}
232 	nis_freeresult(reply);
233 	return (0);
234     }
235 }
236 
237 /* dict_nisplus_close - close NISPLUS map */
238 
239 static void dict_nisplus_close(DICT *dict)
240 {
241     DICT_NISPLUS *dict_nisplus = (DICT_NISPLUS *) dict;
242 
243     myfree(dict_nisplus->template);
244     if (dict->fold_buf)
245 	vstring_free(dict->fold_buf);
246     dict_free(dict);
247 }
248 
249 /* dict_nisplus_open - open NISPLUS map */
250 
251 DICT   *dict_nisplus_open(const char *map, int open_flags, int dict_flags)
252 {
253     const char *myname = "dict_nisplus_open";
254     DICT_NISPLUS *dict_nisplus;
255     char   *col_field;
256 
257     /*
258      * Sanity check.
259      */
260     if (open_flags != O_RDONLY)
261 	msg_fatal("%s:%s map requires O_RDONLY access mode",
262 		  DICT_TYPE_NISPLUS, map);
263 
264     /*
265      * Initialize. This is a read-only map with fixed strings, not with
266      * regular expressions.
267      */
268     dict_nisplus = (DICT_NISPLUS *)
269 	dict_alloc(DICT_TYPE_NISPLUS, map, sizeof(*dict_nisplus));
270     dict_nisplus->dict.lookup = dict_nisplus_lookup;
271     dict_nisplus->dict.close = dict_nisplus_close;
272     dict_nisplus->dict.flags = dict_flags | DICT_FLAG_FIXED;
273     if (dict_flags & DICT_FLAG_FOLD_FIX)
274 	dict_nisplus->dict.fold_buf = vstring_alloc(10);
275 
276     /*
277      * Convert the query template into an indexed name and column number. The
278      * query template looks like:
279      *
280      * [attribute=%s;attribute=value...];simple.name.:column
281      *
282      * One instance of %s gets to be replaced by a version of the lookup key;
283      * other attributes must specify fixed values. The reason for using ';'
284      * is that the comma character is special in main.cf. When no column
285      * number is given at the end of the map name, we use a default column.
286      */
287     dict_nisplus->template = mystrdup(map);
288     translit(dict_nisplus->template, ";", ",");
289     if ((col_field = strstr(dict_nisplus->template, ".:")) != 0) {
290 	col_field[1] = 0;
291 	col_field += 2;
292 	if (!alldig(col_field) || (dict_nisplus->column = atoi(col_field)) < 1)
293 	    msg_fatal("bad column field in NIS+ map name: %s", map);
294     } else {
295 	dict_nisplus->column = 1;
296     }
297     if (msg_verbose)
298 	msg_info("%s: opened NIS+ table %s for column %d",
299 		 myname, dict_nisplus->template, dict_nisplus->column);
300     return (DICT_DEBUG (&dict_nisplus->dict));
301 }
302 
303 #endif
304