update_passwd.c 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  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. * Modified to be able to add or delete users, groups and users to/from groups
  12. * by Tito Ragusa <farmatito@tiscali.it>
  13. *
  14. * Licensed under GPLv2, see file LICENSE in this source tree.
  15. */
  16. #include "libbb.h"
  17. #if ENABLE_SELINUX
  18. static void check_selinux_update_passwd(const char *username)
  19. {
  20. security_context_t context;
  21. char *seuser;
  22. if (getuid() != (uid_t)0 || is_selinux_enabled() == 0)
  23. return; /* No need to check */
  24. if (getprevcon_raw(&context) < 0)
  25. bb_perror_msg_and_die("getprevcon failed");
  26. seuser = strtok(context, ":");
  27. if (!seuser)
  28. bb_error_msg_and_die("invalid context '%s'", context);
  29. if (strcmp(seuser, username) != 0) {
  30. if (checkPasswdAccess(PASSWD__PASSWD) != 0)
  31. bb_error_msg_and_die("SELinux: access denied");
  32. }
  33. if (ENABLE_FEATURE_CLEAN_UP)
  34. freecon(context);
  35. }
  36. #else
  37. # define check_selinux_update_passwd(username) ((void)0)
  38. #endif
  39. /*
  40. 1) add a user: update_passwd(FILE, USER, REMAINING_PWLINE, NULL)
  41. only if CONFIG_ADDUSER=y and applet_name[0] == 'a' like in adduser
  42. 2) add a group: update_passwd(FILE, GROUP, REMAINING_GRLINE, NULL)
  43. only if CONFIG_ADDGROUP=y and applet_name[0] == 'a' like in addgroup
  44. 3) add a user to a group: update_passwd(FILE, GROUP, NULL, MEMBER)
  45. only if CONFIG_FEATURE_ADDUSER_TO_GROUP=y, applet_name[0] == 'a'
  46. like in addgroup and member != NULL
  47. 4) delete a user: update_passwd(FILE, USER, NULL, NULL)
  48. 5) delete a group: update_passwd(FILE, GROUP, NULL, NULL)
  49. 6) delete a user from a group: update_passwd(FILE, GROUP, NULL, MEMBER)
  50. only if CONFIG_FEATURE_DEL_USER_FROM_GROUP=y and member != NULL
  51. 7) change user's password: update_passwd(FILE, USER, NEW_PASSWD, NULL)
  52. only if CONFIG_PASSWD=y and applet_name[0] == 'p' like in passwd
  53. or if CONFIG_CHPASSWD=y and applet_name[0] == 'c' like in chpasswd
  54. 8) delete a user from all groups: update_passwd(FILE, NULL, NULL, MEMBER)
  55. This function does not validate the arguments fed to it
  56. so the calling program should take care of that.
  57. Returns number of lines changed, or -1 on error.
  58. */
  59. int FAST_FUNC update_passwd(const char *filename,
  60. const char *name,
  61. const char *new_passwd,
  62. const char *member)
  63. {
  64. #if !(ENABLE_FEATURE_ADDUSER_TO_GROUP || ENABLE_FEATURE_DEL_USER_FROM_GROUP)
  65. #define member NULL
  66. #endif
  67. struct stat sb;
  68. struct flock lock;
  69. FILE *old_fp;
  70. FILE *new_fp;
  71. char *fnamesfx;
  72. char *sfx_char;
  73. char *name_colon;
  74. int old_fd;
  75. int new_fd;
  76. int i;
  77. int changed_lines;
  78. int ret = -1; /* failure */
  79. /* used as a bool: "are we modifying /etc/shadow?" */
  80. #if ENABLE_FEATURE_SHADOWPASSWDS
  81. const char *shadow = strstr(filename, "shadow");
  82. #else
  83. # define shadow NULL
  84. #endif
  85. filename = xmalloc_follow_symlinks(filename);
  86. if (filename == NULL)
  87. return ret;
  88. if (name)
  89. check_selinux_update_passwd(name);
  90. /* New passwd file, "/etc/passwd+" for now */
  91. fnamesfx = xasprintf("%s+", filename);
  92. sfx_char = &fnamesfx[strlen(fnamesfx)-1];
  93. name_colon = xasprintf("%s:", name ? name : "");
  94. if (shadow)
  95. old_fp = fopen(filename, "r+");
  96. else
  97. old_fp = fopen_or_warn(filename, "r+");
  98. if (!old_fp) {
  99. if (shadow)
  100. ret = 0; /* missing shadow is not an error */
  101. goto free_mem;
  102. }
  103. old_fd = fileno(old_fp);
  104. selinux_preserve_fcontext(old_fd);
  105. /* Try to create "/etc/passwd+". Wait if it exists. */
  106. i = 30;
  107. do {
  108. // FIXME: on last iteration try w/o O_EXCL but with O_TRUNC?
  109. new_fd = open(fnamesfx, O_WRONLY|O_CREAT|O_EXCL, 0600);
  110. if (new_fd >= 0) goto created;
  111. if (errno != EEXIST) break;
  112. usleep(100000); /* 0.1 sec */
  113. } while (--i);
  114. bb_perror_msg("can't create '%s'", fnamesfx);
  115. goto close_old_fp;
  116. created:
  117. if (fstat(old_fd, &sb) == 0) {
  118. fchmod(new_fd, sb.st_mode & 0777); /* ignore errors */
  119. fchown(new_fd, sb.st_uid, sb.st_gid);
  120. }
  121. errno = 0;
  122. new_fp = xfdopen_for_write(new_fd);
  123. /* Backup file is "/etc/passwd-" */
  124. *sfx_char = '-';
  125. /* Delete old backup */
  126. i = (unlink(fnamesfx) && errno != ENOENT);
  127. /* Create backup as a hardlink to current */
  128. if (i || link(filename, fnamesfx))
  129. bb_perror_msg("warning: can't create backup copy '%s'",
  130. fnamesfx);
  131. *sfx_char = '+';
  132. /* Lock the password file before updating */
  133. lock.l_type = F_WRLCK;
  134. lock.l_whence = SEEK_SET;
  135. lock.l_start = 0;
  136. lock.l_len = 0;
  137. if (fcntl(old_fd, F_SETLK, &lock) < 0)
  138. bb_perror_msg("warning: can't lock '%s'", filename);
  139. lock.l_type = F_UNLCK;
  140. /* Read current password file, write updated /etc/passwd+ */
  141. changed_lines = 0;
  142. while (1) {
  143. char *cp, *line;
  144. line = xmalloc_fgetline(old_fp);
  145. if (!line) /* EOF/error */
  146. break;
  147. if (!name && member) {
  148. /* Delete member from all groups */
  149. /* line is "GROUP:PASSWD:[member1[,member2]...]" */
  150. unsigned member_len = strlen(member);
  151. char *list = strrchr(line, ':');
  152. while (list) {
  153. list++;
  154. next_list_element:
  155. if (is_prefixed_with(list, member)) {
  156. char c;
  157. changed_lines++;
  158. c = list[member_len];
  159. if (c == '\0') {
  160. if (list[-1] == ',')
  161. list--;
  162. *list = '\0';
  163. break;
  164. }
  165. if (c == ',') {
  166. overlapping_strcpy(list, list + member_len + 1);
  167. goto next_list_element;
  168. }
  169. changed_lines--;
  170. }
  171. list = strchr(list, ',');
  172. }
  173. fprintf(new_fp, "%s\n", line);
  174. goto next;
  175. }
  176. cp = is_prefixed_with(line, name_colon);
  177. if (!cp) {
  178. fprintf(new_fp, "%s\n", line);
  179. goto next;
  180. }
  181. /* We have a match with "name:"... */
  182. /* cp points past "name:" */
  183. #if ENABLE_FEATURE_ADDUSER_TO_GROUP || ENABLE_FEATURE_DEL_USER_FROM_GROUP
  184. if (member) {
  185. /* It's actually /etc/group+, not /etc/passwd+ */
  186. if (ENABLE_FEATURE_ADDUSER_TO_GROUP
  187. && applet_name[0] == 'a'
  188. ) {
  189. /* Add user to group */
  190. fprintf(new_fp, "%s%s%s\n", line,
  191. last_char_is(line, ':') ? "" : ",",
  192. member);
  193. changed_lines++;
  194. } else if (ENABLE_FEATURE_DEL_USER_FROM_GROUP
  195. /* && applet_name[0] == 'd' */
  196. ) {
  197. /* Delete user from group */
  198. char *tmp;
  199. const char *fmt = "%s";
  200. /* find the start of the member list: last ':' */
  201. cp = strrchr(line, ':');
  202. /* cut it */
  203. *cp++ = '\0';
  204. /* write the cut line name:passwd:gid:
  205. * or name:!:: */
  206. fprintf(new_fp, "%s:", line);
  207. /* parse the tokens of the member list */
  208. tmp = cp;
  209. while ((cp = strsep(&tmp, ",")) != NULL) {
  210. if (strcmp(member, cp) != 0) {
  211. fprintf(new_fp, fmt, cp);
  212. fmt = ",%s";
  213. } else {
  214. /* found member, skip it */
  215. changed_lines++;
  216. }
  217. }
  218. fprintf(new_fp, "\n");
  219. }
  220. } else
  221. #endif
  222. if ((ENABLE_PASSWD && applet_name[0] == 'p')
  223. || (ENABLE_CHPASSWD && applet_name[0] == 'c')
  224. ) {
  225. /* Change passwd */
  226. cp = strchrnul(cp, ':'); /* move past old passwd */
  227. if (shadow && *cp == ':') {
  228. /* /etc/shadow's field 3 (passwd change date) needs updating */
  229. /* move past old change date */
  230. cp = strchrnul(cp + 1, ':');
  231. /* "name:" + "new_passwd" + ":" + "change date" + ":rest of line" */
  232. fprintf(new_fp, "%s%s:%u%s\n", name_colon, new_passwd,
  233. (unsigned)(time(NULL)) / (24*60*60), cp);
  234. } else {
  235. /* "name:" + "new_passwd" + ":rest of line" */
  236. fprintf(new_fp, "%s%s%s\n", name_colon, new_passwd, cp);
  237. }
  238. changed_lines++;
  239. } /* else delete user or group: skip the line */
  240. next:
  241. free(line);
  242. }
  243. if (changed_lines == 0) {
  244. #if ENABLE_FEATURE_ADDUSER_TO_GROUP || ENABLE_FEATURE_DEL_USER_FROM_GROUP
  245. if (member) {
  246. if (ENABLE_ADDGROUP && applet_name[0] == 'a')
  247. bb_error_msg("can't find %s in %s", name, filename);
  248. if (ENABLE_DELGROUP && applet_name[0] == 'd')
  249. bb_error_msg("can't find %s in %s", member, filename);
  250. }
  251. #endif
  252. if ((ENABLE_ADDUSER || ENABLE_ADDGROUP)
  253. && applet_name[0] == 'a' && !member
  254. ) {
  255. /* add user or group */
  256. fprintf(new_fp, "%s%s\n", name_colon, new_passwd);
  257. changed_lines++;
  258. }
  259. }
  260. fcntl(old_fd, F_SETLK, &lock);
  261. /* We do want all of them to execute, thus | instead of || */
  262. errno = 0;
  263. if ((ferror(old_fp) | fflush(new_fp) | fsync(new_fd) | fclose(new_fp))
  264. || rename(fnamesfx, filename)
  265. ) {
  266. /* At least one of those failed */
  267. bb_perror_nomsg();
  268. goto unlink_new;
  269. }
  270. /* Success: ret >= 0 */
  271. ret = changed_lines;
  272. unlink_new:
  273. if (ret < 0)
  274. unlink(fnamesfx);
  275. close_old_fp:
  276. fclose(old_fp);
  277. free_mem:
  278. free(fnamesfx);
  279. free((char *)filename);
  280. free(name_colon);
  281. return ret;
  282. }