Git init
[external/curl.git] / lib / krb4.c
1 /* This source code was modified by Martin Hedenfalk <mhe@stacken.kth.se> for
2  * use in Curl. Martin's latest changes were done 2000-09-18.
3  *
4  * It has since been patched away like a madman by Daniel Stenberg to make it
5  * better applied to curl conditions, and to make it not use globals, pollute
6  * name space and more.
7  *
8  * Copyright (c) 1995, 1996, 1997, 1998, 1999 Kungliga Tekniska Högskolan
9  * (Royal Institute of Technology, Stockholm, Sweden).
10  * Copyright (c) 2004 - 2010 Daniel Stenberg
11  * All rights reserved.
12  *
13  * Redistribution and use in source and binary forms, with or without
14  * modification, are permitted provided that the following conditions
15  * are met:
16  *
17  * 1. Redistributions of source code must retain the above copyright
18  *    notice, this list of conditions and the following disclaimer.
19  *
20  * 2. Redistributions in binary form must reproduce the above copyright
21  *    notice, this list of conditions and the following disclaimer in the
22  *    documentation and/or other materials provided with the distribution.
23  *
24  * 3. Neither the name of the Institute nor the names of its contributors
25  *    may be used to endorse or promote products derived from this software
26  *    without specific prior written permission.
27  *
28  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
29  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
30  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
31  * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
32  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
33  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
34  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
35  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
36  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
37  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
38  * SUCH DAMAGE.
39  *
40  */
41
42 #include "setup.h"
43
44 #ifndef CURL_DISABLE_FTP
45 #ifdef HAVE_KRB4
46
47 #include <stdlib.h>
48 #ifdef HAVE_NETDB_H
49 #include <netdb.h>
50 #endif
51 #include <string.h>
52 #include <krb.h>
53 #include <des.h>
54
55 #ifdef HAVE_UNISTD_H
56 #include <unistd.h> /* for getpid() */
57 #endif
58
59 #include "urldata.h"
60 #include "curl_base64.h"
61 #include "ftp.h"
62 #include "sendf.h"
63 #include "krb4.h"
64 #include "inet_ntop.h"
65 #include "curl_memory.h"
66
67 /* The last #include file should be: */
68 #include "memdebug.h"
69
70 #define LOCAL_ADDR (&conn->local_addr)
71 #define REMOTE_ADDR conn->ip_addr->ai_addr
72 #define myctladdr LOCAL_ADDR
73 #define hisctladdr REMOTE_ADDR
74
75 struct krb4_data {
76   des_cblock key;
77   des_key_schedule schedule;
78   char name[ANAME_SZ];
79   char instance[INST_SZ];
80   char realm[REALM_SZ];
81 };
82
83 #ifndef HAVE_STRLCPY
84 /* if it ever goes non-static, make it Curl_ prefixed! */
85 static size_t
86 strlcpy (char *dst, const char *src, size_t dst_sz)
87 {
88   size_t n;
89   char *p;
90
91   for (p = dst, n = 0;
92        n + 1 < dst_sz && *src != '\0';
93        ++p, ++src, ++n)
94     *p = *src;
95   *p = '\0';
96   if(*src == '\0')
97     return n;
98   else
99     return n + strlen (src);
100 }
101 #else
102 size_t strlcpy (char *dst, const char *src, size_t dst_sz);
103 #endif
104
105 static int
106 krb4_check_prot(void *app_data, int level)
107 {
108   app_data = NULL; /* prevent compiler warning */
109   if(level == PROT_CONFIDENTIAL)
110     return -1;
111   return 0;
112 }
113
114 static int
115 krb4_decode(void *app_data, void *buf, int len, int level,
116             struct connectdata *conn)
117 {
118   MSG_DAT m;
119   int e;
120   struct krb4_data *d = app_data;
121
122   if(level == PROT_SAFE)
123     e = krb_rd_safe(buf, len, &d->key,
124                     (struct sockaddr_in *)REMOTE_ADDR,
125                     (struct sockaddr_in *)LOCAL_ADDR, &m);
126   else
127     e = krb_rd_priv(buf, len, d->schedule, &d->key,
128                     (struct sockaddr_in *)REMOTE_ADDR,
129                     (struct sockaddr_in *)LOCAL_ADDR, &m);
130   if(e) {
131     struct SessionHandle *data = conn->data;
132     infof(data, "krb4_decode: %s\n", krb_get_err_text(e));
133     return -1;
134   }
135   memmove(buf, m.app_data, m.app_length);
136   return m.app_length;
137 }
138
139 static int
140 krb4_overhead(void *app_data, int level, int len)
141 {
142   /* no arguments are used, just init them to prevent compiler warnings */
143   app_data = NULL;
144   level = 0;
145   len = 0;
146   return 31;
147 }
148
149 static int
150 krb4_encode(void *app_data, const void *from, int length, int level, void **to,
151             struct connectdata *conn)
152 {
153   struct krb4_data *d = app_data;
154   *to = malloc(length + 31);
155   if(!*to)
156     return -1;
157   if(level == PROT_SAFE)
158     /* NOTE that the void* cast is safe, krb_mk_safe/priv don't modify the
159      * input buffer
160      */
161     return krb_mk_safe((void*)from, *to, length, &d->key,
162                        (struct sockaddr_in *)LOCAL_ADDR,
163                        (struct sockaddr_in *)REMOTE_ADDR);
164   else if(level == PROT_PRIVATE)
165     return krb_mk_priv((void*)from, *to, length, d->schedule, &d->key,
166                        (struct sockaddr_in *)LOCAL_ADDR,
167                        (struct sockaddr_in *)REMOTE_ADDR);
168   else
169     return -1;
170 }
171
172 static int
173 mk_auth(struct krb4_data *d, KTEXT adat,
174         const char *service, char *host, int checksum)
175 {
176   int ret;
177   CREDENTIALS cred;
178   char sname[SNAME_SZ], inst[INST_SZ], realm[REALM_SZ];
179
180   strlcpy(sname, service, sizeof(sname));
181   strlcpy(inst, krb_get_phost(host), sizeof(inst));
182   strlcpy(realm, krb_realmofhost(host), sizeof(realm));
183   ret = krb_mk_req(adat, sname, inst, realm, checksum);
184   if(ret)
185     return ret;
186   strlcpy(sname, service, sizeof(sname));
187   strlcpy(inst, krb_get_phost(host), sizeof(inst));
188   strlcpy(realm, krb_realmofhost(host), sizeof(realm));
189   ret = krb_get_cred(sname, inst, realm, &cred);
190   memmove(&d->key, &cred.session, sizeof(des_cblock));
191   des_key_sched(&d->key, d->schedule);
192   memset(&cred, 0, sizeof(cred));
193   return ret;
194 }
195
196 #ifdef HAVE_KRB_GET_OUR_IP_FOR_REALM
197 int krb_get_our_ip_for_realm(char *, struct in_addr *);
198 #endif
199
200 static int
201 krb4_auth(void *app_data, struct connectdata *conn)
202 {
203   int ret;
204   char *p;
205   unsigned char *ptr;
206   size_t len;
207   KTEXT_ST adat;
208   MSG_DAT msg_data;
209   int checksum;
210   u_int32_t cs;
211   struct krb4_data *d = app_data;
212   char *host = conn->host.name;
213   ssize_t nread;
214   int l = sizeof(conn->local_addr);
215   struct SessionHandle *data = conn->data;
216   CURLcode result;
217
218   if(getsockname(conn->sock[FIRSTSOCKET],
219                  (struct sockaddr *)LOCAL_ADDR, &l) < 0)
220     perror("getsockname()");
221
222   checksum = getpid();
223   ret = mk_auth(d, &adat, "ftp", host, checksum);
224   if(ret == KDC_PR_UNKNOWN)
225     ret = mk_auth(d, &adat, "rcmd", host, checksum);
226   if(ret) {
227     infof(data, "%s\n", krb_get_err_text(ret));
228     return AUTH_CONTINUE;
229   }
230
231 #ifdef HAVE_KRB_GET_OUR_IP_FOR_REALM
232   if(krb_get_config_bool("nat_in_use")) {
233     struct sockaddr_in *localaddr  = (struct sockaddr_in *)LOCAL_ADDR;
234     struct in_addr natAddr;
235
236     if(krb_get_our_ip_for_realm(krb_realmofhost(host),
237                                  &natAddr) != KSUCCESS
238         && krb_get_our_ip_for_realm(NULL, &natAddr) != KSUCCESS)
239       infof(data, "Can't get address for realm %s\n",
240                  krb_realmofhost(host));
241     else {
242       if(natAddr.s_addr != localaddr->sin_addr.s_addr) {
243         char addr_buf[128];
244         if(Curl_inet_ntop(AF_INET, natAddr, addr_buf, sizeof(addr_buf)))
245           infof(data, "Using NAT IP address (%s) for kerberos 4\n", addr_buf);
246         localaddr->sin_addr = natAddr;
247       }
248     }
249   }
250 #endif
251
252   if(Curl_base64_encode(conn->data, (char *)adat.dat, adat.length, &p) < 1) {
253     Curl_failf(data, "Out of memory base64-encoding");
254     return AUTH_CONTINUE;
255   }
256
257   result = Curl_ftpsendf(conn, "ADAT %s", p);
258
259   free(p);
260
261   if(result)
262     return -2;
263
264   if(Curl_GetFTPResponse(&nread, conn, NULL))
265     return -1;
266
267   if(data->state.buffer[0] != '2'){
268     Curl_failf(data, "Server didn't accept auth data");
269     return AUTH_ERROR;
270   }
271
272   p = strstr(data->state.buffer, "ADAT=");
273   if(!p) {
274     Curl_failf(data, "Remote host didn't send adat reply");
275     return AUTH_ERROR;
276   }
277   p += 5;
278   len = Curl_base64_decode(p, &ptr);
279   if(len > sizeof(adat.dat)-1) {
280     free(ptr);
281     len=0;
282   }
283   if(!len || !ptr) {
284     Curl_failf(data, "Failed to decode base64 from server");
285     return AUTH_ERROR;
286   }
287   memcpy((char *)adat.dat, ptr, len);
288   free(ptr);
289   adat.length = len;
290   ret = krb_rd_safe(adat.dat, adat.length, &d->key,
291                     (struct sockaddr_in *)hisctladdr,
292                     (struct sockaddr_in *)myctladdr, &msg_data);
293   if(ret) {
294     Curl_failf(data, "Error reading reply from server: %s",
295                krb_get_err_text(ret));
296     return AUTH_ERROR;
297   }
298   krb_get_int(msg_data.app_data, &cs, 4, 0);
299   if(cs - checksum != 1) {
300     Curl_failf(data, "Bad checksum returned from server");
301     return AUTH_ERROR;
302   }
303   return AUTH_OK;
304 }
305
306 struct Curl_sec_client_mech Curl_krb4_client_mech = {
307     "KERBEROS_V4",
308     sizeof(struct krb4_data),
309     NULL, /* init */
310     krb4_auth,
311     NULL, /* end */
312     krb4_check_prot,
313     krb4_overhead,
314     krb4_encode,
315     krb4_decode
316 };
317
318 static enum protection_level
319 krb4_set_command_prot(struct connectdata *conn, enum protection_level level)
320 {
321   enum protection_level old = conn->command_prot;
322   DEBUGASSERT(level > PROT_NONE && level < PROT_LAST);
323   conn->command_prot = level;
324   return old;
325 }
326
327 CURLcode Curl_krb_kauth(struct connectdata *conn)
328 {
329   des_cblock key;
330   des_key_schedule schedule;
331   KTEXT_ST tkt, tktcopy;
332   char *name;
333   char *p;
334   char passwd[100];
335   size_t tmp;
336   ssize_t nread;
337   enum protection_level save;
338   CURLcode result;
339   unsigned char *ptr;
340
341   save = krb4_set_command_prot(conn, PROT_PRIVATE);
342
343   result = Curl_ftpsendf(conn, "SITE KAUTH %s", conn->user);
344
345   if(result)
346     return result;
347
348   result = Curl_GetFTPResponse(&nread, conn, NULL);
349   if(result)
350     return result;
351
352   if(conn->data->state.buffer[0] != '3'){
353     krb4_set_command_prot(conn, save);
354     return CURLE_FTP_WEIRD_SERVER_REPLY;
355   }
356
357   p = strstr(conn->data->state.buffer, "T=");
358   if(!p) {
359     Curl_failf(conn->data, "Bad reply from server");
360     krb4_set_command_prot(conn, save);
361     return CURLE_FTP_WEIRD_SERVER_REPLY;
362   }
363
364   p += 2;
365   tmp = Curl_base64_decode(p, &ptr);
366   if(tmp >= sizeof(tkt.dat)) {
367     free(ptr);
368     tmp=0;
369   }
370   if(!tmp || !ptr) {
371     Curl_failf(conn->data, "Failed to decode base64 in reply");
372     krb4_set_command_prot(conn, save);
373     return CURLE_FTP_WEIRD_SERVER_REPLY;
374   }
375   memcpy((char *)tkt.dat, ptr, tmp);
376   free(ptr);
377   tkt.length = tmp;
378   tktcopy.length = tkt.length;
379
380   p = strstr(conn->data->state.buffer, "P=");
381   if(!p) {
382     Curl_failf(conn->data, "Bad reply from server");
383     krb4_set_command_prot(conn, save);
384     return CURLE_FTP_WEIRD_SERVER_REPLY;
385   }
386   name = p + 2;
387   for(; *p && *p != ' ' && *p != '\r' && *p != '\n'; p++);
388   *p = 0;
389
390   des_string_to_key (conn->passwd, &key);
391   des_key_sched(&key, schedule);
392
393   des_pcbc_encrypt((void *)tkt.dat, (void *)tktcopy.dat,
394                    tkt.length,
395                    schedule, &key, DES_DECRYPT);
396   if(strcmp ((char*)tktcopy.dat + 8,
397               KRB_TICKET_GRANTING_TICKET) != 0) {
398     afs_string_to_key(passwd,
399                       krb_realmofhost(conn->host.name),
400                       &key);
401     des_key_sched(&key, schedule);
402     des_pcbc_encrypt((void *)tkt.dat, (void *)tktcopy.dat,
403                      tkt.length,
404                      schedule, &key, DES_DECRYPT);
405   }
406   memset(key, 0, sizeof(key));
407   memset(schedule, 0, sizeof(schedule));
408   memset(passwd, 0, sizeof(passwd));
409   if(Curl_base64_encode(conn->data, (char *)tktcopy.dat, tktcopy.length, &p)
410      < 1) {
411     failf(conn->data, "Out of memory base64-encoding.");
412     krb4_set_command_prot(conn, save);
413     return CURLE_OUT_OF_MEMORY;
414   }
415   memset (tktcopy.dat, 0, tktcopy.length);
416
417   result = Curl_ftpsendf(conn, "SITE KAUTH %s %s", name, p);
418   free(p);
419   if(result)
420     return result;
421
422   result = Curl_GetFTPResponse(&nread, conn, NULL);
423   if(result)
424     return result;
425   krb4_set_command_prot(conn, save);
426
427   return CURLE_OK;
428 }
429
430 #endif /* HAVE_KRB4 */
431 #endif /* CURL_DISABLE_FTP */