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