update_passwd.c 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. /* vi: set sw=4 ts=4: */
  2. /*
  3. * update_passwd
  4. *
  5. * update_passwd is a common function for passwd and chpasswd applets;
  6. * it is responsible for updating password file (i.e. /etc/passwd or
  7. * /etc/shadow) for a given user and password.
  8. *
  9. * Moved from loginutils/passwd.c by Alexander Shishkin <virtuoso@slind.org>
  10. */
  11. #include "libbb.h"
  12. #if ENABLE_SELINUX
  13. static void check_selinux_update_passwd(const char *username)
  14. {
  15. security_context_t context;
  16. char *seuser;
  17. if (getuid() != (uid_t)0 || is_selinux_enabled() == 0)
  18. return; /* No need to check */
  19. if (getprevcon_raw(&context) < 0)
  20. bb_perror_msg_and_die("getprevcon failed");
  21. seuser = strtok(context, ":");
  22. if (!seuser)
  23. bb_error_msg_and_die("invalid context '%s'", context);
  24. if (strcmp(seuser, username) != 0) {
  25. if (checkPasswdAccess(PASSWD__PASSWD) != 0)
  26. bb_error_msg_and_die("SELinux: access denied");
  27. }
  28. if (ENABLE_FEATURE_CLEAN_UP)
  29. freecon(context);
  30. }
  31. #else
  32. #define check_selinux_update_passwd(username) ((void)0)
  33. #endif
  34. int update_passwd(const char *filename, const char *username,
  35. const char *new_pw)
  36. {
  37. struct stat sb;
  38. struct flock lock;
  39. FILE *old_fp;
  40. FILE *new_fp;
  41. char *fnamesfx;
  42. char *sfx_char;
  43. unsigned user_len;
  44. int old_fd;
  45. int new_fd;
  46. int i;
  47. int cnt = 0;
  48. int ret = -1; /* failure */
  49. filename = xmalloc_follow_symlinks(filename);
  50. if (filename == NULL)
  51. return -1;
  52. check_selinux_update_passwd(username);
  53. /* New passwd file, "/etc/passwd+" for now */
  54. fnamesfx = xasprintf("%s+", filename);
  55. sfx_char = &fnamesfx[strlen(fnamesfx)-1];
  56. username = xasprintf("%s:", username);
  57. user_len = strlen(username);
  58. old_fp = fopen(filename, "r+");
  59. if (!old_fp)
  60. goto free_mem;
  61. old_fd = fileno(old_fp);
  62. selinux_preserve_fcontext(old_fd);
  63. /* Try to create "/etc/passwd+". Wait if it exists. */
  64. i = 30;
  65. do {
  66. // FIXME: on last iteration try w/o O_EXCL but with O_TRUNC?
  67. new_fd = open(fnamesfx, O_WRONLY|O_CREAT|O_EXCL, 0600);
  68. if (new_fd >= 0) goto created;
  69. if (errno != EEXIST) break;
  70. usleep(100000); /* 0.1 sec */
  71. } while (--i);
  72. bb_perror_msg("cannot create '%s'", fnamesfx);
  73. goto close_old_fp;
  74. created:
  75. if (!fstat(old_fd, &sb)) {
  76. fchmod(new_fd, sb.st_mode & 0777); /* ignore errors */
  77. fchown(new_fd, sb.st_uid, sb.st_gid);
  78. }
  79. new_fp = fdopen(new_fd, "w");
  80. if (!new_fp) {
  81. close(new_fd);
  82. goto unlink_new;
  83. }
  84. /* Backup file is "/etc/passwd-" */
  85. *sfx_char = '-';
  86. /* Delete old backup */
  87. i = (unlink(fnamesfx) && errno != ENOENT);
  88. /* Create backup as a hardlink to current */
  89. if (i || link(filename, fnamesfx))
  90. bb_perror_msg("warning: cannot create backup copy '%s'", fnamesfx);
  91. *sfx_char = '+';
  92. /* Lock the password file before updating */
  93. lock.l_type = F_WRLCK;
  94. lock.l_whence = SEEK_SET;
  95. lock.l_start = 0;
  96. lock.l_len = 0;
  97. if (fcntl(old_fd, F_SETLK, &lock) < 0)
  98. bb_perror_msg("warning: cannot lock '%s'", filename);
  99. lock.l_type = F_UNLCK;
  100. /* Read current password file, write updated /etc/passwd+ */
  101. while (1) {
  102. char *line = xmalloc_fgets(old_fp);
  103. if (!line) break; /* EOF/error */
  104. if (strncmp(username, line, user_len) == 0) {
  105. /* we have a match with "username:"... */
  106. const char *cp = line + user_len;
  107. /* now cp -> old passwd, skip it: */
  108. cp = strchrnul(cp, ':');
  109. /* now cp -> ':' after old passwd or -> "" */
  110. fprintf(new_fp, "%s%s%s", username, new_pw, cp);
  111. cnt++;
  112. } else
  113. fputs(line, new_fp);
  114. free(line);
  115. }
  116. fcntl(old_fd, F_SETLK, &lock);
  117. /* We do want all of them to execute, thus | instead of || */
  118. if ((ferror(old_fp) | fflush(new_fp) | fsync(new_fd) | fclose(new_fp))
  119. || rename(fnamesfx, filename)
  120. ) {
  121. /* At least one of those failed */
  122. goto unlink_new;
  123. }
  124. ret = cnt; /* whee, success! */
  125. unlink_new:
  126. if (ret < 0) unlink(fnamesfx);
  127. close_old_fp:
  128. fclose(old_fp);
  129. free_mem:
  130. free(fnamesfx);
  131. free((char *)filename);
  132. free((char *)username);
  133. return ret;
  134. }