summaryrefslogtreecommitdiff
path: root/daemon/ntlm.c
blob: 880ae9e565e856637e0b692d1780e32ce1c852ff (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703

/* TODO: Include attribution for ideas, and code from mod_ntlm */

#include "usuals.h"
#include "httpauthd.h"
#include "hash.h"
#include "defaults.h"

#include <syslog.h>

/* The NTLM headers */
#include "ntlmssp.h"

/* -------------------------------------------------------------------------------
 * Defaults and Constants
 */

/* This needs to be the same as an MD5 hash length */
#define NTLM_HASH_KEY_LEN 16
#define NTLM_ESTABLISHED  (void*)1


/* -------------------------------------------------------------------------------
 * Structures and Globals
 */

/* A pending connection */
typedef struct ntlm_connection
{
  void* handle;
  char nonce[NONCE_LEN];
  unsigned int flags;
}
ntlm_connection_t;


/* The main context */
typedef struct ntlm_context
{
  /* Settings ---------------------------------------------------------- */
  const char* server;       /* Server to authenticate against */
  const char* domain;       /* NTLM domain to authenticate against */
  const char* backup;       /* Backup server if primary is down */
  int pending_max;          /* Maximum number of connections at once */
  int pending_timeout;      /* Timeout for authentication (in seconds) */
  const char* basic_realm;  /* The realm for basic authentication */

  /* Context ----------------------------------------------------------- */
  hash_t* pending;        /* Pending connections */
  hash_t* established;    /* Established connections */
}
ntlm_context_t;


/* The default context settings */
static const ntlm_context_t ntlm_defaults =
{
  NULL, NULL, NULL, DEFAULT_PENDING_MAX, DEFAULT_PENDING_TIMEOUT,
  NULL, NULL, NULL
};


/* Mutexes for accessing non-thread-safe smblib */
static pthread_mutex_t g_smblib_mutex;
static pthread_mutexattr_t g_smblib_mutexattr;


/* -------------------------------------------------------------------------------
 * Internal Functions
 */

static ntlm_connection_t* getpending(ntlm_context_t* ctx, const void* key)
{
  ntlm_connection_t* ret;

  ha_lock(NULL);

    if(ret = (ntlm_connection_t*)hash_get(ctx->pending, key))
      hash_rem(ctx->pending, key);

  ha_unlock(NULL);

  return ret;
}

static int putpending(ntlm_context_t* ctx, const void* key, ntlm_connection_t* conn)
{
  int r = 0;

  ha_lock(NULL);

    if(hash_get(ctx->pending, key))
    {
      if(!hash_set(ctx->pending, key, (void*)conn))
      {
        ha_messagex(LOG_ERR, "out of memory");
        r = -1;
      }
    }

  ha_unlock(NULL);

  return r;
}

static ntlm_connection_t* makeconnection(ntlm_context_t* ctx)
{
  ntlm_connection_t* conn;

  conn = (ntlm_connection_t*)malloc(sizeof(ntlm_connection_t));
  if(!conn)
  {
    ha_messagex(LOG_CRIT, "out of memory");
    return NULL;
  }

  memset(conn, 0, sizeof(*conn));

  /*
   * Open a connection to to the domain controller. I don't think
   * we can cache these connections or use them again as opening
   * a connection here results in an nonce being generated.
   */
  conn->handle = ntlmssp_connect(ctx->server, ctx->backup,
                                 ctx->domain, conn->nonce);
  if(!conn->handle)
  {
    ha_messagex(LOG_ERR, "couldn't connect to the domain server %s (backup: %s)",
             ctx->server, ctx->backup ? ctx->backup : "none");
    free(conn);
    return NULL;
  }
}

static void freeconnection(ntlm_context_t* ctx, ntlm_connection_t* conn)
{
  if(conn->handle)
  {
    ntlmssp_disconnect(conn->handle);
    conn->handle = NULL;
  }

  free(conn);
}

int ntlm_auth_basic(ntlm_context_t* ctx, char* key, const char* header,
                    ha_response_t* resp, ha_buffer_t* buf)
{
  ntlm_connection_t* conn;
  char* t;
  ha_basic_header_t basic;
  const char* domain = NULL;

  /*
   * We're doing basic authentication on the connection
   * which invalidates any NTLM authentication we've started
   * or done on this connection.
   */
  conn = getpending(ctx, key);
  if(conn)
    freeconnection(ctx, conn);

  if(ha_parsebasic(header, buf, &basic) == HA_ERROR)
    return HA_ERROR;

  /* Check and see if this connection is in the cache */
  ha_lock(NULL);

    if(hash_get(ctx->established, basic.key) == BASIC_ESTABLISHED)
      found = 1;

  ha_unlock(NULL);


  /* Try to find a domain in the user */
  if((t = strchr(basic.user, '\\')) != NULL ||
     (t = strchr(basic.user, '/')) != NULL)
  {
    /* Break at the domain */
    domain = basic.user;
    basic.user = t + 1;
    *t = 0;

    /* Make sure this is our domain */
    if(strcasecmp(domain, ctx->domain) != 0)
       domain = NULL;
  }

  if(!domain)
  {
    /* Use the default domain if none specified */
    domain = ctx->domain;
  }

  /* Make sure above did not fail */
  if(!found && basic.user && basic.user[0] && basic.password &&
     basic.password[0] && domain && domain[0])
  {
    /* We need to lock to go into smblib */
    ha_lock(&g_smblib_mutex);

      /* Found in smbval/valid.h */
      if(ntlmssp_validuser(basic.user, basic.password, ctx->server,
                           ctx->backup, domain) == NTV_NO_ERROR)
      {
         /* If valid then we return */
         resp->code = HA_SERVER_ACCEPT;
      }

    ha_unlock(&g_smblib_mutex);
  }

  if(resp->code = HA_SERVER_ACCEPT)
  {
    resp->detail = basic.user;

    /* We put this connection into the successful connections */
    if(!hash_set(ctx->established, basic.key, BASIC_ESTABLISHED))
    {
      ha_messagex(LOG_CRIT, "out of memory");
      return HA_ERROR;
    }

    return HA_OK;
  }

  return HA_FALSE;
}

int ntlm_auth_ntlm(ntlm_context_t* ctx, void* key, const char* header,
                   ha_response_t* resp, ha_buffer_t* buf)
{
  ntlmssp_info_rec ntlmssp;
  ntlm_connection_t* conn = NULL;
  unsigned int flags = 0;
  int ret = HA_FALSE;
  int r;

  /*
   * Retrieve and remove the connection from the pending bag.
   * We add it back again below if that's necessary.
   */
  conn = getpending(ctx, key);

  /*
   * We use the flags from an already established connection
   * if we've been pending and stuff
   */

  if(conn && conn->flags)
    flags = conn->flags;

  /*
   * First we figure out what kind of message the client
   * is sending us.
   */

  ha_bufnext(buf);
  ha_bufdec64(buf, header);
  header = ha_bufdata(buf);

  if(ha_buferr(buf))
    goto finally;

  r = ntlmssp_decode_msg(&ntlmssp, ha_bufdata(buf), ha_buflen(buf), &flags);
  if(r != 0)
  {
    ha_messagex(LOG_ERR, "decoding NTLM message failed (error %d)", r);
    resp->code = HA_SERVER_BADREQ;
    goto finally;
  }


  switch(ntlmssp.msg_type)
  {

  /* An initial NTLM request? */
  case 1:
    {
      /* Win9x doesn't seem to send a domain or host */
      int win9x = !ntlmssp.host[0] && !ntlmssp.domain[0];

      /*
       * If we already have a connection to the domain controller
       * then we're in trouble. Basically this is the second
       * type 1 message we've received over this connection.
       *
       * TODO: Eventually what we want to do here is wait for the
       * other authentication request to complete, or something
       * like that.
       */
      if(conn)
      {
        /*
         * In this case we also add the connection back into the
         * pending stack so that the correct request will complete
         * properly when it comes through.
         */
        if(putpending(ctx, key, conn) != -1)
          conn = NULL;

        ha_messagex(LOG_ERR, "received out of order NTLM request from client");
        resp->code = HA_SERVER_BADREQ;
        goto finally;
      }


      /*
       * Check how many connections we have to the domain controller
       * and if too many then cut off here.
       */
      if(ctx->pending_max != -1)
      {
        ha_lock(NULL);
          r = (hash_count(ctx->pending) >= ctx->pending_max);
        ha_unlock(NULL);

        if(r)
        {
          resp->code = HA_SERVER_BUSY;
          goto finally;
        }
      }


      /*
       * Open a connection to to the domain controller. I don't think
       * we can cache these connections or use them again as opening
       * a connection here results in an nonce being generated.
       */
      conn = makeconnection(ctx);

      if(!conn)
      {
        resp->code = HA_SERVER_ERROR;
        goto finally;
      }

      /* Save away any flags given us by ntlm_decode_msg */
      conn->flags = flags;

      /* Start building the header */
      ha_bufnext(buf);
      ha_bufcat(buf, NTLM_PREFIX);

      if(win9x)
      {
        struct ntlm_msg2_win9x msg_win9x;
        ntlmssp_encode_msg2_win9x(conn->nonce, &msg_win9x, (char*)ctx->domain, flags);
        ha_bufenc64(buf, (unsigned char*)&msg_win9x, sizeof(msg_win9x));
      }
      else
      {
        struct ntlm_msg2 msg;
        ntlmssp_encode_msg2(conn->nonce, &msg);
        ha_bufenc64(buf, (unsigned char*)&msg, sizeof(msg));
      }

      if(ha_buferr(buf))
        goto finally;

      /*
       * TODO: Our callers need to be able to keep alive
       * connections that have authentication going on.
       */

      /* Cache this connection in our pending set ... */
      if(putpending(ctx, key, conn) == -1)
      {
        resp->code = HA_SERVER_ERROR;
        goto finally;
      }

      /*
       * By marking this as null, the cleanup code
       * won't free the connection since it's been
       * cached above.
       */
      conn = NULL;

      ha_addheader(resp, "WWW-Authenticate", ha_bufdata(buf));
      resp->code = HA_SERVER_DECLINE;
      goto finally;
    }

  /* A response to a challenge */
  case 3:
    {
      /*
       * We need to have a connection at this point or this whole thing
       * has come in in the wrong order. Actually it's a client error
       * for stuff to come in wrong. But since some web servers also
       * kill keep-alives and stuff, we forgive and just ask the client
       * for the authentication info again.
       */
      if(!conn || !conn->handle)
      {
        ha_messagex(LOG_ERR, "received out of order NTLM response from client");
        resp->code = HA_SERVER_BADREQ;
        goto finally;
      }

      if(!ntlmssp.user)
      {
        ha_messagex(LOG_ERR, "received NTLM response without user name");
        resp->code = HA_SERVER_BADREQ;
        goto finally;
      }

      /* We have to lock while going into smblib */
      ha_lock(&g_smblib_mutex);

        /* Now authenticate them against the DC */
        r = ntlmssp_auth(conn->handle, ntlmssp.user, ntlmssp.nt, 1,
                      ntlmssp.domain[0] ? (char*)ntlmssp.domain : (char*)ctx->domain);

      ha_unlock(&g_smblib_mutex);

      /* The connection gets disconnected below */

      if(r == NTV_LOGON_ERROR)
      {
        /*
         * Note that we don't set a code here. This causes our
         * caller to put in all the proper headers for us.
         */
        ha_messagex(LOG_ERR, "failed NTLM logon for user '%s'", ntlmssp.user);
        ret = HA_FALSE;
      }

      /* A successful login ends here */
      else
      {
        resp->code = HA_SERVER_ACCEPT;

        /* We put this connection into the successful connections */
        if(!hash_set(ctx->established, key, NTLM_ESTABLISHED))
        {
          ha_messagex(LOG_CRIT, "out of memory");
          ret = HA_ERROR;
        }
      }

      goto finally;
    }

  default:
    ha_messagex(LOG_ERR, "received invalid NTLM message (type %d)", ntlmssp.msg_type);
    resp->code = HA_SERVER_BADREQ;
    goto finally;
  };


finally:
  if(ha_buferr(buf))
    ret = HA_ERROR;

  if(conn)
    freeconnection(ctx, conn);

  return ret;
}


/* -------------------------------------------------------------------------------
 * Handler Functions
 */

int ntlm_config(ha_context_t* context, const char* name, const char* value)
{
  ntlm_context_t* ctx = (ntlm_context_t*)(context->data);

  if(strcmp(name, "ntlmserver") == 0)
  {
    ctx->server = value;
    return HA_OK;
  }

  else if(strcmp(name, "ntlmbackup") == 0)
  {
    ctx->backup = value;
    return HA_OK;
  }

  else if(strcmp(name, "ntlmdomain") == 0)
  {
    ctx->domain = value;
    return HA_OK;
  }

  else if(strcmp(name, "pendingmax") == 0)
  {
    return ha_confint(name, value, 1, 256, &(ctx->pending_max));
  }

  else if(strcmp(name, "pendingtimeout") == 0)
  {
    return ha_confint(name, value, 1, 86400, &(ctx->pending_timeout));
  }

  else if(strcmp(name, "realm") == 0)
  {
    ctx->basic_realm = value;
    return HA_OK;
  }

  return HA_FALSE;
}

int ntlm_init(ha_context_t* context)
{
  /* Per context initialization */
  if(context)
  {
    ntlm_context_t* ctx = (ntlm_context_t*)(context->data);

    /* Make sure there are some types of authentication we can do */
    if(!(context->types & (HA_TYPE_BASIC | HA_TYPE_NTLM)))
    {
      ha_messagex(LOG_ERR, "NTLM module configured, but does not implement any "
                           "configured authentication type.");
      return HA_ERROR;
    }

    /* Check for mandatory configuration */
    if(!(ctx->server) || !(ctx->domain))
    {
      ha_messagex(LOG_ERR, "NTLM configuration incomplete. "
                           "Must have NTLMServer and NTLMDomain configured.");
      return HA_ERROR;
    }

    /* Initialize our tables */
    if(!(ctx->pending = hash_create(NTLM_HASH_KEY_LEN)) ||
       !(ctx->established = hash_create(NTLM_HASH_KEY_LEN)))
    {
      ha_messagex(LOG_CRIT, "out of memory");
      return HA_ERROR;
    }
  }

  /* Global Initialization */
  else
  {
    /* Create the smblib mutex */
    if(pthread_mutexattr_init(&g_smblib_mutexattr) != 0 ||
       pthread_mutexattr_settype(&g_smblib_mutexattr, HA_MUTEX_TYPE) ||
       pthread_mutex_init(&g_mutex, &g_smblib_mutexattr) != 0)
    {
      ha_messagex(LOG_CRIT, "threading problem. can't create mutex");
      return HA_ERROR;
    }
  }

  return HA_OK;
}

void ntlm_destroy(ha_context_t* context)
{
  /* Per context destroy */
  if(context)
  {
    ntlm_context_t* ctx = (ntlm_context_t*)(context->data);

    /* Note: We don't need to be thread safe here anymore */
    hash_free(ctx->pending);
    hash_free(ctx->established);
  }

  /* Global Destroy */
  else
  {
    /* Close the mutex */
    pthread_mutex_destroy(&g_smblib_mutex);
    pthread_mutexattr_destroy(&g_smblib_mutexattr);
  }
}

int ntlm_process(ha_context_t* context, ha_request_t* req,
                 ha_response_t* resp, ha_buffer_t* buf)
{
  ntlm_context_t* ctx = (ntlm_context_t*)(context->data);
  void* ntlm_connection_t = NULL;
  unsigned char key[NTLM_HASH_KEY_LEN];
  const char* header = NULL;
  time_t t = time(NULL);
  int ret;

  resp->code = -1;

  /* Hash the unique key */
  ha_md5string(req->args[1], key);


  ha_lock(NULL);

    XXX: These connections aren't being destroyed properly

    /*
     * Purge out stale connection stuff. This includes
     * authenticated connections which have expired as
     * well as half open connections which expire.
     */
    hash_purge(ctx->pending, t - ctx->pending_timeout);
    hash_purge(ctx->established, t - context->timeout);

  ha_unlock(NULL);


  /* Look for a NTLM header */
  if(context->types & HA_TYPES_NTLM)
  {
    header = ha_getheader(req, "Authorization", NTLM_PREFIX);
    if(header)
    {
      /* Trim off for decoding */
      while(*header && isspace(*header))
        header++;

      ret = ntlm_auth_ntlm(ctx, key, header, resp, buf);
      if(ret == HA_ERROR)
        return ret;
    }
  }

  /* If basic is enabled, and no NTLM */
  if(!header && context->types & HA_TYPE_BASIC)
  {
    /* Look for a Basic header */
    header = ha_getheader(req, "Authorization", BASIC_PREFIX);
    if(header)
    {
      /* Trim off for decoding */
      while(*header && isspace(*header))
        header++;

      ret = ntlm_auth_basic(ctx, key, header, resp, buf);
      if(ret == HA_ERROR)
        return ret;
    }
  }

  /* The authorization header was not found */
  else
  {
    ha_lock(NULL);

      /*
       * NTLM trusts a connection after it's been authenticated
       * so just pass success for those. Note that we do this
       * in the absence of a authorization header so that we
       * allow connections to be re-authenticated.
       */

      if(hash_get(ctx->established, key) == NTLM_ESTABLISHED)
      {
        hash_touch(ctx->established, key);
        resp->code = HA_SERVER_ACCEPT;
      }

    ha_unlock(NULL);
  }


  /* If nobody's set any other response then... */
  if(resp->code != -1)
  {
    /* If authentication failed tell the browser about it */
    resp->code = HA_SERVER_DECLINE;

    if(context->types & HA_TYPE_NTLM)
      ha_addheader(resp, "WWW-Authenticate", NTLM_PREFIX);

    if(context->types & HA_TYPE_BASIC)
    {
      ha_bufnext(buf);
      ha_bufcat(buf, BASIC_PREFIX, "realm=\"",
                ctx->basic_realm ? ctx->basic_realm : "", "\"", NULL);

      ha_addheader(resp, "WWW-Authenticate", ha_bufdata(buf));
    }
  }

  return ret;
}



/* -------------------------------------------------------------------------------
 * Handler Definition
 */

ha_handler_t ntlm_handler =
{
  "NTLM",                  /* The type */
  ntlm_init,               /* Initialization function */
  ntlm_destroy,            /* Uninitialization routine */
  ntlm_config,             /* Config routine */
  ntlm_process,            /* Processing routine */
  ntlm_defaults,           /* Default settings */
  sizeof(ntlm_context_t)
};