-
Notifications
You must be signed in to change notification settings - Fork 6
/
priv.c
147 lines (130 loc) · 3.81 KB
/
priv.c
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
143
144
145
146
147
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <pwd.h>
#include <grp.h>
#include <sys/fsuid.h>
#include "pam_2fa.h"
/*
* Two setfsuid() calls in a row are necessary to check
* whether setfsuid() succeeded or not.
*/
static int change_uid(uid_t uid, uid_t *save)
{
uid_t tmp = (uid_t) setfsuid(uid);
if (save)
*save = tmp;
return (uid_t) setfsuid(uid) == uid ? 0 : -1;
}
static int change_gid(gid_t gid, gid_t *save)
{
gid_t tmp = (gid_t) setfsgid(gid);
if (save)
*save = tmp;
return (gid_t) setfsgid(gid) == gid ? 0 : -1;
}
static void cleanup(struct pam_2fa_privs *p)
{
if (p && p->grplist) {
free(p->grplist);
p->grplist = NULL;
p->nbgrps = 0;
}
}
#define PRIV_MAGIC 0x1004000a
#define PRIV_MAGIC_DONOTHING 0xdead000a
int pam_2fa_drop_priv(pam_handle_t *pamh, const module_config * cfg, struct pam_2fa_privs *p, const struct passwd *pw)
{
int res;
memset(p, 0, sizeof(struct pam_2fa_privs));
/*
* If not root, we can do nothing.
* If switching to root, we have nothing to do.
* That is, in both cases, we do not care.
*/
if (geteuid() != 0 || pw->pw_uid == 0) {
p->is_dropped = PRIV_MAGIC_DONOTHING;
return 0;
}
res = getgroups(0, NULL);
if (res < 0) {
ERR_C(pamh, cfg, "pam_2fa_drop_priv: getgroups failed: %m");
return -1;
}
p->grplist = (gid_t *) calloc((size_t) res, sizeof(gid_t));
if (!p->grplist) {
ERR_C(pamh, cfg, "out of memory");
return -1;
}
p->nbgrps = res;
res = getgroups(p->nbgrps, p->grplist);
if (res < 0) {
ERR_C(pamh, cfg, "pam_2fa_drop_priv: getgroups failed: %m");
cleanup(p);
return -1;
}
/*
* We should care to leave process credentials in consistent state.
* That is, e.g. if change_gid() succeeded but change_uid() failed,
* we should try to restore old gid.
*/
if (setgroups(0, NULL)) {
ERR_C(pamh, cfg, "pam_2fa_drop_priv: setgroups failed: %m");
cleanup(p);
return -1;
}
if (change_gid(pw->pw_gid, &p->old_gid)) {
ERR_C(pamh, cfg, "pam_2fa_drop_priv: change_gid failed: %m");
(void) setgroups((size_t) p->nbgrps, p->grplist);
cleanup(p);
return -1;
}
if (change_uid(pw->pw_uid, &p->old_uid)) {
ERR_C(pamh, cfg, "pam_2fa_drop_priv: change_uid failed: %m");
(void) change_gid(p->old_gid, NULL);
(void) setgroups((size_t) p->nbgrps, p->grplist);
cleanup(p);
return -1;
}
p->is_dropped = PRIV_MAGIC;
return 0;
}
int pam_2fa_regain_priv(pam_handle_t *pamh, struct pam_2fa_privs *p, const struct passwd *pw)
{
switch (p->is_dropped) {
case PRIV_MAGIC_DONOTHING:
p->is_dropped = 0;
return 0;
case PRIV_MAGIC:
break;
default:
ERR_sys(pamh, "pam_2fa_regain_priv: called with invalid state");
return -1;
}
/*
* We should care to leave process credentials in consistent state.
* That is, e.g. if change_uid() succeeded but change_gid() failed,
* we should try to restore uid.
*/
if (change_uid(p->old_uid, NULL)) {
ERR_sys(pamh, "pam_2fa_regain_priv: change_uid failed: %m");
cleanup(p);
return -1;
}
if (change_gid(p->old_gid, NULL)) {
ERR_sys(pamh, "pam_2fa_regain_priv: change_gid failed: %m");
(void)change_uid(pw->pw_uid, NULL);
cleanup(p);
return -1;
}
if (setgroups((size_t) p->nbgrps, p->grplist)) {
ERR_sys(pamh, "pam_2fa_regain_priv: setgroups failed: %m");
(void)change_uid(pw->pw_uid, NULL);
(void)change_gid(pw->pw_gid, NULL);
cleanup(p);
return -1;
}
p->is_dropped = 0;
cleanup(p);
return 0;
}