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
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
|
diff --git a/gss-serv-krb5.c b/gss-serv-krb5.c
index 413b845..54dd383 100644
--- a/gss-serv-krb5.c
+++ b/gss-serv-krb5.c
@@ -32,7 +32,9 @@
#include <sys/types.h>
#include <stdarg.h>
+#include <stdio.h>
#include <string.h>
+#include <unistd.h>
#include "xmalloc.h"
#include "sshkey.h"
@@ -45,6 +47,7 @@
#include "ssh-gss.h"
+extern Authctxt *the_authctxt;
extern ServerOptions options;
#ifdef HEIMDAL
@@ -56,6 +59,13 @@ extern ServerOptions options;
# include <gssapi/gssapi_krb5.h>
#endif
+/* all commands are allowed by default */
+char **k5users_allowed_cmds = NULL;
+
+static int ssh_gssapi_k5login_exists();
+static int ssh_gssapi_krb5_cmdok(krb5_principal, const char *, const char *,
+ int);
+
static krb5_context krb_context = NULL;
/* Initialise the krb5 library, for the stuff that GSSAPI won't do */
@@ -88,6 +98,7 @@ ssh_gssapi_krb5_userok(ssh_gssapi_client *client, char *name)
krb5_principal princ;
int retval;
const char *errmsg;
+ int k5login_exists;
if (ssh_gssapi_krb5_init() == 0)
return 0;
@@ -99,10 +110,22 @@ ssh_gssapi_krb5_userok(ssh_gssapi_client *client, char *name)
krb5_free_error_message(krb_context, errmsg);
return 0;
}
- if (krb5_kuserok(krb_context, princ, name)) {
+ /* krb5_kuserok() returns 1 if .k5login DNE and this is self-login.
+ * We have to make sure to check .k5users in that case. */
+ k5login_exists = ssh_gssapi_k5login_exists();
+ /* NOTE: .k5login and .k5users must opened as root, not the user,
+ * because if they are on a krb5-protected filesystem, user credentials
+ * to access these files aren't available yet. */
+ if (krb5_kuserok(krb_context, princ, name) && k5login_exists) {
retval = 1;
logit("Authorized to %s, krb5 principal %s (krb5_kuserok)",
name, (char *)client->displayname.value);
+ } else if (ssh_gssapi_krb5_cmdok(princ, client->exportedname.value,
+ name, k5login_exists)) {
+ retval = 1;
+ logit("Authorized to %s, krb5 principal %s "
+ "(ssh_gssapi_krb5_cmdok)",
+ name, (char *)client->displayname.value);
} else
retval = 0;
@@ -110,6 +133,137 @@ ssh_gssapi_krb5_userok(ssh_gssapi_client *client, char *name)
return retval;
}
+/* Test for existence of .k5login.
+ * We need this as part of our .k5users check, because krb5_kuserok()
+ * returns success if .k5login DNE and user is logging in as himself.
+ * With .k5login absent and .k5users present, we don't want absence
+ * of .k5login to authorize self-login. (absence of both is required)
+ * Returns 1 if .k5login is available, 0 otherwise.
+ */
+static int
+ssh_gssapi_k5login_exists()
+{
+ char file[MAXPATHLEN];
+ struct passwd *pw = the_authctxt->pw;
+
+ snprintf(file, sizeof(file), "%s/.k5login", pw->pw_dir);
+ return access(file, F_OK) == 0;
+}
+
+/* check .k5users for login or command authorization
+ * Returns 1 if principal is authorized, 0 otherwise.
+ * If principal is authorized, (global) k5users_allowed_cmds may be populated.
+ */
+static int
+ssh_gssapi_krb5_cmdok(krb5_principal principal, const char *name,
+ const char *luser, int k5login_exists)
+{
+ FILE *fp;
+ char file[MAXPATHLEN];
+ char *line = NULL;
+ char kuser[65]; /* match krb5_kuserok() */
+ struct stat st;
+ struct passwd *pw = the_authctxt->pw;
+ int found_principal = 0;
+ int ncommands = 0, allcommands = 0;
+ u_long linenum = 0;
+ size_t linesize = 0;
+
+ snprintf(file, sizeof(file), "%s/.k5users", pw->pw_dir);
+ /* If both .k5login and .k5users DNE, self-login is ok. */
+ if (!k5login_exists && (access(file, F_OK) == -1)) {
+ return (krb5_aname_to_localname(krb_context, principal,
+ sizeof(kuser), kuser) == 0) &&
+ (strcmp(kuser, luser) == 0);
+ }
+ if ((fp = fopen(file, "r")) == NULL) {
+ int saved_errno = errno;
+ /* 2nd access check to ease debugging if file perms are wrong.
+ * But we don't want to report this if .k5users simply DNE. */
+ if (access(file, F_OK) == 0) {
+ logit("User %s fopen %s failed: %s",
+ pw->pw_name, file, strerror(saved_errno));
+ }
+ return 0;
+ }
+ /* .k5users must be owned either by the user or by root */
+ if (fstat(fileno(fp), &st) == -1) {
+ /* can happen, but very wierd error so report it */
+ logit("User %s fstat %s failed: %s",
+ pw->pw_name, file, strerror(errno));
+ fclose(fp);
+ return 0;
+ }
+ if (!(st.st_uid == pw->pw_uid || st.st_uid == 0)) {
+ logit("User %s %s is not owned by root or user",
+ pw->pw_name, file);
+ fclose(fp);
+ return 0;
+ }
+ /* .k5users must be a regular file. krb5_kuserok() doesn't do this
+ * check, but we don't want to be deficient if they add a check. */
+ if (!S_ISREG(st.st_mode)) {
+ logit("User %s %s is not a regular file", pw->pw_name, file);
+ fclose(fp);
+ return 0;
+ }
+ /* file exists; initialize k5users_allowed_cmds (to none!) */
+ k5users_allowed_cmds = xcalloc(++ncommands,
+ sizeof(*k5users_allowed_cmds));
+
+ /* Check each line. ksu allows unlimited length lines. */
+ while (!allcommands && getline(&line, &linesize, fp) != -1) {
+ linenum++;
+ char *token;
+
+ /* we parse just like ksu, even though we could do better */
+ if ((token = strtok(line, " \t\n")) == NULL)
+ continue;
+ if (strcmp(name, token) == 0) {
+ /* we matched on client principal */
+ found_principal = 1;
+ if ((token = strtok(NULL, " \t\n")) == NULL) {
+ /* only shell is allowed */
+ k5users_allowed_cmds[ncommands-1] =
+ xstrdup(pw->pw_shell);
+ k5users_allowed_cmds =
+ xreallocarray(k5users_allowed_cmds, ++ncommands,
+ sizeof(*k5users_allowed_cmds));
+ break;
+ }
+ /* process the allowed commands */
+ while (token) {
+ if (strcmp(token, "*") == 0) {
+ allcommands = 1;
+ break;
+ }
+ k5users_allowed_cmds[ncommands-1] =
+ xstrdup(token);
+ k5users_allowed_cmds =
+ xreallocarray(k5users_allowed_cmds, ++ncommands,
+ sizeof(*k5users_allowed_cmds));
+ token = strtok(NULL, " \t\n");
+ }
+ }
+ }
+ free(line);
+ if (k5users_allowed_cmds) {
+ /* terminate vector */
+ k5users_allowed_cmds[ncommands-1] = NULL;
+ /* if all commands are allowed, free vector */
+ if (allcommands) {
+ int i;
+ for (i = 0; i < ncommands; i++) {
+ free(k5users_allowed_cmds[i]);
+ }
+ free(k5users_allowed_cmds);
+ k5users_allowed_cmds = NULL;
+ }
+ }
+ fclose(fp);
+ return found_principal;
+}
+
/* This writes out any forwarded credentials from the structure populated
* during userauth. Called after we have setuid to the user */
diff --git a/session.c b/session.c
index 28659ec..9c94d8e 100644
--- a/session.c
+++ b/session.c
@@ -789,6 +789,29 @@ do_exec(Session *s, const char *command)
command = auth_opts->force_command;
forced = "(key-option)";
}
+#ifdef GSSAPI
+#ifdef KRB5 /* k5users_allowed_cmds only available w/ GSSAPI+KRB5 */
+ else if (k5users_allowed_cmds) {
+ const char *match = command;
+ int allowed = 0, i = 0;
+
+ if (!match)
+ match = s->pw->pw_shell;
+ while (k5users_allowed_cmds[i]) {
+ if (strcmp(match, k5users_allowed_cmds[i++]) == 0) {
+ debug("Allowed command '%.900s'", match);
+ allowed = 1;
+ break;
+ }
+ }
+ if (!allowed) {
+ debug("command '%.900s' not allowed", match);
+ return 1;
+ }
+ }
+#endif
+#endif
+
s->forced = 0;
if (forced != NULL) {
s->forced = 1;
diff --git a/ssh-gss.h b/ssh-gss.h
index 0374c88..509109a 100644
--- a/ssh-gss.h
+++ b/ssh-gss.h
@@ -49,6 +49,10 @@
# endif /* !HAVE_DECL_GSS_C_NT_... */
# endif /* !HEIMDAL */
+
+/* .k5users support */
+extern char **k5users_allowed_cmds;
+
#endif /* KRB5 */
/* draft-ietf-secsh-gsskeyex-06 */
diff --git a/sshd.8 b/sshd.8
index adcaaf9..824163b 100644
--- a/sshd.8
+++ b/sshd.8
@@ -324,6 +324,7 @@ Finally, the server and the client enter an authentication dialog.
The client tries to authenticate itself using
host-based authentication,
public key authentication,
+GSSAPI authentication,
challenge-response authentication,
or password authentication.
.Pp
@@ -800,6 +801,12 @@ This file is used in exactly the same way as
but allows host-based authentication without permitting login with
rlogin/rsh.
.Pp
+.It Pa ~/.k5login
+.It Pa ~/.k5users
+These files enforce GSSAPI/Kerberos authentication access control.
+Further details are described in
+.Xr ksu 1 .
+.Pp
.It Pa ~/.ssh/
This directory is the default location for all user-specific configuration
and authentication information.
|