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 - 2011 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#ifdef HAVE_NETDB_H
48#include <netdb.h>
49#endif
50#include <krb.h>
51#include <des.h>
52
53#ifdef HAVE_UNISTD_H
54#include <unistd.h> /* for getpid() */
55#endif
56
57#include "urldata.h"
58#include "curl_base64.h"
59#include "ftp.h"
60#include "sendf.h"
61#include "krb4.h"
62#include "inet_ntop.h"
63#include "curl_memory.h"
64
65/* The last #include file should be: */
66#include "memdebug.h"
67
68#define LOCAL_ADDR (&conn->local_addr)
69#define REMOTE_ADDR conn->ip_addr->ai_addr
70#define myctladdr LOCAL_ADDR
71#define hisctladdr REMOTE_ADDR
72
73struct krb4_data {
74  des_cblock key;
75  des_key_schedule schedule;
76  char name[ANAME_SZ];
77  char instance[INST_SZ];
78  char realm[REALM_SZ];
79};
80
81#ifndef HAVE_STRLCPY
82/* if it ever goes non-static, make it Curl_ prefixed! */
83static size_t
84strlcpy (char *dst, const char *src, size_t dst_sz)
85{
86  size_t n;
87  char *p;
88
89  for(p = dst, n = 0;
90      n + 1 < dst_sz && *src != '\0';
91      ++p, ++src, ++n)
92    *p = *src;
93  *p = '\0';
94  if(*src == '\0')
95    return n;
96  else
97    return n + strlen (src);
98}
99#else
100size_t strlcpy (char *dst, const char *src, size_t dst_sz);
101#endif
102
103static int
104krb4_check_prot(void *app_data, int level)
105{
106  app_data = NULL; /* prevent compiler warning */
107  if(level == PROT_CONFIDENTIAL)
108    return -1;
109  return 0;
110}
111
112static int
113krb4_decode(void *app_data, void *buf, int len, int level,
114            struct connectdata *conn)
115{
116  MSG_DAT m;
117  int e;
118  struct krb4_data *d = app_data;
119
120  if(level == PROT_SAFE)
121    e = krb_rd_safe(buf, len, &d->key,
122                    (struct sockaddr_in *)REMOTE_ADDR,
123                    (struct sockaddr_in *)LOCAL_ADDR, &m);
124  else
125    e = krb_rd_priv(buf, len, d->schedule, &d->key,
126                    (struct sockaddr_in *)REMOTE_ADDR,
127                    (struct sockaddr_in *)LOCAL_ADDR, &m);
128  if(e) {
129    struct SessionHandle *data = conn->data;
130    infof(data, "krb4_decode: %s\n", krb_get_err_text(e));
131    return -1;
132  }
133  memmove(buf, m.app_data, m.app_length);
134  return m.app_length;
135}
136
137static int
138krb4_overhead(void *app_data, int level, int len)
139{
140  /* no arguments are used, just init them to prevent compiler warnings */
141  app_data = NULL;
142  level = 0;
143  len = 0;
144  return 31;
145}
146
147static int
148krb4_encode(void *app_data, const void *from, int length, int level, void **to,
149            struct connectdata *conn)
150{
151  struct krb4_data *d = app_data;
152  *to = malloc(length + 31);
153  if(!*to)
154    return -1;
155  if(level == PROT_SAFE)
156    /* NOTE that the void* cast is safe, krb_mk_safe/priv don't modify the
157     * input buffer
158     */
159    return krb_mk_safe((void*)from, *to, length, &d->key,
160                       (struct sockaddr_in *)LOCAL_ADDR,
161                       (struct sockaddr_in *)REMOTE_ADDR);
162  else if(level == PROT_PRIVATE)
163    return krb_mk_priv((void*)from, *to, length, d->schedule, &d->key,
164                       (struct sockaddr_in *)LOCAL_ADDR,
165                       (struct sockaddr_in *)REMOTE_ADDR);
166  else
167    return -1;
168}
169
170static int
171mk_auth(struct krb4_data *d, KTEXT adat,
172        const char *service, char *host, int checksum)
173{
174  int ret;
175  CREDENTIALS cred;
176  char sname[SNAME_SZ], inst[INST_SZ], realm[REALM_SZ];
177
178  strlcpy(sname, service, sizeof(sname));
179  strlcpy(inst, krb_get_phost(host), sizeof(inst));
180  strlcpy(realm, krb_realmofhost(host), sizeof(realm));
181  ret = krb_mk_req(adat, sname, inst, realm, checksum);
182  if(ret)
183    return ret;
184  strlcpy(sname, service, sizeof(sname));
185  strlcpy(inst, krb_get_phost(host), sizeof(inst));
186  strlcpy(realm, krb_realmofhost(host), sizeof(realm));
187  ret = krb_get_cred(sname, inst, realm, &cred);
188  memmove(&d->key, &cred.session, sizeof(des_cblock));
189  des_key_sched(&d->key, d->schedule);
190  memset(&cred, 0, sizeof(cred));
191  return ret;
192}
193
194#ifdef HAVE_KRB_GET_OUR_IP_FOR_REALM
195int krb_get_our_ip_for_realm(char *, struct in_addr *);
196#endif
197
198static int
199krb4_auth(void *app_data, struct connectdata *conn)
200{
201  int ret;
202  char *p;
203  unsigned char *ptr;
204  size_t len = 0;
205  KTEXT_ST adat;
206  MSG_DAT msg_data;
207  int checksum;
208  u_int32_t cs;
209  struct krb4_data *d = app_data;
210  char *host = conn->host.name;
211  ssize_t nread;
212  int l = sizeof(conn->local_addr);
213  struct SessionHandle *data = conn->data;
214  CURLcode result;
215  size_t base64_sz = 0;
216
217  if(getsockname(conn->sock[FIRSTSOCKET],
218                 (struct sockaddr *)LOCAL_ADDR, &l) < 0)
219    perror("getsockname()");
220
221  checksum = getpid();
222  ret = mk_auth(d, &adat, "ftp", host, checksum);
223  if(ret == KDC_PR_UNKNOWN)
224    ret = mk_auth(d, &adat, "rcmd", host, checksum);
225  if(ret) {
226    infof(data, "%s\n", krb_get_err_text(ret));
227    return AUTH_CONTINUE;
228  }
229
230#ifdef HAVE_KRB_GET_OUR_IP_FOR_REALM
231  if(krb_get_config_bool("nat_in_use")) {
232    struct sockaddr_in *localaddr  = (struct sockaddr_in *)LOCAL_ADDR;
233    struct in_addr natAddr;
234
235    if(krb_get_our_ip_for_realm(krb_realmofhost(host),
236                                 &natAddr) != KSUCCESS
237        && krb_get_our_ip_for_realm(NULL, &natAddr) != KSUCCESS)
238      infof(data, "Can't get address for realm %s\n",
239                 krb_realmofhost(host));
240    else {
241      if(natAddr.s_addr != localaddr->sin_addr.s_addr) {
242        char addr_buf[128];
243        if(Curl_inet_ntop(AF_INET, natAddr, addr_buf, sizeof(addr_buf)))
244          infof(data, "Using NAT IP address (%s) for kerberos 4\n", addr_buf);
245        localaddr->sin_addr = natAddr;
246      }
247    }
248  }
249#endif
250
251  result = Curl_base64_encode(conn->data, (char *)adat.dat, adat.length,
252                              &p, &base64_sz);
253  if(result) {
254    Curl_failf(data, "base64-encoding: %s", curl_easy_strerror(result));
255    return AUTH_CONTINUE;
256  }
257
258  result = Curl_ftpsendf(conn, "ADAT %s", p);
259
260  free(p);
261
262  if(result)
263    return -2;
264
265  if(Curl_GetFTPResponse(&nread, conn, NULL))
266    return -1;
267
268  if(data->state.buffer[0] != '2') {
269    Curl_failf(data, "Server didn't accept auth data");
270    return AUTH_ERROR;
271  }
272
273  p = strstr(data->state.buffer, "ADAT=");
274  if(!p) {
275    Curl_failf(data, "Remote host didn't send adat reply");
276    return AUTH_ERROR;
277  }
278  p += 5;
279  result = Curl_base64_decode(p, &ptr, &len);
280  if(result) {
281    Curl_failf(data, "base64-decoding: %s", curl_easy_strerror(result));
282    return AUTH_ERROR;
283  }
284  if(len > sizeof(adat.dat)-1) {
285    free(ptr);
286    ptr = NULL;
287    len = 0;
288  }
289  if(!len || !ptr) {
290    Curl_failf(data, "Failed to decode base64 from server");
291    return AUTH_ERROR;
292  }
293  memcpy((char *)adat.dat, ptr, len);
294  free(ptr);
295  adat.length = len;
296  ret = krb_rd_safe(adat.dat, adat.length, &d->key,
297                    (struct sockaddr_in *)hisctladdr,
298                    (struct sockaddr_in *)myctladdr, &msg_data);
299  if(ret) {
300    Curl_failf(data, "Error reading reply from server: %s",
301               krb_get_err_text(ret));
302    return AUTH_ERROR;
303  }
304  krb_get_int(msg_data.app_data, &cs, 4, 0);
305  if(cs - checksum != 1) {
306    Curl_failf(data, "Bad checksum returned from server");
307    return AUTH_ERROR;
308  }
309  return AUTH_OK;
310}
311
312struct Curl_sec_client_mech Curl_krb4_client_mech = {
313    "KERBEROS_V4",
314    sizeof(struct krb4_data),
315    NULL, /* init */
316    krb4_auth,
317    NULL, /* end */
318    krb4_check_prot,
319    krb4_overhead,
320    krb4_encode,
321    krb4_decode
322};
323
324static enum protection_level
325krb4_set_command_prot(struct connectdata *conn, enum protection_level level)
326{
327  enum protection_level old = conn->command_prot;
328  DEBUGASSERT(level > PROT_NONE && level < PROT_LAST);
329  conn->command_prot = level;
330  return old;
331}
332
333CURLcode Curl_krb_kauth(struct connectdata *conn)
334{
335  des_cblock key;
336  des_key_schedule schedule;
337  KTEXT_ST tkt, tktcopy;
338  char *name;
339  char *p;
340  char passwd[100];
341  size_t tmp = 0;
342  ssize_t nread;
343  enum protection_level save;
344  CURLcode result;
345  unsigned char *ptr;
346  size_t base64_sz = 0;
347
348  save = krb4_set_command_prot(conn, PROT_PRIVATE);
349
350  result = Curl_ftpsendf(conn, "SITE KAUTH %s", conn->user);
351
352  if(result)
353    return result;
354
355  result = Curl_GetFTPResponse(&nread, conn, NULL);
356  if(result)
357    return result;
358
359  if(conn->data->state.buffer[0] != '3') {
360    krb4_set_command_prot(conn, save);
361    return CURLE_FTP_WEIRD_SERVER_REPLY;
362  }
363
364  p = strstr(conn->data->state.buffer, "T=");
365  if(!p) {
366    Curl_failf(conn->data, "Bad reply from server");
367    krb4_set_command_prot(conn, save);
368    return CURLE_FTP_WEIRD_SERVER_REPLY;
369  }
370
371  p += 2;
372  result = Curl_base64_decode(p, &ptr, &tmp);
373  if(result) {
374    Curl_failf(conn->data, "base64-decoding: %s", curl_easy_strerror(result));
375    return result;
376  }
377  if(tmp >= sizeof(tkt.dat)) {
378    free(ptr);
379    ptr = NULL;
380    tmp = 0;
381  }
382  if(!tmp || !ptr) {
383    Curl_failf(conn->data, "Failed to decode base64 in reply");
384    krb4_set_command_prot(conn, save);
385    return CURLE_FTP_WEIRD_SERVER_REPLY;
386  }
387  memcpy((char *)tkt.dat, ptr, tmp);
388  free(ptr);
389  tkt.length = tmp;
390  tktcopy.length = tkt.length;
391
392  p = strstr(conn->data->state.buffer, "P=");
393  if(!p) {
394    Curl_failf(conn->data, "Bad reply from server");
395    krb4_set_command_prot(conn, save);
396    return CURLE_FTP_WEIRD_SERVER_REPLY;
397  }
398  name = p + 2;
399  for(; *p && *p != ' ' && *p != '\r' && *p != '\n'; p++);
400  *p = 0;
401
402  des_string_to_key (conn->passwd, &key);
403  des_key_sched(&key, schedule);
404
405  des_pcbc_encrypt((void *)tkt.dat, (void *)tktcopy.dat,
406                   tkt.length,
407                   schedule, &key, DES_DECRYPT);
408  if(strcmp ((char*)tktcopy.dat + 8,
409              KRB_TICKET_GRANTING_TICKET) != 0) {
410    afs_string_to_key(passwd,
411                      krb_realmofhost(conn->host.name),
412                      &key);
413    des_key_sched(&key, schedule);
414    des_pcbc_encrypt((void *)tkt.dat, (void *)tktcopy.dat,
415                     tkt.length,
416                     schedule, &key, DES_DECRYPT);
417  }
418  memset(key, 0, sizeof(key));
419  memset(schedule, 0, sizeof(schedule));
420  memset(passwd, 0, sizeof(passwd));
421  result = Curl_base64_encode(conn->data, (char *)tktcopy.dat, tktcopy.length,
422                              &p, &base64_sz);
423  if(result) {
424    Curl_failf(conn->data, "base64-encoding: %s", curl_easy_strerror(result));
425    krb4_set_command_prot(conn, save);
426    return result;
427  }
428  memset (tktcopy.dat, 0, tktcopy.length);
429
430  result = Curl_ftpsendf(conn, "SITE KAUTH %s %s", name, p);
431  free(p);
432  if(result)
433    return result;
434
435  result = Curl_GetFTPResponse(&nread, conn, NULL);
436  if(result)
437    return result;
438  krb4_set_command_prot(conn, save);
439
440  return CURLE_OK;
441}
442
443#endif /* HAVE_KRB4 */
444#endif /* CURL_DISABLE_FTP */
445