7 /* dictionary manager interface to NIS+ maps
9 /* #include <dict_nisplus.h>
11 /* DICT *dict_nisplus_open(map, open_flags, dict_flags)
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.
20 /* dict(3) generic dictionary manager
26 /* The Secure Mailer license must be distributed with this software.
34 /* based on the code for dict_nis.c et al by :-
37 /* IBM T.J. Watson Research
39 /* Yorktown Heights, NY 10598, USA
50 #include <rpcsvc/nis.h> /* for nis_list */
53 /* Utility library. */
58 #include <stringops.h>
60 #include <dict_nisplus.h>
64 /* Application-specific. */
67 DICT dict
; /* generic members */
68 char *template; /* parsed query template */
69 int column
; /* NIS+ field number (start at 1) */
73 * Begin quote from nis+(1):
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''.
83 * name ::= . | <simple name> | <indexed name>
85 * simple name ::= <string>. | <string>.<simple name>
87 * indexed name ::= <search criterion>,<simple name>
89 * search criterion ::= [ <attribute list> ]
91 * attribute list ::= <attribute> | <attribute>,<attribute list>
93 * attribute ::= <string> = <string>
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.
99 * Terminals that appear in strings must be quoted with `"' (double quote).
100 * The `"' character may be quoted by quoting it with itself `""'.
102 * End quote fron nis+(1).
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.
114 #define STR(x) vstring_str(x)
116 /* dict_nisplus_lookup - find table entry */
118 static const char *dict_nisplus_lookup(DICT
*dict
, const char *key
)
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
;
135 if (quoted_key
== 0) {
136 query
= vstring_alloc(100);
137 retval
= vstring_alloc(100);
138 quoted_key
= vstring_alloc(100);
142 * Optionally fold the key.
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
));
152 * Check that the lookup key does not contain characters disallowed by
155 * XXX Many client implementations don't seem to care about disallowed
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
);
166 } else if (ch
== '"') {
167 VSTRING_ADDCH(quoted_key
, '"');
169 VSTRING_ADDCH(quoted_key
, ch
);
171 VSTRING_ADDCH(quoted_key
, '"');
172 VSTRING_TERMINATE(quoted_key
);
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.
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.
183 vstring_sprintf(query
, dict_nisplus
->template, STR(quoted_key
));
184 reply
= nis_list(STR(query
), FOLLOW_LINKS
| FOLLOW_PATH
, NULL
, NULL
);
187 * When lookup succeeds, the result may be ambiguous, or the requested
188 * column may not exist.
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
);
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
);
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
));
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.
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
;
230 msg_info("%s: not found: query %s", myname
, STR(query
));
232 nis_freeresult(reply
);
237 /* dict_nisplus_close - close NISPLUS map */
239 static void dict_nisplus_close(DICT
*dict
)
241 DICT_NISPLUS
*dict_nisplus
= (DICT_NISPLUS
*) dict
;
243 myfree(dict_nisplus
->template);
245 vstring_free(dict
->fold_buf
);
249 /* dict_nisplus_open - open NISPLUS map */
251 DICT
*dict_nisplus_open(const char *map
, int open_flags
, int dict_flags
)
253 const char *myname
= "dict_nisplus_open";
254 DICT_NISPLUS
*dict_nisplus
;
260 if (open_flags
!= O_RDONLY
)
261 msg_fatal("%s:%s map requires O_RDONLY access mode",
262 DICT_TYPE_NISPLUS
, map
);
265 * Initialize. This is a read-only map with fixed strings, not with
266 * regular expressions.
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);
277 * Convert the query template into an indexed name and column number. The
278 * query template looks like:
280 * [attribute=%s;attribute=value...];simple.name.:column
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.
287 dict_nisplus
->template = mystrdup(map
);
288 translit(dict_nisplus
->template, ";", ",");
289 if ((col_field
= strstr(dict_nisplus
->template, ".:")) != 0) {
292 if (!alldig(col_field
) || (dict_nisplus
->column
= atoi(col_field
)) < 1)
293 msg_fatal("bad column field in NIS+ map name: %s", map
);
295 dict_nisplus
->column
= 1;
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
));