-
Notifications
You must be signed in to change notification settings - Fork 8
/
pam_krb5_ccache.c
226 lines (193 loc) · 8.46 KB
/
pam_krb5_ccache.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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <pwd.h>
#include <errno.h>
#include <syslog.h>
#include <security/pam_appl.h>
#include <security/pam_modules.h>
#include <security/pam_misc.h>
#include <krb5.h>
#define MOD_NAME "pam_krb5_ccache"
/* sudo handles setting credentials for us, just return success */
PAM_EXTERN int
pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) {
return PAM_SUCCESS;
}
PAM_EXTERN int
pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) {
krb5_error_code krb_retval = 0; /* For the return value of each krb5_ call */
const char *krb_error_msg = NULL; /* Human readable error string of krb_retval */
krb5_context krb_context; /* Kerberos context */
const char *env_ccname; /* Environment credential cache name */
const char *cc_source_tag; /* Credential cache name. */
krb5_ccache cc_source = NULL; /* Credential cache handle, derived from cc_source_tag */
krb5_principal client; /* Default client principal of the credential cache */
krb5_principal server; /* Host's principal */
krb5_creds in_creds; /* Input credentials, consisting of host(server) and client principals */
krb5_creds *out_creds = NULL; /* service ticket obtained using in_creds */
krb5_verify_init_creds_opt verify_opts; /* options for krb5_verify_init_creds, require ap_req_nofail is TRUE */
char *ruser = NULL; /* PAM_RUSER, the username of the user who invoked sudo */
uid_t ruid; /* uid of PAM_RUSER */
struct passwd *pw; /* passwd structure to convert ruser to ruid */
char hostname[HOST_NAME_MAX]; /* Hostname is used to obtain the host's principal */
memset(&in_creds, 0, sizeof(in_creds));
setlogmask (LOG_UPTO (LOG_DEBUG));
/* FIXME "sudo" should probably become argv[0] */
openlog ("sudo" , LOG_PID, LOG_AUTHPRIV);
if (PAM_SUCCESS != pam_get_item(pamh, PAM_RUSER, (void *) &ruser)) {
syslog(LOG_ERR, MOD_NAME ": pam_get_item(PAM_RUSER) failed");
goto error_cleanup1;
}
/* Get entry for PAM_RUSER. This is the user calling sudo. */
if (! (pw = getpwnam(ruser)) ) {
syslog(LOG_ERR, MOD_NAME ": getpwnam(PAM_RUSER) not found");
goto error_cleanup1;
}
ruid = pw->pw_uid;
/* Operate as the user who invoked us, ensuring that the ccache is readable
by the calling user. This prevents tricking sudo into opening another
user's ccache by redirecting KRB5CCNAME or similar
*/
syslog(LOG_DEBUG, MOD_NAME ": Invoked by uid: %d", ruid);
if (!ruid) {
syslog(LOG_DEBUG, MOD_NAME ": called by root");
}
if (seteuid(ruid)) {
syslog(LOG_ERR, MOD_NAME ": seteuid to %d failed", ruid);
goto error_cleanup1;
}
/* Used to obtain the host's principal */
if (gethostname(hostname, HOST_NAME_MAX)) {
syslog(LOG_ERR, MOD_NAME ": gethostname() failed. errno=%d", errno);
goto error_cleanup1;
}
hostname[HOST_NAME_MAX - 1] = 0; /* ensure null termination */
/* initialize the krb context. Can't pretty-print errors without valid krb_context */
krb_retval = krb5_init_secure_context(&krb_context);
if (krb_retval) {
syslog(LOG_ERR, MOD_NAME ": krb5_init_secure_context() failed: %d", krb_retval);
goto error_cleanup1;
}
/* Based on https://github.com/krb5/krb5/commit/8b1fff99f59f779bf7f7261f17b835576e20d35d
* We want to obey KRB5CCNAME */
env_ccname = getenv("KRB5CCNAME");
if (env_ccname != NULL) {
if (krb5_cc_set_default_name(krb_context, env_ccname)) {
goto error_cleanup2;
}
}
/* find the ccache file's default location */
cc_source_tag = krb5_cc_default_name(krb_context);
syslog(LOG_DEBUG, MOD_NAME ": cc_source_tag = %s", cc_source_tag);
/* get a handle for the cache as cc_source, using the default name */
if ((krb_retval = krb5_cc_resolve(krb_context, cc_source_tag, &cc_source))){
krb_error_msg = krb5_get_error_message(krb_context, krb_retval);
syslog(LOG_ERR, MOD_NAME ": krb5_cc_resolve() = %s", krb_error_msg);
krb5_free_error_message(krb_context, krb_error_msg);
goto error_cleanup2;
}
/* get krb5 princ from cache, copy to &client.
When used without a ccache, this is generally the call that fails
*/
krb_retval = krb5_cc_get_principal(krb_context, cc_source, &client);
if (krb_retval) {
krb_error_msg = krb5_get_error_message(krb_context, krb_retval);
syslog(LOG_ERR, MOD_NAME ": krb5_cc_get_principal(client) = %s", krb_error_msg);
krb5_free_error_message(krb_context, krb_error_msg);
goto error_cleanup2;
}
/* Copy the user's principal into in_creds */
krb_retval= krb5_copy_principal(krb_context, client, &in_creds.client);
if (krb_retval) {
krb_error_msg = krb5_get_error_message(krb_context, krb_retval);
syslog(LOG_ERR, MOD_NAME ": krb5_copy_principal(client) = %s", krb_error_msg);
krb5_free_error_message(krb_context, krb_error_msg);
goto error_cleanup3;
}
/* Take our hostname and generate the host's principal as server */
krb_retval = krb5_sname_to_principal(krb_context, hostname, NULL, KRB5_NT_SRV_HST, &server);
if (krb_retval) {
krb_error_msg = krb5_get_error_message(krb_context, krb_retval);
syslog(LOG_ERR, MOD_NAME ": krb5_sname_to_principal() = %s", krb_error_msg);
krb5_free_error_message(krb_context, krb_error_msg);
goto error_cleanup4;
}
/* Copy the host's principal into in_creds */
krb_retval= krb5_copy_principal(krb_context, server, &in_creds.server);
if (krb_retval) {
krb_error_msg = krb5_get_error_message(krb_context, krb_retval);
syslog(LOG_ERR, MOD_NAME ": krb5_copy_principal(server) = %s", krb_error_msg);
krb5_free_error_message(krb_context, krb_error_msg);
goto error_cleanup5;
}
/* using the credential cache handle,
the user's principal,
and the host's principal,
generate a service ticket as out_creds
*/
krb_retval = krb5_get_credentials(krb_context, 0, cc_source, &in_creds, &out_creds);
if (krb_retval) {
krb_error_msg = krb5_get_error_message(krb_context, krb_retval);
syslog(LOG_ERR, MOD_NAME ": krb5_get_credentials() = %s", krb_error_msg);
krb5_free_error_message(krb_context, krb_error_msg);
goto error_cleanup6;
}
/* Set verify_opts to require a successful verification */
krb5_verify_init_creds_opt_init(&verify_opts);
krb5_verify_init_creds_opt_set_ap_req_nofail(&verify_opts, 1);
/* Become root before final verification, so we can read /etc/krb5.keytab.
Stay root upon return
*/
if ( seteuid(0) ) {
syslog(LOG_ERR, MOD_NAME " could not seteuid(0) errno = %d", errno);
goto error_cleanup7;
}
/* Verify out_creds against the host's keytab. Destroy any addditional credentials */
krb_retval = krb5_verify_init_creds(krb_context,
out_creds,
server,
NULL /*keytab*/,
NULL /*output ccache*/,
&verify_opts);
if (krb_retval) {
/* unable to verify creds */
krb_error_msg = krb5_get_error_message(krb_context, krb_retval);
syslog(LOG_ERR, MOD_NAME ": krb5_verify_init_creds() = %s", krb_error_msg);
krb5_free_error_message(krb_context, krb_error_msg);
goto error_cleanup7;
} else {
/* we authenticated if krb5_verify_init_creds() succeeded */
syslog(LOG_NOTICE, MOD_NAME ": user %s successfully authenticated using pam_krb5_ccache", ruser);
krb5_free_principal(krb_context, client);
krb5_free_principal(krb_context, in_creds.client);
krb5_free_principal(krb_context, server);
krb5_free_principal(krb_context, in_creds.server);
krb5_free_creds(krb_context, out_creds);
krb5_free_context(krb_context);
closelog();
return PAM_SUCCESS;
}
/* Cleanup is cumulative.
Always seteuid root before returning. Calling twice is harmless.
*/
error_cleanup7:
krb5_free_creds(krb_context, out_creds);
error_cleanup6:
krb5_free_principal(krb_context, in_creds.server);
error_cleanup5:
krb5_free_principal(krb_context, server);
error_cleanup4:
krb5_free_principal(krb_context, in_creds.client);
error_cleanup3:
krb5_free_principal(krb_context, client);
error_cleanup2:
krb5_free_context(krb_context);
error_cleanup1:
syslog(LOG_DEBUG, MOD_NAME ": authentication failed");
closelog();
seteuid(0);
return PAM_AUTH_ERR;
}