1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21 /*
22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
24 *
25 * Copyright 2023 OmniOS Community Edition (OmniOSce) Association.
26 */
27
28 #include <kadm5/admin.h>
29 #include <krb5.h>
30 #include <security/pam_appl.h>
31 #include <security/pam_modules.h>
32 #include <security/pam_impl.h>
33 #include <string.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <pwd.h>
37 #include <syslog.h>
38 #include <libintl.h>
39
40 #define KRB5_AUTOMIGRATE_DATA "SUNW-KRB5-AUTOMIGRATE-DATA"
41
42 static void krb5_migrate_cleanup(pam_handle_t *pamh, void *data,
43 int pam_status);
44
45 /*
46 * pam_sm_authenticate - Authenticate a host-based client service
47 * principal to kadmind in order to permit the creation of a new user
48 * principal in the client's default realm.
49 */
pam_sm_authenticate(pam_handle_t * pamh,int flags,int argc,const char ** argv)50 int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc,
51 const char **argv)
52 {
53 const char *user = NULL;
54 char *userdata = NULL;
55 char *olduserdata = NULL;
56 char *password = NULL;
57 int err, i;
58 time_t now;
59
60 /* pam.conf options */
61 int debug = 0;
62 int quiet = 0;
63 int expire_pw = 0;
64 char *service = NULL;
65
66 /* krb5-specific defines */
67 kadm5_ret_t retval = 0;
68 krb5_context context = NULL;
69 kadm5_config_params params;
70 krb5_principal svcprinc;
71 char *svcprincstr = NULL;
72 krb5_principal userprinc;
73 char *userprincstr = NULL;
74 int strlength = 0;
75 kadm5_principal_ent_rec kadm5_userprinc;
76 char *kadmin_princ = NULL;
77 char *def_realm = NULL;
78 void *handle = NULL;
79 long mask = 0;
80
81 for (i = 0; i < argc; i++) {
82 if (strcmp(argv[i], "debug") == 0) {
83 debug = 1;
84 } else if (strcmp(argv[i], "quiet") == 0) {
85 quiet = 1;
86 } else if (strcmp(argv[i], "expire_pw") == 0) {
87 expire_pw = 1;
88 } else if ((strstr(argv[i], "client_service=") != NULL) &&
89 (strcmp((strstr(argv[i], "=") + 1), "") != 0)) {
90 service = strdup(strstr(argv[i], "=") + 1);
91 } else {
92 __pam_log(LOG_AUTH | LOG_ERR,
93 "PAM-KRB5-AUTOMIGRATE (auth): unrecognized "
94 "option %s", argv[i]);
95 }
96 }
97
98 if (flags & PAM_SILENT)
99 quiet = 1;
100
101 err = pam_get_item(pamh, PAM_USER, (const void **)&user);
102 if (err != PAM_SUCCESS) {
103 goto cleanup;
104 }
105
106 /*
107 * Check if user name is *not* NULL
108 */
109 if (user == NULL || (user[0] == '\0')) {
110 if (debug)
111 __pam_log(LOG_AUTH | LOG_DEBUG,
112 "PAM-KRB5-AUTOMIGRATE (auth): user empty or null");
113 goto cleanup;
114 }
115
116 /*
117 * Can't tolerate memory failure later on. Get a copy
118 * before any work is done.
119 */
120 if ((userdata = strdup(user)) == NULL) {
121 __pam_log(LOG_AUTH | LOG_ERR,
122 "PAM-KRB5-AUTOMIGRATE (auth): Out of memory");
123 goto cleanup;
124 }
125
126 /*
127 * Grok the user password
128 */
129 err = pam_get_item(pamh, PAM_AUTHTOK, (const void **)&password);
130 if (err != PAM_SUCCESS) {
131 goto cleanup;
132 }
133
134 if (password == NULL || (password[0] == '\0')) {
135 if (debug)
136 __pam_log(LOG_AUTH | LOG_DEBUG,
137 "PAM-KRB5-AUTOMIGRATE (auth): "
138 "authentication token is empty or null");
139 goto cleanup;
140 }
141
142
143 /*
144 * Now, lets do the all krb5/kadm5 setup for the principal addition
145 */
146 if (retval = krb5_init_secure_context(&context)) {
147 __pam_log(LOG_AUTH | LOG_ERR,
148 "PAM-KRB5-AUTOMIGRATE (auth): Error initializing "
149 "krb5: %s", error_message(retval));
150 goto cleanup;
151 }
152
153 (void) memset((char *)¶ms, 0, sizeof (params));
154 (void) memset(&kadm5_userprinc, 0, sizeof (kadm5_userprinc));
155
156 if (def_realm == NULL && krb5_get_default_realm(context, &def_realm)) {
157 __pam_log(LOG_AUTH | LOG_ERR,
158 "PAM-KRB5-AUTOMIGRATE (auth): Error while obtaining "
159 "default krb5 realm");
160 goto cleanup;
161 }
162
163 params.mask |= KADM5_CONFIG_REALM;
164 params.realm = def_realm;
165
166 if (kadm5_get_adm_host_srv_name(context, def_realm,
167 &kadmin_princ)) {
168 __pam_log(LOG_AUTH | LOG_ERR,
169 "PAM-KRB5-AUTOMIGRATE (auth): Error while obtaining "
170 "host based service name for realm %s\n", def_realm);
171 goto cleanup;
172 }
173
174 if (retval = krb5_sname_to_principal(context, NULL,
175 (service != NULL) ? service : "host", KRB5_NT_SRV_HST, &svcprinc)) {
176 __pam_log(LOG_AUTH | LOG_ERR,
177 "PAM-KRB5-AUTOMIGRATE (auth): Error while creating "
178 "krb5 host service principal: %s",
179 error_message(retval));
180 goto cleanup;
181 }
182
183 if (retval = krb5_unparse_name(context, svcprinc,
184 &svcprincstr)) {
185 __pam_log(LOG_AUTH | LOG_ERR,
186 "PAM-KRB5-AUTOMIGRATE (auth): Error while "
187 "unparsing principal name: %s", error_message(retval));
188 krb5_free_principal(context, svcprinc);
189 goto cleanup;
190 }
191
192 krb5_free_principal(context, svcprinc);
193
194 /*
195 * Initialize the kadm5 connection using the default keytab
196 */
197 retval = kadm5_init_with_skey(svcprincstr, NULL,
198 kadmin_princ, ¶ms, KADM5_STRUCT_VERSION, KADM5_API_VERSION_2,
199 NULL, &handle);
200 if (retval) {
201 __pam_log(LOG_AUTH | LOG_ERR,
202 "PAM-KRB5-AUTOMIGRATE (auth): Error while "
203 "doing kadm5_init_with_skey: %s", error_message(retval));
204 goto cleanup;
205 }
206
207
208 /*
209 * The RPCSEC_GSS connection has been established; Lets check to see
210 * if the corresponding user principal exists in the KDC database.
211 * If not, lets create a new one.
212 */
213
214 strlength = strlen(user) + strlen(def_realm) + 2;
215 if ((userprincstr = malloc(strlength)) == NULL)
216 goto cleanup;
217 (void) strlcpy(userprincstr, user, strlength);
218 (void) strlcat(userprincstr, "@", strlength);
219 (void) strlcat(userprincstr, def_realm, strlength);
220
221
222 if (retval = krb5_parse_name(context, userprincstr,
223 &userprinc)) {
224 __pam_log(LOG_AUTH | LOG_ERR,
225 "PAM-KRB5-AUTOMIGRATE (auth): Error while "
226 "parsing user principal name: %s",
227 error_message(retval));
228 goto cleanup;
229 }
230
231 retval = kadm5_get_principal(handle, userprinc, &kadm5_userprinc,
232 KADM5_PRINCIPAL_NORMAL_MASK);
233
234 krb5_free_principal(context, userprinc);
235
236 if (retval) {
237 switch (retval) {
238 case KADM5_AUTH_GET:
239 if (debug)
240 __pam_log(LOG_AUTH | LOG_DEBUG,
241 "PAM-KRB5-AUTOMIGRATE (auth): %s does "
242 "not have the GET privilege "
243 "for kadm5_get_principal: %s",
244 svcprincstr, error_message(retval));
245 break;
246
247 case KADM5_UNK_PRINC:
248 default:
249 break;
250 }
251 /*
252 * We will try & add this principal anyways, continue on ...
253 */
254 (void) memset(&kadm5_userprinc, 0, sizeof (kadm5_userprinc));
255 } else {
256 /*
257 * Principal already exists in the KDC database, quit now
258 */
259 if (debug)
260 __pam_log(LOG_AUTH | LOG_DEBUG,
261 "PAM-KRB5-AUTOMIGRATE (auth): Principal %s "
262 "already exists in Kerberos KDC database",
263 userprincstr);
264 goto cleanup;
265 }
266
267
268
269 if (retval = krb5_parse_name(context, userprincstr,
270 &(kadm5_userprinc.principal))) {
271 __pam_log(LOG_AUTH | LOG_ERR,
272 "PAM-KRB5-AUTOMIGRATE (auth): Error while "
273 "parsing user principal name: %s",
274 error_message(retval));
275 goto cleanup;
276 }
277
278 if (expire_pw) {
279 (void) time(&now);
280 /*
281 * The local system time could actually be later than the
282 * system time of the KDC we are authenticating to. We expire
283 * w/the local system time minus clockskew so that we are
284 * assured that it is expired on this login, not the next.
285 */
286 now -= context->clockskew;
287 kadm5_userprinc.pw_expiration = now;
288 mask |= KADM5_PW_EXPIRATION;
289 }
290
291 mask |= KADM5_PRINCIPAL;
292 retval = kadm5_create_principal(handle, &kadm5_userprinc,
293 mask, password);
294 if (retval) {
295 switch (retval) {
296 case KADM5_AUTH_ADD:
297 if (debug)
298 __pam_log(LOG_AUTH | LOG_DEBUG,
299 "PAM-KRB5-AUTOMIGRATE (auth): %s does "
300 "not have the ADD privilege "
301 "for kadm5_create_principal: %s",
302 svcprincstr, error_message(retval));
303 break;
304
305 default:
306 __pam_log(LOG_AUTH | LOG_ERR,
307 "PAM-KRB5-AUTOMIGRATE (auth): Generic error"
308 "while doing kadm5_create_principal: %s",
309 error_message(retval));
310 break;
311 }
312 goto cleanup;
313 }
314
315 /*
316 * Success, new user principal has been added !
317 */
318 if (!quiet) {
319 char messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
320
321 (void) snprintf(messages[0], sizeof (messages[0]),
322 dgettext(TEXT_DOMAIN, "\nUser `%s' has been "
323 "automatically migrated to the Kerberos realm %s\n"),
324 user, def_realm);
325 (void) __pam_display_msg(pamh, PAM_TEXT_INFO, 1,
326 messages, NULL);
327 }
328 if (debug)
329 __pam_log(LOG_AUTH | LOG_DEBUG,
330 "PAM-KRB5-AUTOMIGRATE (auth): User %s "
331 "has been added to the Kerberos KDC database",
332 userprincstr);
333
334 /*
335 * Since this is a new krb5 principal, do a pam_set_data()
336 * for possible use by the acct_mgmt routine of pam_krb5(7)
337 */
338 if (pam_get_data(pamh, KRB5_AUTOMIGRATE_DATA,
339 (const void **)&olduserdata) == PAM_SUCCESS) {
340 /*
341 * We created a princ in a previous run on the same handle and
342 * it must have been for a different PAM_USER / princ name,
343 * otherwise we couldn't succeed here, unless that princ
344 * got deleted.
345 */
346 if (olduserdata != NULL)
347 free(olduserdata);
348 }
349 if (pam_set_data(pamh, KRB5_AUTOMIGRATE_DATA, userdata,
350 krb5_migrate_cleanup) != PAM_SUCCESS) {
351 free(userdata);
352 }
353
354 cleanup:
355 if (service)
356 free(service);
357 if (kadmin_princ)
358 free(kadmin_princ);
359 if (svcprincstr)
360 free(svcprincstr);
361 if (userprincstr)
362 free(userprincstr);
363 if (def_realm)
364 free(def_realm);
365 (void) kadm5_free_principal_ent(handle, &kadm5_userprinc);
366 (void) kadm5_destroy((void *)handle);
367 if (context != NULL)
368 krb5_free_context(context);
369
370 return (PAM_IGNORE);
371 }
372
373 /*ARGSUSED*/
374 static void
krb5_migrate_cleanup(pam_handle_t * pamh,void * data,int pam_status)375 krb5_migrate_cleanup(pam_handle_t *pamh, void *data, int pam_status) {
376 if (data != NULL)
377 free((char *)data);
378 }
379
380 /*ARGSUSED*/
381 int
pam_sm_setcred(pam_handle_t * pamh,int flags,int argc,const char ** argv)382 pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
383 {
384 return (PAM_IGNORE);
385 }
386