chiark / gitweb /
compile fixes for inotify
[inn-innduct.git] / authprogs / auth_krb5.c
1 /*  $Id: auth_krb5.c 7462 2005-12-12 01:06:54Z eagle $
2 **
3 **  Check an username and password against Kerberos v5.
4 **
5 **  Based on nnrpkrb5auth by Christopher P. Lindsey <lindsey@mallorn.com>
6 **  See <http://www.mallorn.com/tools/nnrpkrb5auth>
7 **
8 **  This program takes a username and password pair from nnrpd and checks
9 **  checks their validity against a Kerberos v5 KDC by attempting to obtain a
10 **  TGT.  With the -i <instance> command line option, appends /<instance> to
11 **  the username prior to authentication.
12 **
13 **  Special thanks to Von Welch <vwelch@vwelch.com> for giving me the initial
14 **  code on which the Kerberos V authentication is based many years ago, and
15 **  for introducing me to Kerberos back in '96.
16 **
17 **  Also, thanks to Graeme Mathieson <graeme@mathie.cx> for his inspiration
18 **  through the pamckpasswd program.
19 */
20
21 #include "config.h"
22 #include "clibrary.h"
23 #include "libauth.h"
24 #ifdef HAVE_ET_COM_ERR_H
25 # include <et/com_err.h>
26 #else
27 # include <com_err.h>
28 #endif
29
30 /* krb5_get_in_tkt_with_password is deprecated. */
31 #define KRB5_DEPRECATED 1
32 #include <krb5.h>
33
34 #include "inn/messages.h"
35 #include "libinn.h"
36
37 /*
38  * Default life of the ticket we are getting. Since we are just checking
39  * to see if the user can get one, it doesn't need a long lifetime.
40  */
41 #define KRB5_DEFAULT_LIFE    60 * 5 /* 5 minutes */
42
43
44 /*
45 **  Check the username and password by attempting to get a TGT.  Returns 1 on
46 **  success and 0 on failure.  Errors are reported via com_err.
47 */
48 static int
49 krb5_check_password (char *principal_name, char *password)
50 {
51    krb5_context      kcontext;
52    krb5_creds        creds;
53    krb5_principal    user_principal;
54    krb5_data         *user_realm;
55    krb5_principal    service_principal;
56    krb5_timestamp    now;
57    krb5_address      **addrs = (krb5_address **) NULL;   /* Use default */
58    long              lifetime = KRB5_DEFAULT_LIFE;
59    int               options = 0;
60
61    /* TGT service name for convenience */
62    krb5_data         tgtname = { 0, KRB5_TGS_NAME_SIZE, KRB5_TGS_NAME };
63
64    krb5_preauthtype  *preauth = NULL;
65
66    krb5_error_code   code;
67
68    /* Our return code - 1 is success */
69    int                result = 0;
70    
71    /* Initialize our Kerberos state */
72    code = krb5_init_context (&kcontext);
73    if (code) {
74        com_err (message_program_name, code, "initializing krb5 context");
75        return 0;
76    }
77    
78 #ifdef HAVE_KRB5_INIT_ETS
79    /* Initialize krb5 error tables */    
80    krb5_init_ets (kcontext);
81 #endif
82
83    /* Get current time */
84    code = krb5_timeofday (kcontext, &now);
85    if (code) {
86        com_err (message_program_name, code, "getting time of day");
87        return 0;
88    }
89
90    /* Set up credentials to be filled in */
91    memset (&creds, 0, sizeof(creds));
92
93    /* From here on, goto cleanup to exit */
94
95    /* Parse the username into a krb5 principal */
96    if (!principal_name) {
97        com_err (message_program_name, 0, "passed NULL principal name");
98        goto cleanup;
99    }
100
101    code = krb5_parse_name (kcontext, principal_name, &user_principal);
102    if (code) {
103        com_err (message_program_name, code,
104                 "parsing user principal name %.100s", principal_name);
105        goto cleanup;
106    }
107
108    creds.client = user_principal;
109
110    /* Get the user's realm for building service principal */
111    user_realm = krb5_princ_realm (kcontext, user_principal);
112    
113    /*
114     * Build the service name into a principal. Right now this is
115     * a TGT for the user's realm.
116     */
117    code = krb5_build_principal_ext (kcontext,
118                &service_principal,
119                user_realm->length,
120                user_realm->data,
121                tgtname.length,
122                tgtname.data,
123                user_realm->length,
124                user_realm->data,
125                0 /* terminator */);
126    if (code) {
127        com_err(message_program_name, code, "building service principal name");
128        goto cleanup;
129    }
130
131    creds.server = service_principal;
132
133    creds.times.starttime = 0;   /* Now */
134    creds.times.endtime = now + lifetime;
135    creds.times.renew_till = 0;   /* Unrenewable */
136
137    /* DO IT */
138    code = krb5_get_in_tkt_with_password (kcontext,
139                options,
140                addrs,
141                NULL,
142                preauth,
143                password,
144                0,
145                &creds,
146                0);
147    
148    /* We are done with password at this point... */
149
150    if (code) {   
151       /* FAILURE - Parse a few common errors here */
152       switch (code) {
153       case KRB5KRB_AP_ERR_BAD_INTEGRITY:
154          com_err (message_program_name, 0, "bad password for %.100s",
155                   principal_name);
156          break;
157       case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN:
158          com_err (message_program_name, 0, "unknown user \"%.100s\"",
159                   principal_name);
160          break;
161       default:
162          com_err (message_program_name, code,
163                   "checking Kerberos password for %.100s", principal_name);
164       }
165       result = 0;
166    } else {
167       /* SUCCESS */
168       result = 1;
169    }
170    
171    /* Cleanup */
172  cleanup:
173    krb5_free_cred_contents (kcontext, &creds);
174
175    return result;
176 }
177
178 int
179 main (int argc, char *argv[])
180 {
181     struct auth_info *authinfo;
182     char *new_user;
183
184     message_program_name = "auth_krb5";
185
186     /* Retrieve the username and passwd from nnrpd. */
187     authinfo = get_auth_info(stdin);
188
189     /* Must have a username/password, and no '@' in the address.  @ checking
190       is there to prevent authentication against another Kerberos realm; there
191       should be a -r <realm> commandline option to make this check unnecessary
192       in the future. */
193     if (authinfo == NULL)
194         die("no authentication information from nnrpd");
195     if (authinfo->username[0] == '\0')
196         die("null username");
197     if (strchr(authinfo->username, '@') != NULL)
198         die("username contains @, not allowed");
199
200     /* May need to prepend instance name if -i option was given. */
201     if (argc > 1) {
202         if (argc == 3 && strcmp(argv[1], "-i") == 0) {
203             new_user = concat(authinfo->username, "/", argv[2], (char *) 0);
204             free(authinfo->username);
205             authinfo->username = new_user;
206         } else {
207             die("error parsing command-line options");
208         }
209     }
210
211     if (krb5_check_password(authinfo->username, authinfo->password)) {
212         printf("User:%s\r\n", authinfo->username);
213         exit(0);
214     } else {
215         die("failure validating password");
216     }
217 }