38#define MIGRATE_ATTEMPTS 10000
50 const struct passwd *pwd;
63 char basename[MAXPATHLEN + 1])
65 char copy[MAXPATHLEN + 1];
70 strlcpy(copy,
path,
sizeof(copy)) >=
sizeof(copy)) {
71 fprintf(stderr,
"afppasswd: migration path is empty or too long.\n");
75 length = strlen(copy);
77 if (length > 1 && copy[length - 1] ==
'/') {
78 fprintf(stderr,
"afppasswd: migration path must name a file.\n");
82 slash = strrchr(copy,
'/');
85 strlcpy(parent,
".", MAXPATHLEN + 1);
86 strlcpy(basename, copy, MAXPATHLEN + 1);
87 }
else if (slash == copy) {
88 strlcpy(parent,
"/", MAXPATHLEN + 1);
89 strlcpy(basename, slash + 1, MAXPATHLEN + 1);
92 strlcpy(parent, copy, MAXPATHLEN + 1);
93 strlcpy(basename, slash + 1, MAXPATHLEN + 1);
96 if (basename[0] ==
'\0' || strcmp(basename,
".") == 0 ||
97 strcmp(basename,
"..") == 0) {
98 fprintf(stderr,
"afppasswd: migration path must name a file.\n");
110 st.st_uid != administrator_uid ||
111 (st.st_mode & (S_IWGRP | S_IWOTH))) {
113 "afppasswd: directory containing %s must be administrator-owned and not writable by group or other.\n",
124 if (
fstat(fd, st) < 0) {
125 fprintf(stderr,
"afppasswd: can't inspect legacy SRP file %s: %s\n",
126 path, strerror(errno));
130 if (!S_ISREG(st->st_mode) || st->st_uid != administrator_uid ||
131 !srp_verifier_mode_is_safe(st->st_mode) || st->st_nlink != 1) {
133 "afppasswd: legacy SRP file %s must be a single-link regular file owned by the administrator and accessible only by its owner.\n",
143 for (
size_t i = 0; i < count; i++) {
144 free(records[i].line);
145 free(records[i].username);
152 size_t *capacity,
const char *line,
size_t length,
153 size_t source_length,
size_t line_number)
157 const char *colon = memchr(
line,
':',
length);
158 size_t username_length;
164 username_length = (size_t)(colon -
line);
168 fprintf(stderr,
"afppasswd: out of memory while migrating.\n");
172 if (!srp_valid_username(record.
username) ||
173 !srp_valid_fields(colon + 1)) {
179 "afppasswd: legacy SRP record %zu names unknown local user %s.\n",
185 for (
size_t i = 0; i < *count; i++) {
188 "afppasswd: legacy SRP file contains duplicate user %s.\n",
194 if ((*records)[i].
uid == record.
uid) {
196 "afppasswd: legacy SRP users %s and %s resolve to the same uid %ju.\n",
198 (uintmax_t)record.
uid);
207 fprintf(stderr,
"afppasswd: out of memory while migrating.\n");
217 if (*count == *capacity) {
218 size_t new_capacity = *capacity == 0 ? 16 : *capacity * 2;
220 if (new_capacity < *capacity ||
221 new_capacity > SIZE_MAX /
sizeof(**records)) {
222 fprintf(stderr,
"afppasswd: too many legacy SRP records.\n");
228 grown = realloc(*records, new_capacity *
sizeof(**records));
231 fprintf(stderr,
"afppasswd: out of memory while migrating.\n");
238 *capacity = new_capacity;
241 (*records)[(*count)++] = record;
244 fprintf(stderr,
"afppasswd: malformed legacy SRP record %zu.\n",
255 size_t size = 0, capacity = 0, line_number = 0;
257 int stream_fd = dup(source_fd);
260 if (stream_fd < 0 || lseek(stream_fd, 0, SEEK_SET) < 0 ||
261 (stream = fdopen(stream_fd,
"r")) ==
NULL) {
262 fprintf(stderr,
"afppasswd: can't read legacy SRP file: %s\n",
265 if (stream_fd >= 0) {
272 while ((
length = getline(&
line, &size, stream)) >= 0) {
277 fprintf(stderr,
"afppasswd: malformed legacy SRP record %zu.\n",
286 fprintf(stderr,
"afppasswd: out of memory while migrating.\n");
303 if (ferror(stream)) {
304 fprintf(stderr,
"afppasswd: can't read legacy SRP file: %s\n",
321 size_t record = 0, offset = 0;
324 if (lseek(source_fd, 0, SEEK_SET) < 0) {
329 for (ssize_t i = 0; i <
length; i++) {
330 if (record >= count ||
331 buffer[i] != records[record].
line[offset++]) {
342 return length == 0 && record == count && offset == 0;
360 length -= (size_t)written;
368 int length = snprintf(name, size,
"%ju", (uintmax_t)
uid);
374 uid_t administrator_uid)
376 char name[3 *
sizeof(uid_t) + 1];
378 for (
size_t i = 0; i < count; i++) {
382 const char *fields = strchr(records[i].line,
':') + 1;
383 uid_t owner = *fields ==
'*' ? administrator_uid : records[i].
uid;
385 if (
uid_filename(records[i].uid, name,
sizeof(name)) < 0) {
389 fd = openat(directory_fd, name,
390 O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC | O_NOFOLLOW,
395 "afppasswd: can't create migrated verifier %s/%s: %s\n",
396 path, name, strerror(errno));
400 if (
fchmod(fd, 0600) < 0 ||
401 write_all(fd, records[i].line, records[i].length) < 0 ||
402 fchown(fd, owner, (gid_t) -1) < 0 ||
404 fstat(fd, &st) < 0) {
406 "afppasswd: can't create migrated verifier %s/%s: %s\n",
407 path, name, strerror(errno));
412 if (!S_ISREG(st.st_mode) || st.st_uid != owner ||
413 !srp_verifier_mode_is_safe(st.st_mode) || st.st_nlink != 1) {
415 "afppasswd: migrated verifier %s/%s has unsafe metadata.\n",
423 "afppasswd: can't close migrated verifier %s/%s: %s\n",
424 path, name, strerror(errno));
435 char name[3 *
sizeof(uid_t) + 1];
436 int directory_fd = openat(parent_fd, temporary_name,
437 O_RDONLY | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW);
439 if (directory_fd < 0) {
443 for (
size_t i = 0; i < count; i++) {
444 if (
uid_filename(records[i].uid, name,
sizeof(name)) == 0) {
450 unlinkat(parent_fd, temporary_name, AT_REMOVEDIR);
454 uid_t administrator_uid,
455 char temporary_name[MAXPATHLEN + 1])
458 int length = snprintf(temporary_name, MAXPATHLEN + 1,
459 ".%s.migrate.%ju", basename, (uintmax_t)getpid());
461 if (length < 0 || length > MAXPATHLEN) {
463 "afppasswd: migration path is too long for a temporary sibling.\n");
467 if (
mkdirat(parent_fd, temporary_name, 0700) < 0) {
469 "afppasswd: can't create migration directory: %s\n",
474 fd = openat(parent_fd, temporary_name,
475 O_RDONLY | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW);
477 if (fd < 0 ||
fchown(fd, administrator_uid, (gid_t) -1) < 0 ||
480 "afppasswd: can't prepare migration directory: %s\n",
487 unlinkat(parent_fd, temporary_name, AT_REMOVEDIR);
495 const struct stat *source_st)
498 return fstatat(parent_fd, basename, &st, AT_SYMLINK_NOFOLLOW) == 0 &&
499 st.st_dev == source_st->st_dev && st.st_ino == source_st->st_ino &&
506 char prefix[MAXPATHLEN + 1];
508 struct dirent *
entry;
510 size_t prefix_length;
511 length = snprintf(prefix,
sizeof(prefix),
".%s.migrate.", basename);
513 if (length < 0 || (
size_t)length >=
sizeof(prefix)) {
515 "afppasswd: migration path is too long to inspect temporary siblings.\n");
519 prefix_length = (size_t)length;
520 scan_fd = openat(parent_fd,
".",
521 O_RDONLY | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW);
523 if (scan_fd < 0 || (directory = fdopendir(scan_fd)) ==
NULL) {
525 "afppasswd: can't inspect migration directory for %s: %s\n",
526 path, strerror(errno));
537 while ((
entry = readdir(directory)) !=
NULL) {
538 if (strncmp(
entry->d_name, prefix, prefix_length) == 0) {
540 "afppasswd: found partial migration sibling %s; inspect or remove it before retrying.\n",
549 "afppasswd: can't inspect migration directory for %s: %s\n",
550 path, strerror(errno));
560 char backup_name[MAXPATHLEN + 1])
566 length = snprintf(backup_name, MAXPATHLEN + 1,
"%s.legacy",
569 length = snprintf(backup_name, MAXPATHLEN + 1,
"%s.legacy.%u",
573 if (length < 0 || length > MAXPATHLEN) {
575 "afppasswd: migration path is too long for a backup sibling.\n");
579 if (linkat(parent_fd, basename, parent_fd, backup_name, 0) == 0) {
583 if (errno != EEXIST) {
584 fprintf(stderr,
"afppasswd: can't retain legacy SRP file: %s\n",
590 fprintf(stderr,
"afppasswd: can't allocate a legacy backup name.\n");
601 const char *backup_name)
603 if (linkat(parent_fd, backup_name, parent_fd, basename, 0) < 0 ||
604 fsync(parent_fd) < 0) {
606 "afppasswd: automatic rollback failed; the original remains at %s.\n",
611 if (
unlinkat(parent_fd, backup_name, 0) < 0) {
613 "afppasswd: rollback restored the source but could not remove %s.\n",
618 if (
fsync(parent_fd) < 0) {
620 "afppasswd: rollback restored the source but could not synchronize removal of %s.\n",
631 struct stat source_st;
632 struct flock lock = {0};
633 char parent[MAXPATHLEN + 1], basename[MAXPATHLEN + 1];
634 char temporary_name[MAXPATHLEN + 1] = {0};
635 char backup_name[MAXPATHLEN + 1] = {0};
636 int parent_fd = -1, source_fd = -1, temporary_fd = -1;
638 int result = -1, source_unlinked = 0, directory_installed = 0;
644 parent_fd = open(parent, O_RDONLY | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW);
647 administrator_uid) < 0) {
649 fprintf(stderr,
"afppasswd: can't open directory for %s: %s\n",
650 path, strerror(errno));
656 source_fd = openat(parent_fd, basename,
657 O_RDONLY | O_NONBLOCK | O_CLOEXEC | O_NOFOLLOW);
662 if (fstatat(parent_fd, basename, &st, AT_SYMLINK_NOFOLLOW) == 0 &&
665 "afppasswd: %s is already an SRP verifier directory; there is no legacy file to migrate.\n",
668 fprintf(stderr,
"afppasswd: can't open legacy SRP file %s: %s\n",
669 path, strerror(errno));
675 if (
fstat(source_fd, &source_st) == 0 &&
S_ISDIR(source_st.st_mode)) {
677 "afppasswd: %s is already an SRP verifier directory; there is no legacy file to migrate.\n",
686 lock.l_type = F_RDLCK;
687 lock.l_whence = SEEK_SET;
689 if (
fcntl(source_fd, F_SETLK, &lock) < 0) {
691 "afppasswd: legacy SRP file is busy; stop afpd and retry migration.\n");
707 if (temporary_fd < 0) {
709 temporary_name[0] =
'\0';
714 administrator_uid) < 0 ||
715 fchmod(temporary_fd, 0755) < 0 ||
716 fsync(temporary_fd) < 0) {
718 "afppasswd: migration staging failed; the legacy file was not changed.\n");
725 "afppasswd: legacy SRP file changed during migration; stop afpd and retry.\n");
729 if (
link_backup(parent_fd, basename, backup_name) < 0) {
733 if (
fsync(parent_fd) < 0) {
735 "afppasswd: can't synchronize legacy SRP backup: %s\n",
737 unlinkat(parent_fd, backup_name, 0);
739 backup_name[0] =
'\0';
743 if (
unlinkat(parent_fd, basename, 0) < 0) {
744 fprintf(stderr,
"afppasswd: can't prepare SRP directory install: %s\n",
746 unlinkat(parent_fd, backup_name, 0);
748 backup_name[0] =
'\0';
754 if (
fsync(parent_fd) < 0 ||
755 renameat(parent_fd, temporary_name, parent_fd, basename) < 0) {
758 "afppasswd: can't install SRP verifier directory; rolling back: %s\n",
760 rollback_result =
restore_source(parent_fd, basename, backup_name);
767 backup_name[0] =
'\0';
773 directory_installed = 1;
777 if (
fsync(parent_fd) < 0) {
779 "afppasswd: can't synchronize installed SRP directory; rolling back: %s\n",
782 if (renameat(parent_fd, basename, parent_fd, temporary_name) == 0) {
784 directory_installed = 0;
785 rollback_result =
restore_source(parent_fd, basename, backup_name);
792 backup_name[0] =
'\0';
796 "afppasswd: automatic rollback failed; the original remains at %s.\n",
803 printf(
"afppasswd: migrated %zu SRP verifier%s; original retained as %s/%s\n",
804 count, count == 1 ?
"" :
"s", parent, backup_name);
808 if (temporary_fd >= 0) {
812 if (result < 0 && temporary_name[0] !=
'\0' && !directory_installed) {
816 if (source_unlinked && !directory_installed) {
818 "afppasswd: recovery required; the original remains at %s/%s.\n",
819 parent, backup_name);
822 if (source_fd >= 0) {
826 if (parent_fd >= 0) {
static int create_staging_directory(int parent_fd, const char *basename, uid_t administrator_uid, char temporary_name[MAXPATHLEN+1])
Definition afppasswd_migrate.c:453
static void free_records(struct migrate_record *records, size_t count)
Definition afppasswd_migrate.c:141
static int add_record(struct migrate_record **records, size_t *count, size_t *capacity, const char *line, size_t length, size_t source_length, size_t line_number)
Definition afppasswd_migrate.c:151
static void remove_staging(int parent_fd, const char *temporary_name, const struct migrate_record *records, size_t count)
Definition afppasswd_migrate.c:432
static int validate_source(int fd, const char *path, uid_t administrator_uid, struct stat *st)
Definition afppasswd_migrate.c:121
static int create_verifiers(int directory_fd, const char *path, const struct migrate_record *records, size_t count, uid_t administrator_uid)
Definition afppasswd_migrate.c:372
static int lookup_uid(const char *name, uid_t *uid)
Definition afppasswd_migrate.c:48
static int write_all(int fd, const char *data, size_t length)
Definition afppasswd_migrate.c:345
static int split_path(const char *path, char parent[MAXPATHLEN+1], char basename[MAXPATHLEN+1])
Definition afppasswd_migrate.c:62
static int link_backup(int parent_fd, const char *basename, char backup_name[MAXPATHLEN+1])
Definition afppasswd_migrate.c:559
static int reject_stale_staging(int parent_fd, const char *basename, const char *path)
Definition afppasswd_migrate.c:503
#define MIGRATE_ATTEMPTS
Definition afppasswd_migrate.c:38
static int uid_filename(uid_t uid, char *name, size_t size)
Definition afppasswd_migrate.c:366
static int validate_parent(int fd, const char *path, uid_t administrator_uid)
Definition afppasswd_migrate.c:105
static int read_records(int source_fd, struct migrate_record **records, size_t *count)
Definition afppasswd_migrate.c:250
restore_result
Definition afppasswd_migrate.c:594
@ RESTORE_FAILED
Definition afppasswd_migrate.c:595
@ RESTORE_COMPLETE
Definition afppasswd_migrate.c:597
@ RESTORE_CLEANUP_INCOMPLETE
Definition afppasswd_migrate.c:596
static int source_unchanged(int source_fd, const struct migrate_record *records, size_t count)
Definition afppasswd_migrate.c:316
static enum restore_result restore_source(int parent_fd, const char *basename, const char *backup_name)
Definition afppasswd_migrate.c:600
static int source_still_at_path(int parent_fd, const char *basename, const struct stat *source_st)
Definition afppasswd_migrate.c:494
int afppasswd_migrate_srp(const char *path, uid_t administrator_uid)
Definition afppasswd_migrate.c:628
size_t strlcpy(char *, const char *, size_t)
Definition strlcpy.c:36
#define data
Definition hash.c:52
#define S_ISDIR(s)
Definition megatron.h:96
char buffer[MAXPATHLEN+2]
Definition netacnv.c:35
#define SRP_USERNAME_MAX_LEN
Definition srp.h:14
#define SRP_FORMAT_LEN
Definition srp.h:23
Definition afppasswd_migrate.c:40
uid_t uid
Definition afppasswd_migrate.c:45
size_t source_length
Definition afppasswd_migrate.c:43
size_t length
Definition afppasswd_migrate.c:42
char * username
Definition afppasswd_migrate.c:44
char * line
Definition afppasswd_migrate.c:41
Definition include/atalk/directory.h:140
#define fchown
Definition test_afppasswd.c:45
#define fchmod
Definition test_afppasswd.c:46
#define fstat
Definition test_afppasswd.c:44
#define fsync
Definition test_afppasswd.c:48
#define fcntl
Definition test_afppasswd.c:47
#define unlinkat
Definition test_migrate.c:44
#define mkdirat
Definition test_migrate.c:43
#define write
Definition test_migrate.c:41
#define getpwnam
Definition test_migrate.c:40
#define NULL
Definition utf8util.c:47