nis-util
1.0.D108
|
00001 // 00002 // nis-util - NIS Administration Utilities 00003 // Copyright (C) 2001-2003, 2008, 2009, 2011, 2012 Peter Miller 00004 // 00005 // This program is free software; you can redistribute it and/or modify 00006 // it under the terms of the GNU General Public License as published by 00007 // the Free Software Foundation; either version 2 of the License, or (at 00008 // your option) any later version. 00009 // 00010 // This program is distributed in the hope that it will be useful, 00011 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00013 // General Public License for more details. 00014 // 00015 // You should have received a copy of the GNU General Public License 00016 // along with this program. If not, see <http://www.gnu.org/licenses/>. 00017 // 00018 00019 #include <lib/ac/ctype.h> 00020 #include <lib/ac/string.h> 00021 00022 #include <lib/colon/passwd.h> 00023 #include <lib/configuration.h> 00024 #include <lib/path_join.h> 00025 #include <lib/rcstring/accumulator.h> 00026 00027 00028 colon_passwd::~colon_passwd() 00029 { 00030 } 00031 00032 00033 colon_passwd::colon_passwd(const rcstring &a_filename) : 00034 colon(a_filename), 00035 last_uid(-1) 00036 { 00037 // This host's sysconf(LOGIN_NAME_MAX)-1 may be too generous for 00038 // all NIS clients. Be conservative. 00039 cfg.set_builtin_long(SECTION_PASSWD, ITEM_NAME_LENGTH_MAXIMUM, 8); 00040 // there is no sysconf for this one... 00041 cfg.set_builtin_long(SECTION_PASSWD, "user-id-maximum", 65534); 00042 00043 discard_comments(); 00044 discard_blank_lines(); 00045 } 00046 00047 00048 colon_passwd::pointer 00049 colon_passwd::create(const rcstring &a_filename) 00050 { 00051 return pointer(new colon_passwd(a_filename)); 00052 } 00053 00054 00055 colon_passwd::record::pointer 00056 colon_passwd::get(void) 00057 { 00058 source_location line_locn; 00059 line_t line; 00060 while (read_one_line(line_locn, line)) 00061 { 00062 // 00063 // Check the number of fields 00064 // 00065 if (line.size() < 7) 00066 { 00067 error 00068 ( 00069 line_locn, 00070 "too few fields (expected 7, given %d)", 00071 (int)line.size() 00072 ); 00073 continue; 00074 } 00075 if (line.size() > 7) 00076 { 00077 error 00078 ( 00079 line_locn, 00080 "user %s has too many fields (expected 7, given %ld)", 00081 line[0].quote_c().c_str(), 00082 (long)line.size() 00083 ); 00084 } 00085 00086 // 00087 // Transfer the fields to the structure. 00088 // 00089 record::pointer rp = 00090 record::create 00091 ( 00092 line_locn, 00093 line[0], 00094 line[1], 00095 line[2], 00096 line[3], 00097 line[4], 00098 line[5], 00099 line[6] 00100 ); 00101 00102 // 00103 // If it looks halfway sensable, use it. 00104 // 00105 if (validate(rp)) 00106 return rp; 00107 } 00108 last_uid = -1; 00109 return record::pointer(); 00110 } 00111 00112 00113 bool 00114 colon_passwd::validate(const record::pointer &rp) 00115 { 00116 // 00117 // Check the login name 00118 // 00119 if (rp->get_name().empty()) 00120 { 00121 error 00122 ( 00123 rp->get_source_location(), 00124 "may not have empty login name" 00125 ); 00126 return false; 00127 } 00128 00129 unsigned login_name_maximum_length = 00130 cfg.get_long("passwd", ITEM_NAME_LENGTH_MAXIMUM); 00131 assert(login_name_maximum_length >= 8); 00132 if (rp->get_name().size() > login_name_maximum_length) 00133 { 00134 rcstring suggest = 00135 rp->get_name().substr(0, login_name_maximum_length); 00136 error 00137 ( 00138 rp->get_source_location(), 00139 "login name %s too long, by %ld, suggest %s instead", 00140 rp->get_name().quote_c().c_str(), 00141 (long)(rp->get_name().size() - login_name_maximum_length), 00142 suggest.quote_c().c_str() 00143 ); 00144 return false; 00145 } 00146 00147 { 00148 rcstring suggest = is_ok_user_name(rp->get_name()); 00149 if (suggest != rp->get_name()) 00150 { 00151 error 00152 ( 00153 rp->get_source_location(), 00154 "login name %s unacceptable, suggest %s instead", 00155 rp->get_name().quote_c().c_str(), 00156 suggest.quote_c().c_str() 00157 ); 00158 return false; 00159 } 00160 } 00161 00162 // 00163 // Check the password 00164 // 00165 00166 // 00167 // Check the UID 00168 // 00169 { 00170 long uid = -1; 00171 if (!rp->get_uid().to_long(uid) || uid < 0) 00172 { 00173 error 00174 ( 00175 rp->get_source_location(), 00176 "user %s: user id %s not a positive number", 00177 rp->get_name().quote_c().c_str(), 00178 rp->get_uid().quote_c().c_str() 00179 ); 00180 return false; 00181 } 00182 00183 if (uid < last_uid) 00184 { 00185 error 00186 ( 00187 rp->get_source_location(), 00188 "user %s: user id %s is out of order", 00189 rp->get_name().quote_c().c_str(), 00190 rp->get_uid().quote_c().c_str() 00191 ); 00192 explain_sequence_errors(); 00193 //return false; 00194 } 00195 last_uid = uid; 00196 00197 #if 0 00198 if (uid >= 32768L) 00199 { 00200 error 00201 ( 00202 rp->get_source_location(), 00203 "user %s: user id %s too large (0..32767)", 00204 rp->get_name().quote_c().c_str(), 00205 rp->get_uid().quote_c().c_str() 00206 ); 00207 return false; 00208 } 00209 #endif 00210 00211 rcstring s = rcstring::format("%ld", uid); 00212 if (s != rp->get_uid()) 00213 { 00214 error 00215 ( 00216 rp->get_source_location(), 00217 "user %s: user id %s not in canonical form, use %s instead", 00218 rp->get_name().quote_c().c_str(), 00219 rp->get_uid().quote_c().c_str(), 00220 s.quote_c().c_str() 00221 ); 00222 return false; 00223 } 00224 } 00225 00226 // 00227 // Check the GID field 00228 // 00229 { 00230 long gid = -1; 00231 if (!rp->get_gid().to_long(gid) || gid < 0) 00232 { 00233 error 00234 ( 00235 rp->get_source_location(), 00236 "user %s: group id %s not a positive number", 00237 rp->get_name().quote_c().c_str(), 00238 rp->get_gid().quote_c().c_str() 00239 ); 00240 return false; 00241 } 00242 00243 rcstring s = rcstring::format("%ld", gid); 00244 if (s != rp->get_gid()) 00245 { 00246 error 00247 ( 00248 rp->get_source_location(), 00249 "user %s: group id %s not in canonical form, use %s instead", 00250 rp->get_name().quote_c().c_str(), 00251 rp->get_gid().quote_c().c_str(), 00252 s.quote_c().c_str() 00253 ); 00254 return false; 00255 } 00256 00257 // FIXME: check range? 00258 } 00259 00260 // 00261 // Check full name field. 00262 // 00263 if (rp->get_gecos().empty()) 00264 { 00265 error 00266 ( 00267 rp->get_source_location(), 00268 "user %s has an empty Full Name field", 00269 rp->get_name().quote_c().c_str() 00270 ); 00271 return false; 00272 } 00273 00274 // 00275 // Check home directory 00276 // 00277 if (rp->get_home().empty()) 00278 { 00279 error 00280 ( 00281 rp->get_source_location(), 00282 "user %s has an empty home directory field", 00283 rp->get_name().quote_c().c_str() 00284 ); 00285 return false; 00286 } 00287 00288 if (rp->get_home().front() != '/') 00289 { 00290 rcstring suggest = "/" + rp->get_home(); 00291 if (suggest.size() > 1 && suggest.back() == '/') 00292 suggest = suggest.substr(0, suggest.size() - 1); 00293 error 00294 ( 00295 rp->get_source_location(), 00296 "user %s: home directory %s must be an absolute path, suggest %s", 00297 rp->get_name().quote_c().c_str(), 00298 rp->get_home().quote_c().c_str(), 00299 suggest.quote_c().c_str() 00300 ); 00301 return false; 00302 } 00303 if (rp->get_home().size() > 1 && rp->get_home().back() == '/') 00304 { 00305 rcstring suggest = rp->get_home(); 00306 suggest = suggest.substr(0, suggest.size() - 1); 00307 error 00308 ( 00309 rp->get_source_location(), 00310 "user %s: home directory %s may not end with slash, " 00311 "suggest %s instead", 00312 rp->get_name().quote_c().c_str(), 00313 rp->get_home().quote_c().c_str(), 00314 suggest.quote_c().c_str() 00315 ); 00316 return false; 00317 } 00318 if (rp->get_home().basename() != rp->get_name()) 00319 { 00320 rcstring suggest = path_join(rp->get_home(), rp->get_name()); 00321 error 00322 ( 00323 rp->get_source_location(), 00324 "user %s: home directory %s does not end with %s, " 00325 "did you mean %s instead?", 00326 rp->get_name().quote_c().c_str(), 00327 rp->get_home().quote_c().c_str(), 00328 rp->get_name().quote_c().c_str(), 00329 suggest.quote_c().c_str() 00330 ); 00331 return false; 00332 } 00333 00334 // 00335 // Check shell. 00336 // 00337 // FIXME: validate against /etc/shells 00338 // also take suggestion from /etc/shells 00339 // 00340 if (rp->get_shell().empty()) 00341 { 00342 rcstring suggest = "/bin/bash"; 00343 error 00344 ( 00345 rp->get_source_location(), 00346 "user %s has an empty shell field, suggest %s", 00347 rp->get_name().quote_c().c_str(), 00348 suggest.quote_c().c_str() 00349 ); 00350 return false; 00351 } 00352 if (rp->get_home().front() != '/') 00353 { 00354 rcstring suggest = "/" + rp->get_home(); 00355 error 00356 ( 00357 rp->get_source_location(), 00358 "user %s: shell %s must be an absolute path, " 00359 "suggest %s", 00360 rp->get_name().quote_c().c_str(), 00361 rp->get_shell().quote_c().c_str(), 00362 suggest.quote_c().c_str() 00363 ); 00364 return false; 00365 } 00366 if (rp->get_shell().back() == '/') 00367 { 00368 rcstring suggest = rp->get_shell(); 00369 suggest = suggest.substr(0, suggest.size() - 1); 00370 error 00371 ( 00372 rp->get_source_location(), 00373 "user %s: shell %s may not end with slash, suggest %s", 00374 rp->get_name().quote_c().c_str(), 00375 rp->get_shell().quote_c().c_str(), 00376 suggest.quote_c().c_str() 00377 ); 00378 return false; 00379 } 00380 00381 // check record length 00382 if (rp->representation().size() >= 512) 00383 { 00384 error 00385 ( 00386 rp->get_source_location(), 00387 "line too long (NIS limits records to 511 bytes)" 00388 ); 00389 return false; 00390 } 00391 00392 // 00393 // All good so far. 00394 // 00395 return true; 00396 } 00397 00398 00399 rcstring 00400 colon_passwd::is_ok_user_name(const rcstring &s) 00401 { 00402 // 00403 // Characters you can't have in a login name 00404 // - you can't have upper case, because then the serial 00405 // login programs think you have an upper-case-only terminal. 00406 // - You can't have colon (:) because that's the password 00407 // file field separator. 00408 // - You cant have comma (,) because that's the separator 00409 // in the groups file and the mail aliases file. 00410 // - You can't have space or tab, because they are used as 00411 // field separators in many files. 00412 // - You can't have dot (.) because that's what chown uses 00413 // to separate the user name and the group name. 00414 // - You can't have '#' becuase that looks like a comment 00415 // in too many files. 00416 // - The name must start with a letter, so that it looks 00417 // like a word and not a number or a symbol (many parsers 00418 // need this). 00419 // In general, the more like a C identifier it 00420 // looks, the more programs will be able to cope with it. 00421 // 00422 // The rest of the characters rejected here are just to make 00423 // sure it looks reasonable (as well as making sure the characters 00424 // are printable). 00425 // 00426 if (s.empty()) 00427 return rcstring("_"); 00428 00429 // 00430 // Make sure we have buffer space. 00431 // 00432 rcstring_accumulator acc; 00433 00434 unsigned char c = s.front(); 00435 if (islower(c)) 00436 ; 00437 else if (isupper(c)) 00438 c = tolower(c); 00439 else 00440 c = 'a'; 00441 acc.push_back(c); 00442 for (const char *cp = s.c_str() + 1; *cp; ++cp) 00443 { 00444 c = *cp; 00445 if (isupper(c)) 00446 c = tolower(c); 00447 else if (isalnum(c)) 00448 ; 00449 else if (c != '-' && c != '_') 00450 c = '_'; 00451 acc.push_back(c); 00452 } 00453 return acc.mkstr(); 00454 } 00455 00456 00457 colon_passwd::record::~record() 00458 { 00459 } 00460 00461 00462 colon_passwd::record::record( 00463 const source_location &a_locn, 00464 const rcstring &a_name, 00465 const rcstring &a_password, 00466 const rcstring &a_uid, 00467 const rcstring &a_gid, 00468 const rcstring &a_gecos, 00469 const rcstring &a_home, 00470 const rcstring &a_shell 00471 ) : 00472 locn(a_locn), 00473 name(a_name), 00474 password(a_password), 00475 uid(a_uid), 00476 gid(a_gid), 00477 gecos(a_gecos), 00478 home(a_home), 00479 shell(a_shell) 00480 { 00481 groups.push_back(gid); 00482 } 00483 00484 00485 colon_passwd::record::pointer 00486 colon_passwd::record::create(const source_location &a_locn, 00487 const rcstring &a_name, const rcstring &a_password, const rcstring &a_uid, 00488 const rcstring &a_gid, const rcstring &a_gecos, const rcstring &a_home, 00489 const rcstring &a_shell) 00490 { 00491 return 00492 pointer 00493 ( 00494 new record 00495 ( 00496 a_locn, 00497 a_name, 00498 a_password, 00499 a_uid, 00500 a_gid, 00501 a_gecos, 00502 a_home, 00503 a_shell 00504 ) 00505 ); 00506 } 00507 00508 00509 long 00510 colon_passwd::record::get_uid_binary(void) 00511 const 00512 { 00513 long value = -1; 00514 if (!uid.to_long(value)) 00515 return -1; 00516 return value; 00517 } 00518 00519 00520 long 00521 colon_passwd::record::get_gid_binary(void) 00522 const 00523 { 00524 long value = -1; 00525 if (!gid.to_long(value)) 00526 return -1; 00527 return value; 00528 } 00529 00530 00531 rcstring 00532 colon_passwd::record::representation(void) 00533 const 00534 { 00535 return 00536 rcstring::format 00537 ( 00538 "%s:%s:%s:%s:%s:%s:%s", 00539 name.c_str(), 00540 password.c_str(), 00541 uid.c_str(), 00542 gid.c_str(), 00543 gecos.c_str(), 00544 home.c_str(), 00545 shell.c_str() 00546 ); 00547 } 00548 00549 00550 void 00551 colon_passwd::record::append_group(const rcstring &group_name) 00552 { 00553 groups_t::iterator it = std::find(groups.begin(), groups.end(), group_name); 00554 if (it == groups.end()) 00555 groups.push_back(group_name); 00556 } 00557 00558 00559 // vim: set ts=8 sw=4 et :