int main(int argc, char **argv) { hardened_shadow_openlog("useradd"); if (lckpwdf() != 0) err(EXIT_FAILURE, "lckpwdf"); if (!read_defaults_file()) warnx("failed to read defaults file"); parse_args(argc, argv); if (flag_defaults) return (handle_flag_defaults()) ? EXIT_SUCCESS : EXIT_FAILURE; determine_uid_gid(); if (!flag_no_log_init) initialize_lastlog(); if (flag_create_home) create_home_dir(); if (default_create_mail_spool && !flag_system) create_mail_spool(); create_account(); hardened_shadow_flush_nscd("passwd"); hardened_shadow_flush_nscd("group"); if (ulckpwdf() != 0) warn("ulckpwdf"); return EXIT_SUCCESS; }
int lock_pwdf(void) { int i; int retval; #ifndef HELPER_COMPILE if (unix_selinux_confined()) { return PAM_SUCCESS; } #endif /* These values for the number of attempts and the sleep time are, of course, completely arbitrary. My reading of the PAM docs is that, once pam_chauthtok() has been called with PAM_UPDATE_AUTHTOK, we are obliged to take any reasonable steps to make sure the token is updated; so retrying for 1/10 sec. isn't overdoing it. */ i=0; while((retval = lckpwdf()) != 0 && i < 100) { usleep(1000); i++; } if(retval != 0) { return PAM_AUTHTOK_LOCK_BUSY; } return PAM_SUCCESS; }
uid_t SecurityManager::authenticate(char *userName, char *password) { #ifdef __APPLE__ // lckpwdf etc. are not supported by MacOS. return (uid_t)-1; #else if ((userName == NULL) || (password == NULL)) return (uid_t)-1; uid_t result = (uid_t)-1; lckpwdf(); setpwent(); setspent(); struct passwd *passwdEntry = getpwnam(userName); struct spwd *shadowEntry = getspnam(userName); if ((shadowEntry != NULL) && (passwdEntry != NULL)) { char *plain = password; char *encrypted = shadowEntry->sp_pwdp; if (strcmp(crypt(plain, encrypted), encrypted) == 0) result = passwdEntry->pw_uid; } endpwent(); endspent(); ulckpwdf(); return result; #endif } // end of authenticate(char*, char*)
static VALUE rb_shadow_lckpwdf(VALUE self) { int result; result = lckpwdf(); if( result == -1 ) rb_raise(rb_eFileLock,"password file was locked"); else return Qtrue; };
static void edit_file(int is_shadow) { struct stat begin, end; int passwd_file, ch_ret; FILE *tmp_fd; pw_init(); /* acquire exclusive lock */ if (lckpwdf() < 0) err(EXIT_FAILURE, _("cannot get lock")); passwd_file = open(orig_file, O_RDONLY, 0); if (passwd_file < 0) err(EXIT_FAILURE, _("cannot open %s"), orig_file); tmp_fd = pw_tmpfile(passwd_file); if (fstat(fileno(tmp_fd), &begin)) pw_error(tmp_file, 1, 1); pw_edit(); if (fstat(fileno(tmp_fd), &end)) pw_error(tmp_file, 1, 1); /* Some editors, such as Vim with 'writebackup' mode enabled, * use "atomic save" in which the old file is deleted and a new * one with the same name created in its place. */ if (end.st_nlink == 0) { if (close_stream(tmp_fd) != 0) err(EXIT_FAILURE, _("write error")); tmp_fd = fopen(tmp_file, "r"); if (!tmp_file) err(EXIT_FAILURE, _("cannot open %s"), tmp_file); if (fstat(fileno(tmp_fd), &end)) pw_error(tmp_file, 1, 1); } if (begin.st_mtime == end.st_mtime) { warnx(_("no changes made")); pw_error((char *)NULL, 0, 0); } /* pw_tmpfile() will create the file with mode 600 */ if (!is_shadow) ch_ret = fchmod(fileno(tmp_fd), 0644); else ch_ret = fchmod(fileno(tmp_fd), 0400); if (ch_ret < 0) err(EXIT_FAILURE, "%s: %s", _("cannot chmod file"), orig_file); if (close_stream(tmp_fd) != 0) err(EXIT_FAILURE, _("write error")); pw_write(); close(passwd_file); ulckpwdf(); }
static VALUE rb_shadow_lock_p(VALUE self) { int result; result = lckpwdf(); if( result == -1 ){ return Qtrue; } else{ ulckpwdf(); return Qfalse; }; };
int commonio_lock(struct commonio_db *db) { #ifdef HAVE_LCKPWDF /* * only if the system libc has a real lckpwdf() - the one from * lockpw.c calls us and would cause infinite recursion! */ /* * Call lckpwdf() on the first lock. * If it succeeds, call *_lock() only once * (no retries, it should always succeed). */ if (lock_count == 0) { if (lckpwdf() == -1) return 0; /* failure */ } if (commonio_lock_nowait(db)) return 1; /* success */ ulckpwdf(); return 0; /* failure */ #else int i; /* * lckpwdf() not used - do it the old way. */ #ifndef LOCK_TRIES #define LOCK_TRIES 15 #endif #ifndef LOCK_SLEEP #define LOCK_SLEEP 1 #endif for (i = 0; i < LOCK_TRIES; i++) { if (i > 0) sleep(LOCK_SLEEP); /* delay between retries */ if (commonio_lock_nowait(db)) return 1; /* success */ /* no unnecessary retries on "permission denied" errors */ if (geteuid() != 0) return 0; } return 0; /* failure */ #endif }
/* * lock functions for files repository */ int files_lock(void) { int res; if (lckpwdf()) { switch (errno) { case EINTR: res = PWU_BUSY; break; case EACCES: res = PWU_DENIED; break; case 0: res = PWU_SUCCESS; break; } } else res = PWU_SUCCESS; return (res); }
static VALUE rb_shadow_lock(VALUE self) { int result; if( rb_iterator_p() ){ result = lckpwdf(); if( result == -1 ){ rb_raise(rb_eFileLock,"password file was locked"); } else{ in_lock++; rb_yield(Qnil); in_lock--; ulckpwdf(); }; return Qtrue; } else{ return rb_shadow_lckpwdf(self); }; };
int main(int argc, char **argv) { hardened_shadow_openlog("groupadd"); if (lckpwdf() != 0) err(EXIT_FAILURE, "lckpwdf"); parse_args(argc, argv); if (flag_gid == (gid_t)-1) { const char *gid_key = (flag_system) ? "SYSTEM_GID_RANGE" : "USER_GID_RANGE"; intmax_t gid_min, gid_max; if (!hardened_shadow_config_get_range(gid_key, &gid_min, &gid_max)) errx(EXIT_FAILURE, "Failed to retrieve GID range."); if (!hardened_shadow_allocate_gid(gid_min, gid_max, &flag_gid)) errx(EXIT_FAILURE, "Failed to allocate GID."); } struct group grp; memset(&grp, '\0', sizeof(grp)); grp.gr_name = group_name; grp.gr_passwd = HARDENED_SHADOW_SHADOW_PASSWD; grp.gr_gid = flag_gid; grp.gr_mem = &empty_list; if (!hardened_shadow_replace_group(group_name, &grp)) errx(EXIT_FAILURE, "Failed to update /etc/group."); hardened_shadow_syslog(LOG_INFO, "new group: name=%s, GID=%ju", group_name, (uintmax_t)flag_gid); hardened_shadow_flush_nscd("group"); if (ulckpwdf() != 0) warn("ulckpwdf"); return EXIT_SUCCESS; }
/* * This function looks up "username" in the shadow password file, determines * the hash algorithm type, and returns the salt and the password * hash for that user. * * Given the salt and the user password, then the hash can be created. * The generated hash is used as an SRP password (client side), and * the generator for the SRP secret (server side). * * Crypt password file format references: * http://php.net/manual/en/function.crypt.php * http://en.wikipedia.org/wiki/Crypt_%28C%29#Blowfish-based_scheme * * Look up from the shadow password file the specified user, and if found, * return the salt field parsed out from the hash entry * * Algorithm ID * $1$ MD5 * 12 characters salt follows * * $2a$ Blowfish * $2b$ Blowfish * $2x$ Blowfish * $2y$ Blowfish * Blowfish salt format: * $id$NN$-----22 chars-salt----++++++hash+++++: * * SHA salt format * $5$ SHA-256 * $6$ SHA-512 * $ID$salt$hash */ int get_sp_salt(const char *username, char **ret_salt, char **ret_encpwd) { int st = 0; int is_locked = 0; struct spwd *spval = NULL; struct spwd spval_buf = {0}; char *spbuf_str = NULL; int spbuf_str_len = 256; int salt_len = 0; char *salt = NULL; char *encpwd = NULL; char *sp = NULL; int cur_uid = 0; int error = 0; if (!username || !ret_salt || !ret_encpwd) { st = -1; errno = EINVAL; goto error; } /* Must be root to read shadow password file */ cur_uid = getuid(); error = seteuid(0); if (error != 0) { st = -1; goto error; } /* Obtain password file lock, and hold minimum amount of time */ st = lckpwdf(); if (st == -1) { goto error; } is_locked = 1; spbuf_str = calloc(spbuf_str_len, sizeof(char)); if (!spbuf_str) { st = -1; goto error; } st = getspnam_r(username, &spval_buf, spbuf_str, spbuf_str_len, &spval); if (!spval || st == -1) { /* Failed due to permissions or entry not found */ st = -1; goto error; } salt = strdup(spval->sp_pwdp); if (!salt) { /* errno is set */ st = -1; goto error; } encpwd = strdup(spval->sp_pwdp); if (!encpwd) { /* errno is set */ st = -1; goto error; } ulckpwdf(); error = seteuid(cur_uid); if (error != 0) { st = -1; goto error; } is_locked = 0; /* CRYPT_DES hash is not supported; how to test? */ /* Determine the hash algorithn, and therefore the salt length */ if (!strncmp(salt, CRYPT_MD5, strlen(CRYPT_MD5))) { /* $1$123456789012 */ salt_len = 12 + 3; } else if (!strncmp(salt, CRYPT_BLOWFISH_2A, strlen(CRYPT_BLOWFISH_2A)) || !strncmp(salt, CRYPT_BLOWFISH_2B, strlen(CRYPT_BLOWFISH_2B)) || !strncmp(salt, CRYPT_BLOWFISH_2X, strlen(CRYPT_BLOWFISH_2X)) || !strncmp(salt, CRYPT_BLOWFISH_2Y, strlen(CRYPT_BLOWFISH_2Y))) { /* $2a$05$1234567890123456789012 */ salt_len = 22 + 7; } else if (!strncmp(salt, CRYPT_SHA_256, strlen(CRYPT_SHA_256)) || !strncmp(salt, CRYPT_SHA_512, strlen(CRYPT_SHA_512))) { sp = strrchr(salt, '$'); salt_len = sp - salt + 1; } if(salt_len == 0)//locked user, user with nologin etc { st = -1; errno = EPERM; goto error; } salt[salt_len] = '\0'; *ret_salt = salt; *ret_encpwd = encpwd; salt = NULL; error: if (is_locked) { ulckpwdf(); error = seteuid(cur_uid); } if (spbuf_str) { free(spbuf_str); } if (st == -1) { if (salt) { free(salt); salt = NULL; } if (encpwd) { free(encpwd); encpwd = NULL; } } return st; }
PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags, int argc, const char **argv) { unsigned int ctrl, lctrl; int retval, i; int remember = -1; /* <DO NOT free() THESE> */ const char *user; char *pass_old, *pass_new; /* </DO NOT free() THESE> */ D(("called.")); #ifdef USE_LCKPWDF /* our current locking system requires that we lock the entire password database. This avoids both livelock and deadlock. */ /* These values for the number of attempts and the sleep time are, of course, completely arbitrary. My reading of the PAM docs is that, once pam_chauthtok() has been called with PAM_UPDATE_AUTHTOK, we are obliged to take any reasonable steps to make sure the token is updated; so retrying for 1/10 sec. isn't overdoing it. The other possibility is to call lckpwdf() on the first pam_chauthtok() pass, and hold the lock until released in the second pass--but is this guaranteed to work? -SRL */ i=0; while((retval = lckpwdf()) != 0 && i < 100) { usleep(1000); } if(retval != 0) { return PAM_AUTHTOK_LOCK_BUSY; } #endif ctrl = _set_ctrl(pamh, flags, &remember, argc, argv); /* * First get the name of a user */ retval = pam_get_user(pamh, &user, "Username: "******"bad username [%s]", user); #ifdef USE_LCKPWDF ulckpwdf(); #endif return PAM_USER_UNKNOWN; } if (retval == PAM_SUCCESS && on(UNIX_DEBUG, ctrl)) _log_err(LOG_DEBUG, pamh, "username [%s] obtained", user); } else { if (on(UNIX_DEBUG, ctrl)) _log_err(LOG_DEBUG, pamh, "password - could not identify user"); #ifdef USE_LCKPWDF ulckpwdf(); #endif return retval; } D(("Got username of %s", user)); /* * This is not an AUTH module! */ if (on(UNIX__NONULL, ctrl)) set(UNIX__NULLOK, ctrl); if (on(UNIX__PRELIM, ctrl)) { /* * obtain and verify the current password (OLDAUTHTOK) for * the user. */ char *Announce; D(("prelim check")); if (_unix_blankpasswd(ctrl, user)) { #ifdef USE_LCKPWDF ulckpwdf(); #endif return PAM_SUCCESS; } else if (off(UNIX__IAMROOT, ctrl)) { /* instruct user what is happening */ #define greeting "Changing password for " Announce = (char *) malloc(sizeof(greeting) + strlen(user)); if (Announce == NULL) { _log_err(LOG_CRIT, pamh, "password - out of memory"); #ifdef USE_LCKPWDF ulckpwdf(); #endif return PAM_BUF_ERR; } (void) strcpy(Announce, greeting); (void) strcpy(Announce + sizeof(greeting) - 1, user); #undef greeting lctrl = ctrl; set(UNIX__OLD_PASSWD, lctrl); retval = _unix_read_password(pamh, lctrl ,Announce ,"(current) UNIX password: "******"password - (old) token not obtained"); #ifdef USE_LCKPWDF ulckpwdf(); #endif return retval; } /* verify that this is the password for this user */ retval = _unix_verify_password(pamh, user, pass_old, ctrl); } else { D(("process run by root so do nothing this time around")); pass_old = NULL; retval = PAM_SUCCESS; /* root doesn't have too */ } if (retval != PAM_SUCCESS) { D(("Authentication failed")); pass_old = NULL; #ifdef USE_LCKPWDF ulckpwdf(); #endif return retval; } retval = pam_set_item(pamh, PAM_OLDAUTHTOK, (const void *) pass_old); pass_old = NULL; if (retval != PAM_SUCCESS) { _log_err(LOG_CRIT, pamh, "failed to set PAM_OLDAUTHTOK"); } retval = _unix_verify_shadow(user, ctrl); if (retval == PAM_AUTHTOK_ERR) { if (off(UNIX__IAMROOT, ctrl)) _make_remark(pamh, ctrl, PAM_ERROR_MSG, "You must wait longer to change your password"); else retval = PAM_SUCCESS; } } else if (on(UNIX__UPDATE, ctrl)) { /* * tpass is used below to store the _pam_md() return; it * should be _pam_delete()'d. */ char *tpass = NULL; int retry = 0; /* * obtain the proposed password */ D(("do update")); /* * get the old token back. NULL was ok only if root [at this * point we assume that this has already been enforced on a * previous call to this function]. */ if (off(UNIX_NOT_SET_PASS, ctrl)) { retval = pam_get_item(pamh, PAM_OLDAUTHTOK ,(const void **) &pass_old); } else { retval = pam_get_data(pamh, _UNIX_OLD_AUTHTOK ,(const void **) &pass_old); if (retval == PAM_NO_MODULE_DATA) { retval = PAM_SUCCESS; pass_old = NULL; } } D(("pass_old [%s]", pass_old)); if (retval != PAM_SUCCESS) { _log_err(LOG_NOTICE, pamh, "user not authenticated"); #ifdef USE_LCKPWDF ulckpwdf(); #endif return retval; } retval = _unix_verify_shadow(user, ctrl); if (retval != PAM_SUCCESS) { _log_err(LOG_NOTICE, pamh, "user not authenticated 2"); #ifdef USE_LCKPWDF ulckpwdf(); #endif return retval; } D(("get new password now")); lctrl = ctrl; if (on(UNIX_USE_AUTHTOK, lctrl)) { set(UNIX_USE_FIRST_PASS, lctrl); } retry = 0; retval = PAM_AUTHTOK_ERR; while ((retval != PAM_SUCCESS) && (retry++ < MAX_PASSWD_TRIES)) { /* * use_authtok is to force the use of a previously entered * password -- needed for pluggable password strength checking */ retval = _unix_read_password(pamh, lctrl ,NULL ,"Enter new UNIX password: "******"Retype new UNIX password: "******"password - new password not obtained"); } pass_old = NULL; /* tidy up */ #ifdef USE_LCKPWDF ulckpwdf(); #endif return retval; } D(("returned to _unix_chauthtok")); /* * At this point we know who the user is and what they * propose as their new password. Verify that the new * password is acceptable. */ if (pass_new[0] == '\0') { /* "\0" password = NULL */ pass_new = NULL; } retval = _pam_unix_approve_pass(pamh, ctrl, pass_old, pass_new); } if (retval != PAM_SUCCESS) { _log_err(LOG_NOTICE, pamh, "new password not acceptable"); _pam_overwrite(pass_new); _pam_overwrite(pass_old); pass_new = pass_old = NULL; /* tidy up */ #ifdef USE_LCKPWDF ulckpwdf(); #endif return retval; } /* * By reaching here we have approved the passwords and must now * rebuild the password database file. */ /* * First we encrypt the new password. */ if (on(UNIX_MD5_PASS, ctrl)) { tpass = crypt_md5_wrapper(pass_new); } else { /* * Salt manipulation is stolen from Rick Faith's passwd * program. Sorry Rick :) -- alex */ time_t tm; char salt[3]; time(&tm); salt[0] = bin_to_ascii(tm & 0x3f); salt[1] = bin_to_ascii((tm >> 6) & 0x3f); salt[2] = '\0'; if (off(UNIX_BIGCRYPT, ctrl) && strlen(pass_new) > 8) { /* * to avoid using the _extensions_ of the bigcrypt() * function we truncate the newly entered password */ char *temp = malloc(9); char *e; if (temp == NULL) { _log_err(LOG_CRIT, pamh, "out of memory for password"); _pam_overwrite(pass_new); _pam_overwrite(pass_old); pass_new = pass_old = NULL; /* tidy up */ #ifdef USE_LCKPWDF ulckpwdf(); #endif return PAM_BUF_ERR; } /* copy first 8 bytes of password */ strncpy(temp, pass_new, 8); temp[8] = '\0'; /* no longer need cleartext */ e = bigcrypt(temp, salt); tpass = x_strdup(e); _pam_overwrite(e); _pam_delete(temp); /* tidy up */ } else { char *e; /* no longer need cleartext */ e = bigcrypt(pass_new, salt); tpass = x_strdup(e); _pam_overwrite(e); } } D(("password processed")); /* update the password database(s) -- race conditions..? */ retval = _do_setpass(pamh, user, pass_old, tpass, ctrl, remember); _pam_overwrite(pass_new); _pam_overwrite(pass_old); _pam_delete(tpass); pass_old = pass_new = NULL; } else { /* something has broken with the module */
int auth_change_pwd(char *user, char *newpwd) { struct passwd *spw; struct spwd *stp; char hash[35] = ""; long today; FILE *fd; if (0 != lckpwdf()) { return 1; } /* open passwd */ spw = getpwnam(user); if (spw == 0) { return 1; } if (g_strncmp(spw->pw_passwd, "x", 3) != 0) { /* old system with only passwd */ if (auth_crypt_pwd(spw->pw_passwd, newpwd, hash) != 0) { ulckpwdf(); return 1; } spw->pw_passwd = g_strdup(hash); fd = fopen("/etc/passwd", "rw"); putpwent(spw, fd); } else { /* the system is using shadow */ stp = getspnam(user); if (stp == 0) { return 1; } /* old system with only passwd */ if (auth_crypt_pwd(stp->sp_pwdp, newpwd, hash) != 0) { ulckpwdf(); return 1; } stp->sp_pwdp = g_strdup(hash); today = g_time1() / SECS_PER_DAY; stp->sp_lstchg = today; stp->sp_expire = today + stp->sp_max + stp->sp_inact; fd = fopen("/etc/shadow", "rw"); putspent(stp, fd); } ulckpwdf(); return 0; }
xmlNodePtr users_getxml(xmlNsPtr ns, char** msg) { xmlNodePtr auth_node, user, aux_node; struct passwd *pwd; struct spwd *spwd; const char* value; char *path = NULL; if (!ncds_feature_isenabled("ietf-system", "local-users")) { return (NULL); } /* authentication */ auth_node = xmlNewNode(ns, BAD_CAST "authentication"); /* authentication/user-authentication-order */ asprintf(&path, "/files/%s/PasswordAuthentication", NETOPEER_SSHD_CONF); aug_get(sysaugeas, path, &value); free(path); if (value != NULL && strcmp(value, "yes") == 0) { xmlNewChild(auth_node, auth_node->ns, BAD_CAST "user-authentication-order", BAD_CAST "local-users"); } /* authentication/user[] */ if (lckpwdf() != 0) { *msg = strdup("Failed to acquire shadow file lock."); xmlFreeNode(auth_node); return (NULL); } setpwent(); while ((pwd = getpwent()) != NULL) { /* authentication/user */ user = xmlNewChild(auth_node, auth_node->ns, BAD_CAST "user", NULL); /* authentication/user/name */ xmlNewChild(user, user->ns, BAD_CAST "name", BAD_CAST pwd->pw_name); /* authentication/user/passwd */ if (pwd->pw_passwd[0] == 'x') { /* get data from /etc/shadow */ setspent(); spwd = getspnam(pwd->pw_name); if (spwd != NULL && /* no record, wtf?!? */ spwd->sp_pwdp[0] != '!' && /* account not initiated or locked */ spwd->sp_pwdp[0] != '*') { /* login disabled */ xmlNewChild(user, user->ns, BAD_CAST "password", BAD_CAST spwd->sp_pwdp); } } else if (pwd->pw_passwd[0] != '*') { /* password is stored in /etc/passwd or refers to something else (e.g., NIS server) */ xmlNewChild(user, user->ns, BAD_CAST "password", BAD_CAST pwd->pw_passwd); } /* else password is disabled */ /* authentication/user/authorized-key[] */ if ((aux_node = authkey_getxml(pwd->pw_name, user->ns, msg)) != NULL) { xmlAddChildList(user, aux_node); } else { /* ignore failures in this case */ free(*msg); *msg = NULL; } } endspent(); endpwent(); ulckpwdf(); return (auth_node); }
static const char* set_passwd(const char *name, const char *passwd, char **msg) { FILE *f = NULL; struct spwd *spwd, new_spwd; const char *en_passwd; /* encrypted password */ struct stat st; assert(name); assert(passwd); /* check password format */ if ((passwd[0] != '$') || (passwd[1] != '0' && passwd[1] != '1' && passwd[1] != '5' && passwd[1] != '6') || (passwd[2] != '$')) { asprintf(msg, "Wrong password format (user %s).", name); return (NULL); } if (passwd[1] == '0') { /* encrypt the password */ get_login_defs(); en_passwd = pw_encrypt(&(passwd[3]), crypt_make_salt(NULL, NULL)); } else { en_passwd = passwd; } /* * store encrypted password into shadow */ /* lock shadow file */ if (lckpwdf() != 0) { *msg = strdup("Failed to acquire shadow file lock."); return (NULL); } /* init position in shadow */ setspent(); /* open new shadow */ f = fopen(SHADOW_COPY, "w"); if (f == NULL) { asprintf(msg, "Unable to prepare shadow copy (%s).", strerror(errno)); endspent(); ulckpwdf(); return (NULL); } /* get file stat of the original file to make a nice copy of it */ stat(SHADOW_ORIG, &st); fchmod(fileno(f), st.st_mode); fchown(fileno(f), st.st_uid, st.st_gid); while ((spwd = getspent()) != NULL) { if (strcmp(spwd->sp_namp, name) == 0) { /* * we have the entry to change, * make the copy, modifying the original * structure doesn't seem as a good idea */ memcpy(&new_spwd, spwd, sizeof(struct spwd)); new_spwd.sp_pwdp = (char*) en_passwd; spwd = &new_spwd; } /* store the record into the shadow copy */ putspent(spwd, f); } endspent(); fclose(f); if (rename(SHADOW_COPY, SHADOW_ORIG) == -1) { asprintf(msg, "Unable to rewrite shadow database (%s).", strerror(errno)); unlink(SHADOW_COPY); ulckpwdf(); return (NULL); } ulckpwdf(); return (en_passwd); }
/* * setpwnam () -- * takes a struct passwd in which every field is filled in and valid. * If the given username exists in the passwd file, the entry is * replaced with the given entry. */ int setpwnam(struct passwd *pwd) { FILE *fp = NULL, *pwf = NULL; int save_errno; int found; int namelen; int buflen = 256; int contlen, rc; char *linebuf = NULL; char *tmpname = NULL; pw_init(); if ((fp = xfmkstemp(&tmpname)) == NULL) return -1; /* ptmp should be owned by root.root or root.wheel */ if (fchown(fileno(fp), (uid_t) 0, (gid_t) 0) < 0) goto fail; /* acquire exclusive lock */ if (lckpwdf() < 0) goto fail; pwf = fopen(PASSWD_FILE, "r"); if (!pwf) goto fail; namelen = strlen(pwd->pw_name); linebuf = malloc(buflen); if (!linebuf) goto fail; /* parse the passwd file */ found = false; /* Do you wonder why I don't use getpwent? Read comments at top of * file */ while (fgets(linebuf, buflen, pwf) != NULL) { contlen = strlen(linebuf); while (linebuf[contlen - 1] != '\n' && !feof(pwf)) { char *tmp; /* Extend input buffer if it failed getting the whole line, * so now we double the buffer size */ buflen *= 2; tmp = realloc(linebuf, buflen); if (tmp == NULL) goto fail; linebuf = tmp; /* And fill the rest of the buffer */ if (fgets(&linebuf[contlen], buflen / 2, pwf) == NULL) break; contlen = strlen(linebuf); /* That was a lot of work for nothing. Gimme perl! */ } /* Is this the username we were sent to change? */ if (!found && linebuf[namelen] == ':' && !strncmp(linebuf, pwd->pw_name, namelen)) { /* Yes! So go forth in the name of the Lord and * change it! */ if (putpwent(pwd, fp) < 0) goto fail; found = true; continue; } /* Nothing in particular happened, copy input to output */ fputs(linebuf, fp); } /* xfmkstemp is too restrictive by default for passwd file */ if (fchmod(fileno(fp), 0644) < 0) goto fail; rc = close_stream(fp); fp = NULL; if (rc != 0) goto fail; fclose(pwf); /* I don't think I want to know if this failed */ pwf = NULL; if (!found) { errno = ENOENT; /* give me something better */ goto fail; } /* we don't care if we can't remove the backup file */ unlink(PASSWD_FILE ".OLD"); /* we don't care if we can't create the backup file */ ignore_result(link(PASSWD_FILE, PASSWD_FILE ".OLD")); /* we DO care if we can't rename to the passwd file */ if (rename(tmpname, PASSWD_FILE) < 0) goto fail; /* finally: success */ ulckpwdf(); return 0; fail: save_errno = errno; ulckpwdf(); if (fp != NULL) fclose(fp); if (tmpname != NULL) unlink(tmpname); free(tmpname); if (pwf != NULL) fclose(pwf); free(linebuf); errno = save_errno; return -1; }
static bool ChangePasswordHashUsingLckpwdf(const char *puser, const char *password) { bool result = false; struct stat statbuf; const char *passwd_file = "/etc/shadow"; if (stat(passwd_file, &statbuf) == -1) { passwd_file = "/etc/passwd"; } Log(LOG_LEVEL_VERBOSE, "Changing password hash for user '%s' by editing '%s'.", puser, passwd_file); if (lckpwdf() != 0) { Log(LOG_LEVEL_ERR, "Not able to obtain lock on password database."); return false; } char backup_file[strlen(passwd_file) + strlen(".cf-backup") + 1]; xsnprintf(backup_file, sizeof(backup_file), "%s.cf-backup", passwd_file); unlink(backup_file); char edit_file[strlen(passwd_file) + strlen(".cf-edit") + 1]; xsnprintf(edit_file, sizeof(edit_file), "%s.cf-edit", passwd_file); unlink(edit_file); if (!CopyRegularFileDisk(passwd_file, backup_file)) { Log(LOG_LEVEL_ERR, "Could not back up existing password database '%s' to '%s'.", passwd_file, backup_file); goto unlock_passwd; } FILE *passwd_fd = fopen(passwd_file, "r"); if (!passwd_fd) { Log(LOG_LEVEL_ERR, "Could not open password database '%s'. (fopen: '%s')", passwd_file, GetErrorStr()); goto unlock_passwd; } int edit_fd_int = open(edit_file, O_WRONLY | O_CREAT | O_EXCL, S_IWUSR); if (edit_fd_int < 0) { if (errno == EEXIST) { Log(LOG_LEVEL_CRIT, "Temporary file already existed when trying to open '%s'. (open: '%s') " "This should NEVER happen and could mean that someone is trying to break into your system!!", edit_file, GetErrorStr()); } else { Log(LOG_LEVEL_ERR, "Could not open password database temporary file '%s'. (open: '%s')", edit_file, GetErrorStr()); } goto close_passwd_fd; } FILE *edit_fd = fdopen(edit_fd_int, "w"); if (!edit_fd) { Log(LOG_LEVEL_ERR, "Could not open password database temporary file '%s'. (fopen: '%s')", edit_file, GetErrorStr()); close(edit_fd_int); goto close_passwd_fd; } while (true) { size_t line_size = 0; char *line = NULL; int read_result = CfReadLine(&line, &line_size, passwd_fd); if (read_result < 0) { if (!feof(passwd_fd)) { Log(LOG_LEVEL_ERR, "Error while reading password database: %s", GetErrorStr()); free(line); goto close_both; } else { break; } } // Editing the password database is risky business, so do as little parsing as possible. // Just enough to get the hash in there. char *field_start = NULL; char *field_end = NULL; field_start = strchr(line, ':'); if (field_start) { field_end = strchr(field_start + 1, ':'); } if (!field_start || !field_end) { Log(LOG_LEVEL_ERR, "Unexpected format found in password database while editing user '%s'. Not updating.", puser); free(line); goto close_both; } // Worst case length: Existing password is empty plus one '\n' and one '\0'. char new_line[strlen(line) + strlen(password) + 2]; *field_start = '\0'; *field_end = '\0'; if (strcmp(line, puser) == 0) { xsnprintf(new_line, sizeof(new_line), "%s:%s:%s\n", line, password, field_end + 1); } else { xsnprintf(new_line, sizeof(new_line), "%s:%s:%s\n", line, field_start + 1, field_end + 1); } free(line); size_t new_line_size = strlen(new_line); size_t written_so_far = 0; while (written_so_far < new_line_size) { clearerr(edit_fd); size_t written = fwrite(new_line, 1, new_line_size, edit_fd); if (written == 0) { const char *err_str; if (ferror(edit_fd)) { err_str = GetErrorStr(); } else { err_str = "Unknown error"; } Log(LOG_LEVEL_ERR, "Error while writing to file '%s'. (fwrite: '%s')", edit_file, err_str); goto close_both; } written_so_far += written; } } fclose(edit_fd); fclose(passwd_fd); if (!CopyFilePermissionsDisk(passwd_file, edit_file)) { Log(LOG_LEVEL_ERR, "Could not copy permissions from '%s' to '%s'", passwd_file, edit_file); goto unlock_passwd; } if (rename(edit_file, passwd_file) < 0) { Log(LOG_LEVEL_ERR, "Could not replace '%s' with edited password database '%s'. (rename: '%s')", passwd_file, edit_file, GetErrorStr()); goto unlock_passwd; } result = true; goto unlock_passwd; close_both: fclose(edit_fd); unlink(edit_file); close_passwd_fd: fclose(passwd_fd); unlock_passwd: ulckpwdf(); return result; }
int main(int argc, char **argv) { hardened_shadow_openlog("userdel"); if (lckpwdf() != 0) err(EXIT_FAILURE, "lckpwdf"); parse_args(argc, argv); bool user_private_groups; if (!hardened_shadow_config_get_bool("USER_PRIVATE_GROUPS", &user_private_groups)) { errx(EXIT_FAILURE, "failed to retrieve USER_PRIVATE_GROUPS setting"); } const char *userdel_command = NULL; if (!hardened_shadow_config_get_path("USERDEL_COMMAND", &userdel_command)) errx(EXIT_FAILURE, "failed to retrieve USERDEL_COMMAND setting"); /* Note: shadow-utils try to detect whether user is logged in. * However, it is not obvious how to perform such detection reliably, * especially taking daemons like cron or at into account (they usually * don't go through PAM or anything that'd allow us to prevent * running processes as user being delete in a race-free way. * * Because of those drawbacks, no such checking is performed here. */ { char* user_dir_path = NULL; if (asprintf(&user_dir_path, "/etc/hardened-shadow/%s", user_name) < 0) errx(EXIT_FAILURE, "memory allocation failure"); if (!recursively_remove_path(user_dir_path)) errx(EXIT_FAILURE, "failed to remove %s", user_dir_path); free(user_dir_path); } if (!hardened_shadow_update_group_change_user_name(user_name, NULL)) errx(EXIT_FAILURE, "hardened_shadow_update_group_change_user_name failed"); if (!hardened_shadow_replace_passwd(user_name, NULL)) errx(EXIT_FAILURE, "hardened_shadow_replace_passwd failed"); if (user_private_groups) { struct group *gr = getgrnam(user_name); if (gr) { gid_t private_gid = gr->gr_gid; bool found = false; struct passwd *pwd = NULL; setpwent(); while ((pwd = getpwent())) { if (pwd->pw_gid == private_gid) { found = true; break; } } endpwent(); if (found) { warnx("not removing private user group because it is a primary group " "of at least one other user"); } else if (!hardened_shadow_replace_group(user_name, NULL)) { errx(EXIT_FAILURE, "failed to remove user private group"); } } } /* Note: preserve exact wording of syslog messages from shadow-utils * where possible. */ hardened_shadow_syslog(LOG_INFO, "delete user '%s'", user_name); if (flag_remove && !recursively_remove_path(user_home)) errx(EXIT_FAILURE, "remove_home_directory failed"); if (!run_userdel_command(userdel_command)) errx(EXIT_FAILURE, "failed to run USERDEL_COMMAND"); hardened_shadow_flush_nscd("passwd"); hardened_shadow_flush_nscd("group"); if (ulckpwdf() != 0) warn("ulckpwdf"); free(user_home); return EXIT_SUCCESS; }