]> Pileus Git - ~andy/fetchmail/blob - kerberos.c
Initial revision
[~andy/fetchmail] / kerberos.c
1 /*
2  * kerberos.c -- Kerberos authentication (see RFC 1731).
3  *
4  * For license terms, see the file COPYING in this directory.
5  */
6 #include  "config.h"
7 #include  <stdio.h>
8 #include  <string.h>
9 #include  <ctype.h>
10 #if defined(STDC_HEADERS)
11 #include  <stdlib.h>
12 #endif
13 #include  "fetchmail.h"
14 #include  "socket.h"
15
16 #include  "i18n.h"
17
18 #ifdef KERBEROS_V4
19
20 #  ifdef KERBEROS_V5
21 #    include <kerberosIV/des.h>
22 #    include <kerberosIV/krb.h>
23 #  else
24 #    if defined (__bsdi__)
25 #       include <des.h>
26 #       define krb_get_err_text(e) (krb_err_txt[e])
27 #    endif
28 #    if defined(__NetBSD__) || (__FreeBSD__) || defined(__linux__)
29 #       define krb_get_err_text(e) (krb_err_txt[e])
30 #    endif
31 #    include <krb.h>
32 #  endif
33
34 #if SIZEOF_INT == 4
35 typedef int     int32;
36 #elif SIZEOF_SHORT == 4
37 typedef short   int32;
38 #elif SIZEOF_LONG == 4
39 typedef long    int32;
40 #else
41 #error Cannot deduce a 32-bit-type
42 #endif
43
44 int do_rfc1731(int sock, char *command, char *truename)
45 /* authenticate as per RFC1731 -- note 32-bit integer requirement here */
46 {
47     int result = 0, len;
48     char buf1[4096], buf2[4096];
49     union {
50       int32 cint;
51       char cstr[4];
52     } challenge1, challenge2;
53     char srvinst[INST_SZ];
54     char *p;
55     char srvrealm[REALM_SZ];
56     KTEXT_ST authenticator;
57     CREDENTIALS credentials;
58     char tktuser[MAX_K_NAME_SZ+1+INST_SZ+1+REALM_SZ+1];
59     char tktinst[INST_SZ];
60     char tktrealm[REALM_SZ];
61     des_cblock session;
62     des_key_schedule schedule;
63
64     gen_send(sock, "%s KERBEROS_V4", command);
65
66     /* The data encoded in the first ready response contains a random
67      * 32-bit number in network byte order.  The client should respond
68      * with a Kerberos ticket and an authenticator for the principal
69      * "imap.hostname@realm", where "hostname" is the first component
70      * of the host name of the server with all letters in lower case
71      * and where "realm" is the Kerberos realm of the server.  The
72      * encrypted checksum field included within the Kerberos
73      * authenticator should contain the server provided 32-bit number
74      * in network byte order.
75      */
76
77     if (result = gen_recv(sock, buf1, sizeof buf1)) {
78         return result;
79     }
80
81     len = from64tobits(challenge1.cstr, buf1);
82     if (len < 0) {
83         report(stderr, _("could not decode initial BASE64 challenge\n"));
84         return PS_AUTHFAIL;
85     }
86
87     /* this patch by Dan Root <dar@thekeep.org> solves an endianess
88      * problem. */
89     {
90         char tmp[4];
91
92         *(int *)tmp = ntohl(*(int *) challenge1.cstr);
93         memcpy(challenge1.cstr, tmp, sizeof(tmp));
94     }
95
96     /* Client responds with a Kerberos ticket and an authenticator for
97      * the principal "imap.hostname@realm" where "hostname" is the
98      * first component of the host name of the server with all letters
99      * in lower case and where "realm" is the Kerberos realm of the
100      * server.  The encrypted checksum field included within the
101      * Kerberos authenticator should contain the server-provided
102      * 32-bit number in network byte order.
103      */
104
105     strncpy(srvinst, truename, (sizeof srvinst)-1);
106     srvinst[(sizeof srvinst)-1] = '\0';
107     for (p = srvinst; *p; p++) {
108       if (isupper(*p)) {
109         *p = tolower(*p);
110       }
111     }
112
113     strncpy(srvrealm, (char *)krb_realmofhost(srvinst), (sizeof srvrealm)-1);
114     srvrealm[(sizeof srvrealm)-1] = '\0';
115     if (p = strchr(srvinst, '.')) {
116       *p = '\0';
117     }
118
119     result = krb_mk_req(&authenticator, "imap", srvinst, srvrealm, 0);
120     if (result) {
121         report(stderr, "krb_mq_req: %s\n", krb_get_err_text(result));
122         return PS_AUTHFAIL;
123     }
124
125     result = krb_get_cred("imap", srvinst, srvrealm, &credentials);
126     if (result) {
127         report(stderr, "krb_get_cred: %s\n", krb_get_err_text(result));
128         return PS_AUTHFAIL;
129     }
130
131     memcpy(session, credentials.session, sizeof session);
132     memset(&credentials, 0, sizeof credentials);
133     des_key_sched(session, schedule);
134
135     result = krb_get_tf_fullname(TKT_FILE, tktuser, tktinst, tktrealm);
136     if (result) {
137         report(stderr, "krb_get_tf_fullname: %s\n", krb_get_err_text(result));
138         return PS_AUTHFAIL;
139     }
140
141 #ifdef __UNUSED__
142     /*
143      * Andrew H. Chatham <andrew.chatham@duke.edu> alleges that this check
144      * is not necessary and has consistently been messing him up.
145      */
146     if (strcmp(tktuser, user) != 0) {
147         report(stderr, 
148                _("principal %s in ticket does not match -u %s\n"), tktuser,
149                 user);
150         return PS_AUTHFAIL;
151     }
152 #endif /* __UNUSED__ */
153
154     if (tktinst[0]) {
155         report(stderr, 
156                _("non-null instance (%s) might cause strange behavior\n"),
157                 tktinst);
158         strcat(tktuser, ".");
159         strcat(tktuser, tktinst);
160     }
161
162     if (strcmp(tktrealm, srvrealm) != 0) {
163         strcat(tktuser, "@");
164         strcat(tktuser, tktrealm);
165     }
166
167     result = krb_mk_req(&authenticator, "imap", srvinst, srvrealm,
168             challenge1.cint);
169     if (result) {
170         report(stderr, "krb_mq_req: %s\n", krb_get_err_text(result));
171         return PS_AUTHFAIL;
172     }
173
174     to64frombits(buf1, authenticator.dat, authenticator.length);
175     if (outlevel >= O_MONITOR) {
176         report(stdout, "IMAP> %s\n", buf1);
177     }
178     strcat(buf1, "\r\n");
179     SockWrite(sock, buf1, strlen(buf1));
180
181     /* Upon decrypting and verifying the ticket and authenticator, the
182      * server should verify that the contained checksum field equals
183      * the original server provided random 32-bit number.  Should the
184      * verification be successful, the server must add one to the
185      * checksum and construct 8 octets of data, with the first four
186      * octets containing the incremented checksum in network byte
187      * order, the fifth octet containing a bit-mask specifying the
188      * protection mechanisms supported by the server, and the sixth
189      * through eighth octets containing, in network byte order, the
190      * maximum cipher-text buffer size the server is able to receive.
191      * The server must encrypt the 8 octets of data in the session key
192      * and issue that encrypted data in a second ready response.  The
193      * client should consider the server authenticated if the first
194      * four octets the un-encrypted data is equal to one plus the
195      * checksum it previously sent.
196      */
197     
198     if (result = gen_recv(sock, buf1, sizeof buf1))
199         return result;
200
201     /* The client must construct data with the first four octets
202      * containing the original server-issued checksum in network byte
203      * order, the fifth octet containing the bit-mask specifying the
204      * selected protection mechanism, the sixth through eighth octets
205      * containing in network byte order the maximum cipher-text buffer
206      * size the client is able to receive, and the following octets
207      * containing a user name string.  The client must then append
208      * from one to eight octets so that the length of the data is a
209      * multiple of eight octets. The client must then PCBC encrypt the
210      * data with the session key and respond to the second ready
211      * response with the encrypted data.  The server decrypts the data
212      * and verifies the contained checksum.  The username field
213      * identifies the user for whom subsequent IMAP operations are to
214      * be performed; the server must verify that the principal
215      * identified in the Kerberos ticket is authorized to connect as
216      * that user.  After these verifications, the authentication
217      * process is complete.
218      */
219
220     len = from64tobits(buf2, buf1);
221     if (len < 0) {
222         report(stderr, _("could not decode BASE64 ready response\n"));
223         return PS_AUTHFAIL;
224     }
225
226     des_ecb_encrypt((des_cblock *)buf2, (des_cblock *)buf2, schedule, 0);
227     memcpy(challenge2.cstr, buf2, 4);
228     if (ntohl(challenge2.cint) != challenge1.cint + 1) {
229         report(stderr, _("challenge mismatch\n"));
230         return PS_AUTHFAIL;
231     }       
232
233     memset(authenticator.dat, 0, sizeof authenticator.dat);
234
235     result = htonl(challenge1.cint);
236     memcpy(authenticator.dat, &result, sizeof result);
237
238     /* The protection mechanisms and their corresponding bit-masks are as
239      * follows:
240      *
241      * 1 No protection mechanism
242      * 2 Integrity (krb_mk_safe) protection
243      * 4 Privacy (krb_mk_priv) protection
244      */
245     authenticator.dat[4] = 1;
246
247     len = strlen(tktuser);
248     strncpy(authenticator.dat+8, tktuser, len);
249     authenticator.length = len + 8 + 1;
250     while (authenticator.length & 7) {
251         authenticator.length++;
252     }
253     des_pcbc_encrypt((des_cblock *)authenticator.dat,
254             (des_cblock *)authenticator.dat, authenticator.length, schedule,
255             &session, 1);
256
257     to64frombits(buf1, authenticator.dat, authenticator.length);
258
259     /* ship down the response, accept the server's error/ok indication */
260     suppress_tags = TRUE;
261     result = gen_transact(sock, buf1, strlen(buf1));
262     suppress_tags = FALSE;
263     if (result)
264         return(result);
265     else
266         return(PS_SUCCESS);
267 }
268 #endif /* KERBEROS_V4 */
269
270 /* kerberos.c ends here */
271