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 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 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 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