summaryrefslogtreecommitdiff
path: root/modules/pam_unix/lckpwdf.-c
blob: 7145617e0143ecd351768f54506554330f8ede15 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/*
 * This is a hack, but until libc and glibc both include this function
 * by default (libc only includes it if nys is not being used, at the
 * moment, and glibc doesn't appear to have it at all) we need to have
 * it here, too.  :-(
 *
 * This should not become an official part of PAM.
 *
 * BEGIN_HACK
 */

/*
 * lckpwdf.c -- prevent simultaneous updates of password files
 *
 * Before modifying any of the password files, call lckpwdf().  It may block
 * for up to 15 seconds trying to get the lock.  Return value is 0 on success
 * or -1 on failure.  When you are done, call ulckpwdf() to release the lock.
 * The lock is also released automatically when the process exits.  Only one
 * process at a time may hold the lock.
 *
 * These functions are supposed to be conformant with AT&T SVID Issue 3.
 *
 * Written by Marek Michalkiewicz <marekm@i17linuxb.ists.pwr.wroc.pl>,
 * public domain.
 */

#include <fcntl.h>
#include <signal.h>
#ifdef WITH_SELINUX
#include <selinux/selinux.h>
#endif

#define LOCKFILE "/etc/.pwd.lock"
#define TIMEOUT 15

static int lockfd = -1;

static int set_close_on_exec(int fd)
{
	int flags = fcntl(fd, F_GETFD, 0);
	if (flags == -1)
		return -1;
	flags |= FD_CLOEXEC;
	return fcntl(fd, F_SETFD, flags);
}

static int do_lock(int fd)
{
	struct flock fl;

	memset(&fl, 0, sizeof fl);
	fl.l_type = F_WRLCK;
	fl.l_whence = SEEK_SET;
	return fcntl(fd, F_SETLKW, &fl);
}

static void alarm_catch(int sig)
{
/* does nothing, but fcntl F_SETLKW will fail with EINTR */
}

static int lckpwdf(void)
{
	struct sigaction act, oldact;
	sigset_t set, oldset;

	if (lockfd != -1)
		return -1;

#ifdef WITH_SELINUX
	if(is_selinux_enabled()>0)
	{
		lockfd = open(LOCKFILE, O_WRONLY);
		if(lockfd == -1 && errno == ENOENT)
		{
			security_context_t create_context;
			int rc;

			if(getfilecon("/etc/passwd", &create_context))
				return -1;
			rc = setfscreatecon(create_context);
			freecon(create_context);
			if(rc)
				return -1;
			lockfd = open(LOCKFILE, O_CREAT | O_WRONLY, 0600);
			if(setfscreatecon(NULL))
				return -1;
		}
	}
	else
#endif
	lockfd = open(LOCKFILE, O_CREAT | O_WRONLY, 0600);
	if (lockfd == -1)
		return -1;
	if (set_close_on_exec(lockfd) == -1)
		goto cleanup_fd;

	memset(&act, 0, sizeof act);
	act.sa_handler = alarm_catch;
	act.sa_flags = 0;
	sigfillset(&act.sa_mask);
	if (sigaction(SIGALRM, &act, &oldact) == -1)
		goto cleanup_fd;

	sigemptyset(&set);
	sigaddset(&set, SIGALRM);
	if (sigprocmask(SIG_UNBLOCK, &set, &oldset) == -1)
		goto cleanup_sig;

	alarm(TIMEOUT);
	if (do_lock(lockfd) == -1)
		goto cleanup_alarm;
	alarm(0);
	sigprocmask(SIG_SETMASK, &oldset, NULL);
	sigaction(SIGALRM, &oldact, NULL);
	return 0;

      cleanup_alarm:
	alarm(0);
	sigprocmask(SIG_SETMASK, &oldset, NULL);
      cleanup_sig:
	sigaction(SIGALRM, &oldact, NULL);
      cleanup_fd:
	close(lockfd);
	lockfd = -1;
	return -1;
}

static int ulckpwdf(void)
{
	unlink(LOCKFILE);
	if (lockfd == -1)
		return -1;

	if (close(lockfd) == -1) {
		lockfd = -1;
		return -1;
	}
	lockfd = -1;
	return 0;
}
/* END_HACK */