1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/ccache/ccselect_k5identity.c - k5identity ccselect module */
3 /*
4 * Copyright (C) 2011 by the Massachusetts Institute of Technology.
5 * All rights reserved.
6 *
7 * Export of this software from the United States of America may
8 * require a specific license from the United States Government.
9 * It is the responsibility of any person or organization contemplating
10 * export to obtain such a license before exporting.
11 *
12 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13 * distribute this software and its documentation for any purpose and
14 * without fee is hereby granted, provided that the above copyright
15 * notice appear in all copies and that both that copyright notice and
16 * this permission notice appear in supporting documentation, and that
17 * the name of M.I.T. not be used in advertising or publicity pertaining
18 * to distribution of the software without specific, written prior
19 * permission. Furthermore if you modify this software you must label
20 * your software as modified software and not distribute it in such a
21 * fashion that it might be confused with the original M.I.T. software.
22 * M.I.T. makes no representations about the suitability of
23 * this software for any purpose. It is provided "as is" without express
24 * or implied warranty.
25 */
26
27 #include "k5-int.h"
28 #include "cc-int.h"
29 #include <krb5/ccselect_plugin.h>
30 #include <ctype.h>
31
32 #ifndef _WIN32
33
34 #include <pwd.h>
35
36 static krb5_error_code
k5identity_init(krb5_context context,krb5_ccselect_moddata * data_out,int * priority_out)37 k5identity_init(krb5_context context, krb5_ccselect_moddata *data_out,
38 int *priority_out)
39 {
40 *data_out = NULL;
41 *priority_out = KRB5_CCSELECT_PRIORITY_AUTHORITATIVE;
42 return 0;
43 }
44
45 /* Match data (folded to lowercase if fold_case is set) against pattern. */
46 static krb5_boolean
fnmatch_data(const char * pattern,krb5_data * data,krb5_boolean fold_case)47 fnmatch_data(const char *pattern, krb5_data *data, krb5_boolean fold_case)
48 {
49 krb5_error_code ret;
50 char *str, *p;
51 int res;
52
53 str = k5memdup0(data->data, data->length, &ret);
54 if (str == NULL)
55 return FALSE;
56
57 if (fold_case) {
58 for (p = str; *p != '\0'; p++) {
59 if (isupper((unsigned char)*p))
60 *p = tolower((unsigned char)*p);
61 }
62 }
63
64 res = fnmatch(pattern, str, 0);
65 free(str);
66 return (res == 0);
67 }
68
69 /* Return true if server satisfies the constraint given by name and value. */
70 static krb5_boolean
check_constraint(krb5_context context,const char * name,const char * value,krb5_principal server)71 check_constraint(krb5_context context, const char *name, const char *value,
72 krb5_principal server)
73 {
74 if (strcmp(name, "realm") == 0) {
75 return fnmatch_data(value, &server->realm, FALSE);
76 } else if (strcmp(name, "service") == 0) {
77 return (server->type == KRB5_NT_SRV_HST && server->length >= 2 &&
78 fnmatch_data(value, &server->data[0], FALSE));
79 } else if (strcmp(name, "host") == 0) {
80 return (server->type == KRB5_NT_SRV_HST && server->length >= 2 &&
81 fnmatch_data(value, &server->data[1], TRUE));
82 }
83 /* Assume unrecognized constraints are critical. */
84 return FALSE;
85 }
86
87 /*
88 * If line begins with a valid principal and server matches the constraints
89 * listed afterwards, set *princ_out to the client principal described in line
90 * and return true. Otherwise return false. May destructively affect line.
91 */
92 static krb5_boolean
parse_line(krb5_context context,char * line,krb5_principal server,krb5_principal * princ_out)93 parse_line(krb5_context context, char *line, krb5_principal server,
94 krb5_principal *princ_out)
95 {
96 const char *whitespace = " \t\r\n";
97 char *princ, *princ_end, *field, *field_end, *sep;
98
99 *princ_out = NULL;
100
101 /* Find the bounds of the principal. */
102 princ = line + strspn(line, whitespace);
103 if (*princ == '#')
104 return FALSE;
105 princ_end = princ + strcspn(princ, whitespace);
106 if (princ_end == princ)
107 return FALSE;
108
109 /* Check all constraints. */
110 field = princ_end + strspn(princ_end, whitespace);
111 while (*field != '\0') {
112 field_end = field + strcspn(field, whitespace);
113 if (*field_end != '\0')
114 *field_end++ = '\0';
115 sep = strchr(field, '=');
116 if (sep == NULL) /* Malformed line. */
117 return FALSE;
118 *sep = '\0';
119 if (!check_constraint(context, field, sep + 1, server))
120 return FALSE;
121 field = field_end + strspn(field_end, whitespace);
122 }
123
124 *princ_end = '\0';
125 return (krb5_parse_name(context, princ, princ_out) == 0);
126 }
127
128 /* Determine the current user's homedir. Allow HOME to override the result for
129 * non-secure profiles; otherwise, use the euid's homedir from passwd. */
130 static char *
get_homedir(krb5_context context)131 get_homedir(krb5_context context)
132 {
133 const char *homedir = NULL;
134 char pwbuf[BUFSIZ];
135 struct passwd pwx, *pwd;
136
137 if (!context->profile_secure)
138 homedir = secure_getenv("HOME");
139
140 if (homedir == NULL) {
141 if (k5_getpwuid_r(geteuid(), &pwx, pwbuf, sizeof(pwbuf), &pwd) != 0)
142 return NULL;
143 homedir = pwd->pw_dir;
144 }
145
146 return strdup(homedir);
147 }
148
149 static krb5_error_code
k5identity_choose(krb5_context context,krb5_ccselect_moddata data,krb5_principal server,krb5_ccache * cache_out,krb5_principal * princ_out)150 k5identity_choose(krb5_context context, krb5_ccselect_moddata data,
151 krb5_principal server, krb5_ccache *cache_out,
152 krb5_principal *princ_out)
153 {
154 krb5_error_code ret;
155 krb5_principal princ = NULL;
156 char *filename, *homedir;
157 FILE *fp;
158 char buf[256];
159
160 *cache_out = NULL;
161 *princ_out = NULL;
162
163 /* Open the .k5identity file. */
164 homedir = get_homedir(context);
165 if (homedir == NULL)
166 return KRB5_PLUGIN_NO_HANDLE;
167 ret = k5_path_join(homedir, ".k5identity", &filename);
168 free(homedir);
169 if (ret)
170 return ret;
171 fp = fopen(filename, "r");
172 free(filename);
173 if (fp == NULL)
174 return KRB5_PLUGIN_NO_HANDLE;
175
176 /* Look for a line with constraints matched by server. */
177 while (fgets(buf, sizeof(buf), fp) != NULL) {
178 if (parse_line(context, buf, server, &princ))
179 break;
180 }
181 fclose(fp);
182 if (princ == NULL)
183 return KRB5_PLUGIN_NO_HANDLE;
184
185 /* Look for a ccache with the appropriate client principal. If we don't
186 * find out, set *princ_out to indicate the desired client principal. */
187 ret = krb5_cc_cache_match(context, princ, cache_out);
188 if (ret == 0 || ret == KRB5_CC_NOTFOUND)
189 *princ_out = princ;
190 else
191 krb5_free_principal(context, princ);
192 return ret;
193 }
194
195 krb5_error_code
ccselect_k5identity_initvt(krb5_context context,int maj_ver,int min_ver,krb5_plugin_vtable vtable)196 ccselect_k5identity_initvt(krb5_context context, int maj_ver, int min_ver,
197 krb5_plugin_vtable vtable)
198 {
199 krb5_ccselect_vtable vt;
200
201 if (maj_ver != 1)
202 return KRB5_PLUGIN_VER_NOTSUPP;
203 vt = (krb5_ccselect_vtable)vtable;
204 vt->name = "k5identity";
205 vt->init = k5identity_init;
206 vt->choose = k5identity_choose;
207 return 0;
208 }
209
210 #endif /* not _WIN32 */
211