nis-util  1.0.D108
lib/colon/passwd.cc
Go to the documentation of this file.
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 :