Initial import from FreeBSD RELENG_4:
[dragonfly.git] / lib / libpam / modules / pam_krb5 / support.c
1 /*
2  * support.c
3  *
4  * Support functions for pam_krb5
5  *
6  * $FreeBSD: src/lib/libpam/modules/pam_krb5/support.c,v 1.1.2.1 2001/06/07 09:37:07 markm Exp $
7  */
8
9 static const char rcsid[] = "$Id: support.c,v 1.8 2000/01/04 09:50:03 fcusack Exp $";
10
11 #include <errno.h>
12 #include <stdio.h>      /* BUFSIZ */
13 #include <stdlib.h>     /* malloc */
14 #include <string.h>     /* strncpy */
15 #include <syslog.h>     /* syslog */
16 #include <security/pam_appl.h>
17 #include <security/pam_modules.h>
18 #include <krb5.h>
19 #include <com_err.h>
20 #include "pam_krb5.h"
21
22 /*
23  * Get info from the user. Disallow null responses (regardless of flags).
24  * response gets allocated and filled in on successful return. Caller
25  * is responsible for freeing it.
26  */
27 int
28 get_user_info(pam_handle_t *pamh, char *prompt, int type, char **response)
29 {
30     int pamret;
31     struct pam_message  msg;
32     const struct pam_message *pmsg;
33     struct pam_response *resp = NULL;
34     struct pam_conv     *conv;
35
36     if ((pamret = pam_get_item(pamh, PAM_CONV, (const void **) &conv)) != 0)
37         return pamret;
38
39     /* set up conversation call */
40     pmsg = &msg;
41     msg.msg_style = type;
42     msg.msg = prompt;
43
44     if ((pamret = conv->conv(1, &pmsg, &resp, conv->appdata_ptr)) != 0)
45         return pamret;
46
47     /* Caller should ignore errors for non-response conversations */
48     if (!resp)
49         return PAM_CONV_ERR;
50
51     if (!(resp->resp && resp->resp[0])) {
52         free(resp);
53         return PAM_AUTH_ERR;
54     }
55
56     *response = resp->resp;
57     free(resp);
58     return pamret;
59 }
60
61 /*
62  * This routine with some modification is from the MIT V5B6 appl/bsd/login.c
63  * Modified by Sam Hartman <hartmans@mit.edu> to support PAM services
64  * for Debian.
65  *
66  * Verify the Kerberos ticket-granting ticket just retrieved for the
67  * user.  If the Kerberos server doesn't respond, assume the user is
68  * trying to fake us out (since we DID just get a TGT from what is
69  * supposedly our KDC).  If the host/<host> service is unknown (i.e.,
70  * the local keytab doesn't have it), and we cannot find another
71  * service we do have, let her in.
72  *
73  * Returns 1 for confirmation, -1 for failure, 0 for uncertainty.
74  */
75 int
76 verify_krb_v5_tgt(krb5_context context, krb5_ccache ccache,
77                   char * pam_service, int debug)
78 {
79     char                phost[BUFSIZ];
80     char *services [3];
81     char **service;
82     krb5_error_code     retval = -1;
83     krb5_principal      princ;
84     krb5_keyblock *     keyblock = 0;
85     krb5_data           packet;
86     krb5_auth_context   auth_context = NULL;
87
88     packet.data = 0;
89
90     /*
91     * If possible we want to try and verify the ticket we have
92     * received against a keytab.  We will try multiple service
93     * principals, including at least the host principal and the PAM
94     * service principal.  The host principal is preferred because access
95     * to that key is generally sufficient to compromise root, while the
96     *     service key for this PAM service may be less carefully guarded.
97     * It is important to check the keytab first before the KDC so we do
98     * not get spoofed by a fake  KDC.*/
99     services [0] = "host";
100     services [1] = pam_service;
101     services [2] = NULL;
102     for ( service = &services[0]; *service != NULL; service++ ) {
103       if ((retval = krb5_sname_to_principal(context, NULL, *service, KRB5_NT_SRV_HST,
104                                             &princ)) != 0) {
105         if (debug)
106           syslog(LOG_DEBUG, "pam_krb5: verify_krb_v5_tgt(): %s: %s",
107                  "krb5_sname_to_principal()", error_message(retval));
108         return -1;
109       }
110
111       /* Extract the name directly. */
112       strncpy(phost, compat_princ_component(context, princ, 1), BUFSIZ);
113       phost[BUFSIZ - 1] = '\0';
114
115       /*
116        * Do we have service/<host> keys?
117        * (use default/configured keytab, kvno IGNORE_VNO to get the
118        * first match, and ignore enctype.)
119        */
120       if ((retval = krb5_kt_read_service_key(context, NULL, princ, 0,
121                                              0, &keyblock)) != 0)
122         continue;
123       break;
124     }
125     if (retval != 0 ) {         /* failed to find key */
126         /* Keytab or service key does not exist */
127         if (debug)
128             syslog(LOG_DEBUG, "pam_krb5: verify_krb_v5_tgt(): %s: %s",
129                    "krb5_kt_read_service_key()", error_message(retval));
130         retval = 0;
131         goto cleanup;
132     }
133     if (keyblock)
134         krb5_free_keyblock(context, keyblock);
135
136     /* Talk to the kdc and construct the ticket. */
137     retval = krb5_mk_req(context, &auth_context, 0, *service, phost,
138                          NULL, ccache, &packet);
139     if (auth_context) {
140         krb5_auth_con_free(context, auth_context);
141         auth_context = NULL; /* setup for rd_req */
142     }
143     if (retval) {
144         if (debug)
145             syslog(LOG_DEBUG, "pam_krb5: verify_krb_v5_tgt(): %s: %s",
146                    "krb5_mk_req()", error_message(retval));
147         retval = -1;
148         goto cleanup;
149     }
150
151     /* Try to use the ticket. */
152     retval = krb5_rd_req(context, &auth_context, &packet, princ,
153                          NULL, NULL, NULL);
154     if (retval) {
155         if (debug)
156             syslog(LOG_DEBUG, "pam_krb5: verify_krb_v5_tgt(): %s: %s",
157                    "krb5_rd_req()", error_message(retval));
158         retval = -1;
159     } else {
160         retval = 1;
161     }
162
163 cleanup:
164     if (packet.data)
165         compat_free_data_contents(context, &packet);
166     krb5_free_principal(context, princ);
167     return retval;
168
169 }
170
171
172 /* Free the memory for cache_name. Called by pam_end() */
173 void
174 cleanup_cache(pam_handle_t *pamh, void *data, int pam_end_status)
175 {
176     krb5_context        pam_context;
177     krb5_ccache         ccache;
178
179     if (krb5_init_context(&pam_context))
180         return;
181
182     ccache = (krb5_ccache) data;
183     (void) krb5_cc_destroy(pam_context, ccache);
184     krb5_free_context(pam_context);
185 }