source: squid-ssl/trunk/fuentes/helpers/ntlm_auth/smb_lm/ntlm_smb_lm_auth.cc @ 5495

Last change on this file since 5495 was 5495, checked in by Juanma, 2 years ago

Initial release

File size: 22.4 KB
Line 
1/*
2 * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
3 *
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
7 */
8
9/*
10 * (C) 2000 Francesco Chemolli <kinkie@kame.usr.dsi.unimi.it>
11 * Distributed freely under the terms of the GNU General Public License,
12 * version 2 or later. See the file COPYING for licensing details
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 * GNU General Public License for more details.
18
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
22 */
23
24#include "squid.h"
25#include "base64.h"
26#include "compat/debug.h"
27#include "ntlmauth/ntlmauth.h"
28#include "ntlmauth/support_bits.cci"
29#include "rfcnb/rfcnb.h"
30#include "smblib/smblib.h"
31
32#include <cassert>
33#include <cctype>
34#include <cerrno>
35#include <csignal>
36#include <cstdlib>
37#include <cstring>
38#include <ctime>
39#if HAVE_UNISTD_H
40#include <unistd.h>
41#endif
42#if HAVE_GETOPT_H
43#include <getopt.h>
44#endif
45#if HAVE_UNISTD_H
46#include <unistd.h>
47#endif
48
49/************* CONFIGURATION ***************/
50
51#define DEAD_DC_RETRY_INTERVAL 30
52
53/************* END CONFIGURATION ***************/
54
55/* A couple of harmless helper macros */
56#define SEND(X) debug("sending '%s' to squid\n",X); printf(X "\n");
57#ifdef __GNUC__
58#define SEND2(X,Y...) debug("sending '" X "' to squid\n",Y); printf(X "\n",Y);
59#define SEND3(X,Y...) debug("sending '" X "' to squid\n",Y); printf(X "\n",Y);
60#else
61/* no gcc, no debugging. varargs macros are a gcc extension */
62#define SEND2 printf
63#define SEND3 printf
64#endif
65
66const char *make_challenge(char *domain, char *controller);
67char *ntlm_check_auth(ntlm_authenticate * auth, int auth_length);
68void dc_disconnect(void);
69int connectedp(void);
70int is_dc_ok(char *domain, char *domain_controller);
71
72typedef struct _dc dc;
73struct _dc {
74    char *domain;
75    char *controller;
76    time_t dead;        /* 0 if it's alive, otherwise time of death */
77    dc *next;
78};
79
80/* local functions */
81void usage(void);
82void process_options(int argc, char *argv[]);
83const char * obtain_challenge(void);
84void manage_request(void);
85
86#define ENCODED_PASS_LEN 24
87#define MAX_USERNAME_LEN 255
88#define MAX_DOMAIN_LEN 255
89#define MAX_PASSWD_LEN 31
90
91static unsigned char challenge[NTLM_NONCE_LEN];
92static unsigned char lmencoded_empty_pass[ENCODED_PASS_LEN],
93       ntencoded_empty_pass[ENCODED_PASS_LEN];
94SMB_Handle_Type handle = NULL;
95int ntlm_errno;
96static char credentials[MAX_USERNAME_LEN+MAX_DOMAIN_LEN+2]; /* we can afford to waste */
97static char my_domain[100], my_domain_controller[100];
98static char errstr[1001];
99#if DEBUG
100char error_messages_buffer[NTLM_BLOB_BUFFER_SIZE];
101#endif
102char load_balance = 0, protocol_pedantic = 0;
103dc *controllers = NULL;
104int numcontrollers = 0;
105dc *current_dc;
106char smb_error_buffer[1000];
107
108/* Disconnects from the DC. A reconnection will be done upon the next request
109 */
110void
111dc_disconnect()
112{
113    if (handle != NULL)
114        SMB_Discon(handle, 0);
115    handle = NULL;
116}
117
118int
119connectedp()
120{
121    return (handle != NULL);
122}
123
124/* Tries to connect to a DC. Returns 0 on failure, 1 on OK */
125int
126is_dc_ok(char *domain, char *domain_controller)
127{
128    SMB_Handle_Type h = SMB_Connect_Server(NULL, domain_controller, domain);
129    if (h == NULL)
130        return 0;
131    SMB_Discon(h, 0);
132    return 1;
133}
134
135/* returns 0 on success, > 0 on failure */
136static int
137init_challenge(char *domain, char *domain_controller)
138{
139    int smberr;
140
141    if (handle != NULL) {
142        return 0;
143    }
144    debug("Connecting to server %s domain %s\n", domain_controller, domain);
145    handle = SMB_Connect_Server(NULL, domain_controller, domain);
146    smberr = SMB_Get_Last_Error();
147    SMB_Get_Error_Msg(smberr, errstr, 1000);
148
149    if (handle == NULL) {   /* couldn't connect */
150        debug("Couldn't connect to SMB Server. Error:%s\n", errstr);
151        return 1;
152    }
153    if (SMB_Negotiate(handle, SMB_Prots) < 0) {     /* An error */
154        debug("Error negotiating protocol with SMB Server\n");
155        SMB_Discon(handle, 0);
156        handle = NULL;
157        return 2;
158    }
159    if (handle->Security == 0) {    /* share-level security, unuseable */
160        debug("SMB Server uses share-level security .. we need user security.\n");
161        SMB_Discon(handle, 0);
162        handle = NULL;
163        return 3;
164    }
165    memcpy(challenge, handle->Encrypt_Key, NTLM_NONCE_LEN);
166    SMBencrypt((unsigned char *)"",challenge,lmencoded_empty_pass);
167    SMBNTencrypt((unsigned char *)"",challenge,ntencoded_empty_pass);
168    return 0;
169}
170
171const char *
172make_challenge(char *domain, char *domain_controller)
173{
174    /* trying to circumvent some strange problem wih pointers in SMBLib */
175    /* Ugly as hell, but the lib is going to be dropped... */
176    strncpy(my_domain, domain, sizeof(my_domain)-1);
177    my_domain[sizeof(my_domain)-1] = '\0';
178    strncpy(my_domain_controller, domain_controller, sizeof(my_domain_controller)-1);
179    my_domain_controller[sizeof(my_domain_controller)-1] = '\0';
180
181    if (init_challenge(my_domain, my_domain_controller) > 0) {
182        return NULL;
183    }
184    ntlm_challenge chal;
185    uint32_t flags = NTLM_REQUEST_NON_NT_SESSION_KEY |
186                     NTLM_CHALLENGE_TARGET_IS_DOMAIN |
187                     NTLM_NEGOTIATE_ALWAYS_SIGN |
188                     NTLM_NEGOTIATE_USE_NTLM |
189                     NTLM_NEGOTIATE_USE_LM |
190                     NTLM_NEGOTIATE_ASCII;
191    ntlm_make_challenge(&chal, my_domain, my_domain_controller, (char *)challenge, NTLM_NONCE_LEN, flags);
192    int len = sizeof(chal) - sizeof(chal.payload) + le16toh(chal.target.maxlen);
193    return base64_encode_bin((char *)&chal, len);
194}
195
196/* returns NULL on failure, or a pointer to
197 * the user's credentials (domain\\username)
198 * upon success. WARNING. It's pointing to static storage.
199 * In case of problem sets as side-effect ntlm_errno to one of the
200 * codes defined in ntlm.h
201 */
202char *
203ntlm_check_auth(ntlm_authenticate * auth, int auth_length)
204{
205    int rv;
206    char pass[MAX_PASSWD_LEN+1];
207    char *domain = credentials;
208    char *user;
209    lstring tmp;
210
211    if (handle == NULL) {   /*if null we aren't connected, but it shouldn't happen */
212        debug("Weird, we've been disconnected\n");
213        ntlm_errno = NTLM_ERR_NOT_CONNECTED;
214        return NULL;
215    }
216
217    /*      debug("fetching domain\n"); */
218    tmp = ntlm_fetch_string(&(auth->hdr), auth_length, &auth->domain, auth->flags);
219    if (tmp.str == NULL || tmp.l == 0) {
220        debug("No domain supplied. Returning no-auth\n");
221        ntlm_errno = NTLM_ERR_LOGON;
222        return NULL;
223    }
224    if (tmp.l > MAX_DOMAIN_LEN) {
225        debug("Domain string exceeds %d bytes, rejecting\n", MAX_DOMAIN_LEN);
226        ntlm_errno = NTLM_ERR_LOGON;
227        return NULL;
228    }
229    memcpy(domain, tmp.str, tmp.l);
230    user = domain + tmp.l;
231    *user = '\0';
232    ++user;
233
234    /*      debug("fetching user name\n"); */
235    tmp = ntlm_fetch_string(&(auth->hdr), auth_length, &auth->user, auth->flags);
236    if (tmp.str == NULL || tmp.l == 0) {
237        debug("No username supplied. Returning no-auth\n");
238        ntlm_errno = NTLM_ERR_LOGON;
239        return NULL;
240    }
241    if (tmp.l > MAX_USERNAME_LEN) {
242        debug("Username string exceeds %d bytes, rejecting\n", MAX_USERNAME_LEN);
243        ntlm_errno = NTLM_ERR_LOGON;
244        return NULL;
245    }
246    memcpy(user, tmp.str, tmp.l);
247    *(user + tmp.l) = '\0';
248
249    // grab the *response blobs. these are fixed length 24 bytes of binary
250    const ntlmhdr *packet = &(auth->hdr);
251    {
252        const strhdr * str = &auth->lmresponse;
253
254        int16_t len = le16toh(str->len);
255        int32_t offset = le32toh(str->offset);
256
257        if (len != ENCODED_PASS_LEN || offset + len > auth_length || offset == 0) {
258            debug("LM response: insane data (pkt-sz: %d, fetch len: %d, offset: %d)\n", auth_length, len, offset);
259            ntlm_errno = NTLM_ERR_LOGON;
260            return NULL;
261        }
262        tmp.str = (char *)packet + offset;
263        tmp.l = len;
264    }
265    if (tmp.l > MAX_PASSWD_LEN) {
266        debug("Password string exceeds %d bytes, rejecting\n", MAX_PASSWD_LEN);
267        ntlm_errno = NTLM_ERR_LOGON;
268        return NULL;
269    }
270
271    /* Authenticating against the NT response doesn't seem to work... in SMB LM helper. */
272    memcpy(pass, tmp.str, tmp.l);
273    pass[min(MAX_PASSWD_LEN,tmp.l)] = '\0';
274
275    debug("Empty LM pass detection: user: '%s', ours:'%s', his: '%s' (length: %d)\n",
276          user,lmencoded_empty_pass,tmp.str,tmp.l);
277    if (memcmp(tmp.str,lmencoded_empty_pass,ENCODED_PASS_LEN)==0) {
278        fprintf(stderr,"Empty LM password supplied for user %s\\%s. "
279                "No-auth\n",domain,user);
280        ntlm_errno=NTLM_ERR_LOGON;
281        return NULL;
282    }
283
284    /* still fetch the NT response and check validity against empty password */
285    {
286        const strhdr * str = &auth->ntresponse;
287        int16_t len = le16toh(str->len);
288        // NT response field may be absent. that is okay.
289        if (len != 0) {
290            int32_t offset = le32toh(str->offset);
291
292            if (len != ENCODED_PASS_LEN || offset + len > auth_length || offset == 0) {
293                debug("NT response: insane data (pkt-sz: %d, fetch len: %d, offset: %d)\n", auth_length, len, offset);
294                ntlm_errno = NTLM_ERR_LOGON;
295                return NULL;
296            }
297            tmp.str = (char *)packet + offset;
298            tmp.l = len;
299
300            debug("Empty NT pass detection: user: '%s', ours:'%s', his: '%s' (length: %d)\n",
301                  user,ntencoded_empty_pass,tmp.str,tmp.l);
302            if (memcmp(tmp.str,lmencoded_empty_pass,ENCODED_PASS_LEN)==0) {
303                fprintf(stderr,"ERROR: Empty NT password supplied for user %s\\%s. No-auth\n", domain, user);
304                ntlm_errno = NTLM_ERR_LOGON;
305                return NULL;
306            }
307        }
308    }
309
310    debug("checking domain: '%s', user: '%s', pass='%s'\n", domain, user, pass);
311
312    rv = SMB_Logon_Server(handle, user, pass, domain, 1);
313    debug("Login attempt had result %d\n", rv);
314
315    if (rv != NTLM_ERR_NONE) {  /* failed */
316        ntlm_errno = rv;
317        return NULL;
318    }
319    *(user - 1) = '\\';     /* hack. Performing, but ugly. */
320
321    debug("credentials: %s\n", credentials);
322    return credentials;
323}
324
325extern "C" void timeout_during_auth(int signum);
326
327static char got_timeout = 0;
328/** signal handler to be invoked when the authentication operation
329 * times out */
330void
331timeout_during_auth(int signum)
332{
333    dc_disconnect();
334}
335
336/*
337 * options:
338 * -b try load-balancing the domain-controllers
339 * -f fail-over to another DC if DC connection fails.
340 *    DEPRECATED and VERBOSELY IGNORED. This is on by default now.
341 * -l last-ditch-mode
342 * domain\controller ...
343 */
344char *my_program_name = NULL;
345
346void
347usage()
348{
349    fprintf(stderr,
350            "%s usage:\n%s [-b] [-f] [-d] [-l] domain\\controller [domain\\controller ...]\n"
351            "-b enables load-balancing among controllers\n"
352            "-f enables failover among controllers (DEPRECATED and always active)\n"
353            "-d enables debugging statements if DEBUG was defined at build-time.\n\n"
354            "You MUST specify at least one Domain Controller.\n"
355            "You can use either \\ or / as separator between the domain name \n"
356            "and the controller name\n",
357            my_program_name, my_program_name);
358}
359
360/* int debug_enabled=0; defined in libcompat */
361
362void
363process_options(int argc, char *argv[])
364{
365    int opt, j, had_error = 0;
366    dc *new_dc = NULL, *last_dc = NULL;
367    while (-1 != (opt = getopt(argc, argv, "bfld"))) {
368        switch (opt) {
369        case 'b':
370            load_balance = 1;
371            break;
372        case 'f':
373            fprintf(stderr,
374                    "WARNING. The -f flag is DEPRECATED and always active.\n");
375            break;
376        case 'd':
377            debug_enabled=1;
378            break;
379        default:
380            fprintf(stderr, "unknown option: -%c. Exiting\n", opt);
381            usage();
382            had_error = 1;
383        }
384    }
385    if (had_error)
386        exit(1);
387    /* Okay, now begin filling controllers up */
388    /* we can avoid memcpy-ing, and just reuse argv[] */
389    for (j = optind; j < argc; ++j) {
390        char *d, *c;
391        /* d will not be freed in case of non-error. Since we don't reconfigure,
392         * it's going to live as long as the process anyways */
393        d = static_cast<char*>(xmalloc(strlen(argv[j]) + 1));
394        strcpy(d, argv[j]);
395        debug("Adding domain-controller %s\n", d);
396        if (NULL == (c = strchr(d, '\\')) && NULL == (c = strchr(d, '/'))) {
397            fprintf(stderr, "Couldn't grok domain-controller %s\n", d);
398            free(d);
399            continue;
400        }
401        /* more than one delimiter is not allowed */
402        if (NULL != strchr(c + 1, '\\') || NULL != strchr(c + 1, '/')) {
403            fprintf(stderr, "Broken domain-controller %s\n", d);
404            free(d);
405            continue;
406        }
407        *c= '\0';
408        ++c;
409        new_dc = static_cast<dc *>(xmalloc(sizeof(dc)));
410        if (!new_dc) {
411            fprintf(stderr, "Malloc error while parsing DC options\n");
412            free(d);
413            continue;
414        }
415        /* capitalize */
416        uc(c);
417        uc(d);
418        ++numcontrollers;
419        new_dc->domain = d;
420        new_dc->controller = c;
421        new_dc->dead = 0;
422        if (controllers == NULL) {  /* first controller */
423            controllers = new_dc;
424            last_dc = new_dc;
425        } else {
426            last_dc->next = new_dc; /* can't be null */
427            last_dc = new_dc;
428        }
429    }
430    if (numcontrollers == 0) {
431        fprintf(stderr, "You must specify at least one domain-controller!\n");
432        usage();
433        exit(1);
434    }
435    last_dc->next = controllers;    /* close the queue, now it's circular */
436}
437
438/**
439 * tries connecting to the domain controllers in the "controllers" ring,
440 * with failover if the adequate option is specified.
441 */
442const char *
443obtain_challenge()
444{
445    int j = 0;
446    const char *ch = NULL;
447    for (j = 0; j < numcontrollers; ++j) {
448        debug("obtain_challenge: selecting %s\\%s (attempt #%d)\n",
449              current_dc->domain, current_dc->controller, j + 1);
450        if (current_dc->dead != 0) {
451            if (time(NULL) - current_dc->dead >= DEAD_DC_RETRY_INTERVAL) {
452                /* mark helper as retry-worthy if it's so. */
453                debug("Reviving DC\n");
454                current_dc->dead = 0;
455            } else {        /* skip it */
456                debug("Skipping it\n");
457                continue;
458            }
459        }
460        /* else branch. Here we KNOW that the DC is fine */
461        debug("attempting challenge retrieval\n");
462        ch = make_challenge(current_dc->domain, current_dc->controller);
463        debug("make_challenge retuned %p\n", ch);
464        if (ch) {
465            debug("Got it\n");
466            return ch;      /* All went OK, returning */
467        }
468        /* Huston, we've got a problem. Take this DC out of the loop */
469        debug("Marking DC as DEAD\n");
470        current_dc->dead = time(NULL);
471        /* Try with the next */
472        debug("moving on to next controller\n");
473        current_dc = current_dc->next;
474    }
475    /* all DCs failed. */
476    return NULL;
477}
478
479void
480manage_request()
481{
482    ntlmhdr *fast_header;
483    char buf[NTLM_BLOB_BUFFER_SIZE];
484    char decoded[NTLM_BLOB_BUFFER_SIZE];
485    const char *ch;
486    char *ch2, *cred = NULL;
487
488    if (fgets(buf, NTLM_BLOB_BUFFER_SIZE, stdin) == NULL) {
489        fprintf(stderr, "fgets() failed! dying..... errno=%d (%s)\n", errno,
490                strerror(errno));
491        exit(1);        /* BIIG buffer */
492    }
493    debug("managing request\n");
494    ch2 = (char*)memchr(buf, '\n', NTLM_BLOB_BUFFER_SIZE);  /* safer against overrun than strchr */
495    if (ch2) {
496        *ch2 = '\0';        /* terminate the string at newline. */
497        ch = ch2;
498    }
499    debug("ntlm authenticator. Got '%s' from Squid\n", buf);
500
501    if (memcmp(buf, "KK ", 3) == 0) {   /* authenticate-request */
502        /* figure out what we got */
503        int decodedLen = base64_decode(decoded, sizeof(decoded), buf+3);
504
505        if ((size_t)decodedLen < sizeof(ntlmhdr)) { /* decoding failure, return error */
506            SEND("NA Packet format error, couldn't base64-decode");
507            return;
508        }
509        /* fast-track-decode request type. */
510        fast_header = (ntlmhdr *) decoded;
511
512        /* sanity-check: it IS a NTLMSSP packet, isn't it? */
513        if (ntlm_validate_packet(fast_header, NTLM_ANY) < 0) {
514            SEND("NA Broken authentication packet");
515            return;
516        }
517        switch (le32toh(fast_header->type)) {
518        case NTLM_NEGOTIATE:
519            SEND("NA Invalid negotiation request received");
520            return;
521        /* notreached */
522        case NTLM_CHALLENGE:
523            SEND("NA Got a challenge. We refuse to have our authority disputed");
524            return;
525        /* notreached */
526        case NTLM_AUTHENTICATE:
527            /* check against the DC */
528            signal(SIGALRM, timeout_during_auth);
529            alarm(30);
530            cred = ntlm_check_auth((ntlm_authenticate *) decoded, decodedLen);
531            alarm(0);
532            signal(SIGALRM, SIG_DFL);
533            if (got_timeout != 0) {
534                fprintf(stderr, "ntlm-auth[%ld]: Timeout during authentication.\n", (long)getpid());
535                SEND("BH Timeout during authentication");
536                got_timeout = 0;
537                return;
538            }
539            if (cred == NULL) {
540                int smblib_err, smb_errorclass, smb_errorcode, nb_error;
541                if (ntlm_errno == NTLM_ERR_LOGON) { /* hackish */
542                    SEND("NA Logon Failure");
543                    return;
544                }
545                /* there was an error. We have two errno's to look at.
546                 * libntlmssp's erno is insufficient, we'll have to look at
547                 * the actual SMB library error codes, to acually figure
548                 * out what's happening. The thing has braindamaged interfacess..*/
549                smblib_err = SMB_Get_Last_Error();
550                smb_errorclass = SMBlib_Error_Class(SMB_Get_Last_SMB_Err());
551                smb_errorcode = SMBlib_Error_Code(SMB_Get_Last_SMB_Err());
552                nb_error = RFCNB_Get_Last_Error();
553                debug("No creds. SMBlib error %d, SMB error class %d, SMB error code %d, NB error %d\n",
554                      smblib_err, smb_errorclass, smb_errorcode, nb_error);
555                /* Should I use smblib_err? Actually it seems I can do as well
556                 * without it.. */
557                if (nb_error != 0) {    /* netbios-level error */
558                    SEND("BH NetBios error!");
559                    fprintf(stderr, "NetBios error code %d (%s)\n", nb_error,
560                            RFCNB_Error_Strings[abs(nb_error)]);
561                    return;
562                }
563                switch (smb_errorclass) {
564                case SMBC_SUCCESS:
565                    debug("Huh? Got a SMB success code but could check auth..");
566                    SEND("NA Authentication failed");
567                    return;
568                case SMBC_ERRDOS:
569                    /*this is the most important one for errors */
570                    debug("DOS error\n");
571                    switch (smb_errorcode) {
572                    /* two categories matter to us: those which could be
573                     * server errors, and those which are auth errors */
574                    case SMBD_noaccess: /* 5 */
575                        SEND("NA Access denied");
576                        return;
577                    case SMBD_badformat:
578                        SEND("NA bad format in authentication packet");
579                        return;
580                    case SMBD_badaccess:
581                        SEND("NA Bad access request");
582                        return;
583                    case SMBD_baddata:
584                        SEND("NA Bad Data");
585                        return;
586                    default:
587                        SEND("BH DOS Error");
588                        return;
589                    }
590                case SMBC_ERRSRV:   /* server errors */
591                    debug("Server error");
592                    switch (smb_errorcode) {
593                    /* mostly same as above */
594                    case SMBV_badpw:
595                        SEND("NA Bad password");
596                        return;
597                    case SMBV_access:
598                        SEND("NA Server access error");
599                        return;
600                    default:
601                        SEND("BH Server Error");
602                        return;
603                    }
604                case SMBC_ERRHRD:   /* hardware errors don't really matter */
605                    SEND("BH Domain Controller Hardware error");
606                    return;
607                case SMBC_ERRCMD:
608                    SEND("BH Domain Controller Command Error");
609                    return;
610                }
611                SEND("BH unknown internal error.");
612                return;
613            }
614
615            lc(cred);       /* let's lowercase them for our convenience */
616            SEND2("AF %s", cred);
617            return;
618        default:
619            SEND("BH unknown authentication packet type");
620            return;
621        }
622        /* notreached */
623        return;
624    }
625    if (memcmp(buf, "YR", 2) == 0) {    /* refresh-request */
626        dc_disconnect();
627        ch = obtain_challenge();
628        /* Robert says we can afford to wait forever. I'll trust him on this
629         * one */
630        while (ch == NULL) {
631            sleep(30);
632            ch = obtain_challenge();
633        }
634        SEND2("TT %s", ch);
635        return;
636    }
637    SEND("BH Helper detected protocol error");
638    return;
639    /********* END ********/
640
641}
642
643int
644main(int argc, char *argv[])
645{
646    debug("%s " VERSION " " SQUID_BUILD_INFO " starting up...\n", argv[0]);
647
648    my_program_name = argv[0];
649    process_options(argc, argv);
650
651    debug("options processed OK\n");
652
653    /* initialize FDescs */
654    setbuf(stdout, NULL);
655    setbuf(stderr, NULL);
656
657    /* select the first domain controller we're going to use */
658    current_dc = controllers;
659    if (load_balance != 0 && numcontrollers > 1) {
660        int n;
661        pid_t pid = getpid();
662        n = pid % numcontrollers;
663        debug("load balancing. Selected controller #%d\n", n);
664        while (n > 0) {
665            current_dc = current_dc->next;
666            --n;
667        }
668    }
669    while (1) {
670        manage_request();
671    }
672    /* notreached */
673    return 0;
674}
675
Note: See TracBrowser for help on using the repository browser.