rlm_otp import from HEAD
authorfcusack <fcusack>
Thu, 8 Dec 2005 01:30:48 +0000 (01:30 +0000)
committerfcusack <fcusack>
Thu, 8 Dec 2005 01:30:48 +0000 (01:30 +0000)
data structures (module_t and vp) still need to be modified for 1.1

39 files changed:
doc/ChangeLog
doc/rlm_otp [new file with mode: 0644]
doc/rlm_x99_token [deleted file]
raddb/Makefile
raddb/otp.conf [moved from raddb/x99.conf with 80% similarity]
raddb/otppasswd.sample [new file with mode: 0644]
raddb/radiusd.conf.in
raddb/x99passwd.sample [deleted file]
src/modules/rlm_otp/Makefile.in [moved from src/modules/rlm_x99_token/Makefile.in with 53% similarity]
src/modules/rlm_otp/cardops/Makefile [new file with mode: 0644]
src/modules/rlm_otp/cardops/cryptocard.c [new file with mode: 0644]
src/modules/rlm_otp/cardops/cryptocard.h [new file with mode: 0644]
src/modules/rlm_otp/configure [new file with mode: 0755]
src/modules/rlm_otp/configure.in [new file with mode: 0644]
src/modules/rlm_otp/crcalc.c [new file with mode: 0644]
src/modules/rlm_otp/otp.h [new file with mode: 0644]
src/modules/rlm_otp/otp_cardops.c [new file with mode: 0644]
src/modules/rlm_otp/otp_cardops.h [new file with mode: 0644]
src/modules/rlm_otp/otp_hotp.c [new file with mode: 0644]
src/modules/rlm_otp/otp_log.c [moved from src/modules/rlm_x99_token/x99_log.c with 67% similarity]
src/modules/rlm_otp/otp_pwe.c [new file with mode: 0644]
src/modules/rlm_otp/otp_pwe.h [moved from src/modules/rlm_x99_token/x99_pwe.h with 75% similarity]
src/modules/rlm_otp/otp_rad.h [new file with mode: 0644]
src/modules/rlm_otp/otp_radstate.c [new file with mode: 0644]
src/modules/rlm_otp/otp_rlm.c [new file with mode: 0644]
src/modules/rlm_otp/otp_site.c [moved from src/modules/rlm_x99_token/x99_site.c with 77% similarity]
src/modules/rlm_otp/otp_state.c [new file with mode: 0644]
src/modules/rlm_otp/otp_state.h [moved from src/modules/rlm_x99_token/x99_sync.h with 51% similarity]
src/modules/rlm_otp/otp_util.c [new file with mode: 0644]
src/modules/rlm_otp/otp_x99.c [new file with mode: 0644]
src/modules/rlm_x99_token/crcalc.c [deleted file]
src/modules/rlm_x99_token/x99.h [deleted file]
src/modules/rlm_x99_token/x99_mac.c [deleted file]
src/modules/rlm_x99_token/x99_pwe.c [deleted file]
src/modules/rlm_x99_token/x99_rad.h [deleted file]
src/modules/rlm_x99_token/x99_state.c [deleted file]
src/modules/rlm_x99_token/x99_sync.c [deleted file]
src/modules/rlm_x99_token/x99_util.c [deleted file]
src/modules/stable

index 074caa1..a46e1d7 100644 (file)
@@ -18,6 +18,7 @@ FreeRADIUS 1.1.0 ; $Date$, urgency=low
          spotted by Konstantin Kubatkin.
        * Removed (for almost all uses) length restrictions on vendor names
          and VALUE names.
+       * rlm_x99_token has become rlm_otp (with lots of changes).
 
 FreeRADIUS 1.0.5 ; Date: 2005/09/04 16:23:00, urgency=medium
        Security Fixes
diff --git a/doc/rlm_otp b/doc/rlm_otp
new file mode 100644 (file)
index 0000000..94ac47b
--- /dev/null
@@ -0,0 +1,454 @@
+0.  INTRODUCTION
+
+    This is the combined README for pam_otp_auth, a PAM module, and
+    rlm_otp, a FreeRADIUS module.  See the COPYRIGHT file, included with
+    this distribution, for copyright and redistribution information.
+    If you have questions not answered in this doc, please contact
+    Frank Cusack, <fcusack@fcusack.com>.  Please send bug reports to
+    the same address.
+
+    FreeRADIUS is available at <http://www.freeradius.org/>.  The PAM
+    module is available at <http://www.fcusack.com/>.
+
+    In addition to this module, you need the state manager software.
+    The state manager primarily handles global (across all of your
+    authentication servers) state associated with synchronous mode
+    tokens (see section 4).  It also handles other bookkeeping data
+    used to prevent passcode guessing attacks.  The state manager
+    is available from <http://www.fcusack.com/>.
+
+
+1.  SUPPORTED TOKENS
+
+    Tokens that use ANSI X9.9 or HOTP (these two cover all tokens made
+    today, except for RSA Securid) can theoretically be authenticated
+    via this module.  In practice, however, only the TRI-D Systems and
+    PassGo/Axent "Defender Handheld" tokens are functional, and due to
+    the weakness of X9.9 (see next section) use of the Defender token
+    should, in a just world, cause you to lose your job.
+
+    Various CRYPTOCard tokens are fully supported, but with the problem
+    that you need to either reverse engineer the token programming
+    protocol, or reverse engineer the keystore encryption.  Both of
+    these are quite possible; I've done it myself and have done a very
+    large CRYPTOCard deployment at my former employer.  However, don't
+    ask me for help with this, your message will simply be trashed.
+
+    ActivCard can theoretically be supported, however you'll need to
+    purchase their dev kit ($$$) due to patents they hold on their
+    specific X9.9 implementation.  For exorbitant fees, I can write the
+    code for you.  Note that you may not redistribute any such code,
+    again due to patent issues.
+
+    Other vendors' tokens are also theoretically supported, with the
+    additional problem that you'll need to reverse engineer their
+    synchronous challenge generation algorithm.  Again, I can help you
+    with this for an exorbitant fee.
+
+    I *strongly* discourage the use of "soft tokens" or PDA tokens.
+    These are easily compromisable, since the key is insufficiently
+    protected.
+
+    Throughout the remainder of this document, wherever applicable I
+    point out differences in the two main tokens supported, TRI-D and
+    CRYPTOCard.
+
+
+2.  STRONG WARNING SECTION
+
+    ANSI X9.9 has been withdrawn as a standard, due to the weakness
+    of DES.  An attacker can learn the token's secret by observing
+    two challenge/response pairs.  See ANSI document X9 TG-24-1999,
+    <http://www.x9.org/docs/TG24_1999.pdf>.
+
+    For X9.9 tokens, the obvious fix is to not issue a challenge; the
+    attacker will not have access to the plaintext.  This is possible
+    since most X9.9 tokens support a synchronous mode; the only exception
+    I know of is the PassGo/Axent Defender Handheld.
+
+    The default configuration of this module effectively disables pure
+    challenge/response (hereafter: async) mode, for this reason.
+
+    In practice, async mode authentication is a poor user experience and
+    is exceedingly rare.  No new token deployments should use async mode.
+
+    Does your token use X9.9?  Ask your vendor.  (Don't ask if they use
+    X9.9, ask what response generation method they use.  If they won't
+    give you an answer, email me and I'll tell you what they use.  Then
+    make sure you don't do business with them.)
+
+    CRYPTOCard uses X9.9; TRI-D uses HOTP.
+
+
+3.  INSTALLATION
+
+    You'll need to have DES and SHA-1 libraries in order to build and
+    use this module.  Currently, only OpenSSL is supported.
+
+    You will also need /dev/urandom available.  This is available on all
+    Linux, *BSD and Solaris 9+.  For Solaris 8, you'll need to install
+    patch 112438-01 (sparc) or 112439-01 (x86).  Information for other
+    OS's is welcome.
+
+    You'll also need to write a site-specific challenge transform in
+    order to use async mode.  For CRYPTOCard, you might need async mode to
+    sync the user's token with the server initially.  More on this below.
+    For TRI-D, async mode is not supported.
+
+
+4.  TOKEN OPERATION
+
+    In the very old days, the server would present a challenge to the
+    user, which the user would then enter into their token, and give the
+    server the response.  We call this async mode.  This is "klunky"
+    by modern standards of usability, and for X9.9 tokens is actually
+    unsafe given that DES is so weak.  As noted above, CRYPTOCard supports
+    async mode; TRI-D does not.
+
+    Luckily, most tokens support a synchronous mode which lets the user
+    skip the part where they enter the challenge.  In this mode, the
+    token and the server generate a "next challenge" which is derived
+    from an event and/or time counter and is implicit.  Besides offering
+    better security, this mode also has the advantage of giving a much
+    better user experience.  Both the TRI-D and CRYPTOCard tokens have a
+    synchronous mode.
+
+    For some tokens, the token can display the synchronous challenge.
+    The idea here is that the server would still present a challenge
+    to the user, but the user wouldn't have to enter it--they'd just
+    have to verify it matches.  Then they can safely just press some
+    function key to obtain the response.  From a security perspective,
+    this is no better than pure async mode, since an attacker can still
+    observe the plaintext/ciphertext pair.
+
+    So when operating in this mixed async-sync mode, instead of presenting
+    the synchronous challenge, the server ALWAYS displays a random
+    challenge.  Instead of verifying that the challenge matches the token
+    display, the user should just skip past the token challenge display
+    to obtain the response.  This might be confusing; you will need to
+    train users.  Even with training, they will forget.  Be warned!
+    This mixed mode is useless and stupid.  If you can disable token
+    support for this, do so.
+
+    For other tokens, the token does not display the synchronous
+    challenge--only the response is displayed.  This is a bit easier on
+    the user; they won't be confused as to which number to enter for the
+    response.  I can't recommend this mode highly enough.  With tokens
+    like this, you should configure the server to likewise not present
+    a challenge (this is the default).  This appears to the user to be
+    close to a normal password authentication.
+
+    Older CRYPTOCard tokens only supported the mixed async-sync mode.
+    Newer ones support both sync modes.  TRI-D supports only the "pure"
+    sync mode.
+
+    It's worth repeating that async mode is vastly inferior to either
+    sync mode, and the mixed async-sync mode is vastly inferior to the
+    pure sync mode.  In addition to the shielding of the plaintext,
+    and ease of use, another advantage of sync mode is that it supports
+    authentication methods where a challenge cannot be presented to the
+    user, e.g. PPTP without EAP.
+
+    In sync mode, there are two ways to generate the implied challenge;
+    either event or time based.  "Events" are token operations--each
+    time the token is activated an event counter advances.
+
+    CRYPTOCard is event synchronous; TRI-D is both time and event
+    synchronous.
+
+    Event synchronous tokens have the problem that if users play with
+    the token as a toy (say, to generate winning lottery numbers),
+    the server has no way to know this and so it has a different idea
+    of the counter value.  Since there are typically only 1-10 million
+    passcodes (6-7 digit decimal display), the server cannot simply test
+    "many" passcodes in an attempt to discover the event counter value,
+    because a guessing attack is trivial with such a small response space.
+    Our solution for this is noted in section 6, below.
+
+    Time synchronous tokens solve this problem quite nicely by eliminating
+    the user from the equation.  As PEBKAC is generally the worst kind
+    of problem, and most difficult to solve, this is clearly better than
+    event synchronous.  However, it is not without its own problems.
+    First, a real time clock must be on the token, which today is not
+    a technical hurdle, but it is an added expense.  To keep costs low,
+    the clock on the token keeps poor time, so the server has to track
+    drift.  Also, the token is typically exposed to adverse environmental
+    conditions, which (especially in such a small and necessarily cheap
+    package) affects the clock and so the drift is not constant.
+
+    But even varying clock drift is not especially difficult to handle on
+    the server.  A worse problem is that the timer interval (normally one
+    minute) also limits login rate.  Even "normal" users commonly want
+    to login more frequently than this.  Making users wait one minute to
+    login again is practically forever.  TRI-D addresses this with the
+    activation button on the token.  Each time it is pressed an event
+    counter is combined with the time counter to generate a new passcode.
+    The event counter is reset whenever the time counter advances.
+
+
+5.  SITE-SPECIFIC CHALLENGE TRANSFORM
+
+    Since the normal mode of operation will be sync mode, we really only
+    have async mode support for "last resort" user resync of the event
+    counter.  (For "normal" resync see the rwindow description
+    in section 6.)
+
+    Note that only some tokens support "user" sync/resync.  For others,
+    admin intervention is required for resync.  CRYPTOCard supports
+    this; TRI-D does not (since it is time-based, there is no resync).
+
+    Since pure challenge/response with X9.9 is unsafe, I came up with the
+    concept of the "site-specific challenge transform".  For the user,
+    this means that instead of entering the challenge as presented to
+    them, they enter something based on the challenge.  For example,
+    a simple transform would be to enter the challenge backwards; if
+    the server presents "123456" the user enters "654321".  This has
+    the effect that an observer does not have access to the plaintext.
+
+    This is security through obscurity, and is not really "safe", but
+    for an outsider it may present at least some barrier.  Even though
+    it presents no advantage in the face of a determined attacker,
+    I recommend using it.  It may stop a more opportunistic attacker
+    and isn't difficult to use.
+
+    The server logs each time a user authenticates via async mode,
+    so I recommend a log scanner which alerts you to this.  You should
+    reprogram tokens when the user authenticates via async mode.
+
+    otp_site.c implements the site-specific challenge transform.
+    The default transform is to replace the challenge with the text
+    "DISABLED".  This effectively disables async mode (the user will
+    not be able to enter this into their token).
+
+    DO NOT use the transform suggested above, reversing the challenge.
+    That is now exceptionally weak.  An example of a possibly strong
+    transform is to have the user enter the square of the challenge.
+    The VASCO DigiPass 500 is also a [regular] calculator, so this could
+    be a good one if you use that token.  Well, there's no support
+    for that token, and now that I've mentioned it, it is another
+    exceptionally weak transform, but you get the idea.
+
+    Note that older CRYPTOCard RB-1 tokens support arbitrarily
+    long challenge strings.  You should take advantage of this when
+    implementing your transform.  You will still have to stay under
+    MAX_CHALLENGE_LEN digits.  (This is why MAX_CHALLENGE_LEN is set to 32
+    even though the displayed challenge would generally be much smaller.)
+
+    If you do not believe applying a transform gives any advantage, you
+    can just comment out the single line of code there.  This actually
+    may have some benefit, since your users don't need to be trained.
+    I can guarantee your most annoying user will complain when they
+    can't remember what they really are supposed to enter into the token.
+    Also, this can be safe if you diligently reprogram tokens when async
+    mode has been used.  You might automatically disable a token after
+    two async authentications.
+
+
+6.  CONFIGURATION
+
+    Most of the configuration is documented fairly well in the sample
+    otp.conf file (FreeRADIUS) or man page (PAM).  I will only discuss
+    a few options here.
+
+    softfail/hardfail:
+        After hardfail consecutive failed login attempts, the user's
+        token is disabled.  Because this allows a trivial DoS attack,
+        the default value is 0, and instead we recommend using softfail.
+
+        After softfail consecutive failed login attempts, the user is put
+        into "delay mode", where they are unable to login for a delay which
+        increases for each failed attempt.
+
+        It is critically important to have these options since the
+        passcode (response) space is so small.  Without a delay/lockout,
+        it would be trivially easy for an attacker to just try every
+        possible passcode.  With the default softfail setting of 5, an
+        attacker could try, at most, ~50 passcodes/day.  No indication
+        is given to the user that they are in delay mode (except that
+        a valid passcode doesn't work), further thwarting an attacker,
+        albeit at some small cost to the legitimate user.
+
+    prepend_pin:
+        Some tokens have what we call a "hard PIN"; users enter a PIN into
+        the token to activate it.  This has the advantage that only the
+        user knows the PIN, and that it is only entered into a secure
+        device, however, it has [token] UI challenges.
+
+        For usability reasons, other tokens have a constantly active
+        display and the user enters a "soft PIN" as part of the passcode.
+        This has the advantage of a better UI, but has the disadvantages
+        that the PIN is susceptible to capture, which can reduce the
+        token to a single factor device; and that the server admins know
+        the PIN.  (Note that it doesn't matter for hard PIN devices that
+        admins don't know the PIN, since they know the token secret;
+        the loss incurred by admin exposure is not for security of the
+        device, but compromise of personal information.)
+
+        The prepend_pin setting toggles whether the user must prepend or
+        append the soft PIN; the default is to prepend.  Note that hard
+        PIN devices can utilize a soft PIN as well.
+
+        CRYPTOCard supports a hard PIN; the biometric input on the TRI-D
+        3-factor card can is roughly equivalent to a hard PIN.
+
+    ewindow_size: (event window)
+        For event-synchronous-only tokens (CRYPTOCard), this is how far
+        out of [event] sync the server can get with the token.  The value
+        is how far the user can be ahead of the server--essentially
+        how many times the user can play with the token.  You'll want
+        to set this to at least 1 or 2, in case the user mistypes the
+        response and the token turns off before he is able to try again.
+        A more reasonable value is 5.
+
+        For event+time synchronous tokens (TRI-D), this value has no
+        meaning; the server determines how many events to test based on
+        card capabilities.
+
+        This value is ignored for time-synchronous-only tokens.
+
+        Note that there is no analogous twindow_size setting; for
+        time synchronous (event+time or time only) tokens, the server
+        determines how far forward or backward to look based on card
+        characteristics.
+
+    rwindow_size/rwindow_delay: (resync window)
+        This is similar to ewindow_size.  For event-synchronous-only
+        tokens (CRYPTOCard), when the user goes into delay mode (>softfail
+        consecutive incorrect passcodes), this extends the allowable
+        event window, but requires the user to enter TWO consecutive sync
+        responses corrrectly, within rwindow_delay seconds.  The upside
+        of having to enter 2 passcodes is that the delay is overridden.
+
+        In practice, users that do have problems with the allowable
+        event window (and those users tend to have them consistently)
+        get into long lockout delays and since no indication is given
+        to the user about this state, they need a way to get past it
+        without calling the helpdesk.
+
+        For example, say softfail=1, ewindow_size=2 and rwindow_size=8
+        (ignore rwindow_delay).  The server's state is such that the
+        next 8 responses are 1, 2, ..., 8.  The user, however, has played
+        with the token and the response showing is '3', which he enters
+        as the passcode.
+
+        This is ahead of ewindow_size, so the server refuses him,
+        and places the user into delay mode, since softfail is only 1.
+        Note that even though this response is within rwindow_size events,
+        it is not recorded as such because when checking the passcode,
+        the user was /not yet/ in delay mode and so only ewindow_size
+        events were considered.  /AFTER/ testing the passcode, the user
+        is /THEN/ placed into delay mode.
+
+        The user tries again immediately, using '3' again.  Since the
+        user /is now/ in delay mode, the server would normally refuse
+        him (remember, we said he tried again "immediately").  Even if
+        the user weren't in delay mode (say, softfail is larger), the
+        server would still refuse him because he is too far ahead of
+        the normal ewindow_size window.
+
+        But since he is in delay mode, and rwindow_size is non-zero,
+        instead of simply rejecting responses beyond ewindow_size
+        events, the server looks ahead up to rwindow_size (8 in this
+        case) events.  It sees that '3' is within rwindow_size events,
+        records that the user gave a correct sync response at position 3,
+        and returns failure.
+
+        Now the user tries again immediately, this time using the next
+        response of '4'.  Again, normally this would be refused since
+        the user is in delay mode.  But because rwindow_size is set,
+        the server sees that '4' is within the rwindow_size window,
+        and that the user's previous response ('3') matches the previous
+        response in the window, so the user is authenticated and returned
+        to normal mode.
+
+        Note that the user actually entered 3,3,4 and although the user
+        entered 3 correct passcodes, only the last 2 were consecutive so
+        this seems to match the description of this feature.  However,
+        if the user had entered 3,4,5 he still would have had to enter
+        3 passcodes!  Review the example to understand why.
+
+        In practice, users generally enter a lot of bad passcodes to get
+        into softfail and then finally see what they're doing wrong and
+        so they do only enter 2 correct passcodes, ie if they are even
+        aware of this feature they don't get confused about why they
+        had to enter the '5' part of 3,4,5.
+
+        It is recommended that you tell users to /always/ advance to
+        the next passcode on error, and that they should always try at
+        least 3 (or 4) consecutive entries before calling the helpdesk.
+
+        The Windows VPN password error dialog is confusing and is a
+        major source of duplicate entries, which add an extra passcode
+        entry to rwindow mode.  Another significant source of passcode
+        errors is PC laptop users that have a docking station with
+        keyboard.  Windows keeps the numlock setting when undocking,
+        and my experience is that one of the first things that folks do
+        after undocking is to VPN in.  The '0' key on the number row
+        is a '.' instead of a '0' when numlock is on.  And since the
+        Windows VPN dialog can't know that it's safe to display the
+        passcode, the user can't tell that he's misentering zeroes.
+        This encourages getting out of sync.  Ouch.
+
+        For time synchronous tokens (event+time or time only), the
+        rwindow_size value has no meaning as there is no event counter
+        to lose track of.  (Clock drift affecting the time counter is
+        tracked by the server.)
+
+        However, the rwindow_delay value does have meaning.  If a user
+        goes into softfail (maybe by repeatedly trying their longterm
+        password or by a password guessing attack), they can still get
+        out of delay mode by entering two consecutive passcodes within
+        rwindow_delay seconds.
+
+        Also, for TRI-D tokens, rwindow_delay has an additional meaning.
+        You'll need to read the state manager documentation to understand
+        this, but the TRI-D token supports "null state" meaning that
+        the admin does not have to (and in fact must not) manually
+        initialize state when issuing a token.  State is automatically
+        initialized when a user first authenticates, however, the user
+        must authenticate twice, which uses the softfail mechanism and
+        thus depends on rwindow_delay.  It's not quite softfail because
+        the user cannot simply wait for the delay period to expire and
+        then authenticate only once.
+
+
+7.  FILES
+
+    /etc/otppasswd, a file similar to /etc/passwd, contains usernames
+    and keys.  See the sample otppasswd file.
+
+
+8.  LOG MESSAGES
+
+    All errors begin with "rlm_otp" (FreeRADIUS) or "pam_otp_auth"
+    (PAM).  Only errors are logged, there are no "success" log messages
+    (besides FreeRADIUS/PAM standard messages).  You will want to scan
+    for errors automatically or periodically.
+
+    "bad state" messages (FreeRADIUS) indicate a problem with the State
+    attribute, which the server uses to track async challenges.  They are
+    all of the form "bad state for [%s]: <problem>", where <problem>
+    is one of:
+
+    length:  The length is not as expected.  Could be an attempted attack,
+             but more likely a network blip.
+    hmac:    The state is protected by a cryptographic hash which was not
+             able to be verified.  This could be because you just HUP'd
+             the server.
+    expired: The state is older than maxdelay seconds.  If you get a lot
+             of these you may wish to increase the value.
+
+    Another set of messages you'll want to lookout for is "valid but in
+    hardfail" and "valid but in softfail", which indicate a user that is
+    locked out due to exceeding hardfail or softfail failures.
+
+    Also, look for "[%s] authenticated in async mode" which indicates
+    a user with a sync mode card that used async authentication.  You
+    may wish to reprogram these users' cards.
+
+
+9.  BUGS
+
+    Send bug reports or any other questions to Frank Cusack,
+    <fcusack@fcusack.com>.
+
diff --git a/doc/rlm_x99_token b/doc/rlm_x99_token
deleted file mode 100644 (file)
index fabb5ed..0000000
+++ /dev/null
@@ -1,325 +0,0 @@
-This is the combined README for pam_x99_auth, a PAM module, and
-rlm_x99_token, a FreeRADIUS module.  This module supports ANSI X9.9
-token authentication.  If you have questions not answered in this doc,
-contact Frank Cusack, <frank@google.com>.  Please send bug reports
-to the same address.
-
-This software is Copyright (c) 2002 Google, Inc.
-All Rights Reserved.
-
-See the COPYRIGHT file, included with this distribution, for copyright
-and redistribution information.
-
-
-0.  INTRODUCTION
-
-    This module allows authentication of users who hold ANSI X9.9
-    challenge/response tokens.  These tokens are basically handheld
-    DES calculators and are sold by various vendors.  Currently known
-    vendors are:
-
-       CRYPTOCard (RB-1, KT-1)
-       <http://www.cryptocard.com/>
-       Full support.
-
-       Secure Computing (SafeWord (various models))
-       <http://www.securecomputing.com/index.cfm?sKey=688>
-       Async support only.
-       Formerly Enigma Logic.
-
-       VASCO (DigiPass (various models))
-       <http://www.vasco.com/R3DEngine.asp?reference=03-01.01-01&lang=en>
-       Not supported.
-
-       ActivCard (ActivCard One)
-       <http://www.activcard.com/>
-       Not supported (patented sync mode).
-
-       PassGo (Defender aka SNK, Defender Plus, Defender One)
-       <http://www.passgo.com/products/defender/>
-       Fully supported.
-       This product has a history of acquisition:
-       PassGo, Symantec, AXENT, AssureNet Pathways (name change only),
-       Digital Pathways.  Also, to add to the fun, PassGo was acquired
-       by AXENT, whom Symantec then acquired, and later forked off.  The
-       product was originally named "SecureNet Key" aka SNK or SNK/4.
-
-    If you know of other vendors, please email me (frank@google.com)
-    and I'll add them to the list above.
-
-    Although ActivCard has full specs, I cannot add support to the public
-    codebase because their synchronous algorithm is patented.  Oh, well,
-    their loss.  I can, however, add code which you may not distribute.
-    You will need to obtain their development kit and sign an NDA.  Async
-    mode is not supported for ActivCard because you need the dev. kit (+NDA)
-    to extract or program the keys.
-
-    The PassGo Defender One is an OEM version of the ActivCard One and
-    so it suffers from the same patent issues.  I believe the Defender Plus
-    also uses the same algorithm.
-
-    No support is available for programming tokens.  You will need to
-    write this yourself, or use the vendor's programming tools and extract
-    the key information in order to use this module.
-
-    I *strongly* discourage the use of "soft tokens" or Palm tokens.  These
-    are easily compromisable, since the key is likely to be insufficiently
-    protected.
-
-    This module is available for FreeRADIUS and PAM.  FreeRADIUS is
-    available at <http://www.freeradius.org/>, the PAM module is available
-    at <http://www.fcusack.com/>.
-
-
-1.  STRONG WARNING SECTION
-
-    ANSI X9.9 has been withdrawn as a standard, due to the weakness of DES.
-    An attacker can learn the token's secret by observing two
-    challenge/response pairs.  See ANSI document X9 TG-24-1999,
-    <http://www.x9.org/TG24_1999.pdf>.
-
-    The obvious fix is to not display the challenge; the attacker will
-    not have access to the plaintext.  This is possible since most X9.9
-    tokens support a synchronous mode; the only exception I know of is
-    the PassGo Defender.  So, in synchronous modes, the challenge presented
-    to the user is NOT the challenge used for response calculation.
-    Read on for more info.
-
-    The default configuration of this module effectively disables pure
-    challenge/response (hereafter: async) mode.
-
-
-2.  INSTALLATION
-
-    You'll need to have a DES library in order to build and use this module.
-    Currently, only openssl is supported.
-
-    You will also need /dev/urandom available.  This is available on all
-    Linux, *BSD and Solaris 9.  For Solaris 8, you'll need to install
-    patch 112438-01 (sparc) or 112439-01 (x86).  Information for other OS's
-    is welcome.
-
-    You'll also need to write a site-specific challenge transform in order
-    to use async mode.  You might need async mode to sync the user's token
-    with the server initially.  More on this below.
-
-3.  END-USER OPERATION
-
-    "Normally", upon login, users would enter enter the challenge into
-    their token and give the server the response.  However, this is unsafe
-    given that DES is so weak.  Luckily, most tokens support a synchronous
-    mode which lets the user skip the part where they enter the challenge.
-
-    For some tokens, the token displays the synchronous challenge, which
-    typically the user would verify is the same as the challenge presented
-    by the server.  Then they can safely just press "ENT" and enter the
-    response.  This is very easy to use, but we're still stuck with the
-    problem that an attacker has observed a plaintext/ciphertext pair.
-
-    So instead of presenting the synchronous challenge, the server ALWAYS
-    displays a random challenge.  Instead of verifying that the challenge
-    matches the token display, the user should just press "ENT" and enter
-    the response.  This might be confusing, you will need to train users.
-    Even with training, they will forget.  Be warned!
-
-    For other tokens, the token does not display the synchronous
-    challenge--only the response is displayed.  This is a bit easier on
-    the user, they won't be confused as to which number to enter for the
-    response.  Since the token's challenge display really just serves
-    to verify the sync state, and we don't present that information,
-    I recommend operating tokens in the no-challenge-display mode if
-    possible.
-
-    In addition to the shielding of the plaintext, and ease of use,
-    another advantage of sync mode is to support authentication methods
-    where a challenge cannot be presented to the user, e.g. when using
-    the Microsoft Windows VPN client.
-    
-
-4.  SITE-SPECIFIC CHALLENGE TRANSFORM
-
-    Even though the normal mode of operation will be sync mode,
-    we want async mode support for (at least) two reasons:
-
-        1) to sync/resync the token, and
-        2) because state is not shared across multiple RADIUS/PAM servers.
-
-    Note that only some tokens support "user" sync/resync.  For others,
-    an admin must create the initial state and manual intervention is
-    required for resync.
-
-    Since pure challenge/response is unsafe, I came up with the concept
-    of the "site-specific challenge transform".  For the user, this
-    means that instead of entering the challenge as presented to them,
-    they enter something based on the challenge.  For example, a simple
-    transform would be to enter the challenge backwards; if the server
-    presents "123456" the user enters "654321".  This has the effect
-    that an observer does not have access to the plaintext.
-
-    This is security through obscurity, and is not really "safe", but
-    for an outsider it may present at least some barrier.  Even though
-    it presents no advantage in the face of a determined attacker, I
-    recommend using it.
-
-    The server logs each time a user authenticates via async mode, so
-    I recommend a log scanner which alerts you to this.  You should
-    reprogram tokens when the user authenticates via async mode.
-
-    x99_site.c implements the site-specific challenge transform.  The
-    default transform is to replace the challenge with the text "DISABLED".
-    This effectively disables async mode (the user will not be able to
-    enter this into their token).
-
-    DO NOT use the transform suggested above, reversing the challenge.
-    That is now exceptionally weak.  An example of a possibly strong
-    transform is to have the user enter the square of the challenge.
-    The VASCO DigiPass 500 is also a [regular] calculator, so this
-    could be a good one if you use that token.  Well, there's no support
-    for that token, and now that I've mentioned it, it is another
-    exceptionally weak transform, but you get the idea.
-
-    Note that older CRYPTOCard RB-1 tokens support arbitrarily long
-    challenge strings.  You should take advantage of this when implementing
-    your transform.  You will still have to stay under MAX_CHALLENGE_LEN
-    digits.  (This is why MAX_CHALLENGE_LEN is set to 32 even though
-    the displayed challenge would generally be much smaller.)
-
-    If you do not believe applying a transform gives any advantage,
-    you can just comment out the single line of code there.  This actually
-    may have some benefit, since your users don't need to be trained.
-    I can guarantee your most annoying user will complain when they can't
-    remember what they really are supposed to enter into the token.
-    Also, this can be safe if you diligently reprogram tokens when async
-    mode has been used.  You might automatically disable a token after two
-    async authentications.
-
-
-5.  CONFIGURATION
-
-    Most of the configuration is documented fairly well in the sample
-    x99.conf file (FreeRADIUS) or man page (PAM).  I will only discuss
-    a few options here.
-
-    softfail/hardfail:  See x99.conf (FreeRADIUS) or the man page (PAM)
-    for how these work.  It is critically important to have these since
-    the password (response) space is so small.  Without a delay/lockout,
-    it would be trivially easy for an attacker to just try every
-    possible password.  With the default softfail setting of 5, an
-    attacker could try, at most, ~50 passwords/day.  No indication is
-    given to the user that they are in "delay mode" (except that a valid
-    password doesn't work).
-
-    ewindow_size:  This is how far out of [event] sync the server can
-    get with the token.  The value is how far the user can be ahead of
-    the server -- essentially how many times the user can play with the
-    token.  You'll want to set this to at least 1 or 2, in case the user
-    mistypes the response and the token turns off before he is able to
-    try again.  It's 'e'window because I am reserving twindow for
-    time synchronous modes.
-
-    ewindow2_size:  This is similar to ewindow_size.  In "delay mode",
-    users' responses are normally /not/ checked during the delay period.
-    However, if ewindow2_size is non-zero, the response /is/ checked up to
-    ewindow2_size events/counts.  If the user gets two consecutive sync
-    responses correct within ewindow2_delay seconds, he is authenticated.
-
-       For example, say softfail=1, ewindow_size=2 and ewindow2_size=8.
-       The server's state is such that the next 8 responses are
-       1, 2, ..., 8.  The user, however, has played with the token and
-       the response showing is '3'.
-
-       This is ahead of ewindow_size, so the server refuses them (and
-       records the fact the last response was '3').
-
-       The user tries again immediately, using '3' again.  Since the
-       user is now in "delay mode" (because softfail is only 1),
-       the server would normally refuse them (remember, we said they
-       tried again "immediately").  Even if the user weren't in delay
-       mode, the server would still refuse them because they are too
-       far ahead of the window.
-
-       But since ewindow2_size is non-zero, instead the server looks
-       ahead up to 8 responses.  It stops at 3 and since the previous
-       response was not '2', it refuses the user, but records that the
-       last response was '3'.
-
-       Now the user tries again immediately, this time using the next
-       response of '4'.  Again, normally this would be refused since
-       the user is in delay mode.  But because ewindow2_size is set,
-       the server will check up to 8 responses and since '4' is within
-       the window, and the user's previous response ('3') matches the
-       previous response in the window, the user is authenticated.
-
-       * If an attacker knows a response that is ahead of the window,
-         they can launch a simple attack by just guessing in pairs:
-         <known_response>, <guess>.  However, they could also simply
-         just wait until the user advances the window so that their
-         known response becomes valid, so this shouldn't be an issue.
-         Nevertheless, you may wish to set hardfail when using
-         ewindow2_size (but keep in mind the trivial DoS with hardfail).
-
-       * Setting ewindow2_size to ANY value increases CPU usage.
-         Without it set, when a user is in softfail the server returns
-         failure without checking response values.  With ewindow2_size
-         set, the server now does check response values; and you
-         normally want ewindow2_size to be somewhat large (compared to
-         ewindow_size), so the number of values checked is GREATER when
-         the softfail-imposed delay mode is in effect.
-
-       * Setting ewindow2_size to LARGE values may increase potential
-         for abuse via DoS.  I  have not performed any server sizing
-         exercises, but I expect that for most installations any modern
-         hardware is fast enough for reasonable values.
-
-    radiusd.conf:  x99_token must be listed in both the authorize and
-    authenticate stanzas.  In the authorize code, x99_token will set
-    the Auth-Type to x99_token (ie, itself) if the Auth-Type attribute
-    isn't already present.  You can use this to selectively authenticate
-    users via a token.  Any examples I could give here would be poor,
-    and subject to other modules' [changing] operations, so it's probably
-    best to direct any questions to <freeradius-users@lists.cistron.nl>.
-
-6.  FILES
-
-    See the sample x99passwd file.  State files are stored in
-    /etc/x99sync.d by default.  There is one state file per user.
-    The state file contains the information needed for synchronous
-    mode; also the number of consecutive failed logins and the last
-    time the user authenticated via async mode is stored here.
-
-
-7.  LOG MESSAGES
-
-    All errors begin with "rlm_x99_token" (FreeRADIUS) or "pam_x99_auth"
-    (PAM).  Only errors are logged, there are no "success" log messages.
-    You will want to scan for these automatically or periodically.
-
-    "bad state" messages (FreeRADIUS) indicate a problem with the State
-    attribute, which the server uses to track challenges (for async mode).
-    They are all of the form "bad state for [%s]: <problem>",
-    where <problem> is one of:
-
-    length:  The length is not as expected.  Could be an attempted attack,
-             but more likely a network blip.
-    hmac:    The state is protected by a cryptographic hash which was not
-             able to be verified.  This could be because you just HUP'd
-             the server.
-    expired: The state is older than maxdelay seconds.  If you get a lot
-             of these you may wish to increase the value.
-    missing: This should never happen and indicates a bug.
-
-    Another message you'll want to lookout for is
-    "%d/%d failed/max authentications" which indicates a user that is
-    locked out due to exceeding hardfail/softfail failures.  You can reset
-    this user by editing the state file (see x99passwd.sample).
-
-    Also, look for "[%s] authenticated in async mode" which indicates
-    a user with a sync mode card that used async authentication.  You
-    may wish to reprogram these users' cards.
-
-
-8.  BUGS
-
-    Send bug reports or any other questions to Frank Cusack,
-    <frank@google.com>.
-
index 811f156..c6d7961 100644 (file)
@@ -7,7 +7,7 @@ FILES   = acct_users attrs clients clients.conf dictionary eap.conf \
          experimental.conf hints huntgroups ldap.attrmap \
          mssql.conf naslist naspasswd oraclesql.conf postgresql.conf \
          preproxy_users proxy.conf radiusd.conf realms snmp.conf \
-         sql.conf users x99.conf x99passwd.sample
+         sql.conf users otp.conf otppasswd.sample
 
 all:
 
similarity index 80%
rename from raddb/x99.conf
rename to raddb/otp.conf
index 0f66e53..5481fd1 100644 (file)
@@ -1,18 +1,21 @@
 #
-#  Configuration for the ANSI X9.9 module.
+#  Configuration for the OTP module.
 #
 
-#  This module allows you to use ANSI X9.9 compliant/compatible
-#  challenge/response tokens for authentication
-#  (Auth-Type := x99_token).  These tokens are available from
-#  various vendors.
+#  This module allows you to use various handheld OTP tokens
+#  for authentication (Auth-Type := otp).  These tokens are
+#  available from various vendors.
 #
 #  WARNING  WARNING  WARNING  WARNING  WARNING  WARNING  WARNING
 #
+#  Many tokens use ANSI X9.9 to generate passcodes.  Ask your
+#  vendor what algorithm they use.  If they won't tell you, don't
+#  buy their product!
+#
 #  ANSI X9.9 has been withdrawn as a standard, due to the weakness
 #  of DES.  An attacker can learn the token's secret by observing
 #  two challenge/response pairs.  See ANSI document X9 TG-24-1999
-#  <URL:http://www.x9.org/TG24_1999.pdf>.
+#  <URL:http://www.x9.org/docs/TG24_1999.pdf>.
 #
 #  The obvious fix is to not display the challenge; the attacker
 #  will not have access to the plaintext.  This is possible since
 #  sections in order to use it.  Challenges are generated by the
 #  authorization code, and responses verified by the authentication code.
 #  This is just "how freeradius works".
-x99_token {
+otp {
        # File containing user:card_type:key entries.
-       # See x99passwd.sample for examples of legal entries.
+       # See otppasswd.sample for examples of legal entries.
        # This file must be mode 0400 or 0600, and owned by the user
        # radiusd runs as.
-       # (default: /etc/x99passwd)
-       #pwdfile = /etc/x99passwd
+       # (default: /etc/otppasswd)
+       #pwdfile = /etc/otppasswd
 
-       # Directory containing sync mode and state info.
-       # This directory must be mode 0700, and owned by the
-       # the user radiusd runs as.
-       # (default: /etc/x99sync.d)
-       #syncdir = /etc/x99sync.d
+       # State manager rendezvous point.
+       # (default: /var/run/lsmd/socket)
+       #lsmd_rp = /var/run/lsmd/socket
 
        # Text to use for the challenge.  The '%' character is
        # disallowed, except that you MUST have a single "%s"
@@ -52,7 +53,8 @@ x99_token {
        # Maximum time, in seconds, that a challenge is valid.
        # (The user must respond to a challenge within this time.)
        # It is also the minimal time between consecutive async mode
-       # authentications.  This is used to prevent replay attacks.
+       # authentications, a necessary restriction due to an inherent
+       # weakness of the RADIUS protocol which allows replay attacks.
        # (default: 30)
        #challenge_delay = 30
 
@@ -84,7 +86,7 @@ x99_token {
        # across servers.  It's not just the case that a user will
        # be out of sync, the user (or an attacker!) will be able
        # to reuse a password.  This setting allows you to copy
-       # your /etc/x99passwd file to all servers without changing
+       # your /etc/otppasswd file to all servers without changing
        # the card_type definition. (default: yes)
        #allow_sync = yes
 
@@ -124,6 +126,11 @@ x99_token {
        # (default: "resync")
        #resync_req = "resync"
 
+       # Whether the soft PIN (see accompanying docs) is prepended (yes)
+       # or appended (no) to the passcode.
+       # (default: yes)
+       #prepend_pin = yes
+
        # Tokens that are event synchronous can easily lose sync with
        # the server, eg if the user plays with the token they will
        # increment the token's event counter, leaving the server
@@ -135,24 +142,21 @@ x99_token {
        # accompanying docs for more info.  (max: 10, default: 0)
        #ewindow_size = 0
 
-       # If ewindow2_size and ewindow2_delay are both non-zero, two
+       # If rwindow_size and rwindow_delay are both non-zero, two
        # consecutive correct sync passwords within the specified window,
-       # and within ewindow2_delay seconds of each other, will override
+       # and within rwindow_delay seconds of each other, will override
        # the "delay mode" forced by the softfail option and the user
-       # will be authenticated.  It does not make sense for ewindow2_size
+       # will be authenticated.  It does not make sense for rwindow_size
        # to be less than ewindow_size.  Setting this value very high can
        # encourage/aggravate DoS (CPU %util).
-       # (default ewindow2_size: 0)
-       # (default ewindow2_delay: 60)
-       #ewindow2_size = 0
-       #ewindow2_delay = 60
-
-       # The following are MS-CHAP/MPPE items.  They don't properly
-       # belong in x99_token's configuration, but for the time being
-       # x99_token does it's own CHAP/MS-CHAP/MS-CHAPv2; the freeradius
-       # core doesn't support the testing of multiple passwords which
-       # the event window (ewindow_size, above) requires.  Hopefully,
-       # this will change sometime.  Note that MS-CHAP (v1) is strongly
+       # (default rwindow_size: 0)
+       # (default rwindow_delay: 60)
+       #rwindow_size = 0
+       #rwindow_delay = 60
+
+       # The following are MPPE settings.  The otp module must be aware
+        # of these (at least for now), because we can't properly offload
+        # this to the mschap module.  Note that MS-CHAP (v1) is strongly
        # discouraged and does not build by default.  All possible values
        # are listed as {value = meaning}.  Default values are first.
        #mschapv2_mppe = {2 = required, 1 = optional, 0 = forbidden}
diff --git a/raddb/otppasswd.sample b/raddb/otppasswd.sample
new file mode 100644 (file)
index 0000000..c7dfa33
--- /dev/null
@@ -0,0 +1,33 @@
+This is a sample "passwd" file used for OTP token authentication.
+Actually, it's not a sample file, but it does document the format and
+legal values.  The default location is /etc/otppasswd.
+
+This file must be mode 0400 or 0600 and owned by the user the radius
+server runs as (for FreeRADIUS) or root (for PAM).
+
+The format is username:card_type:key[:pin], eg
+
+bob:cryptocard-d8-es:0101010101010101
+
+The username is limited in that the ':' character may not appear.
+The pin is optional (do not include the bracket characters!).
+The valid card types are:
+
+CRYPTOCard:
+cryptocard-h8-rc       random challenge, 8 digit hex response.
+cryptocard-d8-rc       random challenge, 8 digit decimal response.
+cryptocard-h7-rc       random challenge, 7 digit hex response.
+cryptocard-d7-rc       random challenge, 7 digit dec response.
+cryptocard-h8-es       event synchronous (only), 8 digit hex response.
+cryptocard-d8-es       event synchronous (only), 8 digit decimal response.
+cryptocard-h7-es       event synchronous (only), 7 digit hex response.
+cryptocard-d7-es       event synchronous (only), 7 digit decimal response.
+cryptocard-h8-rs       rc or es, 8 digit hex response.
+cryptocard-d8-rs       rc or es, 8 digit decimal response.
+cryptocard-h7-rs       rc or es, 7 digit hex response.
+cryptocard-d7-rs       rc or es, 7 digit decimal response.
+
+TRI-D: (email fcusack@fcusack.com)
+trid-alpha-3           TRI-D alpha card
+trid-beta-1            TRI-D beta card
+trid-beta-2            TRI-D beta card
index ff90dd8..75afdfc 100644 (file)
@@ -1035,6 +1035,7 @@ $INCLUDE ${confdir}/eap.conf
        files {
                usersfile = ${confdir}/users
                acctusersfile = ${confdir}/acct_users
+               preproxy_usersfile = ${confdir}/preproxy_users
 
                #  If you want to use the old Cistron 'users' file
                #  with FreeRADIUS, you should change the next line
@@ -1508,8 +1509,8 @@ $INCLUDE ${confdir}/eap.conf
                maximum-timeout = 0
        }
 
-       # ANSI X9.9 token support.  Not included by default.
-       # $INCLUDE  ${confdir}/x99.conf
+       # OTP token support.  Not included by default.
+       # $INCLUDE  ${confdir}/otp.conf
 
 }
 
@@ -1863,6 +1864,10 @@ post-auth {
 pre-proxy {
 #      attr_rewrite
 
+       #  Uncomment the following line if you want to change attributes
+       #  as defined in the preproxy_users file.
+#      files
+
        #  If you want to have a log of packets proxied to a home
        #  server, un-comment the following line, and the
        #  'detail pre_proxy_log' section, above.
@@ -1875,7 +1880,6 @@ pre-proxy {
 #  post-proxy stage.
 #
 post-proxy {
-       #
 
        #  If you want to have a log of replies from a home server,
        #  un-comment the following line, and the 'detail post_proxy_log'
diff --git a/raddb/x99passwd.sample b/raddb/x99passwd.sample
deleted file mode 100644 (file)
index 01022ee..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-This is a sample "passwd" file used for ANSI X9.9 token authentication.
-Actually, it's not a sample file, but it does document the format and
-legal values.  The default location is /etc/x99passwd.
-
-This file must be mode 0400 or 0600 and owned by the user the radius
-server runs as.
-
-The format is username:card_type:key, eg
-
-bob:cryptocard-d8-es:0101010101010101
-
-The username is limited in that the ':' character may not appear.
-The key must be 8 octets (sixteen hex digits) and must be a valid DES key.
-The valid card types are:
-
-x9.9           A generic x9.9 card.  random challenge, 8 digit hex response.
-generic                synonym for x9.9
-
-cryptocard-h8-rc       random challenge, 8 digit hex response.
-cryptocard-d8-rc       random challenge, 8 digit decimal response.
-cryptocard-h7-rc       random challenge, 7 digit hex response.
-cryptocard-d7-rc       random challenge, 7 digit dec response.
-cryptocard-h8-es       event synchronous (only), 8 digit hex response.
-cryptocard-d8-es       event synchronous (only), 8 digit decimal response.
-cryptocard-h7-es       event synchronous (only), 7 digit hex response.
-cryptocard-d7-es       event synchronous (only), 7 digit decimal response.
-cryptocard-h8-rs       rc or es, 8 digit hex response.
-cryptocard-d8-rs       rc or es, 8 digit decimal response.
-cryptocard-h7-rs       rc or es, 7 digit hex response.
-cryptocard-d7-rs       rc or es, 7 digit decimal response.
-
-The -es and -rs options may require some explanation.  CRYPTOCard supports
-two event synchronous modes, one in which the challenge is presented to
-the user (so he can verify it against the challenge given to him by the
-server), and one in which the challenge is not presented.  The latter mode
-is easier to use when the user cannot be presented with a challenge, or
-if the presented challenge is not expected to match the token's displayed
-challenge (as we do), since the user is not confused as to which number
-he is supposed to enter as his password.  This mode (-es) is easier for
-the user, but it precludes the user being able to resync his token.
-
-The reason the two modes are distinguished is so that the server doesn't
-have to generate state and test if a response is an async response
-(for -es); this saves at least one DES operation and two hmac operations
-per authentication.
-
-
-STATE:
-
-Along with the passwd file is a "state file" which contains state
-needed to authenticate synchronously, along with other persistent data.
-There is one state file per user, with the same name as the user.
-The default location for state files is in /etc/x99sync.d.  For most
-filesystems, this doesn't scale well beyond a few thousand users.
-
-The format is
-  "version:user:challenge:key:last_auth_s:last_auth_t:last_auth_p:".
-Note that the trailing colon is required.
-
-    version: 2
-       user: this is a sanity check field
-  challenge: the next synchronous challenge
-        key: the key used for the next challenge (currently unused)
-last_auth_s: 0 if the last auth was successful,
-             number of consecutive failures if unsucessful
-last_auth_t: the last time the user authenticated (success or failure)
-last_auth_p: >1 if the last auth was sync+correct and user is in softfail,
-             0 otherwise; if >1, it is the ewindow position of the last auth.
-
-If this file does not exist, the user must authenticate asynchronously.
-Once that happens, the server will create this file.  For tokens/modes
-that don't support async auth at all (or if you disallow it anyway),
-you will need to create this file at the time you add the user to
-/etc/x99passwd.  For CRYPTOCard, you can obtain the first sync challenge
-by compiling crcalc.c, included with the module.  When prompted for the
-challenge, just hit return.  This will encrypt a zero block, which is
-what the token will also do upon programming.  crcalc will display the
-next challenge, which you can then use to initialize the state file.
-For example:
-
-1:bob:12345678:deadbeefdeadbeef:0:0:
-
-When creating this file, make sure it is owned by the user the server
-runs as.
-
-To reset locked-out users, you can set the failures field to 0.  You
-may also need to reset the challenge field if they are too far out of
-sync.  (Determine the next challenge from the token's display.)
-
similarity index 53%
rename from src/modules/rlm_x99_token/Makefile.in
rename to src/modules/rlm_otp/Makefile.in
index f02dad3..99608c1 100644 (file)
 # some particular portion of the module.  Usually, leave it blank.
 #
 #######################################################################
-TARGET      = @targetname@
-SRCS        = x99_rlm.c x99_util.c x99_state.c x99_mac.c x99_sync.c
-SRCS       += x99_site.c x99_pwe.c x99_log.c
-HEADERS     = x99.h x99_rad.h x99_sync.h x99_pwe.h
-RLM_CFLAGS  = @x99_token_cflags@
-RLM_LIBS    = @x99_token_ldflags@
+TARGET         = @targetname@
+SRCS           = otp_rlm.c otp_util.c otp_radstate.c otp_x99.c otp_state.c
+SRCS          += otp_site.c otp_pwe.c otp_log.c otp_cardops.c otp_hotp.c
+HEADERS        = otp.h otp_rad.h otp_pwe.h otp_cardops.h
+RLM_CFLAGS     = @otp_cflags@ $(OPENSSL_INCLUDE)
+CARDOPS_LTLIBS = $(patsubst %.c,%.lo,$(wildcard cardops/*.c))
+RLM_LIBS       = @otp_ldflags@ $(OPENSSL_LIBS) $(CARDOPS_LTLIBS)
+
+RLM_SUBDIRS = cardops
 
 ## this uses the RLM_CFLAGS and RLM_LIBS and SRCS defs to make TARGET.
 include ../rules.mak
 
-$(STATIC_OBJS): $(HEADERS)
+# Not part of RLM_CFLAGS to avoid propagation to subdirs
+CFLAGS     += -Wno-unused-label -Wno-cast-qual
+
+$(STATIC_OBJS): $(HEADERS) $(CARDOPS_LTLIBS)
 
-$(DYNAMIC_OBJS): $(HEADERS)
+$(DYNAMIC_OBJS): $(HEADERS) $(CARDOPS_LTLIBS)
 
+# Note: dynamic libs only
+$(CARDOPS_LTLIBS) common:
+       @what=$(WHAT_TO_MAKE); \
+       [ -z "$$what" ] && what=dynamic; \
+       for dir in $(RLM_SUBDIRS); do \
+               echo "Making $$what in $$dir ..."; \
+               $(MAKE) $(MFLAGS) -C $$dir RLM_CFLAGS="$(RLM_CFLAGS)" $$what || exit $?; \
+       done
diff --git a/src/modules/rlm_otp/cardops/Makefile b/src/modules/rlm_otp/cardops/Makefile
new file mode 100644 (file)
index 0000000..b693f73
--- /dev/null
@@ -0,0 +1,11 @@
+TARGET      = notused
+SRCS        = $(wildcard *.c)
+HEADERS     = $(wildcard *.h)
+#RLM_CFLAGS  = @otp_cflags@ $(OPENSSL_INCLUDE)
+
+RLM_DIR=../
+include $(RLM_DIR)../rules.mak
+
+$(STATIC_OBJS): $(HEADERS)
+
+$(DYNAMIC_OBJS): $(HEADERS)
diff --git a/src/modules/rlm_otp/cardops/cryptocard.c b/src/modules/rlm_otp/cardops/cryptocard.c
new file mode 100644 (file)
index 0000000..42f7355
--- /dev/null
@@ -0,0 +1,327 @@
+/*
+ * cryptocard.c
+ * $Id$
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * Copyright 2005 TRI-D Systems, Inc.
+ */
+
+#include <inttypes.h>
+#include <string.h>
+#include <time.h>
+
+#include "../otp.h"
+#include "../otp_cardops.h"
+#include "cryptocard.h"
+
+/* Card name to feature mask mappings */
+static struct {
+  const char *name;
+  uint32_t fm;
+} card[] = {
+  { "cryptocard-h8-rc", CRYPTOCARD_H8_RC },
+  { "cryptocard-d8-rc", CRYPTOCARD_D8_RC },
+  { "cryptocard-h7-rc", CRYPTOCARD_H7_RC },
+  { "cryptocard-d7-rc", CRYPTOCARD_D7_RC },
+  { "cryptocard-h8-es", CRYPTOCARD_H8_ES },
+  { "cryptocard-d8-es", CRYPTOCARD_D8_ES },
+  { "cryptocard-h7-es", CRYPTOCARD_H7_ES },
+  { "cryptocard-d7-es", CRYPTOCARD_D7_ES },
+  { "cryptocard-h8-rs", CRYPTOCARD_H8_RS },
+  { "cryptocard-d8-rs", CRYPTOCARD_D8_RS },
+  { "cryptocard-h7-rs", CRYPTOCARD_H7_RS },
+  { "cryptocard-d7-rs", CRYPTOCARD_D7_RS },
+
+  { NULL, 0 }                                  /* end of list */
+};
+
+
+/*
+ * Convert card name to feature mask.
+ * Returns 0 on success, non-zero otherwise.
+ */
+static int
+cryptocard_name2fm(const char *name, uint32_t *featuremask)
+{
+  int i;
+
+  for (i = 0; card[i].name; ++i) {
+    if (!strcasecmp(name, card[i].name)) {
+      *featuremask = card[i].fm;
+      return 0;
+    }
+  }
+  return 1;
+}
+
+
+/*
+ * Convert an ASCII keystring to a keyblock.
+ * Returns keylen on success, -1 otherwise.
+ */
+static int
+cryptocard_keystring2keyblock(const char *keystring,
+                              unsigned char keyblock[OTP_MAX_KEY_LEN])
+{
+  /* 64-bit DES key with optional line ending */
+  if ((strlen(keystring) & ~1) != 16)
+    return 1;
+
+  return otp_keystring2keyblock(keystring, keyblock);
+}
+
+
+/*
+ * Set nullstate.
+ * We don't currently support nullstate for CRYPTOCard, so return -1.
+ */
+static int
+cryptocard_nullstate(
+#ifdef __GNUC__
+__attribute__ ((unused))
+#endif
+                      const otp_option_t *opt,
+#ifdef __GNUC__
+__attribute__ ((unused))
+#endif
+                      const otp_card_info_t *card_info,
+#ifdef __GNUC__
+__attribute__ ((unused))
+#endif
+                      otp_user_state_t *user_state, 
+#ifdef __GNUC__
+__attribute__ ((unused))
+#endif
+                      time_t when,
+                      const char *log_prefix)
+{
+  otp_log(OTP_LOG_ERR, "%s: %s: null state not supported for CRYPTOCard",
+          log_prefix, __func__);
+  return -1;
+}
+
+
+/*
+ * Return a synchronous challenge.
+ * Returns 0 on success, -1 otherwise.
+ * (-2 rc is for early challenge, N/A for cryptocard.)
+ */
+static int
+cryptocard_challenge(const otp_card_info_t *card_info,
+                     otp_user_state_t *user_state,
+                     unsigned char challenge[OTP_MAX_CHALLENGE_LEN],
+#ifdef __GNUC__
+__attribute__ ((unused))
+#endif
+                     time_t when,
+#ifdef __GNUC__
+__attribute__ ((unused))
+#endif
+                     int twin,
+#ifdef __GNUC__
+__attribute__ ((unused))
+#endif
+                     int ewin,
+                     const char *log_prefix)
+{
+  unsigned char output[8];
+  int i;
+
+  /* run x99 once on the previous challenge */
+  if (otp_x99_mac(challenge, user_state->clen, output, card_info->keyblock,
+                  log_prefix))
+    return -1;
+
+  /* convert the mac into the next challenge */
+  for (i = 0; i < 8; ++i) {
+    output[i] &= 0x0f;
+    if (output[i] > 9)
+      output[i] -= 10;
+    output[i] |= 0x30;
+  }
+  (void) memcpy(challenge, output, 8);
+  user_state->clen = 8;
+
+  return 0;
+}
+
+
+/*
+ * Return the expected card response for a given challenge.
+ * Returns 0 on success, non-zero otherwise.
+ *
+ * The X9.9 MAC is used by CRYPTOcard in the following manner:
+ *
+ * 1. Convert the challenge to ASCII (eg "12345" -> 0x3132333435).
+ *    We don't actually do a conversion, the challenge is already ASCII.
+ *    Note that Secure Computing SafeWord Gold/Platinum tokens can use
+ *    "raw" challenge bytes.
+ * 2. Use the challenge as the plaintext input to the X9.9 MAC algorithm.
+ *
+ * 3. Convert the 32 bit MAC to ASCII (eg 0x1234567f -> "1234567f").
+ *    Note that SafeWord Gold/Platinum tokens can display a 64 bit MAC.
+ * 4. Possibly apply transformations on chars "a" thru "f".
+ * 5. Truncate the response for 7 digit display modes.
+ */
+static int
+cryptocard_response(otp_card_info_t *card_info,
+                    const unsigned char challenge[OTP_MAX_CHALLENGE_LEN],
+                    size_t len, char response[OTP_MAX_RESPONSE_LEN + 1],
+                    const char *log_prefix)
+{
+  unsigned char output[8];
+  const char *conversion;
+
+  /* Step 1, 2. */
+  if (otp_x99_mac(challenge, len, output,
+                  card_info->keyblock, log_prefix) !=0)
+    return 1;
+
+  /* Setup for step 4. */
+  if (card_info->featuremask & OTP_CF_DD)
+    conversion = otp_cc_dec_conversion;
+  else
+    conversion = otp_hex_conversion;
+
+  /* Step 3, 4. */
+  (void) otp_keyblock2keystring(response, output, 4, conversion);
+
+  /* Step 5. */
+  if (card_info->featuremask & OTP_CF_R7)
+    (void) memmove(&response[3], &response[4], 5);
+
+  return 0;
+}
+
+
+/*
+ * Update rd (there is no csd for cryptocard).
+ * Returns 0 if succesful, -1 otherwise.
+ */
+static int
+cryptocard_updatecsd(otp_user_state_t *user_state,
+#ifdef __GNUC__
+__attribute__ ((unused))
+#endif
+                     time_t when,
+#ifdef __GNUC__
+__attribute__ ((unused))
+#endif
+                     int twin,
+                     int ewin,
+                     int auth_rc)
+{
+  if (auth_rc == OTP_RC_OK)
+    user_state->rd[0] = '\0';                          /* reset */
+  else
+    (void) sprintf(user_state->rd, "%" PRIx32,
+                   (int32_t) ewin);                    /* rwindow candidate */
+
+  return 0;
+}
+
+
+/*
+ * Determine if a window position if consecutive relative to a saved
+ * (rwindow candidate) window position, for rwindow override.
+ * user_state contains the previous auth position, twin and ewin the current.
+ * Returns 1 on success (consecutive), 0 otherwise.
+ */
+static int
+cryptocard_isconsecutive(
+#ifdef __GNUC__
+__attribute__ ((unused))
+#endif
+                         const otp_card_info_t *card_info,
+                         const otp_user_state_t *user_state,
+                         int thisewin, const char *log_prefix)
+{
+  int nextewin;
+
+  /* extract the saved rwindow candidate position */
+  if (sscanf(user_state->rd, "%" SCNx32, &nextewin) != 1) {
+    otp_log(OTP_LOG_ERR, "%s: %s: invalid rwindow data for [%s]",
+            log_prefix, __func__, card_info->username);
+    return 0;
+  }
+  nextewin++;
+
+  /* Is this the next passcode? */
+  if (thisewin == nextewin)
+    return 1;  /* yes */
+  else
+    return 0;  /* no */
+}
+
+
+/* no twin so just return 0 */
+static int
+cryptocard_maxtwin(
+#ifdef __GNUC__
+__attribute__ ((unused))
+#endif
+                    const otp_card_info_t *card_info,
+#ifdef __GNUC__
+__attribute__ ((unused))
+#endif
+                    const char csd[OTP_MAX_CSD_LEN + 1])
+{
+  return 0;
+}
+
+
+/* return human-readable challenge */
+static char *
+cryptocard_printchallenge(char s[OTP_MAX_CHALLENGE_LEN * 2 + 1],
+                          const unsigned char challenge[OTP_MAX_CHALLENGE_LEN],
+                          size_t len)
+{
+  /* cryptocard challenge is implicitly ASCII */
+  (void) memcpy(s, challenge, len);
+  s[len] = '\0';
+  return s;
+}
+
+
+/* cardops instance */
+static cardops_t cryptocard_cardops = {
+  .prefix              = "cryptocard",
+  .prefix_len          = 10, /* strlen("cryptocard") */
+
+  .name2fm             = cryptocard_name2fm,
+  .keystring2keyblock  = cryptocard_keystring2keyblock,
+  .nullstate           = cryptocard_nullstate,
+  .challenge           = cryptocard_challenge,
+  .response            = cryptocard_response,
+  .updatecsd           = cryptocard_updatecsd,
+  .isconsecutive       = cryptocard_isconsecutive,
+  .maxtwin             = cryptocard_maxtwin,
+  .printchallenge      = cryptocard_printchallenge,
+};
+
+
+/* constructor */
+void
+cryptocard_init(void)
+{
+  if (otp_num_cardops == OTP_MAX_VENDORS) {
+    otp_log(OTP_LOG_ERR, "cryptocard_init: module limit exceeded");
+    return;
+  }
+
+  otp_cardops[otp_num_cardops++] = cryptocard_cardops;
+  otp_log(OTP_LOG_DEBUG, "cryptocard_init: loaded");
+}
diff --git a/src/modules/rlm_otp/cardops/cryptocard.h b/src/modules/rlm_otp/cardops/cryptocard.h
new file mode 100644 (file)
index 0000000..c534b91
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * cryptocard.h
+ * $Id$
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * Copyright 2005 TRI-D Systems, Inc.
+ */
+
+#ifndef CRYPTOCARD_H
+#define CRYPTOCARD_H
+
+#include "../otp.h"
+#include "../otp_cardops.h"
+
+/* card modes */
+#define CRYPTOCARD_H8_RC (OTP_CF_HD|OTP_CF_R8|OTP_CF_AM|OTP_CF_C8)
+#define CRYPTOCARD_H7_RC (OTP_CF_HD|OTP_CF_R7|OTP_CF_AM|OTP_CF_C8)
+#define CRYPTOCARD_D8_RC (OTP_CF_DD|OTP_CF_R8|OTP_CF_AM|OTP_CF_C8)
+#define CRYPTOCARD_D7_RC (OTP_CF_DD|OTP_CF_R7|OTP_CF_AM|OTP_CF_C8)
+#define CRYPTOCARD_H8_ES (OTP_CF_HD|OTP_CF_R8|OTP_CF_ES|OTP_CF_C8)
+#define CRYPTOCARD_H7_ES (OTP_CF_HD|OTP_CF_R7|OTP_CF_ES|OTP_CF_C8)
+#define CRYPTOCARD_D8_ES (OTP_CF_DD|OTP_CF_R8|OTP_CF_ES|OTP_CF_C8)
+#define CRYPTOCARD_D7_ES (OTP_CF_DD|OTP_CF_R7|OTP_CF_ES|OTP_CF_C8)
+#define CRYPTOCARD_H8_RS (CRYPTOCARD_H8_RC|CRYPTOCARD_H8_ES)
+#define CRYPTOCARD_H7_RS (CRYPTOCARD_H7_RC|CRYPTOCARD_H7_ES)
+#define CRYPTOCARD_D8_RS (CRYPTOCARD_D8_RC|CRYPTOCARD_D8_ES)
+#define CRYPTOCARD_D7_RS (CRYPTOCARD_D7_RC|CRYPTOCARD_D7_ES)
+
+static int cryptocard_name2fm(const char *, uint32_t *);
+static int cryptocard_keystring2keyblock(const char *,
+                                         unsigned char [OTP_MAX_KEY_LEN]);
+static int cryptocard_nullstate(const otp_option_t *, const otp_card_info_t *,
+                                otp_user_state_t *, time_t, const char *);
+static int cryptocard_challenge(const otp_card_info_t *, otp_user_state_t *,
+                                unsigned char [OTP_MAX_CHALLENGE_LEN], time_t,
+                                int, int, const char *);
+static int cryptocard_response(otp_card_info_t *,
+                               const unsigned char [OTP_MAX_CHALLENGE_LEN],
+                               size_t, char [OTP_MAX_RESPONSE_LEN + 1],
+                               const char *);
+static int cryptocard_updatecsd(otp_user_state_t *, time_t, int, int, int);
+static int cryptocard_isconsecutive(const otp_card_info_t *,
+                                    const otp_user_state_t *, int,
+                                    const char *);
+static int cryptocard_maxtwin(const otp_card_info_t *,
+                              const char [OTP_MAX_CSD_LEN + 1]);
+static char *cryptocard_printchallenge(char [OTP_MAX_CHALLENGE_LEN * 2 + 1],
+                                   const unsigned char [OTP_MAX_CHALLENGE_LEN],
+                                       size_t);
+
+#ifdef __GNUC__
+__attribute__ ((constructor))
+#endif
+void cryptocard_init(void);
+
+#endif /* CRYPTOCARD_H */
diff --git a/src/modules/rlm_otp/configure b/src/modules/rlm_otp/configure
new file mode 100755 (executable)
index 0000000..9c20df0
--- /dev/null
@@ -0,0 +1,3801 @@
+#! /bin/sh
+# From configure.in Revision: 1.1 .
+# Guess values for system-dependent variables and create Makefiles.
+# Generated by GNU Autoconf 2.59.
+#
+# Copyright (C) 2003 Free Software Foundation, Inc.
+# This configure script is free software; the Free Software Foundation
+# gives unlimited permission to copy, distribute and modify it.
+## --------------------- ##
+## M4sh Initialization.  ##
+## --------------------- ##
+
+# Be Bourne compatible
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
+  emulate sh
+  NULLCMD=:
+  # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
+  # is contrary to our usage.  Disable this feature.
+  alias -g '${1+"$@"}'='"$@"'
+elif test -n "${BASH_VERSION+set}" && (set -o posix) >/dev/null 2>&1; then
+  set -o posix
+fi
+DUALCASE=1; export DUALCASE # for MKS sh
+
+# Support unset when possible.
+if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then
+  as_unset=unset
+else
+  as_unset=false
+fi
+
+
+# Work around bugs in pre-3.0 UWIN ksh.
+$as_unset ENV MAIL MAILPATH
+PS1='$ '
+PS2='> '
+PS4='+ '
+
+# NLS nuisances.
+for as_var in \
+  LANG LANGUAGE LC_ADDRESS LC_ALL LC_COLLATE LC_CTYPE LC_IDENTIFICATION \
+  LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER \
+  LC_TELEPHONE LC_TIME
+do
+  if (set +x; test -z "`(eval $as_var=C; export $as_var) 2>&1`"); then
+    eval $as_var=C; export $as_var
+  else
+    $as_unset $as_var
+  fi
+done
+
+# Required to use basename.
+if expr a : '\(a\)' >/dev/null 2>&1; then
+  as_expr=expr
+else
+  as_expr=false
+fi
+
+if (basename /) >/dev/null 2>&1 && test "X`basename / 2>&1`" = "X/"; then
+  as_basename=basename
+else
+  as_basename=false
+fi
+
+
+# Name of the executable.
+as_me=`$as_basename "$0" ||
+$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
+        X"$0" : 'X\(//\)$' \| \
+        X"$0" : 'X\(/\)$' \| \
+        .     : '\(.\)' 2>/dev/null ||
+echo X/"$0" |
+    sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/; q; }
+         /^X\/\(\/\/\)$/{ s//\1/; q; }
+         /^X\/\(\/\).*/{ s//\1/; q; }
+         s/.*/./; q'`
+
+
+# PATH needs CR, and LINENO needs CR and PATH.
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+  echo "#! /bin/sh" >conf$$.sh
+  echo  "exit 0"   >>conf$$.sh
+  chmod +x conf$$.sh
+  if (PATH="/nonexistent;."; conf$$.sh) >/dev/null 2>&1; then
+    PATH_SEPARATOR=';'
+  else
+    PATH_SEPARATOR=:
+  fi
+  rm -f conf$$.sh
+fi
+
+
+  as_lineno_1=$LINENO
+  as_lineno_2=$LINENO
+  as_lineno_3=`(expr $as_lineno_1 + 1) 2>/dev/null`
+  test "x$as_lineno_1" != "x$as_lineno_2" &&
+  test "x$as_lineno_3"  = "x$as_lineno_2"  || {
+  # Find who we are.  Look in the path if we contain no path at all
+  # relative or not.
+  case $0 in
+    *[\\/]* ) as_myself=$0 ;;
+    *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+done
+
+       ;;
+  esac
+  # We did not find ourselves, most probably we were run as `sh COMMAND'
+  # in which case we are not to be found in the path.
+  if test "x$as_myself" = x; then
+    as_myself=$0
+  fi
+  if test ! -f "$as_myself"; then
+    { echo "$as_me: error: cannot find myself; rerun with an absolute path" >&2
+   { (exit 1); exit 1; }; }
+  fi
+  case $CONFIG_SHELL in
+  '')
+    as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  for as_base in sh bash ksh sh5; do
+        case $as_dir in
+        /*)
+          if ("$as_dir/$as_base" -c '
+  as_lineno_1=$LINENO
+  as_lineno_2=$LINENO
+  as_lineno_3=`(expr $as_lineno_1 + 1) 2>/dev/null`
+  test "x$as_lineno_1" != "x$as_lineno_2" &&
+  test "x$as_lineno_3"  = "x$as_lineno_2" ') 2>/dev/null; then
+            $as_unset BASH_ENV || test "${BASH_ENV+set}" != set || { BASH_ENV=; export BASH_ENV; }
+            $as_unset ENV || test "${ENV+set}" != set || { ENV=; export ENV; }
+            CONFIG_SHELL=$as_dir/$as_base
+            export CONFIG_SHELL
+            exec "$CONFIG_SHELL" "$0" ${1+"$@"}
+          fi;;
+        esac
+       done
+done
+;;
+  esac
+
+  # Create $as_me.lineno as a copy of $as_myself, but with $LINENO
+  # uniformly replaced by the line number.  The first 'sed' inserts a
+  # line-number line before each line; the second 'sed' does the real
+  # work.  The second script uses 'N' to pair each line-number line
+  # with the numbered line, and appends trailing '-' during
+  # substitution so that $LINENO is not a special case at line end.
+  # (Raja R Harinath suggested sed '=', and Paul Eggert wrote the
+  # second 'sed' script.  Blame Lee E. McMahon for sed's syntax.  :-)
+  sed '=' <$as_myself |
+    sed '
+      N
+      s,$,-,
+      : loop
+      s,^\(['$as_cr_digits']*\)\(.*\)[$]LINENO\([^'$as_cr_alnum'_]\),\1\2\1\3,
+      t loop
+      s,-$,,
+      s,^['$as_cr_digits']*\n,,
+    ' >$as_me.lineno &&
+  chmod +x $as_me.lineno ||
+    { echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2
+   { (exit 1); exit 1; }; }
+
+  # Don't try to exec as it changes $[0], causing all sort of problems
+  # (the dirname of $[0] is not the place where we might find the
+  # original and so on.  Autoconf is especially sensible to this).
+  . ./$as_me.lineno
+  # Exit status is that of the last command.
+  exit
+}
+
+
+case `echo "testing\c"; echo 1,2,3`,`echo -n testing; echo 1,2,3` in
+  *c*,-n*) ECHO_N= ECHO_C='
+' ECHO_T='     ' ;;
+  *c*,*  ) ECHO_N=-n ECHO_C= ECHO_T= ;;
+  *)       ECHO_N= ECHO_C='\c' ECHO_T= ;;
+esac
+
+if expr a : '\(a\)' >/dev/null 2>&1; then
+  as_expr=expr
+else
+  as_expr=false
+fi
+
+rm -f conf$$ conf$$.exe conf$$.file
+echo >conf$$.file
+if ln -s conf$$.file conf$$ 2>/dev/null; then
+  # We could just check for DJGPP; but this test a) works b) is more generic
+  # and c) will remain valid once DJGPP supports symlinks (DJGPP 2.04).
+  if test -f conf$$.exe; then
+    # Don't use ln at all; we don't have any links
+    as_ln_s='cp -p'
+  else
+    as_ln_s='ln -s'
+  fi
+elif ln conf$$.file conf$$ 2>/dev/null; then
+  as_ln_s=ln
+else
+  as_ln_s='cp -p'
+fi
+rm -f conf$$ conf$$.exe conf$$.file
+
+if mkdir -p . 2>/dev/null; then
+  as_mkdir_p=:
+else
+  test -d ./-p && rmdir ./-p
+  as_mkdir_p=false
+fi
+
+as_executable_p="test -f"
+
+# Sed expression to map a string onto a valid CPP name.
+as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
+
+# Sed expression to map a string onto a valid variable name.
+as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
+
+
+# IFS
+# We need space, tab and new line, in precisely that order.
+as_nl='
+'
+IFS="  $as_nl"
+
+# CDPATH.
+$as_unset CDPATH
+
+
+# Name of the host.
+# hostname on some systems (SVR3.2, Linux) returns a bogus exit status,
+# so uname gets run too.
+ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q`
+
+exec 6>&1
+
+#
+# Initializations.
+#
+ac_default_prefix=/usr/local
+ac_config_libobj_dir=.
+cross_compiling=no
+subdirs=
+MFLAGS=
+MAKEFLAGS=
+SHELL=${CONFIG_SHELL-/bin/sh}
+
+# Maximum number of lines to put in a shell here document.
+# This variable seems obsolete.  It should probably be removed, and
+# only ac_max_sed_lines should be used.
+: ${ac_max_here_lines=38}
+
+# Identity of this package.
+PACKAGE_NAME=
+PACKAGE_TARNAME=
+PACKAGE_VERSION=
+PACKAGE_STRING=
+PACKAGE_BUGREPORT=
+
+ac_unique_file="otp_rlm.c"
+# Factoring default headers for most tests.
+ac_includes_default="\
+#include <stdio.h>
+#if HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+#if HAVE_SYS_STAT_H
+# include <sys/stat.h>
+#endif
+#if STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# if HAVE_STDLIB_H
+#  include <stdlib.h>
+# endif
+#endif
+#if HAVE_STRING_H
+# if !STDC_HEADERS && HAVE_MEMORY_H
+#  include <memory.h>
+# endif
+# include <string.h>
+#endif
+#if HAVE_STRINGS_H
+# include <strings.h>
+#endif
+#if HAVE_INTTYPES_H
+# include <inttypes.h>
+#else
+# if HAVE_STDINT_H
+#  include <stdint.h>
+# endif
+#endif
+#if HAVE_UNISTD_H
+# include <unistd.h>
+#endif"
+
+ac_subst_vars='SHELL PATH_SEPARATOR PACKAGE_NAME PACKAGE_TARNAME PACKAGE_VERSION PACKAGE_STRING PACKAGE_BUGREPORT exec_prefix prefix program_transform_name bindir sbindir libexecdir datadir sysconfdir sharedstatedir localstatedir libdir includedir oldincludedir infodir mandir build_alias host_alias target_alias DEFS ECHO_C ECHO_N ECHO_T LIBS CC CFLAGS LDFLAGS CPPFLAGS ac_ct_CC EXEEXT OBJEXT CPP EGREP otp_cflags otp_ldflags targetname LIBOBJS LTLIBOBJS'
+ac_subst_files=''
+
+# Initialize some variables set by options.
+ac_init_help=
+ac_init_version=false
+# The variables have the same names as the options, with
+# dashes changed to underlines.
+cache_file=/dev/null
+exec_prefix=NONE
+no_create=
+no_recursion=
+prefix=NONE
+program_prefix=NONE
+program_suffix=NONE
+program_transform_name=s,x,x,
+silent=
+site=
+srcdir=
+verbose=
+x_includes=NONE
+x_libraries=NONE
+
+# Installation directory options.
+# These are left unexpanded so users can "make install exec_prefix=/foo"
+# and all the variables that are supposed to be based on exec_prefix
+# by default will actually change.
+# Use braces instead of parens because sh, perl, etc. also accept them.
+bindir='${exec_prefix}/bin'
+sbindir='${exec_prefix}/sbin'
+libexecdir='${exec_prefix}/libexec'
+datadir='${prefix}/share'
+sysconfdir='${prefix}/etc'
+sharedstatedir='${prefix}/com'
+localstatedir='${prefix}/var'
+libdir='${exec_prefix}/lib'
+includedir='${prefix}/include'
+oldincludedir='/usr/include'
+infodir='${prefix}/info'
+mandir='${prefix}/man'
+
+ac_prev=
+for ac_option
+do
+  # If the previous option needs an argument, assign it.
+  if test -n "$ac_prev"; then
+    eval "$ac_prev=\$ac_option"
+    ac_prev=
+    continue
+  fi
+
+  ac_optarg=`expr "x$ac_option" : 'x[^=]*=\(.*\)'`
+
+  # Accept the important Cygnus configure options, so we can diagnose typos.
+
+  case $ac_option in
+
+  -bindir | --bindir | --bindi | --bind | --bin | --bi)
+    ac_prev=bindir ;;
+  -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*)
+    bindir=$ac_optarg ;;
+
+  -build | --build | --buil | --bui | --bu)
+    ac_prev=build_alias ;;
+  -build=* | --build=* | --buil=* | --bui=* | --bu=*)
+    build_alias=$ac_optarg ;;
+
+  -cache-file | --cache-file | --cache-fil | --cache-fi \
+  | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c)
+    ac_prev=cache_file ;;
+  -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \
+  | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*)
+    cache_file=$ac_optarg ;;
+
+  --config-cache | -C)
+    cache_file=config.cache ;;
+
+  -datadir | --datadir | --datadi | --datad | --data | --dat | --da)
+    ac_prev=datadir ;;
+  -datadir=* | --datadir=* | --datadi=* | --datad=* | --data=* | --dat=* \
+  | --da=*)
+    datadir=$ac_optarg ;;
+
+  -disable-* | --disable-*)
+    ac_feature=`expr "x$ac_option" : 'x-*disable-\(.*\)'`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_feature" : ".*[^-_$as_cr_alnum]" >/dev/null &&
+      { echo "$as_me: error: invalid feature name: $ac_feature" >&2
+   { (exit 1); exit 1; }; }
+    ac_feature=`echo $ac_feature | sed 's/-/_/g'`
+    eval "enable_$ac_feature=no" ;;
+
+  -enable-* | --enable-*)
+    ac_feature=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_feature" : ".*[^-_$as_cr_alnum]" >/dev/null &&
+      { echo "$as_me: error: invalid feature name: $ac_feature" >&2
+   { (exit 1); exit 1; }; }
+    ac_feature=`echo $ac_feature | sed 's/-/_/g'`
+    case $ac_option in
+      *=*) ac_optarg=`echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"`;;
+      *) ac_optarg=yes ;;
+    esac
+    eval "enable_$ac_feature='$ac_optarg'" ;;
+
+  -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \
+  | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \
+  | --exec | --exe | --ex)
+    ac_prev=exec_prefix ;;
+  -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \
+  | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \
+  | --exec=* | --exe=* | --ex=*)
+    exec_prefix=$ac_optarg ;;
+
+  -gas | --gas | --ga | --g)
+    # Obsolete; use --with-gas.
+    with_gas=yes ;;
+
+  -help | --help | --hel | --he | -h)
+    ac_init_help=long ;;
+  -help=r* | --help=r* | --hel=r* | --he=r* | -hr*)
+    ac_init_help=recursive ;;
+  -help=s* | --help=s* | --hel=s* | --he=s* | -hs*)
+    ac_init_help=short ;;
+
+  -host | --host | --hos | --ho)
+    ac_prev=host_alias ;;
+  -host=* | --host=* | --hos=* | --ho=*)
+    host_alias=$ac_optarg ;;
+
+  -includedir | --includedir | --includedi | --included | --include \
+  | --includ | --inclu | --incl | --inc)
+    ac_prev=includedir ;;
+  -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \
+  | --includ=* | --inclu=* | --incl=* | --inc=*)
+    includedir=$ac_optarg ;;
+
+  -infodir | --infodir | --infodi | --infod | --info | --inf)
+    ac_prev=infodir ;;
+  -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*)
+    infodir=$ac_optarg ;;
+
+  -libdir | --libdir | --libdi | --libd)
+    ac_prev=libdir ;;
+  -libdir=* | --libdir=* | --libdi=* | --libd=*)
+    libdir=$ac_optarg ;;
+
+  -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \
+  | --libexe | --libex | --libe)
+    ac_prev=libexecdir ;;
+  -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \
+  | --libexe=* | --libex=* | --libe=*)
+    libexecdir=$ac_optarg ;;
+
+  -localstatedir | --localstatedir | --localstatedi | --localstated \
+  | --localstate | --localstat | --localsta | --localst \
+  | --locals | --local | --loca | --loc | --lo)
+    ac_prev=localstatedir ;;
+  -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \
+  | --localstate=* | --localstat=* | --localsta=* | --localst=* \
+  | --locals=* | --local=* | --loca=* | --loc=* | --lo=*)
+    localstatedir=$ac_optarg ;;
+
+  -mandir | --mandir | --mandi | --mand | --man | --ma | --m)
+    ac_prev=mandir ;;
+  -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*)
+    mandir=$ac_optarg ;;
+
+  -nfp | --nfp | --nf)
+    # Obsolete; use --without-fp.
+    with_fp=no ;;
+
+  -no-create | --no-create | --no-creat | --no-crea | --no-cre \
+  | --no-cr | --no-c | -n)
+    no_create=yes ;;
+
+  -no-recursion | --no-recursion | --no-recursio | --no-recursi \
+  | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r)
+    no_recursion=yes ;;
+
+  -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \
+  | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \
+  | --oldin | --oldi | --old | --ol | --o)
+    ac_prev=oldincludedir ;;
+  -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \
+  | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \
+  | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*)
+    oldincludedir=$ac_optarg ;;
+
+  -prefix | --prefix | --prefi | --pref | --pre | --pr | --p)
+    ac_prev=prefix ;;
+  -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*)
+    prefix=$ac_optarg ;;
+
+  -program-prefix | --program-prefix | --program-prefi | --program-pref \
+  | --program-pre | --program-pr | --program-p)
+    ac_prev=program_prefix ;;
+  -program-prefix=* | --program-prefix=* | --program-prefi=* \
+  | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*)
+    program_prefix=$ac_optarg ;;
+
+  -program-suffix | --program-suffix | --program-suffi | --program-suff \
+  | --program-suf | --program-su | --program-s)
+    ac_prev=program_suffix ;;
+  -program-suffix=* | --program-suffix=* | --program-suffi=* \
+  | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*)
+    program_suffix=$ac_optarg ;;
+
+  -program-transform-name | --program-transform-name \
+  | --program-transform-nam | --program-transform-na \
+  | --program-transform-n | --program-transform- \
+  | --program-transform | --program-transfor \
+  | --program-transfo | --program-transf \
+  | --program-trans | --program-tran \
+  | --progr-tra | --program-tr | --program-t)
+    ac_prev=program_transform_name ;;
+  -program-transform-name=* | --program-transform-name=* \
+  | --program-transform-nam=* | --program-transform-na=* \
+  | --program-transform-n=* | --program-transform-=* \
+  | --program-transform=* | --program-transfor=* \
+  | --program-transfo=* | --program-transf=* \
+  | --program-trans=* | --program-tran=* \
+  | --progr-tra=* | --program-tr=* | --program-t=*)
+    program_transform_name=$ac_optarg ;;
+
+  -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+  | -silent | --silent | --silen | --sile | --sil)
+    silent=yes ;;
+
+  -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
+    ac_prev=sbindir ;;
+  -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
+  | --sbi=* | --sb=*)
+    sbindir=$ac_optarg ;;
+
+  -sharedstatedir | --sharedstatedir | --sharedstatedi \
+  | --sharedstated | --sharedstate | --sharedstat | --sharedsta \
+  | --sharedst | --shareds | --shared | --share | --shar \
+  | --sha | --sh)
+    ac_prev=sharedstatedir ;;
+  -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \
+  | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \
+  | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \
+  | --sha=* | --sh=*)
+    sharedstatedir=$ac_optarg ;;
+
+  -site | --site | --sit)
+    ac_prev=site ;;
+  -site=* | --site=* | --sit=*)
+    site=$ac_optarg ;;
+
+  -srcdir | --srcdir | --srcdi | --srcd | --src | --sr)
+    ac_prev=srcdir ;;
+  -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*)
+    srcdir=$ac_optarg ;;
+
+  -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \
+  | --syscon | --sysco | --sysc | --sys | --sy)
+    ac_prev=sysconfdir ;;
+  -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \
+  | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*)
+    sysconfdir=$ac_optarg ;;
+
+  -target | --target | --targe | --targ | --tar | --ta | --t)
+    ac_prev=target_alias ;;
+  -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*)
+    target_alias=$ac_optarg ;;
+
+  -v | -verbose | --verbose | --verbos | --verbo | --verb)
+    verbose=yes ;;
+
+  -version | --version | --versio | --versi | --vers | -V)
+    ac_init_version=: ;;
+
+  -with-* | --with-*)
+    ac_package=`expr "x$ac_option" : 'x-*with-\([^=]*\)'`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_package" : ".*[^-_$as_cr_alnum]" >/dev/null &&
+      { echo "$as_me: error: invalid package name: $ac_package" >&2
+   { (exit 1); exit 1; }; }
+    ac_package=`echo $ac_package| sed 's/-/_/g'`
+    case $ac_option in
+      *=*) ac_optarg=`echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"`;;
+      *) ac_optarg=yes ;;
+    esac
+    eval "with_$ac_package='$ac_optarg'" ;;
+
+  -without-* | --without-*)
+    ac_package=`expr "x$ac_option" : 'x-*without-\(.*\)'`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_package" : ".*[^-_$as_cr_alnum]" >/dev/null &&
+      { echo "$as_me: error: invalid package name: $ac_package" >&2
+   { (exit 1); exit 1; }; }
+    ac_package=`echo $ac_package | sed 's/-/_/g'`
+    eval "with_$ac_package=no" ;;
+
+  --x)
+    # Obsolete; use --with-x.
+    with_x=yes ;;
+
+  -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \
+  | --x-incl | --x-inc | --x-in | --x-i)
+    ac_prev=x_includes ;;
+  -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \
+  | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*)
+    x_includes=$ac_optarg ;;
+
+  -x-libraries | --x-libraries | --x-librarie | --x-librari \
+  | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l)
+    ac_prev=x_libraries ;;
+  -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \
+  | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*)
+    x_libraries=$ac_optarg ;;
+
+  -*) { echo "$as_me: error: unrecognized option: $ac_option
+Try \`$0 --help' for more information." >&2
+   { (exit 1); exit 1; }; }
+    ;;
+
+  *=*)
+    ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_envvar" : ".*[^_$as_cr_alnum]" >/dev/null &&
+      { echo "$as_me: error: invalid variable name: $ac_envvar" >&2
+   { (exit 1); exit 1; }; }
+    ac_optarg=`echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"`
+    eval "$ac_envvar='$ac_optarg'"
+    export $ac_envvar ;;
+
+  *)
+    # FIXME: should be removed in autoconf 3.0.
+    echo "$as_me: WARNING: you should use --build, --host, --target" >&2
+    expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null &&
+      echo "$as_me: WARNING: invalid host type: $ac_option" >&2
+    : ${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}
+    ;;
+
+  esac
+done
+
+if test -n "$ac_prev"; then
+  ac_option=--`echo $ac_prev | sed 's/_/-/g'`
+  { echo "$as_me: error: missing argument to $ac_option" >&2
+   { (exit 1); exit 1; }; }
+fi
+
+# Be sure to have absolute paths.
+for ac_var in exec_prefix prefix
+do
+  eval ac_val=$`echo $ac_var`
+  case $ac_val in
+    [\\/$]* | ?:[\\/]* | NONE | '' ) ;;
+    *)  { echo "$as_me: error: expected an absolute directory name for --$ac_var: $ac_val" >&2
+   { (exit 1); exit 1; }; };;
+  esac
+done
+
+# Be sure to have absolute paths.
+for ac_var in bindir sbindir libexecdir datadir sysconfdir sharedstatedir \
+             localstatedir libdir includedir oldincludedir infodir mandir
+do
+  eval ac_val=$`echo $ac_var`
+  case $ac_val in
+    [\\/$]* | ?:[\\/]* ) ;;
+    *)  { echo "$as_me: error: expected an absolute directory name for --$ac_var: $ac_val" >&2
+   { (exit 1); exit 1; }; };;
+  esac
+done
+
+# There might be people who depend on the old broken behavior: `$host'
+# used to hold the argument of --host etc.
+# FIXME: To remove some day.
+build=$build_alias
+host=$host_alias
+target=$target_alias
+
+# FIXME: To remove some day.
+if test "x$host_alias" != x; then
+  if test "x$build_alias" = x; then
+    cross_compiling=maybe
+    echo "$as_me: WARNING: If you wanted to set the --build type, don't use --host.
+    If a cross compiler is detected then cross compile mode will be used." >&2
+  elif test "x$build_alias" != "x$host_alias"; then
+    cross_compiling=yes
+  fi
+fi
+
+ac_tool_prefix=
+test -n "$host_alias" && ac_tool_prefix=$host_alias-
+
+test "$silent" = yes && exec 6>/dev/null
+
+
+# Find the source files, if location was not specified.
+if test -z "$srcdir"; then
+  ac_srcdir_defaulted=yes
+  # Try the directory containing this script, then its parent.
+  ac_confdir=`(dirname "$0") 2>/dev/null ||
+$as_expr X"$0" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+        X"$0" : 'X\(//\)[^/]' \| \
+        X"$0" : 'X\(//\)$' \| \
+        X"$0" : 'X\(/\)' \| \
+        .     : '\(.\)' 2>/dev/null ||
+echo X"$0" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/; q; }
+         /^X\(\/\/\)[^/].*/{ s//\1/; q; }
+         /^X\(\/\/\)$/{ s//\1/; q; }
+         /^X\(\/\).*/{ s//\1/; q; }
+         s/.*/./; q'`
+  srcdir=$ac_confdir
+  if test ! -r $srcdir/$ac_unique_file; then
+    srcdir=..
+  fi
+else
+  ac_srcdir_defaulted=no
+fi
+if test ! -r $srcdir/$ac_unique_file; then
+  if test "$ac_srcdir_defaulted" = yes; then
+    { echo "$as_me: error: cannot find sources ($ac_unique_file) in $ac_confdir or .." >&2
+   { (exit 1); exit 1; }; }
+  else
+    { echo "$as_me: error: cannot find sources ($ac_unique_file) in $srcdir" >&2
+   { (exit 1); exit 1; }; }
+  fi
+fi
+(cd $srcdir && test -r ./$ac_unique_file) 2>/dev/null ||
+  { echo "$as_me: error: sources are in $srcdir, but \`cd $srcdir' does not work" >&2
+   { (exit 1); exit 1; }; }
+srcdir=`echo "$srcdir" | sed 's%\([^\\/]\)[\\/]*$%\1%'`
+ac_env_build_alias_set=${build_alias+set}
+ac_env_build_alias_value=$build_alias
+ac_cv_env_build_alias_set=${build_alias+set}
+ac_cv_env_build_alias_value=$build_alias
+ac_env_host_alias_set=${host_alias+set}
+ac_env_host_alias_value=$host_alias
+ac_cv_env_host_alias_set=${host_alias+set}
+ac_cv_env_host_alias_value=$host_alias
+ac_env_target_alias_set=${target_alias+set}
+ac_env_target_alias_value=$target_alias
+ac_cv_env_target_alias_set=${target_alias+set}
+ac_cv_env_target_alias_value=$target_alias
+ac_env_CC_set=${CC+set}
+ac_env_CC_value=$CC
+ac_cv_env_CC_set=${CC+set}
+ac_cv_env_CC_value=$CC
+ac_env_CFLAGS_set=${CFLAGS+set}
+ac_env_CFLAGS_value=$CFLAGS
+ac_cv_env_CFLAGS_set=${CFLAGS+set}
+ac_cv_env_CFLAGS_value=$CFLAGS
+ac_env_LDFLAGS_set=${LDFLAGS+set}
+ac_env_LDFLAGS_value=$LDFLAGS
+ac_cv_env_LDFLAGS_set=${LDFLAGS+set}
+ac_cv_env_LDFLAGS_value=$LDFLAGS
+ac_env_CPPFLAGS_set=${CPPFLAGS+set}
+ac_env_CPPFLAGS_value=$CPPFLAGS
+ac_cv_env_CPPFLAGS_set=${CPPFLAGS+set}
+ac_cv_env_CPPFLAGS_value=$CPPFLAGS
+ac_env_CPP_set=${CPP+set}
+ac_env_CPP_value=$CPP
+ac_cv_env_CPP_set=${CPP+set}
+ac_cv_env_CPP_value=$CPP
+
+#
+# Report the --help message.
+#
+if test "$ac_init_help" = "long"; then
+  # Omit some internal or obsolete options to make the list less imposing.
+  # This message is too long to be a string in the A/UX 3.1 sh.
+  cat <<_ACEOF
+\`configure' configures this package to adapt to many kinds of systems.
+
+Usage: $0 [OPTION]... [VAR=VALUE]...
+
+To assign environment variables (e.g., CC, CFLAGS...), specify them as
+VAR=VALUE.  See below for descriptions of some of the useful variables.
+
+Defaults for the options are specified in brackets.
+
+Configuration:
+  -h, --help              display this help and exit
+      --help=short        display options specific to this package
+      --help=recursive    display the short help of all the included packages
+  -V, --version           display version information and exit
+  -q, --quiet, --silent   do not print \`checking...' messages
+      --cache-file=FILE   cache test results in FILE [disabled]
+  -C, --config-cache      alias for \`--cache-file=config.cache'
+  -n, --no-create         do not create output files
+      --srcdir=DIR        find the sources in DIR [configure dir or \`..']
+
+_ACEOF
+
+  cat <<_ACEOF
+Installation directories:
+  --prefix=PREFIX         install architecture-independent files in PREFIX
+                         [$ac_default_prefix]
+  --exec-prefix=EPREFIX   install architecture-dependent files in EPREFIX
+                         [PREFIX]
+
+By default, \`make install' will install all the files in
+\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc.  You can specify
+an installation prefix other than \`$ac_default_prefix' using \`--prefix',
+for instance \`--prefix=\$HOME'.
+
+For better control, use the options below.
+
+Fine tuning of the installation directories:
+  --bindir=DIR           user executables [EPREFIX/bin]
+  --sbindir=DIR          system admin executables [EPREFIX/sbin]
+  --libexecdir=DIR       program executables [EPREFIX/libexec]
+  --datadir=DIR          read-only architecture-independent data [PREFIX/share]
+  --sysconfdir=DIR       read-only single-machine data [PREFIX/etc]
+  --sharedstatedir=DIR   modifiable architecture-independent data [PREFIX/com]
+  --localstatedir=DIR    modifiable single-machine data [PREFIX/var]
+  --libdir=DIR           object code libraries [EPREFIX/lib]
+  --includedir=DIR       C header files [PREFIX/include]
+  --oldincludedir=DIR    C header files for non-gcc [/usr/include]
+  --infodir=DIR          info documentation [PREFIX/info]
+  --mandir=DIR           man documentation [PREFIX/man]
+_ACEOF
+
+  cat <<\_ACEOF
+_ACEOF
+fi
+
+if test -n "$ac_init_help"; then
+
+  cat <<\_ACEOF
+
+Some influential environment variables:
+  CC          C compiler command
+  CFLAGS      C compiler flags
+  LDFLAGS     linker flags, e.g. -L<lib dir> if you have libraries in a
+              nonstandard directory <lib dir>
+  CPPFLAGS    C/C++ preprocessor flags, e.g. -I<include dir> if you have
+              headers in a nonstandard directory <include dir>
+  CPP         C preprocessor
+
+Use these variables to override the choices made by `configure' or to help
+it to find libraries and programs with nonstandard names/locations.
+
+_ACEOF
+fi
+
+if test "$ac_init_help" = "recursive"; then
+  # If there are subdirs, report their specific --help.
+  ac_popdir=`pwd`
+  for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue
+    test -d $ac_dir || continue
+    ac_builddir=.
+
+if test "$ac_dir" != .; then
+  ac_dir_suffix=/`echo "$ac_dir" | sed 's,^\.[\\/],,'`
+  # A "../" for each directory in $ac_dir_suffix.
+  ac_top_builddir=`echo "$ac_dir_suffix" | sed 's,/[^\\/]*,../,g'`
+else
+  ac_dir_suffix= ac_top_builddir=
+fi
+
+case $srcdir in
+  .)  # No --srcdir option.  We are building in place.
+    ac_srcdir=.
+    if test -z "$ac_top_builddir"; then
+       ac_top_srcdir=.
+    else
+       ac_top_srcdir=`echo $ac_top_builddir | sed 's,/$,,'`
+    fi ;;
+  [\\/]* | ?:[\\/]* )  # Absolute path.
+    ac_srcdir=$srcdir$ac_dir_suffix;
+    ac_top_srcdir=$srcdir ;;
+  *) # Relative path.
+    ac_srcdir=$ac_top_builddir$srcdir$ac_dir_suffix
+    ac_top_srcdir=$ac_top_builddir$srcdir ;;
+esac
+
+# Do not use `cd foo && pwd` to compute absolute paths, because
+# the directories may not exist.
+case `pwd` in
+.) ac_abs_builddir="$ac_dir";;
+*)
+  case "$ac_dir" in
+  .) ac_abs_builddir=`pwd`;;
+  [\\/]* | ?:[\\/]* ) ac_abs_builddir="$ac_dir";;
+  *) ac_abs_builddir=`pwd`/"$ac_dir";;
+  esac;;
+esac
+case $ac_abs_builddir in
+.) ac_abs_top_builddir=${ac_top_builddir}.;;
+*)
+  case ${ac_top_builddir}. in
+  .) ac_abs_top_builddir=$ac_abs_builddir;;
+  [\\/]* | ?:[\\/]* ) ac_abs_top_builddir=${ac_top_builddir}.;;
+  *) ac_abs_top_builddir=$ac_abs_builddir/${ac_top_builddir}.;;
+  esac;;
+esac
+case $ac_abs_builddir in
+.) ac_abs_srcdir=$ac_srcdir;;
+*)
+  case $ac_srcdir in
+  .) ac_abs_srcdir=$ac_abs_builddir;;
+  [\\/]* | ?:[\\/]* ) ac_abs_srcdir=$ac_srcdir;;
+  *) ac_abs_srcdir=$ac_abs_builddir/$ac_srcdir;;
+  esac;;
+esac
+case $ac_abs_builddir in
+.) ac_abs_top_srcdir=$ac_top_srcdir;;
+*)
+  case $ac_top_srcdir in
+  .) ac_abs_top_srcdir=$ac_abs_builddir;;
+  [\\/]* | ?:[\\/]* ) ac_abs_top_srcdir=$ac_top_srcdir;;
+  *) ac_abs_top_srcdir=$ac_abs_builddir/$ac_top_srcdir;;
+  esac;;
+esac
+
+    cd $ac_dir
+    # Check for guested configure; otherwise get Cygnus style configure.
+    if test -f $ac_srcdir/configure.gnu; then
+      echo
+      $SHELL $ac_srcdir/configure.gnu  --help=recursive
+    elif test -f $ac_srcdir/configure; then
+      echo
+      $SHELL $ac_srcdir/configure  --help=recursive
+    elif test -f $ac_srcdir/configure.ac ||
+          test -f $ac_srcdir/configure.in; then
+      echo
+      $ac_configure --help
+    else
+      echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2
+    fi
+    cd $ac_popdir
+  done
+fi
+
+test -n "$ac_init_help" && exit 0
+if $ac_init_version; then
+  cat <<\_ACEOF
+
+Copyright (C) 2003 Free Software Foundation, Inc.
+This configure script is free software; the Free Software Foundation
+gives unlimited permission to copy, distribute and modify it.
+_ACEOF
+  exit 0
+fi
+exec 5>config.log
+cat >&5 <<_ACEOF
+This file contains any messages produced by compilers while
+running configure, to aid debugging if configure makes a mistake.
+
+It was created by $as_me, which was
+generated by GNU Autoconf 2.59.  Invocation command line was
+
+  $ $0 $@
+
+_ACEOF
+{
+cat <<_ASUNAME
+## --------- ##
+## Platform. ##
+## --------- ##
+
+hostname = `(hostname || uname -n) 2>/dev/null | sed 1q`
+uname -m = `(uname -m) 2>/dev/null || echo unknown`
+uname -r = `(uname -r) 2>/dev/null || echo unknown`
+uname -s = `(uname -s) 2>/dev/null || echo unknown`
+uname -v = `(uname -v) 2>/dev/null || echo unknown`
+
+/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown`
+/bin/uname -X     = `(/bin/uname -X) 2>/dev/null     || echo unknown`
+
+/bin/arch              = `(/bin/arch) 2>/dev/null              || echo unknown`
+/usr/bin/arch -k       = `(/usr/bin/arch -k) 2>/dev/null       || echo unknown`
+/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown`
+hostinfo               = `(hostinfo) 2>/dev/null               || echo unknown`
+/bin/machine           = `(/bin/machine) 2>/dev/null           || echo unknown`
+/usr/bin/oslevel       = `(/usr/bin/oslevel) 2>/dev/null       || echo unknown`
+/bin/universe          = `(/bin/universe) 2>/dev/null          || echo unknown`
+
+_ASUNAME
+
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  echo "PATH: $as_dir"
+done
+
+} >&5
+
+cat >&5 <<_ACEOF
+
+
+## ----------- ##
+## Core tests. ##
+## ----------- ##
+
+_ACEOF
+
+
+# Keep a trace of the command line.
+# Strip out --no-create and --no-recursion so they do not pile up.
+# Strip out --silent because we don't want to record it for future runs.
+# Also quote any args containing shell meta-characters.
+# Make two passes to allow for proper duplicate-argument suppression.
+ac_configure_args=
+ac_configure_args0=
+ac_configure_args1=
+ac_sep=
+ac_must_keep_next=false
+for ac_pass in 1 2
+do
+  for ac_arg
+  do
+    case $ac_arg in
+    -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;;
+    -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+    | -silent | --silent | --silen | --sile | --sil)
+      continue ;;
+    *" "*|*"   "*|*[\[\]\~\#\$\^\&\*\(\)\{\}\\\|\;\<\>\?\"\']*)
+      ac_arg=`echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;;
+    esac
+    case $ac_pass in
+    1) ac_configure_args0="$ac_configure_args0 '$ac_arg'" ;;
+    2)
+      ac_configure_args1="$ac_configure_args1 '$ac_arg'"
+      if test $ac_must_keep_next = true; then
+       ac_must_keep_next=false # Got value, back to normal.
+      else
+       case $ac_arg in
+         *=* | --config-cache | -C | -disable-* | --disable-* \
+         | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \
+         | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \
+         | -with-* | --with-* | -without-* | --without-* | --x)
+           case "$ac_configure_args0 " in
+             "$ac_configure_args1"*" '$ac_arg' "* ) continue ;;
+           esac
+           ;;
+         -* ) ac_must_keep_next=true ;;
+       esac
+      fi
+      ac_configure_args="$ac_configure_args$ac_sep'$ac_arg'"
+      # Get rid of the leading space.
+      ac_sep=" "
+      ;;
+    esac
+  done
+done
+$as_unset ac_configure_args0 || test "${ac_configure_args0+set}" != set || { ac_configure_args0=; export ac_configure_args0; }
+$as_unset ac_configure_args1 || test "${ac_configure_args1+set}" != set || { ac_configure_args1=; export ac_configure_args1; }
+
+# When interrupted or exit'd, cleanup temporary files, and complete
+# config.log.  We remove comments because anyway the quotes in there
+# would cause problems or look ugly.
+# WARNING: Be sure not to use single quotes in there, as some shells,
+# such as our DU 5.0 friend, will then `close' the trap.
+trap 'exit_status=$?
+  # Save into config.log some information that might help in debugging.
+  {
+    echo
+
+    cat <<\_ASBOX
+## ---------------- ##
+## Cache variables. ##
+## ---------------- ##
+_ASBOX
+    echo
+    # The following way of writing the cache mishandles newlines in values,
+{
+  (set) 2>&1 |
+    case `(ac_space='"'"' '"'"'; set | grep ac_space) 2>&1` in
+    *ac_space=\ *)
+      sed -n \
+       "s/'"'"'/'"'"'\\\\'"'"''"'"'/g;
+         s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='"'"'\\2'"'"'/p"
+      ;;
+    *)
+      sed -n \
+       "s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1=\\2/p"
+      ;;
+    esac;
+}
+    echo
+
+    cat <<\_ASBOX
+## ----------------- ##
+## Output variables. ##
+## ----------------- ##
+_ASBOX
+    echo
+    for ac_var in $ac_subst_vars
+    do
+      eval ac_val=$`echo $ac_var`
+      echo "$ac_var='"'"'$ac_val'"'"'"
+    done | sort
+    echo
+
+    if test -n "$ac_subst_files"; then
+      cat <<\_ASBOX
+## ------------- ##
+## Output files. ##
+## ------------- ##
+_ASBOX
+      echo
+      for ac_var in $ac_subst_files
+      do
+       eval ac_val=$`echo $ac_var`
+       echo "$ac_var='"'"'$ac_val'"'"'"
+      done | sort
+      echo
+    fi
+
+    if test -s confdefs.h; then
+      cat <<\_ASBOX
+## ----------- ##
+## confdefs.h. ##
+## ----------- ##
+_ASBOX
+      echo
+      sed "/^$/d" confdefs.h | sort
+      echo
+    fi
+    test "$ac_signal" != 0 &&
+      echo "$as_me: caught signal $ac_signal"
+    echo "$as_me: exit $exit_status"
+  } >&5
+  rm -f core *.core &&
+  rm -rf conftest* confdefs* conf$$* $ac_clean_files &&
+    exit $exit_status
+     ' 0
+for ac_signal in 1 2 13 15; do
+  trap 'ac_signal='$ac_signal'; { (exit 1); exit 1; }' $ac_signal
+done
+ac_signal=0
+
+# confdefs.h avoids OS command line length limits that DEFS can exceed.
+rm -rf conftest* confdefs.h
+# AIX cpp loses on an empty file, so make sure it contains at least a newline.
+echo >confdefs.h
+
+# Predefined preprocessor variables.
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_NAME "$PACKAGE_NAME"
+_ACEOF
+
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_TARNAME "$PACKAGE_TARNAME"
+_ACEOF
+
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_VERSION "$PACKAGE_VERSION"
+_ACEOF
+
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_STRING "$PACKAGE_STRING"
+_ACEOF
+
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT"
+_ACEOF
+
+
+# Let the site file select an alternate cache file if it wants to.
+# Prefer explicitly selected file to automatically selected ones.
+if test -z "$CONFIG_SITE"; then
+  if test "x$prefix" != xNONE; then
+    CONFIG_SITE="$prefix/share/config.site $prefix/etc/config.site"
+  else
+    CONFIG_SITE="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site"
+  fi
+fi
+for ac_site_file in $CONFIG_SITE; do
+  if test -r "$ac_site_file"; then
+    { echo "$as_me:$LINENO: loading site script $ac_site_file" >&5
+echo "$as_me: loading site script $ac_site_file" >&6;}
+    sed 's/^/| /' "$ac_site_file" >&5
+    . "$ac_site_file"
+  fi
+done
+
+if test -r "$cache_file"; then
+  # Some versions of bash will fail to source /dev/null (special
+  # files actually), so we avoid doing that.
+  if test -f "$cache_file"; then
+    { echo "$as_me:$LINENO: loading cache $cache_file" >&5
+echo "$as_me: loading cache $cache_file" >&6;}
+    case $cache_file in
+      [\\/]* | ?:[\\/]* ) . $cache_file;;
+      *)                      . ./$cache_file;;
+    esac
+  fi
+else
+  { echo "$as_me:$LINENO: creating cache $cache_file" >&5
+echo "$as_me: creating cache $cache_file" >&6;}
+  >$cache_file
+fi
+
+# Check that the precious variables saved in the cache have kept the same
+# value.
+ac_cache_corrupted=false
+for ac_var in `(set) 2>&1 |
+              sed -n 's/^ac_env_\([a-zA-Z_0-9]*\)_set=.*/\1/p'`; do
+  eval ac_old_set=\$ac_cv_env_${ac_var}_set
+  eval ac_new_set=\$ac_env_${ac_var}_set
+  eval ac_old_val="\$ac_cv_env_${ac_var}_value"
+  eval ac_new_val="\$ac_env_${ac_var}_value"
+  case $ac_old_set,$ac_new_set in
+    set,)
+      { echo "$as_me:$LINENO: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5
+echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;}
+      ac_cache_corrupted=: ;;
+    ,set)
+      { echo "$as_me:$LINENO: error: \`$ac_var' was not set in the previous run" >&5
+echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;}
+      ac_cache_corrupted=: ;;
+    ,);;
+    *)
+      if test "x$ac_old_val" != "x$ac_new_val"; then
+       { echo "$as_me:$LINENO: error: \`$ac_var' has changed since the previous run:" >&5
+echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;}
+       { echo "$as_me:$LINENO:   former value:  $ac_old_val" >&5
+echo "$as_me:   former value:  $ac_old_val" >&2;}
+       { echo "$as_me:$LINENO:   current value: $ac_new_val" >&5
+echo "$as_me:   current value: $ac_new_val" >&2;}
+       ac_cache_corrupted=:
+      fi;;
+  esac
+  # Pass precious variables to config.status.
+  if test "$ac_new_set" = set; then
+    case $ac_new_val in
+    *" "*|*"   "*|*[\[\]\~\#\$\^\&\*\(\)\{\}\\\|\;\<\>\?\"\']*)
+      ac_arg=$ac_var=`echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;;
+    *) ac_arg=$ac_var=$ac_new_val ;;
+    esac
+    case " $ac_configure_args " in
+      *" '$ac_arg' "*) ;; # Avoid dups.  Use of quotes ensures accuracy.
+      *) ac_configure_args="$ac_configure_args '$ac_arg'" ;;
+    esac
+  fi
+done
+if $ac_cache_corrupted; then
+  { echo "$as_me:$LINENO: error: changes in the environment can compromise the build" >&5
+echo "$as_me: error: changes in the environment can compromise the build" >&2;}
+  { { echo "$as_me:$LINENO: error: run \`make distclean' and/or \`rm $cache_file' and start over" >&5
+echo "$as_me: error: run \`make distclean' and/or \`rm $cache_file' and start over" >&2;}
+   { (exit 1); exit 1; }; }
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+SMART_CFLAGS=
+if test x$with_rlm_otp != xno; then
+
+
+               ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args.
+set dummy ${ac_tool_prefix}gcc; ac_word=$2
+echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6
+if test "${ac_cv_prog_CC+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  for ac_exec_ext in '' $ac_executable_extensions; do
+  if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_CC="${ac_tool_prefix}gcc"
+    echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+done
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  echo "$as_me:$LINENO: result: $CC" >&5
+echo "${ECHO_T}$CC" >&6
+else
+  echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6
+fi
+
+fi
+if test -z "$ac_cv_prog_CC"; then
+  ac_ct_CC=$CC
+  # Extract the first word of "gcc", so it can be a program name with args.
+set dummy gcc; ac_word=$2
+echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6
+if test "${ac_cv_prog_ac_ct_CC+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  if test -n "$ac_ct_CC"; then
+  ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  for ac_exec_ext in '' $ac_executable_extensions; do
+  if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_ac_ct_CC="gcc"
+    echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+done
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+  echo "$as_me:$LINENO: result: $ac_ct_CC" >&5
+echo "${ECHO_T}$ac_ct_CC" >&6
+else
+  echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6
+fi
+
+  CC=$ac_ct_CC
+else
+  CC="$ac_cv_prog_CC"
+fi
+
+if test -z "$CC"; then
+  if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args.
+set dummy ${ac_tool_prefix}cc; ac_word=$2
+echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6
+if test "${ac_cv_prog_CC+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  for ac_exec_ext in '' $ac_executable_extensions; do
+  if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_CC="${ac_tool_prefix}cc"
+    echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+done
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  echo "$as_me:$LINENO: result: $CC" >&5
+echo "${ECHO_T}$CC" >&6
+else
+  echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6
+fi
+
+fi
+if test -z "$ac_cv_prog_CC"; then
+  ac_ct_CC=$CC
+  # Extract the first word of "cc", so it can be a program name with args.
+set dummy cc; ac_word=$2
+echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6
+if test "${ac_cv_prog_ac_ct_CC+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  if test -n "$ac_ct_CC"; then
+  ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  for ac_exec_ext in '' $ac_executable_extensions; do
+  if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_ac_ct_CC="cc"
+    echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+done
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+  echo "$as_me:$LINENO: result: $ac_ct_CC" >&5
+echo "${ECHO_T}$ac_ct_CC" >&6
+else
+  echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6
+fi
+
+  CC=$ac_ct_CC
+else
+  CC="$ac_cv_prog_CC"
+fi
+
+fi
+if test -z "$CC"; then
+  # Extract the first word of "cc", so it can be a program name with args.
+set dummy cc; ac_word=$2
+echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6
+if test "${ac_cv_prog_CC+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+  ac_prog_rejected=no
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  for ac_exec_ext in '' $ac_executable_extensions; do
+  if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then
+       ac_prog_rejected=yes
+       continue
+     fi
+    ac_cv_prog_CC="cc"
+    echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+done
+
+if test $ac_prog_rejected = yes; then
+  # We found a bogon in the path, so make sure we never use it.
+  set dummy $ac_cv_prog_CC
+  shift
+  if test $# != 0; then
+    # We chose a different compiler from the bogus one.
+    # However, it has the same basename, so the bogon will be chosen
+    # first if we set CC to just the basename; use the full file name.
+    shift
+    ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@"
+  fi
+fi
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  echo "$as_me:$LINENO: result: $CC" >&5
+echo "${ECHO_T}$CC" >&6
+else
+  echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6
+fi
+
+fi
+if test -z "$CC"; then
+  if test -n "$ac_tool_prefix"; then
+  for ac_prog in cl
+  do
+    # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
+set dummy $ac_tool_prefix$ac_prog; ac_word=$2
+echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6
+if test "${ac_cv_prog_CC+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  for ac_exec_ext in '' $ac_executable_extensions; do
+  if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_CC="$ac_tool_prefix$ac_prog"
+    echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+done
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  echo "$as_me:$LINENO: result: $CC" >&5
+echo "${ECHO_T}$CC" >&6
+else
+  echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6
+fi
+
+    test -n "$CC" && break
+  done
+fi
+if test -z "$CC"; then
+  ac_ct_CC=$CC
+  for ac_prog in cl
+do
+  # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6
+if test "${ac_cv_prog_ac_ct_CC+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  if test -n "$ac_ct_CC"; then
+  ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  for ac_exec_ext in '' $ac_executable_extensions; do
+  if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_ac_ct_CC="$ac_prog"
+    echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+done
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+  echo "$as_me:$LINENO: result: $ac_ct_CC" >&5
+echo "${ECHO_T}$ac_ct_CC" >&6
+else
+  echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6
+fi
+
+  test -n "$ac_ct_CC" && break
+done
+
+  CC=$ac_ct_CC
+fi
+
+fi
+
+
+test -z "$CC" && { { echo "$as_me:$LINENO: error: no acceptable C compiler found in \$PATH
+See \`config.log' for more details." >&5
+echo "$as_me: error: no acceptable C compiler found in \$PATH
+See \`config.log' for more details." >&2;}
+   { (exit 1); exit 1; }; }
+
+# Provide some information about the compiler.
+echo "$as_me:$LINENO:" \
+     "checking for C compiler version" >&5
+ac_compiler=`set X $ac_compile; echo $2`
+{ (eval echo "$as_me:$LINENO: \"$ac_compiler --version </dev/null >&5\"") >&5
+  (eval $ac_compiler --version </dev/null >&5) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }
+{ (eval echo "$as_me:$LINENO: \"$ac_compiler -v </dev/null >&5\"") >&5
+  (eval $ac_compiler -v </dev/null >&5) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }
+{ (eval echo "$as_me:$LINENO: \"$ac_compiler -V </dev/null >&5\"") >&5
+  (eval $ac_compiler -V </dev/null >&5) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }
+
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+ac_clean_files_save=$ac_clean_files
+ac_clean_files="$ac_clean_files a.out a.exe b.out"
+# Try to create an executable without -o first, disregard a.out.
+# It will help us diagnose broken compilers, and finding out an intuition
+# of exeext.
+echo "$as_me:$LINENO: checking for C compiler default output file name" >&5
+echo $ECHO_N "checking for C compiler default output file name... $ECHO_C" >&6
+ac_link_default=`echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'`
+if { (eval echo "$as_me:$LINENO: \"$ac_link_default\"") >&5
+  (eval $ac_link_default) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; then
+  # Find the output, starting from the most likely.  This scheme is
+# not robust to junk in `.', hence go to wildcards (a.*) only as a last
+# resort.
+
+# Be careful to initialize this variable, since it used to be cached.
+# Otherwise an old cache value of `no' led to `EXEEXT = no' in a Makefile.
+ac_cv_exeext=
+# b.out is created by i960 compilers.
+for ac_file in a_out.exe a.exe conftest.exe a.out conftest a.* conftest.* b.out
+do
+  test -f "$ac_file" || continue
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.o | *.obj )
+       ;;
+    conftest.$ac_ext )
+       # This is the source file.
+       ;;
+    [ab].out )
+       # We found the default executable, but exeext='' is most
+       # certainly right.
+       break;;
+    *.* )
+       ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
+       # FIXME: I believe we export ac_cv_exeext for Libtool,
+       # but it would be cool to find out if it's true.  Does anybody
+       # maintain Libtool? --akim.
+       export ac_cv_exeext
+       break;;
+    * )
+       break;;
+  esac
+done
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+{ { echo "$as_me:$LINENO: error: C compiler cannot create executables
+See \`config.log' for more details." >&5
+echo "$as_me: error: C compiler cannot create executables
+See \`config.log' for more details." >&2;}
+   { (exit 77); exit 77; }; }
+fi
+
+ac_exeext=$ac_cv_exeext
+echo "$as_me:$LINENO: result: $ac_file" >&5
+echo "${ECHO_T}$ac_file" >&6
+
+# Check the compiler produces executables we can run.  If not, either
+# the compiler is broken, or we cross compile.
+echo "$as_me:$LINENO: checking whether the C compiler works" >&5
+echo $ECHO_N "checking whether the C compiler works... $ECHO_C" >&6
+# FIXME: These cross compiler hacks should be removed for Autoconf 3.0
+# If not cross compiling, check that we can run a simple program.
+if test "$cross_compiling" != yes; then
+  if { ac_try='./$ac_file'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; }; then
+    cross_compiling=no
+  else
+    if test "$cross_compiling" = maybe; then
+       cross_compiling=yes
+    else
+       { { echo "$as_me:$LINENO: error: cannot run C compiled programs.
+If you meant to cross compile, use \`--host'.
+See \`config.log' for more details." >&5
+echo "$as_me: error: cannot run C compiled programs.
+If you meant to cross compile, use \`--host'.
+See \`config.log' for more details." >&2;}
+   { (exit 1); exit 1; }; }
+    fi
+  fi
+fi
+echo "$as_me:$LINENO: result: yes" >&5
+echo "${ECHO_T}yes" >&6
+
+rm -f a.out a.exe conftest$ac_cv_exeext b.out
+ac_clean_files=$ac_clean_files_save
+# Check the compiler produces executables we can run.  If not, either
+# the compiler is broken, or we cross compile.
+echo "$as_me:$LINENO: checking whether we are cross compiling" >&5
+echo $ECHO_N "checking whether we are cross compiling... $ECHO_C" >&6
+echo "$as_me:$LINENO: result: $cross_compiling" >&5
+echo "${ECHO_T}$cross_compiling" >&6
+
+echo "$as_me:$LINENO: checking for suffix of executables" >&5
+echo $ECHO_N "checking for suffix of executables... $ECHO_C" >&6
+if { (eval echo "$as_me:$LINENO: \"$ac_link\"") >&5
+  (eval $ac_link) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; then
+  # If both `conftest.exe' and `conftest' are `present' (well, observable)
+# catch `conftest.exe'.  For instance with Cygwin, `ls conftest' will
+# work properly (i.e., refer to `conftest.exe'), while it won't with
+# `rm'.
+for ac_file in conftest.exe conftest conftest.*; do
+  test -f "$ac_file" || continue
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.o | *.obj ) ;;
+    *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
+         export ac_cv_exeext
+         break;;
+    * ) break;;
+  esac
+done
+else
+  { { echo "$as_me:$LINENO: error: cannot compute suffix of executables: cannot compile and link
+See \`config.log' for more details." >&5
+echo "$as_me: error: cannot compute suffix of executables: cannot compile and link
+See \`config.log' for more details." >&2;}
+   { (exit 1); exit 1; }; }
+fi
+
+rm -f conftest$ac_cv_exeext
+echo "$as_me:$LINENO: result: $ac_cv_exeext" >&5
+echo "${ECHO_T}$ac_cv_exeext" >&6
+
+rm -f conftest.$ac_ext
+EXEEXT=$ac_cv_exeext
+ac_exeext=$EXEEXT
+echo "$as_me:$LINENO: checking for suffix of object files" >&5
+echo $ECHO_N "checking for suffix of object files... $ECHO_C" >&6
+if test "${ac_cv_objext+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.o conftest.obj
+if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
+  (eval $ac_compile) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; then
+  for ac_file in `(ls conftest.o conftest.obj; ls conftest.*) 2>/dev/null`; do
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg ) ;;
+    *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'`
+       break;;
+  esac
+done
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+{ { echo "$as_me:$LINENO: error: cannot compute suffix of object files: cannot compile
+See \`config.log' for more details." >&5
+echo "$as_me: error: cannot compute suffix of object files: cannot compile
+See \`config.log' for more details." >&2;}
+   { (exit 1); exit 1; }; }
+fi
+
+rm -f conftest.$ac_cv_objext conftest.$ac_ext
+fi
+echo "$as_me:$LINENO: result: $ac_cv_objext" >&5
+echo "${ECHO_T}$ac_cv_objext" >&6
+OBJEXT=$ac_cv_objext
+ac_objext=$OBJEXT
+echo "$as_me:$LINENO: checking whether we are using the GNU C compiler" >&5
+echo $ECHO_N "checking whether we are using the GNU C compiler... $ECHO_C" >&6
+if test "${ac_cv_c_compiler_gnu+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+
+int
+main ()
+{
+#ifndef __GNUC__
+       choke me
+#endif
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
+  (eval $ac_compile) 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } &&
+        { ac_try='test -z "$ac_c_werror_flag"
+                        || test ! -s conftest.err'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; } &&
+        { ac_try='test -s conftest.$ac_objext'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; }; then
+  ac_compiler_gnu=yes
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ac_compiler_gnu=no
+fi
+rm -f conftest.err conftest.$ac_objext conftest.$ac_ext
+ac_cv_c_compiler_gnu=$ac_compiler_gnu
+
+fi
+echo "$as_me:$LINENO: result: $ac_cv_c_compiler_gnu" >&5
+echo "${ECHO_T}$ac_cv_c_compiler_gnu" >&6
+GCC=`test $ac_compiler_gnu = yes && echo yes`
+ac_test_CFLAGS=${CFLAGS+set}
+ac_save_CFLAGS=$CFLAGS
+CFLAGS="-g"
+echo "$as_me:$LINENO: checking whether $CC accepts -g" >&5
+echo $ECHO_N "checking whether $CC accepts -g... $ECHO_C" >&6
+if test "${ac_cv_prog_cc_g+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
+  (eval $ac_compile) 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } &&
+        { ac_try='test -z "$ac_c_werror_flag"
+                        || test ! -s conftest.err'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; } &&
+        { ac_try='test -s conftest.$ac_objext'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; }; then
+  ac_cv_prog_cc_g=yes
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ac_cv_prog_cc_g=no
+fi
+rm -f conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+echo "$as_me:$LINENO: result: $ac_cv_prog_cc_g" >&5
+echo "${ECHO_T}$ac_cv_prog_cc_g" >&6
+if test "$ac_test_CFLAGS" = set; then
+  CFLAGS=$ac_save_CFLAGS
+elif test $ac_cv_prog_cc_g = yes; then
+  if test "$GCC" = yes; then
+    CFLAGS="-g -O2"
+  else
+    CFLAGS="-g"
+  fi
+else
+  if test "$GCC" = yes; then
+    CFLAGS="-O2"
+  else
+    CFLAGS=
+  fi
+fi
+echo "$as_me:$LINENO: checking for $CC option to accept ANSI C" >&5
+echo $ECHO_N "checking for $CC option to accept ANSI C... $ECHO_C" >&6
+if test "${ac_cv_prog_cc_stdc+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  ac_cv_prog_cc_stdc=no
+ac_save_CC=$CC
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+#include <stdarg.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+/* Most of the following tests are stolen from RCS 5.7's src/conf.sh.  */
+struct buf { int x; };
+FILE * (*rcsopen) (struct buf *, struct stat *, int);
+static char *e (p, i)
+     char **p;
+     int i;
+{
+  return p[i];
+}
+static char *f (char * (*g) (char **, int), char **p, ...)
+{
+  char *s;
+  va_list v;
+  va_start (v,p);
+  s = g (p, va_arg (v,int));
+  va_end (v);
+  return s;
+}
+
+/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default.  It has
+   function prototypes and stuff, but not '\xHH' hex character constants.
+   These don't provoke an error unfortunately, instead are silently treated
+   as 'x'.  The following induces an error, until -std1 is added to get
+   proper ANSI mode.  Curiously '\x00'!='x' always comes out true, for an
+   array size at least.  It's necessary to write '\x00'==0 to get something
+   that's true only with -std1.  */
+int osf4_cc_array ['\x00' == 0 ? 1 : -1];
+
+int test (int i, double x);
+struct s1 {int (*f) (int a);};
+struct s2 {int (*f) (double a);};
+int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int);
+int argc;
+char **argv;
+int
+main ()
+{
+return f (e, argv, 0) != argv[0]  ||  f (e, argv, 1) != argv[1];
+  ;
+  return 0;
+}
+_ACEOF
+# Don't try gcc -ansi; that turns off useful extensions and
+# breaks some systems' header files.
+# AIX                  -qlanglvl=ansi
+# Ultrix and OSF/1     -std1
+# HP-UX 10.20 and later        -Ae
+# HP-UX older versions -Aa -D_HPUX_SOURCE
+# SVR4                 -Xc -D__EXTENSIONS__
+for ac_arg in "" -qlanglvl=ansi -std1 -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__"
+do
+  CC="$ac_save_CC $ac_arg"
+  rm -f conftest.$ac_objext
+if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
+  (eval $ac_compile) 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } &&
+        { ac_try='test -z "$ac_c_werror_flag"
+                        || test ! -s conftest.err'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; } &&
+        { ac_try='test -s conftest.$ac_objext'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; }; then
+  ac_cv_prog_cc_stdc=$ac_arg
+break
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+fi
+rm -f conftest.err conftest.$ac_objext
+done
+rm -f conftest.$ac_ext conftest.$ac_objext
+CC=$ac_save_CC
+
+fi
+
+case "x$ac_cv_prog_cc_stdc" in
+  x|xno)
+    echo "$as_me:$LINENO: result: none needed" >&5
+echo "${ECHO_T}none needed" >&6 ;;
+  *)
+    echo "$as_me:$LINENO: result: $ac_cv_prog_cc_stdc" >&5
+echo "${ECHO_T}$ac_cv_prog_cc_stdc" >&6
+    CC="$CC $ac_cv_prog_cc_stdc" ;;
+esac
+
+# Some people use a C++ compiler to compile C.  Since we use `exit',
+# in C++ we need to declare it.  In case someone uses the same compiler
+# for both compiling C and C++ we need to have the C++ compiler decide
+# the declaration of exit, since it's the most demanding environment.
+cat >conftest.$ac_ext <<_ACEOF
+#ifndef __cplusplus
+  choke me
+#endif
+_ACEOF
+rm -f conftest.$ac_objext
+if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
+  (eval $ac_compile) 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } &&
+        { ac_try='test -z "$ac_c_werror_flag"
+                        || test ! -s conftest.err'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; } &&
+        { ac_try='test -s conftest.$ac_objext'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; }; then
+  for ac_declaration in \
+   '' \
+   'extern "C" void std::exit (int) throw (); using std::exit;' \
+   'extern "C" void std::exit (int); using std::exit;' \
+   'extern "C" void exit (int) throw ();' \
+   'extern "C" void exit (int);' \
+   'void exit (int);'
+do
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_declaration
+#include <stdlib.h>
+int
+main ()
+{
+exit (42);
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
+  (eval $ac_compile) 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } &&
+        { ac_try='test -z "$ac_c_werror_flag"
+                        || test ! -s conftest.err'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; } &&
+        { ac_try='test -s conftest.$ac_objext'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; }; then
+  :
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+continue
+fi
+rm -f conftest.err conftest.$ac_objext conftest.$ac_ext
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_declaration
+int
+main ()
+{
+exit (42);
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
+  (eval $ac_compile) 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } &&
+        { ac_try='test -z "$ac_c_werror_flag"
+                        || test ! -s conftest.err'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; } &&
+        { ac_try='test -s conftest.$ac_objext'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; }; then
+  break
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+fi
+rm -f conftest.err conftest.$ac_objext conftest.$ac_ext
+done
+rm -f conftest*
+if test -n "$ac_declaration"; then
+  echo '#ifdef __cplusplus' >>confdefs.h
+  echo $ac_declaration      >>confdefs.h
+  echo '#endif'             >>confdefs.h
+fi
+
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+fi
+rm -f conftest.err conftest.$ac_objext conftest.$ac_ext
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+echo "$as_me:$LINENO: checking how to run the C preprocessor" >&5
+echo $ECHO_N "checking how to run the C preprocessor... $ECHO_C" >&6
+# On Suns, sometimes $CPP names a directory.
+if test -n "$CPP" && test -d "$CPP"; then
+  CPP=
+fi
+if test -z "$CPP"; then
+  if test "${ac_cv_prog_CPP+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+      # Double quotes because CPP needs to be expanded
+    for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp"
+    do
+      ac_preproc_ok=false
+for ac_c_preproc_warn_flag in '' yes
+do
+  # Use a header file that comes with gcc, so configuring glibc
+  # with a fresh cross-compiler works.
+  # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+  # <limits.h> exists even on freestanding compilers.
+  # On the NeXT, cc -E runs the code through the compiler's parser,
+  # not just through cpp. "Syntax error" is here to catch this case.
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+                    Syntax error
+_ACEOF
+if { (eval echo "$as_me:$LINENO: \"$ac_cpp conftest.$ac_ext\"") >&5
+  (eval $ac_cpp conftest.$ac_ext) 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } >/dev/null; then
+  if test -s conftest.err; then
+    ac_cpp_err=$ac_c_preproc_warn_flag
+    ac_cpp_err=$ac_cpp_err$ac_c_werror_flag
+  else
+    ac_cpp_err=
+  fi
+else
+  ac_cpp_err=yes
+fi
+if test -z "$ac_cpp_err"; then
+  :
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+  # Broken: fails on valid input.
+continue
+fi
+rm -f conftest.err conftest.$ac_ext
+
+  # OK, works on sane cases.  Now check whether non-existent headers
+  # can be detected and how.
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+#include <ac_nonexistent.h>
+_ACEOF
+if { (eval echo "$as_me:$LINENO: \"$ac_cpp conftest.$ac_ext\"") >&5
+  (eval $ac_cpp conftest.$ac_ext) 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } >/dev/null; then
+  if test -s conftest.err; then
+    ac_cpp_err=$ac_c_preproc_warn_flag
+    ac_cpp_err=$ac_cpp_err$ac_c_werror_flag
+  else
+    ac_cpp_err=
+  fi
+else
+  ac_cpp_err=yes
+fi
+if test -z "$ac_cpp_err"; then
+  # Broken: success on invalid input.
+continue
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+  # Passes both tests.
+ac_preproc_ok=:
+break
+fi
+rm -f conftest.err conftest.$ac_ext
+
+done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.err conftest.$ac_ext
+if $ac_preproc_ok; then
+  break
+fi
+
+    done
+    ac_cv_prog_CPP=$CPP
+
+fi
+  CPP=$ac_cv_prog_CPP
+else
+  ac_cv_prog_CPP=$CPP
+fi
+echo "$as_me:$LINENO: result: $CPP" >&5
+echo "${ECHO_T}$CPP" >&6
+ac_preproc_ok=false
+for ac_c_preproc_warn_flag in '' yes
+do
+  # Use a header file that comes with gcc, so configuring glibc
+  # with a fresh cross-compiler works.
+  # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+  # <limits.h> exists even on freestanding compilers.
+  # On the NeXT, cc -E runs the code through the compiler's parser,
+  # not just through cpp. "Syntax error" is here to catch this case.
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+                    Syntax error
+_ACEOF
+if { (eval echo "$as_me:$LINENO: \"$ac_cpp conftest.$ac_ext\"") >&5
+  (eval $ac_cpp conftest.$ac_ext) 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } >/dev/null; then
+  if test -s conftest.err; then
+    ac_cpp_err=$ac_c_preproc_warn_flag
+    ac_cpp_err=$ac_cpp_err$ac_c_werror_flag
+  else
+    ac_cpp_err=
+  fi
+else
+  ac_cpp_err=yes
+fi
+if test -z "$ac_cpp_err"; then
+  :
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+  # Broken: fails on valid input.
+continue
+fi
+rm -f conftest.err conftest.$ac_ext
+
+  # OK, works on sane cases.  Now check whether non-existent headers
+  # can be detected and how.
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+#include <ac_nonexistent.h>
+_ACEOF
+if { (eval echo "$as_me:$LINENO: \"$ac_cpp conftest.$ac_ext\"") >&5
+  (eval $ac_cpp conftest.$ac_ext) 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } >/dev/null; then
+  if test -s conftest.err; then
+    ac_cpp_err=$ac_c_preproc_warn_flag
+    ac_cpp_err=$ac_cpp_err$ac_c_werror_flag
+  else
+    ac_cpp_err=
+  fi
+else
+  ac_cpp_err=yes
+fi
+if test -z "$ac_cpp_err"; then
+  # Broken: success on invalid input.
+continue
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+  # Passes both tests.
+ac_preproc_ok=:
+break
+fi
+rm -f conftest.err conftest.$ac_ext
+
+done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.err conftest.$ac_ext
+if $ac_preproc_ok; then
+  :
+else
+  { { echo "$as_me:$LINENO: error: C preprocessor \"$CPP\" fails sanity check
+See \`config.log' for more details." >&5
+echo "$as_me: error: C preprocessor \"$CPP\" fails sanity check
+See \`config.log' for more details." >&2;}
+   { (exit 1); exit 1; }; }
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+echo "$as_me:$LINENO: checking for egrep" >&5
+echo $ECHO_N "checking for egrep... $ECHO_C" >&6
+if test "${ac_cv_prog_egrep+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  if echo a | (grep -E '(a|b)') >/dev/null 2>&1
+    then ac_cv_prog_egrep='grep -E'
+    else ac_cv_prog_egrep='egrep'
+    fi
+fi
+echo "$as_me:$LINENO: result: $ac_cv_prog_egrep" >&5
+echo "${ECHO_T}$ac_cv_prog_egrep" >&6
+ EGREP=$ac_cv_prog_egrep
+
+
+echo "$as_me:$LINENO: checking for ANSI C header files" >&5
+echo $ECHO_N "checking for ANSI C header files... $ECHO_C" >&6
+if test "${ac_cv_header_stdc+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <float.h>
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
+  (eval $ac_compile) 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } &&
+        { ac_try='test -z "$ac_c_werror_flag"
+                        || test ! -s conftest.err'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; } &&
+        { ac_try='test -s conftest.$ac_objext'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; }; then
+  ac_cv_header_stdc=yes
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ac_cv_header_stdc=no
+fi
+rm -f conftest.err conftest.$ac_objext conftest.$ac_ext
+
+if test $ac_cv_header_stdc = yes; then
+  # SunOS 4.x string.h does not declare mem*, contrary to ANSI.
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+#include <string.h>
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+  $EGREP "memchr" >/dev/null 2>&1; then
+  :
+else
+  ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+fi
+
+if test $ac_cv_header_stdc = yes; then
+  # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI.
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+#include <stdlib.h>
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+  $EGREP "free" >/dev/null 2>&1; then
+  :
+else
+  ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+fi
+
+if test $ac_cv_header_stdc = yes; then
+  # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi.
+  if test "$cross_compiling" = yes; then
+  :
+else
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+#include <ctype.h>
+#if ((' ' & 0x0FF) == 0x020)
+# define ISLOWER(c) ('a' <= (c) && (c) <= 'z')
+# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c))
+#else
+# define ISLOWER(c) \
+                  (('a' <= (c) && (c) <= 'i') \
+                    || ('j' <= (c) && (c) <= 'r') \
+                    || ('s' <= (c) && (c) <= 'z'))
+# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c))
+#endif
+
+#define XOR(e, f) (((e) && !(f)) || (!(e) && (f)))
+int
+main ()
+{
+  int i;
+  for (i = 0; i < 256; i++)
+    if (XOR (islower (i), ISLOWER (i))
+       || toupper (i) != TOUPPER (i))
+      exit(2);
+  exit (0);
+}
+_ACEOF
+rm -f conftest$ac_exeext
+if { (eval echo "$as_me:$LINENO: \"$ac_link\"") >&5
+  (eval $ac_link) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && { ac_try='./conftest$ac_exeext'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; }; then
+  :
+else
+  echo "$as_me: program exited with status $ac_status" >&5
+echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+( exit $ac_status )
+ac_cv_header_stdc=no
+fi
+rm -f core *.core gmon.out bb.out conftest$ac_exeext conftest.$ac_objext conftest.$ac_ext
+fi
+fi
+fi
+echo "$as_me:$LINENO: result: $ac_cv_header_stdc" >&5
+echo "${ECHO_T}$ac_cv_header_stdc" >&6
+if test $ac_cv_header_stdc = yes; then
+
+cat >>confdefs.h <<\_ACEOF
+#define STDC_HEADERS 1
+_ACEOF
+
+fi
+
+# On IRIX 5.3, sys/types and inttypes.h are conflicting.
+
+
+
+
+
+
+
+
+
+for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \
+                 inttypes.h stdint.h unistd.h
+do
+as_ac_Header=`echo "ac_cv_header_$ac_header" | $as_tr_sh`
+echo "$as_me:$LINENO: checking for $ac_header" >&5
+echo $ECHO_N "checking for $ac_header... $ECHO_C" >&6
+if eval "test \"\${$as_ac_Header+set}\" = set"; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_includes_default
+
+#include <$ac_header>
+_ACEOF
+rm -f conftest.$ac_objext
+if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
+  (eval $ac_compile) 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } &&
+        { ac_try='test -z "$ac_c_werror_flag"
+                        || test ! -s conftest.err'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; } &&
+        { ac_try='test -s conftest.$ac_objext'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; }; then
+  eval "$as_ac_Header=yes"
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+eval "$as_ac_Header=no"
+fi
+rm -f conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+echo "$as_me:$LINENO: result: `eval echo '${'$as_ac_Header'}'`" >&5
+echo "${ECHO_T}`eval echo '${'$as_ac_Header'}'`" >&6
+if test `eval echo '${'$as_ac_Header'}'` = yes; then
+  cat >>confdefs.h <<_ACEOF
+#define `echo "HAVE_$ac_header" | $as_tr_cpp` 1
+_ACEOF
+
+fi
+
+done
+
+
+if test "${ac_cv_header_inttypes_h+set}" = set; then
+  echo "$as_me:$LINENO: checking for inttypes.h" >&5
+echo $ECHO_N "checking for inttypes.h... $ECHO_C" >&6
+if test "${ac_cv_header_inttypes_h+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+fi
+echo "$as_me:$LINENO: result: $ac_cv_header_inttypes_h" >&5
+echo "${ECHO_T}$ac_cv_header_inttypes_h" >&6
+else
+  # Is the header compilable?
+echo "$as_me:$LINENO: checking inttypes.h usability" >&5
+echo $ECHO_N "checking inttypes.h usability... $ECHO_C" >&6
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+$ac_includes_default
+#include <inttypes.h>
+_ACEOF
+rm -f conftest.$ac_objext
+if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
+  (eval $ac_compile) 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } &&
+        { ac_try='test -z "$ac_c_werror_flag"
+                        || test ! -s conftest.err'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; } &&
+        { ac_try='test -s conftest.$ac_objext'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; }; then
+  ac_header_compiler=yes
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ac_header_compiler=no
+fi
+rm -f conftest.err conftest.$ac_objext conftest.$ac_ext
+echo "$as_me:$LINENO: result: $ac_header_compiler" >&5
+echo "${ECHO_T}$ac_header_compiler" >&6
+
+# Is the header present?
+echo "$as_me:$LINENO: checking inttypes.h presence" >&5
+echo $ECHO_N "checking inttypes.h presence... $ECHO_C" >&6
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+#include <inttypes.h>
+_ACEOF
+if { (eval echo "$as_me:$LINENO: \"$ac_cpp conftest.$ac_ext\"") >&5
+  (eval $ac_cpp conftest.$ac_ext) 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } >/dev/null; then
+  if test -s conftest.err; then
+    ac_cpp_err=$ac_c_preproc_warn_flag
+    ac_cpp_err=$ac_cpp_err$ac_c_werror_flag
+  else
+    ac_cpp_err=
+  fi
+else
+  ac_cpp_err=yes
+fi
+if test -z "$ac_cpp_err"; then
+  ac_header_preproc=yes
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+  ac_header_preproc=no
+fi
+rm -f conftest.err conftest.$ac_ext
+echo "$as_me:$LINENO: result: $ac_header_preproc" >&5
+echo "${ECHO_T}$ac_header_preproc" >&6
+
+# So?  What about this header?
+case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in
+  yes:no: )
+    { echo "$as_me:$LINENO: WARNING: inttypes.h: accepted by the compiler, rejected by the preprocessor!" >&5
+echo "$as_me: WARNING: inttypes.h: accepted by the compiler, rejected by the preprocessor!" >&2;}
+    { echo "$as_me:$LINENO: WARNING: inttypes.h: proceeding with the compiler's result" >&5
+echo "$as_me: WARNING: inttypes.h: proceeding with the compiler's result" >&2;}
+    ac_header_preproc=yes
+    ;;
+  no:yes:* )
+    { echo "$as_me:$LINENO: WARNING: inttypes.h: present but cannot be compiled" >&5
+echo "$as_me: WARNING: inttypes.h: present but cannot be compiled" >&2;}
+    { echo "$as_me:$LINENO: WARNING: inttypes.h:     check for missing prerequisite headers?" >&5
+echo "$as_me: WARNING: inttypes.h:     check for missing prerequisite headers?" >&2;}
+    { echo "$as_me:$LINENO: WARNING: inttypes.h: see the Autoconf documentation" >&5
+echo "$as_me: WARNING: inttypes.h: see the Autoconf documentation" >&2;}
+    { echo "$as_me:$LINENO: WARNING: inttypes.h:     section \"Present But Cannot Be Compiled\"" >&5
+echo "$as_me: WARNING: inttypes.h:     section \"Present But Cannot Be Compiled\"" >&2;}
+    { echo "$as_me:$LINENO: WARNING: inttypes.h: proceeding with the preprocessor's result" >&5
+echo "$as_me: WARNING: inttypes.h: proceeding with the preprocessor's result" >&2;}
+    { echo "$as_me:$LINENO: WARNING: inttypes.h: in the future, the compiler will take precedence" >&5
+echo "$as_me: WARNING: inttypes.h: in the future, the compiler will take precedence" >&2;}
+    (
+      cat <<\_ASBOX
+## ------------------------------------------ ##
+## Report this to the AC_PACKAGE_NAME lists.  ##
+## ------------------------------------------ ##
+_ASBOX
+    ) |
+      sed "s/^/$as_me: WARNING:     /" >&2
+    ;;
+esac
+echo "$as_me:$LINENO: checking for inttypes.h" >&5
+echo $ECHO_N "checking for inttypes.h... $ECHO_C" >&6
+if test "${ac_cv_header_inttypes_h+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  ac_cv_header_inttypes_h=$ac_header_preproc
+fi
+echo "$as_me:$LINENO: result: $ac_cv_header_inttypes_h" >&5
+echo "${ECHO_T}$ac_cv_header_inttypes_h" >&6
+
+fi
+if test $ac_cv_header_inttypes_h = yes; then
+  :
+else
+   fail="$fail inttypes.h"
+
+fi
+
+
+
+       if test "x$OPENSSL_LIBS" = "x"; then
+               fail="$fail OpenSSL"
+       fi
+
+       targetname=rlm_otp     # keep this!  Don't change!
+else
+       targetname=            # keep this!  Don't change!
+       echo \*\*\* module rlm_otp is disabled.  # keep this!  Don't change!
+fi
+
+if test x"$fail" != x""; then
+       if test x"${enable_strict_dependencies}" = x"yes"; then
+               { { echo "$as_me:$LINENO: error: set --without-rlm_otp to disable it explicitly." >&5
+echo "$as_me: error: set --without-rlm_otp to disable it explicitly." >&2;}
+   { (exit 1); exit 1; }; }
+       else
+               { echo "$as_me:$LINENO: WARNING: silently not building rlm_otp." >&5
+echo "$as_me: WARNING: silently not building rlm_otp." >&2;}
+               { echo "$as_me:$LINENO: WARNING: FAILURE: rlm_otp requires: $fail." >&5
+echo "$as_me: WARNING: FAILURE: rlm_otp requires: $fail." >&2;};
+               targetname=""
+       fi
+fi
+
+otp_cflags="$otp_cflags -DOTP_MODULE_NAME=\\\"rlm_otp\\\" $SMART_CFLAGS"
+otp_cflags="$otp_cflags -DFREERADIUS"
+
+
+
+  # keep this!  Don't change!
+          ac_config_files="$ac_config_files Makefile"
+cat >confcache <<\_ACEOF
+# This file is a shell script that caches the results of configure
+# tests run on this system so they can be shared between configure
+# scripts and configure runs, see configure's option --config-cache.
+# It is not useful on other systems.  If it contains results you don't
+# want to keep, you may remove or edit it.
+#
+# config.status only pays attention to the cache file if you give it
+# the --recheck option to rerun configure.
+#
+# `ac_cv_env_foo' variables (set or unset) will be overridden when
+# loading this file, other *unset* `ac_cv_foo' will be assigned the
+# following values.
+
+_ACEOF
+
+# The following way of writing the cache mishandles newlines in values,
+# but we know of no workaround that is simple, portable, and efficient.
+# So, don't put newlines in cache variables' values.
+# Ultrix sh set writes to stderr and can't be redirected directly,
+# and sets the high bit in the cache file unless we assign to the vars.
+{
+  (set) 2>&1 |
+    case `(ac_space=' '; set | grep ac_space) 2>&1` in
+    *ac_space=\ *)
+      # `set' does not quote correctly, so add quotes (double-quote
+      # substitution turns \\\\ into \\, and sed turns \\ into \).
+      sed -n \
+       "s/'/'\\\\''/g;
+         s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p"
+      ;;
+    *)
+      # `set' quotes correctly as required by POSIX, so do not add quotes.
+      sed -n \
+       "s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1=\\2/p"
+      ;;
+    esac;
+} |
+  sed '
+     t clear
+     : clear
+     s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/
+     t end
+     /^ac_cv_env/!s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/
+     : end' >>confcache
+if diff $cache_file confcache >/dev/null 2>&1; then :; else
+  if test -w $cache_file; then
+    test "x$cache_file" != "x/dev/null" && echo "updating cache $cache_file"
+    cat confcache >$cache_file
+  else
+    echo "not updating unwritable cache $cache_file"
+  fi
+fi
+rm -f confcache
+
+test "x$prefix" = xNONE && prefix=$ac_default_prefix
+# Let make expand exec_prefix.
+test "x$exec_prefix" = xNONE && exec_prefix='${prefix}'
+
+# VPATH may cause trouble with some makes, so we remove $(srcdir),
+# ${srcdir} and @srcdir@ from VPATH if srcdir is ".", strip leading and
+# trailing colons and then remove the whole line if VPATH becomes empty
+# (actually we leave an empty line to preserve line numbers).
+if test "x$srcdir" = x.; then
+  ac_vpsub='/^[         ]*VPATH[        ]*=/{
+s/:*\$(srcdir):*/:/;
+s/:*\${srcdir}:*/:/;
+s/:*@srcdir@:*/:/;
+s/^\([^=]*=[    ]*\):*/\1/;
+s/:*$//;
+s/^[^=]*=[      ]*$//;
+}'
+fi
+
+# Transform confdefs.h into DEFS.
+# Protect against shell expansion while executing Makefile rules.
+# Protect against Makefile macro expansion.
+#
+# If the first sed substitution is executed (which looks for macros that
+# take arguments), then we branch to the quote section.  Otherwise,
+# look for a macro that doesn't take arguments.
+cat >confdef2opt.sed <<\_ACEOF
+t clear
+: clear
+s,^[    ]*#[    ]*define[       ][      ]*\([^  (][^    (]*([^)]*)\)[   ]*\(.*\),-D\1=\2,g
+t quote
+s,^[    ]*#[    ]*define[       ][      ]*\([^  ][^     ]*\)[   ]*\(.*\),-D\1=\2,g
+t quote
+d
+: quote
+s,[     `~#$^&*(){}\\|;'"<>?],\\&,g
+s,\[,\\&,g
+s,\],\\&,g
+s,\$,$$,g
+p
+_ACEOF
+# We use echo to avoid assuming a particular line-breaking character.
+# The extra dot is to prevent the shell from consuming trailing
+# line-breaks from the sub-command output.  A line-break within
+# single-quotes doesn't work because, if this script is created in a
+# platform that uses two characters for line-breaks (e.g., DOS), tr
+# would break.
+ac_LF_and_DOT=`echo; echo .`
+DEFS=`sed -n -f confdef2opt.sed confdefs.h | tr "$ac_LF_and_DOT" ' .'`
+rm -f confdef2opt.sed
+
+
+ac_libobjs=
+ac_ltlibobjs=
+for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue
+  # 1. Remove the extension, and $U if already installed.
+  ac_i=`echo "$ac_i" |
+        sed 's/\$U\././;s/\.o$//;s/\.obj$//'`
+  # 2. Add them.
+  ac_libobjs="$ac_libobjs $ac_i\$U.$ac_objext"
+  ac_ltlibobjs="$ac_ltlibobjs $ac_i"'$U.lo'
+done
+LIBOBJS=$ac_libobjs
+
+LTLIBOBJS=$ac_ltlibobjs
+
+
+
+: ${CONFIG_STATUS=./config.status}
+ac_clean_files_save=$ac_clean_files
+ac_clean_files="$ac_clean_files $CONFIG_STATUS"
+{ echo "$as_me:$LINENO: creating $CONFIG_STATUS" >&5
+echo "$as_me: creating $CONFIG_STATUS" >&6;}
+cat >$CONFIG_STATUS <<_ACEOF
+#! $SHELL
+# Generated by $as_me.
+# Run this file to recreate the current configuration.
+# Compiler output produced by configure, useful for debugging
+# configure, is in config.log if it exists.
+
+debug=false
+ac_cs_recheck=false
+ac_cs_silent=false
+SHELL=\${CONFIG_SHELL-$SHELL}
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF
+## --------------------- ##
+## M4sh Initialization.  ##
+## --------------------- ##
+
+# Be Bourne compatible
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
+  emulate sh
+  NULLCMD=:
+  # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
+  # is contrary to our usage.  Disable this feature.
+  alias -g '${1+"$@"}'='"$@"'
+elif test -n "${BASH_VERSION+set}" && (set -o posix) >/dev/null 2>&1; then
+  set -o posix
+fi
+DUALCASE=1; export DUALCASE # for MKS sh
+
+# Support unset when possible.
+if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then
+  as_unset=unset
+else
+  as_unset=false
+fi
+
+
+# Work around bugs in pre-3.0 UWIN ksh.
+$as_unset ENV MAIL MAILPATH
+PS1='$ '
+PS2='> '
+PS4='+ '
+
+# NLS nuisances.
+for as_var in \
+  LANG LANGUAGE LC_ADDRESS LC_ALL LC_COLLATE LC_CTYPE LC_IDENTIFICATION \
+  LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER \
+  LC_TELEPHONE LC_TIME
+do
+  if (set +x; test -z "`(eval $as_var=C; export $as_var) 2>&1`"); then
+    eval $as_var=C; export $as_var
+  else
+    $as_unset $as_var
+  fi
+done
+
+# Required to use basename.
+if expr a : '\(a\)' >/dev/null 2>&1; then
+  as_expr=expr
+else
+  as_expr=false
+fi
+
+if (basename /) >/dev/null 2>&1 && test "X`basename / 2>&1`" = "X/"; then
+  as_basename=basename
+else
+  as_basename=false
+fi
+
+
+# Name of the executable.
+as_me=`$as_basename "$0" ||
+$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
+        X"$0" : 'X\(//\)$' \| \
+        X"$0" : 'X\(/\)$' \| \
+        .     : '\(.\)' 2>/dev/null ||
+echo X/"$0" |
+    sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/; q; }
+         /^X\/\(\/\/\)$/{ s//\1/; q; }
+         /^X\/\(\/\).*/{ s//\1/; q; }
+         s/.*/./; q'`
+
+
+# PATH needs CR, and LINENO needs CR and PATH.
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+  echo "#! /bin/sh" >conf$$.sh
+  echo  "exit 0"   >>conf$$.sh
+  chmod +x conf$$.sh
+  if (PATH="/nonexistent;."; conf$$.sh) >/dev/null 2>&1; then
+    PATH_SEPARATOR=';'
+  else
+    PATH_SEPARATOR=:
+  fi
+  rm -f conf$$.sh
+fi
+
+
+  as_lineno_1=$LINENO
+  as_lineno_2=$LINENO
+  as_lineno_3=`(expr $as_lineno_1 + 1) 2>/dev/null`
+  test "x$as_lineno_1" != "x$as_lineno_2" &&
+  test "x$as_lineno_3"  = "x$as_lineno_2"  || {
+  # Find who we are.  Look in the path if we contain no path at all
+  # relative or not.
+  case $0 in
+    *[\\/]* ) as_myself=$0 ;;
+    *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+done
+
+       ;;
+  esac
+  # We did not find ourselves, most probably we were run as `sh COMMAND'
+  # in which case we are not to be found in the path.
+  if test "x$as_myself" = x; then
+    as_myself=$0
+  fi
+  if test ! -f "$as_myself"; then
+    { { echo "$as_me:$LINENO: error: cannot find myself; rerun with an absolute path" >&5
+echo "$as_me: error: cannot find myself; rerun with an absolute path" >&2;}
+   { (exit 1); exit 1; }; }
+  fi
+  case $CONFIG_SHELL in
+  '')
+    as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  for as_base in sh bash ksh sh5; do
+        case $as_dir in
+        /*)
+          if ("$as_dir/$as_base" -c '
+  as_lineno_1=$LINENO
+  as_lineno_2=$LINENO
+  as_lineno_3=`(expr $as_lineno_1 + 1) 2>/dev/null`
+  test "x$as_lineno_1" != "x$as_lineno_2" &&
+  test "x$as_lineno_3"  = "x$as_lineno_2" ') 2>/dev/null; then
+            $as_unset BASH_ENV || test "${BASH_ENV+set}" != set || { BASH_ENV=; export BASH_ENV; }
+            $as_unset ENV || test "${ENV+set}" != set || { ENV=; export ENV; }
+            CONFIG_SHELL=$as_dir/$as_base
+            export CONFIG_SHELL
+            exec "$CONFIG_SHELL" "$0" ${1+"$@"}
+          fi;;
+        esac
+       done
+done
+;;
+  esac
+
+  # Create $as_me.lineno as a copy of $as_myself, but with $LINENO
+  # uniformly replaced by the line number.  The first 'sed' inserts a
+  # line-number line before each line; the second 'sed' does the real
+  # work.  The second script uses 'N' to pair each line-number line
+  # with the numbered line, and appends trailing '-' during
+  # substitution so that $LINENO is not a special case at line end.
+  # (Raja R Harinath suggested sed '=', and Paul Eggert wrote the
+  # second 'sed' script.  Blame Lee E. McMahon for sed's syntax.  :-)
+  sed '=' <$as_myself |
+    sed '
+      N
+      s,$,-,
+      : loop
+      s,^\(['$as_cr_digits']*\)\(.*\)[$]LINENO\([^'$as_cr_alnum'_]\),\1\2\1\3,
+      t loop
+      s,-$,,
+      s,^['$as_cr_digits']*\n,,
+    ' >$as_me.lineno &&
+  chmod +x $as_me.lineno ||
+    { { echo "$as_me:$LINENO: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&5
+echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2;}
+   { (exit 1); exit 1; }; }
+
+  # Don't try to exec as it changes $[0], causing all sort of problems
+  # (the dirname of $[0] is not the place where we might find the
+  # original and so on.  Autoconf is especially sensible to this).
+  . ./$as_me.lineno
+  # Exit status is that of the last command.
+  exit
+}
+
+
+case `echo "testing\c"; echo 1,2,3`,`echo -n testing; echo 1,2,3` in
+  *c*,-n*) ECHO_N= ECHO_C='
+' ECHO_T='     ' ;;
+  *c*,*  ) ECHO_N=-n ECHO_C= ECHO_T= ;;
+  *)       ECHO_N= ECHO_C='\c' ECHO_T= ;;
+esac
+
+if expr a : '\(a\)' >/dev/null 2>&1; then
+  as_expr=expr
+else
+  as_expr=false
+fi
+
+rm -f conf$$ conf$$.exe conf$$.file
+echo >conf$$.file
+if ln -s conf$$.file conf$$ 2>/dev/null; then
+  # We could just check for DJGPP; but this test a) works b) is more generic
+  # and c) will remain valid once DJGPP supports symlinks (DJGPP 2.04).
+  if test -f conf$$.exe; then
+    # Don't use ln at all; we don't have any links
+    as_ln_s='cp -p'
+  else
+    as_ln_s='ln -s'
+  fi
+elif ln conf$$.file conf$$ 2>/dev/null; then
+  as_ln_s=ln
+else
+  as_ln_s='cp -p'
+fi
+rm -f conf$$ conf$$.exe conf$$.file
+
+if mkdir -p . 2>/dev/null; then
+  as_mkdir_p=:
+else
+  test -d ./-p && rmdir ./-p
+  as_mkdir_p=false
+fi
+
+as_executable_p="test -f"
+
+# Sed expression to map a string onto a valid CPP name.
+as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
+
+# Sed expression to map a string onto a valid variable name.
+as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
+
+
+# IFS
+# We need space, tab and new line, in precisely that order.
+as_nl='
+'
+IFS="  $as_nl"
+
+# CDPATH.
+$as_unset CDPATH
+
+exec 6>&1
+
+# Open the log real soon, to keep \$[0] and so on meaningful, and to
+# report actual input values of CONFIG_FILES etc. instead of their
+# values after options handling.  Logging --version etc. is OK.
+exec 5>>config.log
+{
+  echo
+  sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX
+## Running $as_me. ##
+_ASBOX
+} >&5
+cat >&5 <<_CSEOF
+
+This file was extended by $as_me, which was
+generated by GNU Autoconf 2.59.  Invocation command line was
+
+  CONFIG_FILES    = $CONFIG_FILES
+  CONFIG_HEADERS  = $CONFIG_HEADERS
+  CONFIG_LINKS    = $CONFIG_LINKS
+  CONFIG_COMMANDS = $CONFIG_COMMANDS
+  $ $0 $@
+
+_CSEOF
+echo "on `(hostname || uname -n) 2>/dev/null | sed 1q`" >&5
+echo >&5
+_ACEOF
+
+# Files that config.status was made for.
+if test -n "$ac_config_files"; then
+  echo "config_files=\"$ac_config_files\"" >>$CONFIG_STATUS
+fi
+
+if test -n "$ac_config_headers"; then
+  echo "config_headers=\"$ac_config_headers\"" >>$CONFIG_STATUS
+fi
+
+if test -n "$ac_config_links"; then
+  echo "config_links=\"$ac_config_links\"" >>$CONFIG_STATUS
+fi
+
+if test -n "$ac_config_commands"; then
+  echo "config_commands=\"$ac_config_commands\"" >>$CONFIG_STATUS
+fi
+
+cat >>$CONFIG_STATUS <<\_ACEOF
+
+ac_cs_usage="\
+\`$as_me' instantiates files from templates according to the
+current configuration.
+
+Usage: $0 [OPTIONS] [FILE]...
+
+  -h, --help       print this help, then exit
+  -V, --version    print version number, then exit
+  -q, --quiet      do not print progress messages
+  -d, --debug      don't remove temporary files
+      --recheck    update $as_me by reconfiguring in the same conditions
+  --file=FILE[:TEMPLATE]
+                  instantiate the configuration file FILE
+
+Configuration files:
+$config_files
+
+Report bugs to <bug-autoconf@gnu.org>."
+_ACEOF
+
+cat >>$CONFIG_STATUS <<_ACEOF
+ac_cs_version="\\
+config.status
+configured by $0, generated by GNU Autoconf 2.59,
+  with options \\"`echo "$ac_configure_args" | sed 's/[\\""\`\$]/\\\\&/g'`\\"
+
+Copyright (C) 2003 Free Software Foundation, Inc.
+This config.status script is free software; the Free Software Foundation
+gives unlimited permission to copy, distribute and modify it."
+srcdir=$srcdir
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF
+# If no file are specified by the user, then we need to provide default
+# value.  By we need to know if files were specified by the user.
+ac_need_defaults=:
+while test $# != 0
+do
+  case $1 in
+  --*=*)
+    ac_option=`expr "x$1" : 'x\([^=]*\)='`
+    ac_optarg=`expr "x$1" : 'x[^=]*=\(.*\)'`
+    ac_shift=:
+    ;;
+  -*)
+    ac_option=$1
+    ac_optarg=$2
+    ac_shift=shift
+    ;;
+  *) # This is not an option, so the user has probably given explicit
+     # arguments.
+     ac_option=$1
+     ac_need_defaults=false;;
+  esac
+
+  case $ac_option in
+  # Handling of the options.
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF
+  -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r)
+    ac_cs_recheck=: ;;
+  --version | --vers* | -V )
+    echo "$ac_cs_version"; exit 0 ;;
+  --he | --h)
+    # Conflict between --help and --header
+    { { echo "$as_me:$LINENO: error: ambiguous option: $1
+Try \`$0 --help' for more information." >&5
+echo "$as_me: error: ambiguous option: $1
+Try \`$0 --help' for more information." >&2;}
+   { (exit 1); exit 1; }; };;
+  --help | --hel | -h )
+    echo "$ac_cs_usage"; exit 0 ;;
+  --debug | --d* | -d )
+    debug=: ;;
+  --file | --fil | --fi | --f )
+    $ac_shift
+    CONFIG_FILES="$CONFIG_FILES $ac_optarg"
+    ac_need_defaults=false;;
+  --header | --heade | --head | --hea )
+    $ac_shift
+    CONFIG_HEADERS="$CONFIG_HEADERS $ac_optarg"
+    ac_need_defaults=false;;
+  -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+  | -silent | --silent | --silen | --sile | --sil | --si | --s)
+    ac_cs_silent=: ;;
+
+  # This is an error.
+  -*) { { echo "$as_me:$LINENO: error: unrecognized option: $1
+Try \`$0 --help' for more information." >&5
+echo "$as_me: error: unrecognized option: $1
+Try \`$0 --help' for more information." >&2;}
+   { (exit 1); exit 1; }; } ;;
+
+  *) ac_config_targets="$ac_config_targets $1" ;;
+
+  esac
+  shift
+done
+
+ac_configure_extra_args=
+
+if $ac_cs_silent; then
+  exec 6>/dev/null
+  ac_configure_extra_args="$ac_configure_extra_args --silent"
+fi
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF
+if \$ac_cs_recheck; then
+  echo "running $SHELL $0 " $ac_configure_args \$ac_configure_extra_args " --no-create --no-recursion" >&6
+  exec $SHELL $0 $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion
+fi
+
+_ACEOF
+
+
+
+
+
+cat >>$CONFIG_STATUS <<\_ACEOF
+for ac_config_target in $ac_config_targets
+do
+  case "$ac_config_target" in
+  # Handling of arguments.
+  "Makefile" ) CONFIG_FILES="$CONFIG_FILES Makefile" ;;
+  *) { { echo "$as_me:$LINENO: error: invalid argument: $ac_config_target" >&5
+echo "$as_me: error: invalid argument: $ac_config_target" >&2;}
+   { (exit 1); exit 1; }; };;
+  esac
+done
+
+# If the user did not use the arguments to specify the items to instantiate,
+# then the envvar interface is used.  Set only those that are not.
+# We use the long form for the default assignment because of an extremely
+# bizarre bug on SunOS 4.1.3.
+if $ac_need_defaults; then
+  test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files
+fi
+
+# Have a temporary directory for convenience.  Make it in the build tree
+# simply because there is no reason to put it here, and in addition,
+# creating and moving files from /tmp can sometimes cause problems.
+# Create a temporary directory, and hook for its removal unless debugging.
+$debug ||
+{
+  trap 'exit_status=$?; rm -rf $tmp && exit $exit_status' 0
+  trap '{ (exit 1); exit 1; }' 1 2 13 15
+}
+
+# Create a (secure) tmp directory for tmp files.
+
+{
+  tmp=`(umask 077 && mktemp -d -q "./confstatXXXXXX") 2>/dev/null` &&
+  test -n "$tmp" && test -d "$tmp"
+}  ||
+{
+  tmp=./confstat$$-$RANDOM
+  (umask 077 && mkdir $tmp)
+} ||
+{
+   echo "$me: cannot create a temporary directory in ." >&2
+   { (exit 1); exit 1; }
+}
+
+_ACEOF
+
+cat >>$CONFIG_STATUS <<_ACEOF
+
+#
+# CONFIG_FILES section.
+#
+
+# No need to generate the scripts if there are no CONFIG_FILES.
+# This happens for instance when ./config.status config.h
+if test -n "\$CONFIG_FILES"; then
+  # Protect against being on the right side of a sed subst in config.status.
+  sed 's/,@/@@/; s/@,/@@/; s/,;t t\$/@;t t/; /@;t t\$/s/[\\\\&,]/\\\\&/g;
+   s/@@/,@/; s/@@/@,/; s/@;t t\$/,;t t/' >\$tmp/subs.sed <<\\CEOF
+s,@SHELL@,$SHELL,;t t
+s,@PATH_SEPARATOR@,$PATH_SEPARATOR,;t t
+s,@PACKAGE_NAME@,$PACKAGE_NAME,;t t
+s,@PACKAGE_TARNAME@,$PACKAGE_TARNAME,;t t
+s,@PACKAGE_VERSION@,$PACKAGE_VERSION,;t t
+s,@PACKAGE_STRING@,$PACKAGE_STRING,;t t
+s,@PACKAGE_BUGREPORT@,$PACKAGE_BUGREPORT,;t t
+s,@exec_prefix@,$exec_prefix,;t t
+s,@prefix@,$prefix,;t t
+s,@program_transform_name@,$program_transform_name,;t t
+s,@bindir@,$bindir,;t t
+s,@sbindir@,$sbindir,;t t
+s,@libexecdir@,$libexecdir,;t t
+s,@datadir@,$datadir,;t t
+s,@sysconfdir@,$sysconfdir,;t t
+s,@sharedstatedir@,$sharedstatedir,;t t
+s,@localstatedir@,$localstatedir,;t t
+s,@libdir@,$libdir,;t t
+s,@includedir@,$includedir,;t t
+s,@oldincludedir@,$oldincludedir,;t t
+s,@infodir@,$infodir,;t t
+s,@mandir@,$mandir,;t t
+s,@build_alias@,$build_alias,;t t
+s,@host_alias@,$host_alias,;t t
+s,@target_alias@,$target_alias,;t t
+s,@DEFS@,$DEFS,;t t
+s,@ECHO_C@,$ECHO_C,;t t
+s,@ECHO_N@,$ECHO_N,;t t
+s,@ECHO_T@,$ECHO_T,;t t
+s,@LIBS@,$LIBS,;t t
+s,@CC@,$CC,;t t
+s,@CFLAGS@,$CFLAGS,;t t
+s,@LDFLAGS@,$LDFLAGS,;t t
+s,@CPPFLAGS@,$CPPFLAGS,;t t
+s,@ac_ct_CC@,$ac_ct_CC,;t t
+s,@EXEEXT@,$EXEEXT,;t t
+s,@OBJEXT@,$OBJEXT,;t t
+s,@CPP@,$CPP,;t t
+s,@EGREP@,$EGREP,;t t
+s,@otp_cflags@,$otp_cflags,;t t
+s,@otp_ldflags@,$otp_ldflags,;t t
+s,@targetname@,$targetname,;t t
+s,@LIBOBJS@,$LIBOBJS,;t t
+s,@LTLIBOBJS@,$LTLIBOBJS,;t t
+CEOF
+
+_ACEOF
+
+  cat >>$CONFIG_STATUS <<\_ACEOF
+  # Split the substitutions into bite-sized pieces for seds with
+  # small command number limits, like on Digital OSF/1 and HP-UX.
+  ac_max_sed_lines=48
+  ac_sed_frag=1 # Number of current file.
+  ac_beg=1 # First line for current file.
+  ac_end=$ac_max_sed_lines # Line after last line for current file.
+  ac_more_lines=:
+  ac_sed_cmds=
+  while $ac_more_lines; do
+    if test $ac_beg -gt 1; then
+      sed "1,${ac_beg}d; ${ac_end}q" $tmp/subs.sed >$tmp/subs.frag
+    else
+      sed "${ac_end}q" $tmp/subs.sed >$tmp/subs.frag
+    fi
+    if test ! -s $tmp/subs.frag; then
+      ac_more_lines=false
+    else
+      # The purpose of the label and of the branching condition is to
+      # speed up the sed processing (if there are no `@' at all, there
+      # is no need to browse any of the substitutions).
+      # These are the two extra sed commands mentioned above.
+      (echo ':t
+  /@[a-zA-Z_][a-zA-Z_0-9]*@/!b' && cat $tmp/subs.frag) >$tmp/subs-$ac_sed_frag.sed
+      if test -z "$ac_sed_cmds"; then
+       ac_sed_cmds="sed -f $tmp/subs-$ac_sed_frag.sed"
+      else
+       ac_sed_cmds="$ac_sed_cmds | sed -f $tmp/subs-$ac_sed_frag.sed"
+      fi
+      ac_sed_frag=`expr $ac_sed_frag + 1`
+      ac_beg=$ac_end
+      ac_end=`expr $ac_end + $ac_max_sed_lines`
+    fi
+  done
+  if test -z "$ac_sed_cmds"; then
+    ac_sed_cmds=cat
+  fi
+fi # test -n "$CONFIG_FILES"
+
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF
+for ac_file in : $CONFIG_FILES; do test "x$ac_file" = x: && continue
+  # Support "outfile[:infile[:infile...]]", defaulting infile="outfile.in".
+  case $ac_file in
+  - | *:- | *:-:* ) # input from stdin
+       cat >$tmp/stdin
+       ac_file_in=`echo "$ac_file" | sed 's,[^:]*:,,'`
+       ac_file=`echo "$ac_file" | sed 's,:.*,,'` ;;
+  *:* ) ac_file_in=`echo "$ac_file" | sed 's,[^:]*:,,'`
+       ac_file=`echo "$ac_file" | sed 's,:.*,,'` ;;
+  * )   ac_file_in=$ac_file.in ;;
+  esac
+
+  # Compute @srcdir@, @top_srcdir@, and @INSTALL@ for subdirectories.
+  ac_dir=`(dirname "$ac_file") 2>/dev/null ||
+$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+        X"$ac_file" : 'X\(//\)[^/]' \| \
+        X"$ac_file" : 'X\(//\)$' \| \
+        X"$ac_file" : 'X\(/\)' \| \
+        .     : '\(.\)' 2>/dev/null ||
+echo X"$ac_file" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/; q; }
+         /^X\(\/\/\)[^/].*/{ s//\1/; q; }
+         /^X\(\/\/\)$/{ s//\1/; q; }
+         /^X\(\/\).*/{ s//\1/; q; }
+         s/.*/./; q'`
+  { if $as_mkdir_p; then
+    mkdir -p "$ac_dir"
+  else
+    as_dir="$ac_dir"
+    as_dirs=
+    while test ! -d "$as_dir"; do
+      as_dirs="$as_dir $as_dirs"
+      as_dir=`(dirname "$as_dir") 2>/dev/null ||
+$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+        X"$as_dir" : 'X\(//\)[^/]' \| \
+        X"$as_dir" : 'X\(//\)$' \| \
+        X"$as_dir" : 'X\(/\)' \| \
+        .     : '\(.\)' 2>/dev/null ||
+echo X"$as_dir" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/; q; }
+         /^X\(\/\/\)[^/].*/{ s//\1/; q; }
+         /^X\(\/\/\)$/{ s//\1/; q; }
+         /^X\(\/\).*/{ s//\1/; q; }
+         s/.*/./; q'`
+    done
+    test ! -n "$as_dirs" || mkdir $as_dirs
+  fi || { { echo "$as_me:$LINENO: error: cannot create directory \"$ac_dir\"" >&5
+echo "$as_me: error: cannot create directory \"$ac_dir\"" >&2;}
+   { (exit 1); exit 1; }; }; }
+
+  ac_builddir=.
+
+if test "$ac_dir" != .; then
+  ac_dir_suffix=/`echo "$ac_dir" | sed 's,^\.[\\/],,'`
+  # A "../" for each directory in $ac_dir_suffix.
+  ac_top_builddir=`echo "$ac_dir_suffix" | sed 's,/[^\\/]*,../,g'`
+else
+  ac_dir_suffix= ac_top_builddir=
+fi
+
+case $srcdir in
+  .)  # No --srcdir option.  We are building in place.
+    ac_srcdir=.
+    if test -z "$ac_top_builddir"; then
+       ac_top_srcdir=.
+    else
+       ac_top_srcdir=`echo $ac_top_builddir | sed 's,/$,,'`
+    fi ;;
+  [\\/]* | ?:[\\/]* )  # Absolute path.
+    ac_srcdir=$srcdir$ac_dir_suffix;
+    ac_top_srcdir=$srcdir ;;
+  *) # Relative path.
+    ac_srcdir=$ac_top_builddir$srcdir$ac_dir_suffix
+    ac_top_srcdir=$ac_top_builddir$srcdir ;;
+esac
+
+# Do not use `cd foo && pwd` to compute absolute paths, because
+# the directories may not exist.
+case `pwd` in
+.) ac_abs_builddir="$ac_dir";;
+*)
+  case "$ac_dir" in
+  .) ac_abs_builddir=`pwd`;;
+  [\\/]* | ?:[\\/]* ) ac_abs_builddir="$ac_dir";;
+  *) ac_abs_builddir=`pwd`/"$ac_dir";;
+  esac;;
+esac
+case $ac_abs_builddir in
+.) ac_abs_top_builddir=${ac_top_builddir}.;;
+*)
+  case ${ac_top_builddir}. in
+  .) ac_abs_top_builddir=$ac_abs_builddir;;
+  [\\/]* | ?:[\\/]* ) ac_abs_top_builddir=${ac_top_builddir}.;;
+  *) ac_abs_top_builddir=$ac_abs_builddir/${ac_top_builddir}.;;
+  esac;;
+esac
+case $ac_abs_builddir in
+.) ac_abs_srcdir=$ac_srcdir;;
+*)
+  case $ac_srcdir in
+  .) ac_abs_srcdir=$ac_abs_builddir;;
+  [\\/]* | ?:[\\/]* ) ac_abs_srcdir=$ac_srcdir;;
+  *) ac_abs_srcdir=$ac_abs_builddir/$ac_srcdir;;
+  esac;;
+esac
+case $ac_abs_builddir in
+.) ac_abs_top_srcdir=$ac_top_srcdir;;
+*)
+  case $ac_top_srcdir in
+  .) ac_abs_top_srcdir=$ac_abs_builddir;;
+  [\\/]* | ?:[\\/]* ) ac_abs_top_srcdir=$ac_top_srcdir;;
+  *) ac_abs_top_srcdir=$ac_abs_builddir/$ac_top_srcdir;;
+  esac;;
+esac
+
+
+
+  if test x"$ac_file" != x-; then
+    { echo "$as_me:$LINENO: creating $ac_file" >&5
+echo "$as_me: creating $ac_file" >&6;}
+    rm -f "$ac_file"
+  fi
+  # Let's still pretend it is `configure' which instantiates (i.e., don't
+  # use $as_me), people would be surprised to read:
+  #    /* config.h.  Generated by config.status.  */
+  if test x"$ac_file" = x-; then
+    configure_input=
+  else
+    configure_input="$ac_file.  "
+  fi
+  configure_input=$configure_input"Generated from `echo $ac_file_in |
+                                    sed 's,.*/,,'` by configure."
+
+  # First look for the input files in the build tree, otherwise in the
+  # src tree.
+  ac_file_inputs=`IFS=:
+    for f in $ac_file_in; do
+      case $f in
+      -) echo $tmp/stdin ;;
+      [\\/$]*)
+        # Absolute (can't be DOS-style, as IFS=:)
+        test -f "$f" || { { echo "$as_me:$LINENO: error: cannot find input file: $f" >&5
+echo "$as_me: error: cannot find input file: $f" >&2;}
+   { (exit 1); exit 1; }; }
+        echo "$f";;
+      *) # Relative
+        if test -f "$f"; then
+          # Build tree
+          echo "$f"
+        elif test -f "$srcdir/$f"; then
+          # Source tree
+          echo "$srcdir/$f"
+        else
+          # /dev/null tree
+          { { echo "$as_me:$LINENO: error: cannot find input file: $f" >&5
+echo "$as_me: error: cannot find input file: $f" >&2;}
+   { (exit 1); exit 1; }; }
+        fi;;
+      esac
+    done` || { (exit 1); exit 1; }
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF
+  sed "$ac_vpsub
+$extrasub
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF
+:t
+/@[a-zA-Z_][a-zA-Z_0-9]*@/!b
+s,@configure_input@,$configure_input,;t t
+s,@srcdir@,$ac_srcdir,;t t
+s,@abs_srcdir@,$ac_abs_srcdir,;t t
+s,@top_srcdir@,$ac_top_srcdir,;t t
+s,@abs_top_srcdir@,$ac_abs_top_srcdir,;t t
+s,@builddir@,$ac_builddir,;t t
+s,@abs_builddir@,$ac_abs_builddir,;t t
+s,@top_builddir@,$ac_top_builddir,;t t
+s,@abs_top_builddir@,$ac_abs_top_builddir,;t t
+" $ac_file_inputs | (eval "$ac_sed_cmds") >$tmp/out
+  rm -f $tmp/stdin
+  if test x"$ac_file" != x-; then
+    mv $tmp/out $ac_file
+  else
+    cat $tmp/out
+    rm -f $tmp/out
+  fi
+
+done
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF
+
+{ (exit 0); exit 0; }
+_ACEOF
+chmod +x $CONFIG_STATUS
+ac_clean_files=$ac_clean_files_save
+
+
+# configure is writing to config.log, and then calls config.status.
+# config.status does its own redirection, appending to config.log.
+# Unfortunately, on DOS this fails, as config.log is still kept open
+# by configure, so config.status won't be able to write to it; its
+# output is simply discarded.  So we exec the FD to /dev/null,
+# effectively closing config.log, so it can be properly (re)opened and
+# appended to by config.status.  When coming back to configure, we
+# need to make the FD available again.
+if test "$no_create" != yes; then
+  ac_cs_success=:
+  ac_config_status_args=
+  test "$silent" = yes &&
+    ac_config_status_args="$ac_config_status_args --quiet"
+  exec 5>/dev/null
+  $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false
+  exec 5>>config.log
+  # Use ||, not &&, to avoid exiting from the if with $? = 1, which
+  # would make configure fail if this is the last instruction.
+  $ac_cs_success || { (exit 1); exit 1; }
+fi
+  # keep this!  Don't change!
diff --git a/src/modules/rlm_otp/configure.in b/src/modules/rlm_otp/configure.in
new file mode 100644 (file)
index 0000000..2b0af76
--- /dev/null
@@ -0,0 +1,45 @@
+AC_PREREQ([2.53])
+AC_INIT(otp_rlm.c)
+AC_REVISION($Revision$)
+AC_DEFUN(modname,[rlm_otp])
+
+SMART_CFLAGS=
+if test x$with_[]modname != xno; then
+
+       dnl put configuration checks here.  
+       dnl set $fail to what's missing, on fatal errors.
+       dnl use AC_MSG_WARN() on important messages.
+
+       dnl test for almost-c99 compliant compiler
+       AC_CHECK_HEADER(inttypes.h, ,
+               [ fail="$fail inttypes.h" ]
+       )
+
+       if test "x$OPENSSL_LIBS" = "x"; then
+               fail="$fail OpenSSL"
+       fi
+
+       targetname=modname     # keep this!  Don't change!
+else
+       targetname=            # keep this!  Don't change!
+       echo \*\*\* module modname is disabled.  # keep this!  Don't change!
+fi
+
+dnl  Don't change this section.
+if test x"$fail" != x""; then
+       if test x"${enable_strict_dependencies}" = x"yes"; then
+               AC_MSG_ERROR([set --without-]modname[ to disable it explicitly.])
+       else
+               AC_MSG_WARN([silently not building ]modname[.])
+               AC_MSG_WARN([FAILURE: ]modname[ requires: $fail.]); 
+               targetname=""
+       fi
+fi
+
+otp_cflags="$otp_cflags -DOTP_MODULE_NAME=\\\"modname\\\" $SMART_CFLAGS"
+otp_cflags="$otp_cflags -DFREERADIUS"
+AC_SUBST(otp_cflags)
+AC_SUBST(otp_ldflags)
+
+AC_SUBST(targetname)  # keep this!  Don't change!
+AC_OUTPUT(Makefile)  # keep this!  Don't change!
diff --git a/src/modules/rlm_otp/crcalc.c b/src/modules/rlm_otp/crcalc.c
new file mode 100644 (file)
index 0000000..1359fe4
--- /dev/null
@@ -0,0 +1,190 @@
+/*
+ * crcalc.c
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * Copyright 2001,2002 Google, Inc.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <openssl/des.h>
+
+/*
+ * Convert the ASCII string representation of a DES key to raw octets.
+ * keyblock is filled in.  Returns 0 on success, -1 otherwise.
+ */
+static
+int string_to_keyblock(const char *s, des_cblock keyblock)
+{
+  int i;
+
+  if (s == NULL || strlen(s) < 16)
+    return -1;
+
+  /*
+   * We could just use sscanf, but we do this a lot, and have very
+   * specific needs, and it's easy to implement, so let's go for it!
+   */
+  for (i = 0; i < 8; ++i) {
+    unsigned int n[2];
+
+    n[0] = *s++ - '0';
+    n[1] = *s++ - '0';
+    if (n[0] > 9)
+      n[0] -= 'a' - '9' - 1;
+    if (n[1] > 9)
+      n[1] -= 'a' - '9' - 1;
+
+    keyblock[i]  = n[0] << 4;
+    keyblock[i] += n[1];
+  }
+  return 0;
+}
+
+
+/* Character maps for generic hex and vendor specific decimal modes */
+static const char ascii_conversion[]  = "0123456789abcdef";
+static const char cc_dec_conversion[] = "0123456789012345";
+
+/*
+ * Convert a DES keyblock to an ASCII string.
+ * Fills in s, which must point to at least 17 bytes of space.
+ * Note that each octet expands into 2 hex digits in ASCII (0xAA -> 0x4141);
+ * add a NULL string terminator and you get the 17 byte requirement.
+ */
+static
+void keyblock_to_string(char *s, const des_cblock keyblock,
+                        const char conversion[17])
+{
+    int i;
+
+  for (i = 0; i < 8; ++i) {
+    unsigned n[2];
+
+    n[0] = (keyblock[i] >> 4) & 0x0f;
+    n[1] = keyblock[i] & 0x0f;
+    s[2 * i + 0] = conversion[n[0]];
+    s[2 * i + 1] = conversion[n[1]];
+  }
+  s[16] = '\0';
+}
+
+
+int
+main(int argc, char *argv[])
+{
+  char ascii_key[17];
+  char challenge[10], response[9], response_long[17];
+  char buf[BUFSIZ];
+  des_cblock keyblock;
+  des_key_schedule ks;
+  char *p;
+  int i, rc;
+
+  memset(ascii_key, 0, sizeof(ascii_key));
+
+  /* get the key */
+  fprintf(stdout, "Enter a DES key as 16 hex digits (spaces allowed): ");
+  fgets(buf, sizeof(buf), stdin);
+  buf[strlen(buf) - 1] = '\0'; /* strip '\n' */
+  p = buf;
+
+  /* setup key */
+  if (buf[0] == '0' && (buf[1] == 'x' || buf[1] == 'X'))
+    p += 2;
+  i = 0;
+  while (*p) {
+    if (*p == ' ') {
+      p++;
+      continue;
+    }
+    if (*p < '0' || *p > '9') {
+      if (*p < 'a' || *p > 'f') {
+        if (*p < 'A' || *p > 'F') {
+          fprintf(stderr, "bad key\n");
+          exit(1);
+        }
+      }
+    }
+    if (i > 15) {
+      fprintf(stderr, "key too long\n");
+      exit(1);
+    }
+    ascii_key[i++] = tolower((int) *p++);
+  }
+  if (strlen(ascii_key) < 16) {
+    fprintf(stderr, "key too short\n");
+    exit(1);
+  }
+  string_to_keyblock(ascii_key, keyblock);
+
+  /* verify the key. */
+key_verify:
+  if ((rc = des_set_key_checked(&keyblock, ks)) != 0) {
+    fprintf(stderr, "key %s\n",
+            rc == -1 ? "has incorrect parity" : "is weak");
+    if (rc == -1) {
+      des_set_odd_parity(&keyblock);
+      goto key_verify;
+    } else {
+      exit(1);
+    }
+  }
+
+  fprintf(stdout, "Enter the challenge: ");
+  fgets(challenge, sizeof(challenge), stdin);
+  challenge[strlen(challenge) - 1] = '\0'; /* strip '\n' */
+  /* encrypt null block if no challenge */
+
+  /*
+   * Calculate the response.  The algorithm is:
+   * 1. Convert the challenge to ASCII bytes (eg "12345" -> 0x3132333435).
+   * 2. Pad LSB of a 64-bit block w/ 0 bytes if challenge < 8 bytes (digits).
+   * 3. Encrypt w/ DES (whose block size is 64 bits).
+   * 4. Convert the most significant 32 bits of the ciphertext
+   *    to 8 hex digits as a string (eg 0x1234567f -> "1234567f").
+   */
+  {
+    des_cblock input, output;
+
+    /* Step 1, 2 (conversion is already done, just copy and pad) */
+    (void) memset(input, 0, sizeof(input));
+    (void) memcpy(input, challenge, strlen(challenge));
+
+    /* Step 3 */
+    des_ecb_encrypt(&input, &output, ks, 1);
+
+    /* Step 4, 5 */
+    keyblock_to_string(response_long, output, ascii_conversion);
+    (void) memcpy(response, response_long, 8);
+    response[8] = '\0';
+    memcpy(challenge, output, 8);
+    challenge[8] = '\0';
+  }
+
+  /* calculate the next challenge for cryptocard */
+  for (i = 0; i < 8; ++i) {
+    challenge[i] &= 0x0f;
+    if (challenge[i] > 9)
+      challenge[i] -= 10;
+    challenge[i] |= 0x30;
+  }
+
+  fprintf(stdout, "response is %s [%s]\n", response, &response_long[8]);
+  fprintf(stdout, "next challenge is %s\n", challenge);
+  exit(0);
+}
diff --git a/src/modules/rlm_otp/otp.h b/src/modules/rlm_otp/otp.h
new file mode 100644 (file)
index 0000000..26c4d8d
--- /dev/null
@@ -0,0 +1,218 @@
+/*
+ * otp.h
+ * $Id$
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * Copyright 2001-2005 Google, Inc.
+ * Copyright 2005 TRI-D Systems, Inc.
+ */
+
+#ifndef OTP_H
+#define OTP_H
+
+#ifndef _REENTRANT
+#define _REENTRANT
+#endif
+#include <pthread.h>
+
+#include <inttypes.h>
+#include <openssl/des.h> /* des_cblock */
+#include <time.h>        /* time_t */
+
+/*
+ * Things you might like to change (although most are configurables)
+ */
+
+/* Default passwd file */
+#define OTP_PWDFILE "/etc/otppasswd"
+
+/* state manager rendezvous point */
+#define OTP_LSMD_RP "/var/run/lsmd/socket"
+
+/* Default prompt for presentation of challenge */
+#define OTP_CHALLENGE_PROMPT "Challenge: %s\n Response: "
+
+/* Must be a multiple of sizeof(des_cblock) (8); read src before changing. */
+#define OTP_MAX_CHALLENGE_LEN 16
+
+/* Password that means "challenge me" in fast_sync mode */
+#define OTP_CHALLENGE_REQ "challenge"
+
+/* Password that means "challenge me and resync" in fast_sync mode */
+#define OTP_RESYNC_REQ "resync"
+
+/* Max event window size for sync modes */
+#define OTP_MAX_EWINDOW_SIZE 10
+/* Max time window size for sync modes.  More than 10 may not be usable. */
+#define OTP_MAX_TWINDOW_SIZE 10
+
+/*
+ * PRNG device that does not block;
+ * /dev/urandom is "merely" cryptographically strong on Linux. :-)
+ */
+#define OTP_DEVURANDOM "/dev/urandom"
+
+
+/*
+ * You shouldn't change anything past this point
+ */
+
+
+/* struct used for instance/option data */
+typedef struct otp_option_t {
+  char *pwdfile;       /* file containing user:card_type:key entries      */
+  char *lsmd_rp;       /* state manager rendezvous point                  */
+  char *chal_prompt;   /* text to present challenge to user, must have %s */
+  int chal_len;                /* challenge length, min 5 digits                  */
+  int softfail;                /* number of auth fails before time delay starts   */
+  int hardfail;                /* number of auth fails when user is locked out    */
+  int fast_sync;       /* response-before-challenge mode                  */
+  int allow_sync;      /* useful to override pwdfile card_type settings   */
+  int allow_async;     /* C/R mode allowed?                               */
+  char *chal_req;      /* keyword requesting challenge for fast_sync mode */
+  char *resync_req;    /* keyword requesting resync for fast_sync mode    */
+  int prepend_pin;     /* prepend (vs. append) PIN?                       */
+  int ewindow_size;    /* sync mode event window size (right side value)  */
+  int rwindow_size;    /* softfail override event window size             */
+  int rwindow_delay;   /* softfail override max time delay                */
+  int debug;           /* print debug info?                               */
+#if defined(FREERADIUS)
+  /* freeradius-specific items */
+  int chal_delay;              /* max delay time for response, in seconds */
+  const char *name;            /* instance name for otp_token_authorize() */
+  int mschapv2_mppe_policy;    /* whether or not do to mppe for mschapv2  */
+  int mschapv2_mppe_types;     /* key type/length for mschapv2/mppe       */
+  int mschap_mppe_policy;      /* whether or not do to mppe for mschap    */
+  int mschap_mppe_types;       /* key type/length for mschap/mppe         */
+#elif defined(PAM)
+  /* PAM specific items */
+  char *fast_prompt;   /* fast mode prompt                                */
+#endif
+} otp_option_t;
+
+/* user-specific info */
+#define OTP_MAX_CARDNAME_LEN 32
+#define OTP_MAX_KEY_LEN 256
+#define OTP_MAX_PIN_LEN 256
+struct cardops_t;
+typedef struct otp_card_info_t {
+  const char *username;
+  struct cardops_t *cardops;
+
+  char card[OTP_MAX_CARDNAME_LEN + 1];
+  uint32_t featuremask;
+
+  char keystring[OTP_MAX_KEY_LEN * 2 + 1];
+  unsigned char keyblock[OTP_MAX_KEY_LEN];
+  char pin[OTP_MAX_PIN_LEN + 1];
+#if 0
+  void *keyschedule;
+#endif
+} otp_card_info_t;
+
+/* state manager fd pool */
+typedef struct lsmd_fd_t {
+  pthread_mutex_t      mutex;
+  int                  fd;
+  struct lsmd_fd_t     *next;
+} lsmd_fd_t;
+
+/* user-specific state info */
+#define OTP_MAX_CSD_LEN 64
+#define OTP_MAX_RD_LEN 8
+typedef struct otp_user_state_t {
+  int          locked;                 /* locked aka success flag        */
+  lsmd_fd_t    *fdp;                   /* fd for return data             */
+  int          nullstate;              /* null state?                    */
+  int          updated;                /* state updated? (1 unless err)  */
+  ssize_t      clen;                   /* challenge length               */
+
+  unsigned char        challenge[OTP_MAX_CHALLENGE_LEN];       /* prev sync chal */
+  char         csd[OTP_MAX_CSD_LEN+1]; /* card-specific data             */
+  char         rd[OTP_MAX_RD_LEN+1];   /* rwindow data                   */
+  uint32_t     failcount;              /* number of consecutive failures */
+  uint32_t     authtime;               /* time of last auth              */
+  uint32_t     mincardtime;            /* minimum cardtime               */
+
+  int32_t      scratch1;               /* card-specific scratch data     */
+  int32_t      scratch2;               /* card-specific scratch data     */
+  int32_t      scratch3;               /* card-specific scratch data     */
+} otp_user_state_t;
+
+/* fc (failcondition) shortcuts */
+#define OTP_FC_FAIL_NONE 0     /* no failures */
+#define OTP_FC_FAIL_HARD 1     /* failed hard */
+#define OTP_FC_FAIL_SOFT 2     /* failed soft */
+
+/* otp_cardops.c */
+/* return codes from otp_pw_valid() */
+#define OTP_RC_OK              0
+#define OTP_RC_USER_UNKNOWN    1
+#define OTP_RC_AUTHINFO_UNAVAIL        2
+#define OTP_RC_AUTH_ERR                3
+#define OTP_RC_MAXTRIES                4
+#define OTP_RC_SERVICE_ERR     5
+struct otp_pwe_cmp_t;
+typedef int (*cmpfunc_t)(struct otp_pwe_cmp_t *, const char *, const char *);
+extern int otp_pw_valid(const char *, char *, const char *, int,
+                        const otp_option_t *, cmpfunc_t, void *, const char *);
+
+/* otp_x99.c */
+extern int otp_x99_mac(const unsigned char *, size_t, unsigned char [8],
+                       const unsigned char [OTP_MAX_KEY_LEN], const char *);
+
+/* otp_hotp.c */
+extern int otp_hotp_mac(const unsigned char [8], unsigned char [7],
+                        const unsigned char [OTP_MAX_KEY_LEN], size_t,
+                        const char *);
+
+/* otp_util.c */
+/* Character maps for generic hex and vendor specific decimal modes */
+extern const char otp_hex_conversion[];
+extern const char otp_cc_dec_conversion[];
+extern const char otp_snk_dec_conversion[];
+extern const char otp_sc_friendly_conversion[];
+
+extern int otp_get_random(int, unsigned char *, int, const char *);
+extern int otp_async_challenge(int, char *, int, const char *);
+
+extern ssize_t otp_keystring2keyblock(const char *, unsigned char []);
+extern char *otp_keyblock2keystring(char *, const unsigned char [], size_t,
+                                    const char [17]);
+
+extern int otp_get_card_info(const char *, const char *, otp_card_info_t *,
+                             const char *);
+
+/* otp_state.c */
+extern int otp_state_get(const otp_option_t *, const char *,
+                         otp_user_state_t *, const char *);
+extern int otp_state_put(const char *, otp_user_state_t *, const char *);
+
+/* otp_site.c */
+extern ssize_t otp_challenge_transform(const char *,
+                                       unsigned char [OTP_MAX_CHALLENGE_LEN],
+                                       size_t);
+
+/* otp_log.c */
+extern void otp_log(int, const char *, ...);
+
+#if defined(FREERADIUS)
+#include "otp_rad.h"
+#elif defined(PAM)
+#include "otp_pam.h"
+#endif
+
+#endif /* OTP_H */
diff --git a/src/modules/rlm_otp/otp_cardops.c b/src/modules/rlm_otp/otp_cardops.c
new file mode 100644 (file)
index 0000000..b1c1dfc
--- /dev/null
@@ -0,0 +1,554 @@
+/*
+ * otp_cardops.c
+ * $Id$
+ *
+ * Passcode verification functions for otp.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ *
+ * Copyright 2002-2005  Google, Inc.
+ * Copyright 2005 TRI-D Systems, Inc.
+ */
+
+static const char rcsid[] = "$Id$";
+
+#if defined(__linux__) && !defined(_GNU_SOURCE)
+#define _GNU_SOURCE    /* RTLD_DEFAULT */
+#endif
+#include <dlfcn.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "otp.h"
+#include "otp_cardops.h"
+
+/* Global data */
+cardops_t otp_cardops[OTP_MAX_VENDORS];        /* cardops objects */
+int otp_num_cardops = 0;               /* number of cardops modules loaded */
+
+
+/*
+ * Test for passcode validity.
+ *
+ * If challenge is supplied, it is used to generate the card response
+ * against which the passcode will be compared.  If challenge is not
+ * supplied, or if the comparison fails, synchronous responses are
+ * generated and tested.  NOTE: for async authentications, sync mode
+ * responses are still considered valid!  (Assuming module configuration
+ * allows sync mode.)
+ *
+ * If passcode is supplied, a simple string comparison is done, else if
+ * cmp is supplied, it is called to test for validity.  The cmp function
+ * is required for RADIUS, where we might have a CHAP response instead
+ * of the plaintext of the passcode from the user.
+ *
+ * If challenge is supplied, then resync is used to determine if the card
+ * should be resynced or if this is a one-off response.  (If challenge is
+ * not supplied, this is sync mode response and the card is always resynced.)
+ *
+ * Returns one of the OTP_RC_* return codes.  challenge is overwritten.
+ */
+int
+otp_pw_valid(const char *username, char *challenge, const char *passcode,
+             int resync, const otp_option_t *opt,
+             cmpfunc_t cmpfunc, void *data,
+             const char *log_prefix)
+{
+  int  rc, nmatch, i;
+  int  e = 0, t = 0;   /* must initialize for async auth path */
+  int  fc;             /* failcondition */
+
+       /* expected response */
+  char         e_response[OTP_MAX_RESPONSE_LEN + OTP_MAX_PIN_LEN + 1];
+  int  pin_offset = 0; /* pin offset in e_response (prepend or append) */
+
+  otp_card_info_t      card_info  = { .cardops = NULL };
+  otp_user_state_t     user_state = { .locked = 0 };
+
+  time_t now = time(NULL);
+
+  /*
+   * In the Cyclades ACS environment (2.3.0 tested), the runtime linker
+   * apparently does not run static constructors in ELF .ctors sections.
+   * Since that is how we initialize cardops modules, we have an ugly
+   * workaround here.  Our other choice is to implement cardops modules
+   * as full-fledged shared libraries, which is just too much work.
+   */
+  if (otp_num_cardops == 0) {
+    void (*cardops_init)(void);
+
+    /* ctors did not run; execute all known constructors */
+    if ((cardops_init = dlsym(RTLD_DEFAULT, "cryptocard_init")))
+      (*cardops_init)();
+    if ((cardops_init = dlsym(RTLD_DEFAULT, "trid_init")))
+      (*cardops_init)();
+  }
+
+  /* sanity */
+  if (!challenge) {
+    rc = OTP_RC_SERVICE_ERR;
+    goto auth_done_service_err;
+  }
+  if (!passcode && !cmpfunc) {
+    otp_log(OTP_LOG_CRIT, "%s: %s: Can't test passcode validity!",
+            log_prefix, __func__);
+    rc = OTP_RC_SERVICE_ERR;
+    goto auth_done_service_err;
+  }
+
+  /* Look up user info.  (errors are logged by the function) */
+  rc = otp_get_card_info(opt->pwdfile, username, &card_info, log_prefix);
+  if (rc == -1) {
+    rc = OTP_RC_USER_UNKNOWN;
+    goto auth_done_service_err;
+  } else if (rc == -2) {
+    rc = OTP_RC_AUTHINFO_UNAVAIL;
+    goto auth_done_service_err;
+  }
+  card_info.username = username;
+
+  /* Find the correct cardops module. */
+  for (i = 0; otp_cardops[i].prefix; i++) {
+    if (!strncasecmp(otp_cardops[i].prefix, card_info.card,
+                     otp_cardops[i].prefix_len)) {
+      card_info.cardops = &otp_cardops[i];
+      break;
+    }
+  }
+  if (!card_info.cardops) {
+    otp_log(OTP_LOG_ERR, "%s: %s: invalid card type '%s' for [%s]",
+            log_prefix, __func__, card_info.card, username);
+    rc = OTP_RC_SERVICE_ERR;
+    goto auth_done_service_err;
+  }
+
+  /* Convert name to a feature mask once, for fast operations later. */
+  if (card_info.cardops->name2fm(card_info.card, &card_info.featuremask)) {
+    otp_log(OTP_LOG_ERR, "%s: %s: invalid card type '%s' for [%s]",
+            log_prefix, __func__, card_info.card, username);
+    rc = OTP_RC_SERVICE_ERR;
+    goto auth_done_service_err;
+  }
+
+  /* Convert keystring to a keyblock. */
+  if (card_info.cardops->keystring2keyblock(card_info.keystring,
+                                            card_info.keyblock) < 0) {
+    otp_log(OTP_LOG_ERR, "%s: %s: invalid key '%s' for [%s]",
+            log_prefix, __func__, card_info.keystring, username);
+    rc = OTP_RC_SERVICE_ERR;
+    goto auth_done_service_err;
+  }
+
+  /* Adjust e_response for PIN prepend. */
+  if (opt->prepend_pin) {
+    (void) strcpy(e_response, card_info.pin);
+    pin_offset = strlen(e_response);
+  }
+
+  /* Get user state. */
+  if (otp_state_get(opt, username, &user_state, log_prefix) != 0) {
+    otp_log(OTP_LOG_ERR, "%s: %s: unable to get state for [%s]",
+            log_prefix, __func__, username);
+    rc = OTP_RC_SERVICE_ERR;
+    goto auth_done_service_err;
+  }
+  if (user_state.nullstate) {
+    if (card_info.cardops->nullstate(opt, &card_info, &user_state,
+                                     now, log_prefix)) {
+      otp_log(OTP_LOG_ERR, "%s: %s: unable to set null state for [%s]",
+              log_prefix, __func__, username);
+      rc = OTP_RC_SERVICE_ERR;
+      goto auth_done_service_err;
+    }
+  }
+
+  /* Set fc (failcondition). */
+  if (opt->hardfail && user_state.failcount >= (unsigned) opt->hardfail) {
+    /* NOTE: persistent softfail stops working */
+    fc = OTP_FC_FAIL_HARD;
+  } else if (opt->softfail && user_state.authtime == INT32_MAX) {
+    fc = OTP_FC_FAIL_SOFT;
+  } else if (opt->softfail &&
+             user_state.failcount >= (unsigned) opt->softfail) {
+    uint32_t when;
+    int fcount;
+
+    /*
+     * Determine the next time this user can authenticate.
+     *
+     * Once we hit softfail, we introduce a 1m delay before the user
+     * can authenticate.  For each successive failed authentication,
+     * we double the delay time, up to a max of 32 minutes.  While in
+     * the "delay mode" of operation, all authentication ATTEMPTS are
+     * considered failures.  Also, each attempt during the delay period
+     * restarts the clock.
+     *
+     * The advantage of a delay instead of a simple lockout is that an
+     * attacker can't lock out a user as easily; the user need only wait
+     * a bit before he can authenticate.
+     *
+     * Note that if the user waits for the delay period to expire, he
+     * is no longer in softfail and can only use ewindow, not rwindow.
+     */
+    fcount = user_state.failcount - opt->softfail;
+    /*
+     * when and authtime are uint32, but time is saved as int32,
+     * so this can't overflow
+     */
+    when = user_state.authtime +
+           (fcount > 5 ? 32 * 60 : (1 << fcount) * 60);
+    if ((uint32_t) now < when)
+      fc = OTP_FC_FAIL_SOFT;
+    else
+      fc = OTP_FC_FAIL_NONE;
+  } else {
+    fc = OTP_FC_FAIL_NONE;
+  }
+
+async_response:
+  /*
+   * Test async response.
+   */
+  if (*challenge && (card_info.featuremask & OTP_CF_AM) && opt->allow_async) {
+    ssize_t chal_len;
+
+    /* Perform any site-specific transforms of the challenge. */
+    if ((chal_len = otp_challenge_transform(username,
+                                            challenge, opt->chal_len)) < 0) {
+      otp_log(OTP_LOG_ERR, "%s: %s: challenge transform failed for [%s]",
+              log_prefix, __func__, username);
+      rc = OTP_RC_SERVICE_ERR;
+      goto auth_done_service_err;
+      /* NB: state not updated. */
+    }
+
+    /* Calculate the async response. */
+    if (card_info.cardops->response(&card_info, challenge, chal_len,
+                                    &e_response[pin_offset],
+                                    log_prefix) != 0) {
+      otp_log(OTP_LOG_ERR,
+              "%s: %s: unable to calculate async response for [%s], "
+              "to challenge %s", log_prefix, __func__, username, challenge);
+      rc = OTP_RC_SERVICE_ERR;
+      goto auth_done_service_err;
+      /* NB: state not updated. */
+    }
+
+    /* NOTE: We do not display the PIN. */
+    if (opt->debug) {
+      char s[OTP_MAX_CHALLENGE_LEN * 2 + 1];
+
+      otp_log(OTP_LOG_DEBUG,
+              "%s: %s: [%s], async challenge %s, expecting response %s",
+              log_prefix, __func__, username,
+              card_info.cardops->printchallenge(s, challenge, chal_len),
+              &e_response[pin_offset]);
+    }
+
+    /* Add PIN if needed. */
+    if (!opt->prepend_pin)
+      (void) strcat(e_response, card_info.pin);
+
+    /* Test user-supplied passcode. */
+    if (passcode)
+      nmatch = strcmp(passcode, e_response);
+    else
+      nmatch = cmpfunc(data, e_response, log_prefix);
+    if (!nmatch) {
+      if (!opt->allow_async) {
+        otp_log(OTP_LOG_AUTH, "%s: %s: bad async auth for [%s]: "
+                              "valid but disallowed by config",
+                log_prefix, __func__, username);
+        rc = OTP_RC_AUTH_ERR;
+        goto auth_done;
+      }
+      if (fc == OTP_FC_FAIL_HARD) {
+        otp_log(OTP_LOG_AUTH,
+                "%s: %s: bad async auth for [%s]: valid but in hardfail "
+                " (%d/%d failed/max)", log_prefix, __func__, username,
+                user_state.failcount, opt->hardfail);
+        rc = OTP_RC_MAXTRIES;
+        goto auth_done;
+      }
+      if (fc == OTP_FC_FAIL_SOFT) {
+        otp_log(OTP_LOG_AUTH,
+                "%s: %s: bad async auth for [%s]: valid but in softfail "
+                " (%d/%d failed/max)", log_prefix, __func__, username,
+                user_state.failcount, opt->softfail);
+        rc = OTP_RC_MAXTRIES;
+        goto auth_done;
+      }
+#ifdef FREERADIUS
+      if ((uint32_t) now - user_state.authtime < (unsigned) opt->chal_delay) {
+        otp_log(OTP_LOG_AUTH,
+                "%s: %s: bad async auth for [%s]: valid but too soon",
+                log_prefix, __func__, username);
+        rc = OTP_RC_MAXTRIES;
+        goto auth_done;
+      }
+#endif
+
+      /* Authenticated in async mode. */
+      rc = OTP_RC_OK;
+      /* special log message for sync users */
+      if (card_info.featuremask & OTP_CF_SM)
+        otp_log(OTP_LOG_INFO, "%s: %s: [%s] authenticated in async mode",
+                log_prefix, __func__, username);
+      goto auth_done;
+    } /* if (user authenticated async) */
+  } /* if (async mode possible) */
+
+sync_response:
+  /*
+   * Calculate and test sync responses in the window.  Note that we
+   * always accept a sync response, even if a challenge or resync was
+   * explicitly requested.
+   *
+   * Note that we always start at the t=0 e=0 window position, even
+   * though we may already know a previous authentication is further
+   * ahead in the window (when in softfail).  This has the effect that
+   * an rwindow softfail override can occur in a sequence like 6,3,4.
+   * That is, the user can always move backwards in the window to
+   * restart the rwindow sequence, as long as they don't go beyond
+   * (prior to) the last successful authentication.
+   */
+  if ((card_info.featuremask & OTP_CF_SM) && opt->allow_sync) {
+    int tend, end, ewindow, rwindow;
+
+    /* set ending ewin counter */
+    if (card_info.featuremask & OTP_CF_FRW) {
+      /* force rwindow for e+t cards */
+      rwindow = (card_info.featuremask & OTP_CF_FRW) >> OTP_CF_FRW_SHIFT;
+      ewindow = 0; /* quiet compiler */
+    } else {
+      ewindow = opt->ewindow_size;
+      /* Increase window for softfail+rwindow. */
+      if (opt->rwindow_size && fc == OTP_FC_FAIL_SOFT)
+        rwindow = opt->rwindow_size;
+      else
+        rwindow = 0;
+    }
+    end = rwindow ? rwindow : ewindow;
+
+    /* Setup initial challenge. */
+    (void) memcpy(challenge, user_state.challenge, user_state.clen);
+
+    /* Test each sync response in the window. */
+    tend = card_info.cardops->maxtwin(&card_info, user_state.csd);
+    for (t = 0; t <= tend; ++t) {
+      for (e = 0; e <= end; ++e) {
+        /* Get next challenge. */
+        rc = card_info.cardops->challenge(&card_info, &user_state, challenge,
+                                          now, t, e, log_prefix);
+        if (rc == -1) {
+          otp_log(OTP_LOG_ERR,
+                  "%s: %s: unable to get sync challenge t:%d e:%d for [%s]",
+                  log_prefix, __func__, t, e, username);
+          rc = OTP_RC_SERVICE_ERR;
+          goto auth_done_service_err;
+          /* NB: state not updated. */
+        } else if (rc == -2) {
+          /*
+           * For event synchronous modes, we can never go backwards (the
+           * challenge() method can only walk forward on the event counter),
+           * so there is an implicit guarantee that we'll never get a
+           * response matching an event in the past.
+           *
+           * But for time synchronous modes, challenge() can walk backwards,
+           * in order to accomodate clock drift.  We must never allow a
+           * successful auth for a correct passcode earlier in time than
+           * one already used successfully, so we skip out early here.
+           */
+          if (opt->debug)
+            otp_log(OTP_LOG_DEBUG,
+                    "%s: %s: [%s], sync challenge t:%d e:%d is early",
+                    log_prefix, __func__, username, t, e);
+          continue;
+        }
+
+        /* Calculate sync response. */
+        if (card_info.cardops->response(&card_info, challenge, user_state.clen,
+                                        &e_response[pin_offset],
+                                        log_prefix) != 0) {
+          otp_log(OTP_LOG_ERR,
+                  "%s: %s: unable to calculate sync response "
+                  "t:%d e:%d for [%s], to challenge %s",
+                  log_prefix, __func__, t, e, username, challenge);
+          rc = OTP_RC_SERVICE_ERR;
+          goto auth_done_service_err;
+          /* NB: state not updated. */
+        }
+
+        /* NOTE: We do not display the PIN. */
+        if (opt->debug) {
+          char s[OTP_MAX_CHALLENGE_LEN * 2 + 1];
+
+          otp_log(OTP_LOG_DEBUG, "%s: %s: [%s], sync challenge t:%d e:%d %s, "
+                                 "expecting response %s",
+                  log_prefix, __func__, username, t, e,
+                  card_info.cardops->printchallenge(s, challenge,
+                                                    user_state.clen),
+                  &e_response[pin_offset]);
+        }
+
+        /* Add PIN if needed. */
+        if (!opt->prepend_pin)
+          (void) strcat(e_response, card_info.pin);
+
+        /* Test user-supplied passcode. */
+        if (passcode)
+          nmatch = strcmp(passcode, e_response);
+        else
+          nmatch = cmpfunc(data, e_response, log_prefix);
+        if (!nmatch) {
+          if (fc == OTP_FC_FAIL_HARD) {
+            otp_log(OTP_LOG_AUTH,
+                    "%s: %s: bad sync auth for [%s]: valid but in hardfail "
+                    " (%d/%d failed/max)", log_prefix, __func__, username,
+                    user_state.failcount, opt->hardfail);
+            rc = OTP_RC_MAXTRIES;
+          } else if (fc == OTP_FC_FAIL_SOFT) {
+            /*
+             * rwindow logic
+             */
+            if (!rwindow) {
+              /* rwindow mode not configured */
+              otp_log(OTP_LOG_AUTH,
+                      "%s: %s: bad sync auth for [%s]: valid but in softfail "
+                      " (%d/%d failed/max)", log_prefix, __func__, username,
+                      user_state.failcount, opt->softfail);
+              rc = OTP_RC_MAXTRIES;
+              goto auth_done;
+            }
+
+            /*
+             * User must enter two consecutive correct sync passcodes
+             * for rwindow softfail override.
+             */
+            if (card_info.cardops->isconsecutive(&card_info, &user_state,
+                                                 e, log_prefix)) {
+              /*
+               * This is the 2nd of two consecutive responses, is it
+               * soon enough?  Note that for persistent softfail we can't
+               * enforce rwindow_delay b/c the previous authtime is also
+               * the persistent softfail sentinel.
+               */
+              if (user_state.authtime == INT32_MAX ||
+                  now - user_state.authtime < (unsigned) opt->rwindow_delay) {
+                otp_log(OTP_LOG_AUTH,
+                        "%s: %s: rwindow softfail override for [%s] at "
+                        "window position t:%d e:%d", log_prefix, __func__,
+                        username, t, e);
+                rc = OTP_RC_OK;
+                goto sync_done;
+              } else {
+                /* consecutive but not soon enough */
+                otp_log(OTP_LOG_AUTH,
+                        "%s: %s: late override for [%s] at "
+                        "window position t:%d e:%d", log_prefix, __func__,
+                        username, t, e);
+                rc = OTP_RC_AUTH_ERR;
+              }
+            } /* if (passcode is consecutive */
+
+            /* passcode correct, but not consecutive or not soon enough */
+            if (opt->debug)
+              otp_log(OTP_LOG_DEBUG,
+                      "%s: %s: [%s] rwindow candidate "
+                      "at window position t:%d e:%d", log_prefix, __func__,
+                      username, t, e);
+            rc = OTP_RC_AUTH_ERR;
+
+          } else {
+            /* normal sync mode auth */
+            rc = OTP_RC_OK;
+          } /* else (!hardfail && !softfail) */
+
+sync_done:
+          /* force resync; this only has an effect if (rc == OTP_RC_OK) */
+          resync = 1;
+          /* update csd (et al.) on successful auth or rwindow candidate */
+          if (card_info.cardops->updatecsd(&user_state, now, t, e, rc) != 0) {
+            otp_log(OTP_LOG_ERR, "%s: %s: unable to update csd for [%s]",
+                    log_prefix, __func__, username);
+            rc = OTP_RC_SERVICE_ERR;
+            goto auth_done_service_err;
+            /* NB: state not updated. */
+          }
+          goto auth_done;
+
+        } /* if (passcode is valid) */
+      } /* for (each slot in the ewindow) */
+    } /* for (each slot in the twindow) */
+  } /* if (sync mode possible) */
+
+  /* Both async and sync mode failed. */
+  rc = OTP_RC_AUTH_ERR;
+
+auth_done:
+  if (rc == OTP_RC_OK) {
+    if (resync)
+      (void) memcpy(user_state.challenge, challenge, user_state.clen);
+    user_state.failcount   = 0;
+    user_state.authtime    = now;
+  } else {
+    if (++user_state.failcount == UINT_MAX)
+      user_state.failcount--;
+    /* authtime set to INT32_MAX by nullstate(); leave it to force softfail */
+    if (user_state.authtime != INT32_MAX)
+      user_state.authtime = (int32_t) now;     /* cast prevents overflow */
+    /*
+     * Note that we don't update the challenge.  Even for softfail (where
+     * we might have actually had a good auth), we want the challenge
+     * unchanged because we always start our sync loop at e=0 t=0 (and so
+     * challenge must stay as the 0'th challenge regardless of next valid
+     * window position, because the challenge() method can't return
+     * arbitrary event window positions--since we start at e=0 the challenge
+     * must be the 0th challenge, i.e. unchanged).
+     */
+  } /* else (rc != OTP_RC_OK) */
+  user_state.updated = 1;
+
+auth_done_service_err: /* exit here for system errors */
+  /*
+   * Release and update state.
+   *
+   * We "fail-out" if we can't do this, because for sync mode the
+   * response can be reused until state data is updated, an obvious
+   * replay attack.
+   *
+   * For async mode with RADIUS, if we can't update the last auth
+   * time, we will be open to a less obvious replay attack over the
+   * lifetime of the State attribute (opt->chal_delay): if someone
+   * that can see RADIUS traffic captures an Access-Request containing
+   * a State attribute, and can cause the NAS to cycle the request id
+   * within opt->chal_delay secs, then they can login to the NAS and
+   * insert the captured State attribute into the new Access-Request,
+   * and we'll give an Access-Accept.
+   */
+  if (user_state.locked) {
+    if (otp_state_put(username, &user_state, log_prefix) != 0) {
+      otp_log(OTP_LOG_ERR, "%s: %s: unable to put state for [%s]",
+              log_prefix, __func__, username);
+      rc = OTP_RC_SERVICE_ERR; /* no matter what it might have been */
+    }
+  }
+
+  return rc;
+}
diff --git a/src/modules/rlm_otp/otp_cardops.h b/src/modules/rlm_otp/otp_cardops.h
new file mode 100644 (file)
index 0000000..87b5fb0
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * otp_cardops.h
+ * $Id$
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * Copyright 2005 TRI-D Systems, Inc.
+ */
+
+#ifndef OTP_CARDOPS_H
+#define OTP_CARDOPS_H
+
+#include "otp.h"
+
+/*
+ * Card Features bitmask.
+ */
+#define OTP_CF_NONE            0
+/* sync/async modes */
+#define OTP_CF_AM              0x01 << 1  /* async mode (chal/resp) */
+#define OTP_CF_ES              0x01 << 2  /* event synchronous      */
+#define OTP_CF_TS              0x01 << 3  /* time synchronous       */
+#define OTP_CF_SM              (OTP_CF_ES|OTP_CF_TS)
+/* display modes */
+#define OTP_CF_HD              0x01 << 4  /* hex display            */
+#define OTP_CF_DD              0x01 << 5  /* dec display            */
+#define OTP_CF_R8              0x01 << 6  /* 8 digit response       */
+#define OTP_CF_R7              0x01 << 7  /* 7 digit response       */
+#define OTP_CF_R6              0x01 << 8  /* 6 digit response       */
+/* sync challenge length */
+#define OTP_CF_C8              0x01 << 9  /* 8 byte challenge       */
+#define OTP_CF_C4              0x01 << 10 /* 4 byte challenge       */
+#define OTP_CF_CL              (OTP_CF_C8|OTP_CF_C4)
+#define OTP_CF_CL_SHIFT                9          /* convert mask to value  */
+/* nominal twindow (TRI-D) */
+#define OTP_CF_TW0             0x01 << 11 /* twindow 2^0            */
+#define OTP_CF_TW1             0x01 << 12 /* twindow 2^1            */
+#define OTP_CF_TW2             0x01 << 13 /* twindow 2^2            */
+#define OTP_CF_TW3             0x01 << 14 /* twindow 2^3            */
+#define OTP_CF_TW              (OTP_CF_TW0|OTP_CF_TW1|OTP_CF_TW2|OTP_CF_TW3)
+#define OTP_CF_TW_SHIFT                11        /* convert mask to value  */
+/* force rwindow for event+time sync cards (TRI-D) */
+#define OTP_CF_FRW0            0x01 << 15 /* force event window 2^0 */
+#define OTP_CF_FRW1            0x01 << 16 /* force event window 2^1 */
+#define OTP_CF_FRW2            0x01 << 17 /* force event window 2^2 */
+#define OTP_CF_FRW             (OTP_CF_FRW0|OTP_CF_FRW1|OTP_CF_FRW2)
+#define OTP_CF_FRW_SHIFT       15         /* convert mask to value  */
+/* vendor specific */
+#define OTP_CF_VS1             0x01 << 18 /* vendor specific 1      */
+#define OTP_CF_VS2             0x01 << 19 /* vendor specific 2      */
+#define OTP_CF_VS3             0x01 << 20 /* vendor specific 3      */
+#define OTP_CF_VS4             0x01 << 21 /* vendor specific 4      */
+
+#define OTP_CF_MAX             0x01 << 31 /* MAX placeholder        */
+
+#define OTP_MAX_RESPONSE_LEN 16                /* Secure Computing can do 16 */
+
+/* cardops object */
+typedef struct cardops_t {
+  const char *prefix;
+  size_t prefix_len;   /* to avoid strlen(prefix) */
+
+  int (*name2fm)(const char *, uint32_t *);
+  int (*keystring2keyblock)(const char *, unsigned char [OTP_MAX_KEY_LEN]);
+  int (*nullstate)(const otp_option_t *, const otp_card_info_t *,
+                   otp_user_state_t *, time_t, const char *);
+  int (*challenge)(const otp_card_info_t *, otp_user_state_t *,
+                   unsigned char [OTP_MAX_CHALLENGE_LEN], time_t, int, int,
+                   const char *);
+  int (*response)(otp_card_info_t *,
+                  const unsigned char [OTP_MAX_CHALLENGE_LEN], size_t,
+                  char [OTP_MAX_RESPONSE_LEN + 1], const char *);
+  int (*updatecsd)(otp_user_state_t *, time_t, int, int, int);
+  int (*isconsecutive)(const otp_card_info_t *, const otp_user_state_t *, int,
+                       const char *);
+  int (*maxtwin)(const otp_card_info_t *, const char [OTP_MAX_CSD_LEN + 1]);
+  char *(*printchallenge)(char [OTP_MAX_CHALLENGE_LEN * 2 + 1],
+                          const unsigned char [OTP_MAX_CHALLENGE_LEN], size_t);
+} cardops_t;
+#define OTP_MAX_VENDORS 16
+extern cardops_t otp_cardops[OTP_MAX_VENDORS];
+extern int otp_num_cardops;
+
+#endif /* OTP_CARDOPS_H */
diff --git a/src/modules/rlm_otp/otp_hotp.c b/src/modules/rlm_otp/otp_hotp.c
new file mode 100644 (file)
index 0000000..eb79a67
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * otp_hotp.c
+ * $Id$
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * Copyright 2005 TRI-D Systems, Inc.
+ */
+
+#include <openssl/hmac.h>
+
+#include "otp.h"
+
+static const char rcsid[] = "$Id$";
+
+
+/*
+ * This implements HOTP per draft-mraihi-oath-hmac-otp-04.txt, for Digit = 6.
+ *
+ * The HOTP algorithm is:
+ * 1. HS = HMAC-SHA-1(K, C)
+ *    Take the SHA-1 HMAC of the key (K) and counter (C).
+ * 2. S = DT(HS)
+ *    Take the "Dynamic Truncation" of the HMAC.
+ * 3. HOTP = StToNum(S) % 10^Digit
+ *    Take the modulus of S as a bigendian integer.
+ *
+ * Returns 0 on success, -1 on failure.  output is the ASCII HOTP on success.
+ */
+int
+otp_hotp_mac(const unsigned char counter[8], unsigned char output[7],
+             const unsigned char keyblock[OTP_MAX_KEY_LEN], size_t key_len,
+             const char *log_prefix)
+{
+  unsigned char hmac[EVP_MAX_MD_SIZE]; /* >=20 */
+  int hmac_len = 0;
+  uint32_t dbc;                /* "dynamic binary code" from HOTP draft */
+
+  /* 1. hmac */
+  if (!HMAC(EVP_sha1(), keyblock, key_len, counter, 8, hmac, &hmac_len) ||
+      hmac_len != 20) {
+    otp_log(OTP_LOG_ERR, "%s: %s: HMAC failed", log_prefix, __func__);
+    return -1;
+  }
+
+  /* 2. the truncate step is unnecessarily complex */
+  {
+    int offset;
+
+    offset = hmac[19] & 0x0F;
+    /* we can't just cast hmac[offset] because of alignment and endianness */
+    dbc = (hmac[offset] & 0x7F) << 24 |
+          hmac[offset + 1]      << 16 |
+          hmac[offset + 2]      << 8  |
+          hmac[offset + 3];
+  }
+
+  /* 3. int conversion and modulus (as string) */
+  (void) sprintf(output, "%06lu", dbc % 1000000L);
+
+  return 0;
+}
similarity index 67%
rename from src/modules/rlm_x99_token/x99_log.c
rename to src/modules/rlm_otp/otp_log.c
index 33bde3a..86a7178 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * x99_log.c
+ * otp_log.c
  * $Id$
  *
  *   This program is free software; you can redistribute it and/or modify
  *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  *
  * Copyright 2002  Google, Inc.
+ * Copyright 2005 TRI-D Systems, Inc.
  */
 
-#ifdef FREERADIUS
-#include "radiusd.h"
-#endif
-#include "x99.h"
+#include "otp.h"
 
 #include <stdio.h>
 #include <stdlib.h>
 #include <stdarg.h>
 #include <string.h>
-#ifndef FREERADIUS
+#ifdef PAM
 #include <syslog.h>
 #endif
 
 static const char rcsid[] = "$Id$";
 
 void
-x99_log(int level, const char *format, ...)
+otp_log(int level, const char *format, ...)
 {
-    va_list ap;
-    char *fmt;
-
-    va_start(ap, format);
-    fmt = malloc(strlen(X99_MODULE_NAME) + strlen(format) + 3);
-    if (!fmt) {
-       va_end(ap);
-       return;
-    }
-    (void) sprintf(fmt, "%s: %s", X99_MODULE_NAME, format);
+  va_list ap;
+  va_start(ap, format);
 
 #ifdef FREERADIUS
-    (void) vradlog(level, fmt, ap);
+  (void) vradlog(level, format, ap);
 #else
-    vsyslog(level, fmt, ap);
+  vsyslog(level, format, ap);
 #endif
 
-    va_end(ap);
-    free(fmt);
+  va_end(ap);
 }
-
diff --git a/src/modules/rlm_otp/otp_pwe.c b/src/modules/rlm_otp/otp_pwe.c
new file mode 100644 (file)
index 0000000..0eac7fc
--- /dev/null
@@ -0,0 +1,767 @@
+/*
+ * otp_pwe.c
+ * $Id$
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * Copyright 2001,2002  Google, Inc.
+ * Copyright 2005 TRI-D Systems, Inc.
+ */
+
+/*
+ * This file implements passcode (password) checking functions for each
+ * supported encoding (PAP, CHAP, etc.).  The current libradius interface
+ * is not sufficient for X9.9 use.
+ */
+
+#ifdef FREERADIUS
+#define _LRAD_MD4_H
+#define _LRAD_SHA1_H
+#include <freeradius-devel/rad_assert.h>
+#endif
+#include "otp.h"
+#include "otp_pwe.h"
+
+#include <openssl/des.h>
+#include <openssl/md4.h>
+#include <openssl/md5.h>
+#include <openssl/sha.h>
+
+#include <string.h>
+
+static const char rcsid[] = "$Id$";
+
+
+/* Attribute IDs for supported password encodings. */
+static int pwattr[8];
+
+
+/* Initialize the pwattr array for supported password encodings. */
+void
+otp_pwe_init(void)
+{
+  DICT_ATTR *da;
+  int i = 0;
+
+  /*
+   * Setup known password types.  These are pairs.
+   * NB: Increase pwattr array size when adding a type.
+   *     It should be sized as (number of password types * 2)
+   */
+  (void) memset(pwattr, 0, sizeof(pwattr));
+
+  /* PAP */
+  if ((da = dict_attrbyname("User-Password")) != NULL) {
+    pwattr[i++] = da->attr;
+    pwattr[i++] = da->attr;
+  }
+
+  /* CHAP */
+  if ((da = dict_attrbyname("CHAP-Challenge")) != NULL) {
+    pwattr[i++] = da->attr;
+    if ((da = dict_attrbyname("CHAP-Password")) != NULL)
+      pwattr[i++] = da->attr;
+    else
+      pwattr[--i] = 0;
+  }
+
+#if 0
+  /* MS-CHAP (recommended not to use) */
+  if ((da = dict_attrbyname("MS-CHAP-Challenge")) != NULL) {
+    pwattr[i++] = da->attr;
+    if ((da = dict_attrbyname("MS-CHAP-Response")) != NULL)
+      pwattr[i++] = da->attr;
+    else
+      pwattr[--i] = 0;
+  }
+#endif /* 0 */
+
+  /* MS-CHAPv2 */
+  if ((da = dict_attrbyname("MS-CHAP-Challenge")) != NULL) {
+    pwattr[i++] = da->attr;
+    if ((da = dict_attrbyname("MS-CHAP2-Response")) != NULL)
+      pwattr[i++] = da->attr;
+    else
+      pwattr[--i] = 0;
+  }
+}
+
+
+/*
+ * Test for password presence in an Access-Request packet.
+ * Returns 0 for "no supported password present", or an non-zero
+ * opaque value that must be used when calling otp_pwe_cmp().
+ */
+int
+otp_pwe_present(const REQUEST *request, const char *log_prefix)
+{
+  unsigned i;
+
+  for (i = 0; i < sizeof(pwattr) && pwattr[i]; i += 2) {
+    if (pairfind(request->packet->vps, pwattr[i]) &&
+        pairfind(request->packet->vps, pwattr[i + 1])) {
+      DEBUG("%s: %s: password attributes %d, %d", log_prefix, __func__,
+             pwattr[i], pwattr[i + 1]);
+      return i + 1; /* Can't return 0 (indicates failure) */
+    }
+  }
+
+  DEBUG("%s: %s: no password attributes present", log_prefix, __func__);
+  return 0;
+}
+
+
+/*
+ * Test for passcode (password) equality.
+ * returns 0 for match, non-zero for non-match.
+ * If data->returned_vps is non-null, then on matches, it will point to
+ * vps that should be added to an Access-Accept packet.  If access is denied,
+ * the caller is responsible for freeing any vps returned.
+ */
+int
+otp_pwe_cmp(struct otp_pwe_cmp_t *data, const char *password,
+            const char *log_prefix)
+{
+  const REQUEST *request       = data->request;
+  const otp_option_t *inst     = data->inst;
+  int attr                     = data->pwattr;
+  VALUE_PAIR **vps             = data->returned_vps;
+
+  int nmatch = -1;
+  VALUE_PAIR *chal_vp, *resp_vp;
+
+  /*
+   * A module that does this might want to verify the presence of these.
+   * This code is self contained to otp, so I know these exist.
+   */
+  chal_vp = pairfind(request->packet->vps, pwattr[attr - 1]);
+  resp_vp = pairfind(request->packet->vps, pwattr[attr]);
+
+  /* Prepare for failure return. */
+  if (vps)
+    *vps = NULL;
+
+  /* If modular, this would actually call the authentication function. */
+  switch(pwattr[attr]) {
+  case PW_PASSWORD:
+    DEBUG("%s: %s: handling PW_PASSWORD", log_prefix, __func__);
+    nmatch = strcmp(password, resp_vp->vp_strvalue);
+    break;
+
+  case PW_CHAP_PASSWORD:
+  {
+    /*
+     * See RFC 1994.
+     * A CHAP password is MD5(CHAP_ID|SECRET|CHAP_CHALLENGE).
+     * CHAP_ID is a value set by the authenticator (the NAS), and used
+     * in the response calculation.  It is available as the first byte
+     * of the CHAP-Password attribute.
+     * SECRET is the password.
+     * CHAP_CHALLENGE is the challenge given to the peer (the user).
+     * The CHAP-Challenge Attribute may be missing, in which case the
+     * challenge is taken to be the Request Authenticator.  We don't
+     * handle this case.
+     */
+    /*                 ID       password    chal */
+    unsigned char input[1 + MAX_STRING_LEN + 16];
+    unsigned char output[MD5_DIGEST_LENGTH];
+
+    DEBUG("%s: %s: handling PW_CHAP_PASSWORD", log_prefix, __func__);
+    if (1 + strlen(password) + chal_vp->length > sizeof(input)) {
+      DEBUG("%s: %s: CHAP-Challenge/password too long", log_prefix, __func__);
+      nmatch = -1;
+      break;
+    }
+    if (resp_vp->length != 17) {
+      otp_log(OTP_LOG_AUTH, "%s: %s: CHAP-Password wrong size",
+              log_prefix, __func__);
+      nmatch = -1;
+      break;
+    }
+    input[0] = *(resp_vp->vp_strvalue);
+    (void) memcpy(&input[1], password, strlen(password));
+    (void) memcpy(&input[1+strlen(password)], chal_vp->vp_strvalue,
+                  chal_vp->length);
+    (void) MD5(input, 1 + strlen(password) + chal_vp->length, output);
+    nmatch = memcmp(output, &(resp_vp->vp_strvalue)[1], MD5_DIGEST_LENGTH);
+  } /* case PW_CHAP_PASSWORD */
+  break;
+
+#if 0
+  case PW_MS_CHAP_RESPONSE:
+  {
+    /*
+     * See RFCs 2548, 2433, 3079.
+     * An MS-CHAP response is (IDENT|FLAGS|LM_RESPONSE|NT_RESPONSE).
+     *                 octets:   1     1       24           24
+     * IDENT is not used by RADIUS (it is the PPP MS-CHAP Identifier).
+     * FLAGS is 1 to indicate the NT_RESPONSE should be preferred.
+     * LM_RESPONSE is the LAN Manager compatible response.
+     * NT_RESPONSE is the NT compatible response.
+     * Either response may be zero-filled indicating its absence.
+     * Use of the LM response has been deprecated (RFC 2433, par. 6),
+     * so we don't handle it.
+     *
+     * The NT_RESPONSE is (DES(CHAL,K1)|DES(CHAL,K2)|DES(CHAL,K3)), where
+     * CHAL is the 8-octet challenge, and K1, K2, K3 are 7-octet pieces
+     * of MD4(unicode(password)), zero-filled to 21 octets.  Sigh.
+     */
+    unsigned char nt_keys[21]; /* sized for 3 DES keys */
+    unsigned char input[MAX_STRING_LEN * 2]; /* doubled for unicode */
+    unsigned char output[24];
+    int password_len, i;
+    VALUE_PAIR *vp;
+
+    DEBUG("%s: %s: handling PW_MS_CHAP_RESPONSE", log_prefix, __func__);
+    if (chal_vp->length != 8) {
+      otp_log(OTP_LOG_AUTH, "%s: %s: MS-CHAP-Challenge wrong size",
+              log_prefix, __func__);
+      nmatch = -1;
+      break;
+    }
+    if (resp_vp->length != 50) {
+      otp_log(OTP_LOG_AUTH, "%s: %s: MS-CHAP-Response wrong size",
+              log_prefix, __func__);
+      nmatch = -1;
+      break;
+    }
+    if ((resp_vp->vp_strvalue)[1] != 1) {
+      otp_log(OTP_LOG_AUTH,
+              "%s: %s: MS-CHAP-Response bad flags (LM not supported)",
+              log_prefix, __func__);
+      nmatch = -1;
+      break;
+    }
+    /* This is probably overkill. */
+    if (strlen(password) > MAX_STRING_LEN) {
+      otp_log(OTP_LOG_AUTH, "%s: %s: MS-CHAP password too long",
+              log_prefix, __func__);
+      nmatch = -1;
+      break;
+    }
+
+    /*
+     * Start by hashing the unicode password.
+     * This is broken because unicode chars are machine-ordered,
+     * but the spec (RFC 2433) doesn't say how to prepare
+     * the password for md4 (other than by example values).
+     */
+    password_len = strlen(password);
+    for (i = 0; i < password_len; ++i) {
+      /* Set the high order 8 bits to 0 (little-endian) */
+      input[i * 2] = *password++;
+      input[i * 2 + 1] = 0;
+    }
+    (void) memset(nt_keys, 0, sizeof(nt_keys));
+    (void) MD4(input, 2 * password_len, nt_keys);
+
+    /* The challenge gets encrypted. */
+    (void) memcpy(input, chal_vp->vp_strvalue, 8);
+
+    /* Convert the password hash to keys, and do the encryptions. */
+    for (i = 0; i < 3; ++i) {
+      des_cblock key;
+      des_key_schedule ks;
+
+      otp_key_from_hash(&key, &nt_keys[i * 7]);
+      des_set_key_unchecked(&key, ks);
+      des_ecb_encrypt((des_cblock *) input,
+                      (des_cblock *) &output[i * 8],
+                      ks, DES_ENCRYPT);
+    }
+
+    nmatch = memcmp(output, resp_vp->vp_strvalue + 26, 24);
+    if (nmatch || !vps)
+      break;
+
+    /*
+     * Generate the MS-CHAP-MPPE-Keys attribute if needed.  This is not
+     * specified anywhere -- RFC 2548, par. 2.4.1 is the authority but
+     * it has typos and omissions that make this unimplementable.  The
+     * code here is based on experimental results provided by
+     * Takahiro Wagatsuma <waga@sic.shibaura-it.ac.jp>.
+     * We only support 128-bit keys derived from the NT hash; 40-bit
+     * and 56-bit keys are derived from the LM hash, which besides
+     * being deprecated, has severe security problems.
+     */
+
+    /* First, set some related attributes. */
+    vp = pairmake("MS-MPPE-Encryption-Policy",
+                  otp_mppe_policy[inst->mschap_mppe_policy], T_OP_EQ);
+    rad_assert(vp != NULL);
+    pairadd(vps, vp);
+    vp = pairmake("MS-MPPE-Encryption-Types",
+                  otp_mppe_types[inst->mschap_mppe_types], T_OP_EQ);
+    rad_assert(vp != NULL);
+    pairadd(vps, vp);
+
+    if (inst->mschap_mppe_policy) {
+      unsigned char mppe_keys[32];
+      /*                    0x    ASCII(mppe_keys)      '\0' */
+      char mppe_keys_string[2 + (2 * sizeof(mppe_keys)) + 1];
+
+      unsigned char md5_md[MD5_DIGEST_LENGTH];
+      unsigned char encode_buf[AUTH_VECTOR_LEN + MAX_STRING_LEN];
+      int secretlen;
+
+      /* Zero the LM-Key sub-field (and padding). */
+      (void) memset(mppe_keys, 0, sizeof(mppe_keys));
+      /* The NT-Key sub-field is MD4(MD4(unicode(password))). */
+      (void) MD4(nt_keys, 16, &mppe_keys[8]);
+
+#if 0 /* encoding now handled in lib/radius.c:rad_pwencode() */
+      /* Now we must encode the key as User-Password is encoded. */
+      secretlen = strlen(request->secret);
+      (void) memcpy(encode_buf, request->secret, secretlen);
+      (void) memcpy(encode_buf + secretlen, request->packet->vector,
+                    AUTH_VECTOR_LEN);
+      (void) MD5(encode_buf, secretlen + AUTH_VECTOR_LEN, md5_md);
+      for (i = 0; i < 16; ++i)
+        mppe_keys[i] ^= md5_md[i];
+      (void) memcpy(encode_buf + secretlen, mppe_keys, MD5_DIGEST_LENGTH);
+      (void) MD5(encode_buf, secretlen + MD5_DIGEST_LENGTH, md5_md);
+      for (i = 0; i < 16; ++i)
+        mppe_keys[i + 16] ^= md5_md[i];
+#endif /* 0 */
+
+      /* Whew.  Now stringify it for pairmake(). */
+      mppe_keys_string[0] = '0';
+      mppe_keys_string[1] = 'x';
+      for (i = 0; i < 32; ++i)
+        (void) sprintf(&mppe_keys_string[i*2+2], "%02X", mppe_keys[i]);
+      vp = pairmake("MS-CHAP-MPPE-Keys", mppe_keys_string, T_OP_EQ);
+      rad_assert(vp != NULL);
+      pairadd(vps, vp);
+    } /* if (doing mppe) */
+
+  } /* case PW_MS_CHAP_RESPONSE */
+  break;
+#endif /* 0 (MS_CHAP) */
+
+  case PW_MS_CHAP2_RESPONSE:
+  {
+    /*
+     * See RFCs 2548, 2759, 3079.
+     * An MS-CHAPv2 response is
+     *          (IDENT|FLAGS|PEER_CHALLENGE|RESERVED|NT_RESPONSE).
+     *   octets:   1     1         16          8        24
+     * IDENT is the PPP MS-CHAPv2 Identifier, used in MS-CHAP2-Success.
+     * FLAGS is currently unused.
+     * PEER_CHALLENGE is a random number, generated by the peer.
+     * NT_RESPONSE is (DES(CHAL,K1)|DES(CHAL,K2)|DES(CHAL,K3)), where
+     * K1, K2, K3 are 7-octet pieces of MD4(unicode(password)), zero-
+     * filled to 21 octets (just as in MS-CHAP); and CHAL is
+     * MSB8(SHA(PEER_CHALLENGE|MS_CHAP_CHALLENGE|USERNAME)).
+     */
+    unsigned char nt_keys[21]; /* aka "password_md", sized for 3 DES keys */
+    unsigned char password_md_md[MD4_DIGEST_LENGTH]; /* for mutual auth */
+    unsigned char input[MAX_STRING_LEN * 2]; /* doubled for unicode */
+    unsigned char output[24];
+    unsigned password_len, i;
+    VALUE_PAIR *vp;
+
+    DEBUG("%s: %s: handling PW_MS_CHAP2_RESPONSE", log_prefix, __func__);
+    if (chal_vp->length != 16) {
+      otp_log(OTP_LOG_AUTH, "%s: %s: MS-CHAP-Challenge (v2) wrong size",
+              log_prefix, __func__);
+      nmatch = -1;
+      break;
+    }
+    if (resp_vp->length != 50) {
+      otp_log(OTP_LOG_AUTH, "%s: %s: MS-CHAP2-Response wrong size",
+              log_prefix, __func__);
+      nmatch = -1;
+      break;
+    }
+    /* This is probably overkill. */
+    if (strlen(password) > MAX_STRING_LEN) {
+      otp_log(OTP_LOG_AUTH, "%s: %s: MS-CHAPv2 password too long",
+              log_prefix, __func__);
+      nmatch = -1;
+      break;
+    }
+
+    /*
+     * Start by hashing the unicode password.
+     * This is broken because unicode chars are machine-ordered,
+     * but the spec (RFC 2759) doesn't say how to prepare
+     * the password for md4 (other than by example values).
+     */
+    password_len = strlen(password);
+    for (i = 0; i < password_len; ++i) {
+      /* Set the high order 8 bits to 0 (little-endian) */
+      input[i * 2] = *password++;
+      input[i * 2 + 1] = 0;
+    }
+    (void) memset(nt_keys, 0, sizeof(nt_keys));
+    (void) MD4(input, 2 * password_len, nt_keys);
+
+    /* Now calculate the CHAL value from our various inputs. */
+    {
+      SHA_CTX ctx;
+      unsigned char md[SHA_DIGEST_LENGTH];
+      char *username = request->username->vp_strvalue;
+      int username_len = request->username->length;
+
+      SHA1_Init(&ctx);
+      SHA1_Update(&ctx, resp_vp->vp_strvalue + 2, 16);
+      SHA1_Update(&ctx, chal_vp->vp_strvalue, 16);
+      SHA1_Update(&ctx, username, username_len);
+      SHA1_Final(md, &ctx);
+
+      (void) memcpy(input, md, 8);
+    }
+
+    /* Convert the password hash to keys, and do the encryptions. */
+    for (i = 0; i < 3; ++i) {
+      des_cblock key;
+      des_key_schedule ks;
+
+      otp_key_from_hash(&key, &nt_keys[i * 7]);
+      des_set_key_unchecked(&key, ks);
+      des_ecb_encrypt((des_cblock *) input,
+                      (des_cblock *) &output[i * 8],
+                      ks, DES_ENCRYPT);
+    }
+
+    nmatch = memcmp(output, resp_vp->vp_strvalue + 26, 24);
+    if (nmatch || !vps)
+      break;
+
+    /*
+     * MS-CHAPv2 requires mutual authentication; we must prove
+     * that we know the secret.  This is a bit circuitous: set
+     * MD1 = SHA(MD4(MD4(unicode(password)))|NT_RESPONSE|MAGIC1),
+     * MD2 = MSB8(SHA(PEER_CHALLENGE|MS_CHAP_CHALLENGE|USERNAME)),
+     * and finally use SHA(MD1|MD2|MAGIC2) as the authenticator.
+     * The authenticator is returned as the string "S=<auth>",
+     * <auth> is the authenticator expressed as [uppercase] ASCII.
+     * See RFC 2759.
+     */
+    {
+      SHA_CTX ctx;
+      unsigned char md1[SHA_DIGEST_LENGTH];
+      unsigned char md2[SHA_DIGEST_LENGTH];
+      unsigned char auth_md[SHA_DIGEST_LENGTH];
+      /*                  S=  (  ASCII(auth_md)   )  \0 */
+      char auth_md_string[2 + (2 * sizeof(auth_md)) + 1];
+      /*
+       * ugh.  The ASCII authenticator (auth_md_string) is sent
+       * along with a single (useless) binary byte (the ID).
+       * So we must "stringify" it again (for pairmake()) since the
+       * binary byte requires the attribute to be of type "octets".
+       */
+      /*                    0x  (ID) ( ASCII("S="ASCII(auth_md))) */
+      char auth_octet_string[2 + 2 + (2 * sizeof(auth_md_string))];
+
+      char *username = request->username->vp_strvalue;
+      int username_len = request->username->length;
+
+      /* "Magic server to client signing constant" */
+      unsigned char magic1[39] =
+        { 0x4D, 0x61, 0x67, 0x69, 0x63, 0x20, 0x73, 0x65, 0x72, 0x76,
+          0x65, 0x72, 0x20, 0x74, 0x6F, 0x20, 0x63, 0x6C, 0x69, 0x65,
+          0x6E, 0x74, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x69, 0x6E, 0x67,
+          0x20, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74 };
+      /* "Pad to make it do more than one iteration" */
+      unsigned char magic2[41] =
+        { 0x50, 0x61, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x6D, 0x61, 0x6B,
+          0x65, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6F, 0x20, 0x6D, 0x6F,
+          0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x6E, 0x20, 0x6F, 0x6E,
+          0x65, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6F,
+          0x6E };
+
+      /* MD1 */
+      (void) MD4(nt_keys, MD4_DIGEST_LENGTH, password_md_md);
+      SHA1_Init(&ctx);
+      SHA1_Update(&ctx, password_md_md, MD4_DIGEST_LENGTH);
+      SHA1_Update(&ctx, resp_vp->vp_strvalue + 26, 24);
+      SHA1_Update(&ctx, magic1, sizeof(magic1));
+      SHA1_Final(md1, &ctx);
+
+      /* MD2 */
+      SHA1_Init(&ctx);
+      SHA1_Update(&ctx, resp_vp->vp_strvalue + 2, 16);
+      SHA1_Update(&ctx, chal_vp->vp_strvalue, 16);
+      SHA1_Update(&ctx, username, username_len);
+      SHA1_Final(md2, &ctx);
+
+      /* The Authenticator */
+      SHA1_Init(&ctx);
+      SHA1_Update(&ctx, md1, SHA_DIGEST_LENGTH);
+      SHA1_Update(&ctx, md2, 8);
+      SHA1_Update(&ctx, magic2, sizeof(magic2));
+      SHA1_Final(auth_md, &ctx);
+
+      /* String conversion. */
+      auth_md_string[0] = 'S';
+      auth_md_string[1] = '=';
+      for (i = 0; i < sizeof(auth_md); ++i)
+        (void) sprintf(&auth_md_string[i * 2 + 2], "%02X", auth_md[i]);
+
+      /* And then octet conversion.  Ugh! */
+      auth_octet_string[0] = '0';
+      auth_octet_string[1] = 'x';
+      (void) sprintf(&auth_octet_string[2], "%02X", resp_vp->vp_strvalue[0]);
+      for (i = 0; i < sizeof(auth_md_string) - 1; ++i)
+        (void) sprintf(&auth_octet_string[i * 2 +4], "%02X", auth_md_string[i]);
+
+      vp = pairmake("MS-CHAP2-Success", auth_octet_string, T_OP_EQ);
+      rad_assert(vp != NULL);
+      pairadd(vps, vp);
+    } /* Generate mutual auth info. */
+
+    /*
+     * Generate the MPPE initial session key if needed, per RFC 3079.
+     * (Although, RFC 2548 leaves us guessing at how to generate this.)
+     * For MS-CHAPv2 we support all key lengths (40-, 56- and 128-bit),
+     * although MPPE via RADIUS supports only 40- and 128-bit keys.
+     * This is a bit more complicated than MS-CHAP.  Start by generating
+     * a "master session key"
+     *    MSB16(SHA(NTPasswordHashHash|NT_RESPONSE|MAGIC1)), where
+     * NTPasswordHashHash is MD4(MD4(unicode(password))), NT_RESPONSE
+     * is from the MS-CHAP2-Response attribute, and MAGIC1 is a
+     * constant from RFC 3079.  Then, we derive asymmetric send/receive
+     * keys from the master session key.  The "master send key" is
+     *     MSBx(SHA(MASTERKEY|SHSPAD1|MAGIC3|SHSPAD2)),
+     * and the "master receive key" is
+     *     MSBx(SHA(MASTERKEY|SHSPAD1|MAGIC2|SHSPAD2)), where
+     * MASTERKEY is the "master session key" generated above, and the
+     * other values are constants from RFC 3079.  MSBx is the x-most
+     * significant bytes, where x is 5, 7, or 16 as appropriate for
+     * the desired key length.  We always generate 16 byte (128-bit)
+     * keys, the NAS is required to truncate as needed.
+     */
+
+    /* First, set some related attributes. */
+    vp = pairmake("MS-MPPE-Encryption-Policy",
+                  otp_mppe_policy[inst->mschapv2_mppe_policy], T_OP_EQ);
+    rad_assert(vp != NULL);
+    pairadd(vps, vp);
+    vp = pairmake("MS-MPPE-Encryption-Types",
+                  otp_mppe_types[inst->mschapv2_mppe_types], T_OP_EQ);
+    rad_assert(vp != NULL);
+    pairadd(vps, vp);
+
+    if (inst->mschapv2_mppe_policy) {
+      /* These constants and key vars are named from RFC 3079. */
+      /* "This is the MPPE Master Key" */
+      unsigned char Magic1[27] =
+        { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
+          0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
+          0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
+      /* "On the client side, this is the send key; "
+         "on the server side, it is the receive key." */
+      unsigned char Magic2[84] =
+        { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
+          0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
+          0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
+          0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
+          0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
+          0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
+          0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
+          0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
+          0x6b, 0x65, 0x79, 0x2e };
+      /* "On the client side, this is the receive key; "
+         "on the server side, it is the send key." */
+      unsigned char Magic3[84] =
+        { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
+          0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
+          0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
+          0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
+          0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
+          0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
+          0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
+          0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
+          0x6b, 0x65, 0x79, 0x2e };
+      unsigned char SHSpad1[40] =
+        { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+      unsigned char SHSpad2[40] =
+        { 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
+          0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
+          0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
+          0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 };
+      unsigned char MasterKey[16];
+      unsigned char MasterSendKey[16];
+      unsigned char MasterReceiveKey[16];
+
+      SHA_CTX ctx;
+      unsigned char sha_md[SHA_DIGEST_LENGTH];
+#if 0 /* salting/encoding now handled in lib/radius.c:tunnel_pwencode() */
+      unsigned char md5_md[MD5_DIGEST_LENGTH];
+
+      /*   From RFC 2548:           S                 R           A */
+      unsigned char encode_buf[MAX_STRING_LEN + AUTH_VECTOR_LEN + 2];
+      int secretlen;
+
+      /* A useless value required by RFC 2548. */
+      unsigned char salt[2];
+      unsigned char mppe_key[32]; /* 1 + 16 + padding */
+      /*                           0x   (   ASCII(salt)  ) */
+      unsigned char mppe_key_string[2 + (2 * sizeof(salt)) +
+      /*                            (   ASCII(mppe_key)  )  \0 */
+                                    (2 * sizeof(mppe_key)) + 1];
+#else /* 0 */
+      /*                           0x   (   ASCII(mppe_key)   )  \0 */
+      unsigned char mppe_key_string[2 + (2 * sizeof(MasterKey)) + 1];
+#endif /* 0 */
+
+      /* Generate the master session key. */
+      SHA1_Init(&ctx);
+      SHA1_Update(&ctx, password_md_md, MD4_DIGEST_LENGTH);
+      SHA1_Update(&ctx, resp_vp->vp_strvalue + 26, 24);
+      SHA1_Update(&ctx, Magic1, sizeof(Magic1));
+      SHA1_Final(sha_md, &ctx);
+      (void) memcpy(MasterKey, sha_md, 16);
+
+      /* Generate the master send key. */
+      SHA1_Init(&ctx);
+      SHA1_Update(&ctx, MasterKey, 16);
+      SHA1_Update(&ctx, SHSpad1, 40);
+      SHA1_Update(&ctx, Magic3, sizeof(Magic3));
+      SHA1_Update(&ctx, SHSpad2, 40);
+      SHA1_Final(sha_md, &ctx);
+      (void) memcpy(MasterSendKey, sha_md, 16);
+
+      /* Generate the master receive key. */
+      SHA1_Init(&ctx);
+      SHA1_Update(&ctx, MasterKey, 16);
+      SHA1_Update(&ctx, SHSpad1, 40);
+      SHA1_Update(&ctx, Magic2, sizeof(Magic3));
+      SHA1_Update(&ctx, SHSpad2, 40);
+      SHA1_Final(sha_md, &ctx);
+      (void) memcpy(MasterReceiveKey, sha_md, 16);
+
+      /* Now, generate the MS-MPPE-Send-Key attribute. */
+
+#if 0
+      /* Setup the salt value. */
+      salt[0] = 0x80;
+      salt[1] = 0x01;
+
+      /* Encode the key. */
+      (void) memset(mppe_key, 0, sizeof(mppe_key));
+      mppe_key[0] = 16; /* length */
+      (void) memcpy(&mppe_key[1], MasterSendKey, 16);
+      secretlen = strlen(request->secret);
+      (void) memcpy(encode_buf, request->secret, secretlen);
+      (void) memcpy(encode_buf + secretlen, request->packet->vector,
+                    AUTH_VECTOR_LEN);
+      (void) memcpy(encode_buf + secretlen + 16, salt, 2);
+      (void) MD5(encode_buf, secretlen + AUTH_VECTOR_LEN + 2, md5_md);
+      for (i = 0; i < 16; ++i)
+        mppe_key[i] ^= md5_md[i];
+      (void) memcpy(encode_buf + secretlen, mppe_key, 16);
+      (void) MD5(encode_buf, secretlen + 16, md5_md);
+      for (i = 0; i < 16; ++i)
+        mppe_key[i + 16] ^= md5_md[i];
+
+      /* Whew.  Now stringify it for pairmake(). */
+      mppe_key_string[0] = '0';
+      mppe_key_string[1] = 'x';
+      (void) sprintf(&mppe_key_string[2], "%02X", salt[0]);
+      (void) sprintf(&mppe_key_string[4], "%02X", salt[1]);
+      for (i = 0; i < sizeof(mppe_key); ++i)
+        (void) sprintf(&mppe_key_string[i*2+6], "%02X", mppe_key[i]);
+#else /* 0 */
+      mppe_key_string[0] = '0';
+      mppe_key_string[1] = 'x';
+      for (i = 0; i < sizeof(MasterSendKey); ++i)
+        (void) sprintf(&mppe_key_string[i*2+2], "%02X", MasterSendKey[i]);
+#endif /* 0 */
+      vp = pairmake("MS-MPPE-Send-Key", mppe_key_string, T_OP_EQ);
+      rad_assert(vp != NULL);
+      pairadd(vps, vp);
+
+      /* Generate the MS-MPPE-Recv-Key attribute. */
+
+#if 0
+      /* Setup the salt value. */
+      salt[0] = 0x80;
+      salt[1] = 0x02;
+
+      /* Encode the key. */
+      (void) memset(mppe_key, 0, sizeof(mppe_key));
+      mppe_key[0] = 16; /* length */
+      (void) memcpy(&mppe_key[1], MasterReceiveKey, 16);
+      secretlen = strlen(request->secret);
+      (void) memcpy(encode_buf, request->secret, secretlen);
+      (void) memcpy(encode_buf + secretlen, request->packet->vector,
+                    AUTH_VECTOR_LEN);
+      (void) memcpy(encode_buf + secretlen + 16, salt, 2);
+      (void) MD5(encode_buf, secretlen + AUTH_VECTOR_LEN + 2, md5_md);
+      for (i = 0; i < 16; ++i)
+        mppe_key[i] ^= md5_md[i];
+      (void) memcpy(encode_buf + secretlen, mppe_key, 16);
+      (void) MD5(encode_buf, secretlen + 16, md5_md);
+      for (i = 0; i < 16; ++i)
+        mppe_key[i + 16] ^= md5_md[i];
+
+      /* Whew.  Now stringify it for pairmake(). */
+      mppe_key_string[0] = '0';
+      mppe_key_string[1] = 'x';
+      (void) sprintf(&mppe_key_string[2], "%02X", salt[0]);
+      (void) sprintf(&mppe_key_string[4], "%02X", salt[1]);
+      for (i = 0; i < sizeof(mppe_key); ++i)
+        (void) sprintf(&mppe_key_string[i*2+6], "%02X", mppe_key[i]);
+#else /* 0 */
+      mppe_key_string[0] = '0';
+      mppe_key_string[1] = 'x';
+      for (i = 0; i < sizeof(MasterReceiveKey); ++i)
+        (void) sprintf(&mppe_key_string[i*2+2], "%02X", MasterReceiveKey[i]);
+#endif /* 0 */
+      vp = pairmake("MS-MPPE-Recv-Key", mppe_key_string, T_OP_EQ);
+      rad_assert(vp != NULL);
+      pairadd(vps, vp);
+
+    } /* if (doing mppe) */
+
+  } /* case PW_MS_CHAP2_RESPONSE */
+  break;
+
+  default:
+    DEBUG("%s: %s: unknown password type", log_prefix, __func__);
+    nmatch = -1;
+    break;
+
+  } /* switch(pwattr[attr]) */
+
+  return nmatch;
+}
+
+
+/*
+ * #$!#@ have to convert 7 octet ranges into 8 octet keys.
+ * Implementation cribbed (and slightly modified) from
+ * rlm_mschap.c by Jay Miller <jaymiller@socket.net>.
+ * We don't bother checking/setting parity.
+ */
+static void
+otp_key_from_hash(des_cblock *key, const unsigned char hashbytes[7])
+{
+  int i;
+  unsigned char working;
+  unsigned char next = 0;
+
+  for (i = 0; i < 7; ++i) {
+    working = hashbytes[i];
+    (*key)[i] = (working >> i) | next;
+    next = (working << (7 - i));
+  }
+  (*key)[i] = next;
+}
similarity index 75%
rename from src/modules/rlm_x99_token/x99_pwe.h
rename to src/modules/rlm_otp/otp_pwe.h
index 2319759..7e631ed 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * x99_pwe.h
+ * otp_pwe.h
  * $Id$
  *
  *   This program is free software; you can redistribute it and/or modify
  *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  *
  * Copyright 2001,2002  Google, Inc.
+ * Copyright 2005 TRI-D Systems, Inc.
  */
 
-#ifndef X99_PWE_H
-#define X99_PWE_H
+#ifndef OTP_PWE_H
+#define OTP_PWE_H
 
 /* Some hardcoding here ... because not all types have #defines */
 #define PW_MS_CHAP_CHALLENGE  ((311 << 16) | 11)
 #define MPPE_ENC_TYPES_RC4_40_128 "0x00000006"
 
 /* Translate the above into something easily usable. */
-static const char *x99_mppe_policy[3] = {
-    MPPE_ENC_POL_ENCRYPTION_FORBIDDEN,
-    MPPE_ENC_POL_ENCRYPTION_ALLOWED,
-    MPPE_ENC_POL_ENCRYPTION_REQUIRED };
+static const char *otp_mppe_policy[3] = {
+  MPPE_ENC_POL_ENCRYPTION_FORBIDDEN,
+  MPPE_ENC_POL_ENCRYPTION_ALLOWED,
+  MPPE_ENC_POL_ENCRYPTION_REQUIRED };
 
-static const char *x99_mppe_types[3] = {
-    MPPE_ENC_TYPES_RC4_40,
-    MPPE_ENC_TYPES_RC4_128,
-    MPPE_ENC_TYPES_RC4_40_128 };
+static const char *otp_mppe_types[3] = {
+  MPPE_ENC_TYPES_RC4_40,
+  MPPE_ENC_TYPES_RC4_128,
+  MPPE_ENC_TYPES_RC4_40_128 };
 
-static void x99_key_from_hash(des_cblock *key,const unsigned char hashbytes[7]);
+static void otp_key_from_hash(des_cblock *, const unsigned char [7]);
 
-#endif /* X99_PWE_H */
+#endif /* OTP_PWE_H */
 
diff --git a/src/modules/rlm_otp/otp_rad.h b/src/modules/rlm_otp/otp_rad.h
new file mode 100644 (file)
index 0000000..dfad2d3
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * otp_rad.h
+ * $Id$
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * Copyright 2001,2002  Google, Inc.
+ * Copyright 2005 TRI-D Systems, Inc.
+ */
+
+#ifndef OTP_RAD_H
+#define OTP_RAD_H
+
+#include <freeradius-devel/autoconf.h>
+#include <freeradius-devel/radiusd.h>
+#define OTP_LOG_DEBUG L_DBG
+#define OTP_LOG_ERR   L_ERR
+#define OTP_LOG_AUTH  L_AUTH
+#define OTP_LOG_INFO  L_INFO
+#define OTP_LOG_CRIT  (L_ERR|L_CONS)
+
+/* otp_radstate.c */
+extern int otp_gen_state(char **, unsigned char **,
+                         const unsigned char [OTP_MAX_CHALLENGE_LEN], size_t,
+                         int32_t, int32_t, const unsigned char [16]);
+
+/* otp_pwe.c */
+#include <freeradius-devel/libradius.h>   /* VALUE_PAIR */
+struct otp_pwe_cmp_t {
+  const REQUEST                *request;
+  const otp_option_t   *inst;
+  int                  pwattr; /* return value from otp_pwe_present() */
+  VALUE_PAIR           **returned_vps;
+};
+extern void otp_pwe_init(void);
+extern int otp_pwe_present(const REQUEST *, const char *);
+extern int otp_pwe_cmp(struct otp_pwe_cmp_t *, const char *, const char *);
+
+#endif /* OTP_RAD_H */
diff --git a/src/modules/rlm_otp/otp_radstate.c b/src/modules/rlm_otp/otp_radstate.c
new file mode 100644 (file)
index 0000000..3202ebd
--- /dev/null
@@ -0,0 +1,150 @@
+/*
+ * otp_radstate.c
+ * $Id$
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * Copyright 2001,2002  Google, Inc.
+ * Copyright 2005 TRI-D Systems, Inc.
+ */
+
+#ifdef FREERADIUS
+#define _LRAD_MD4_H
+#define _LRAD_SHA1_H
+#endif
+#include "otp.h"
+
+#include <string.h>
+#include <openssl/des.h> /* des_cblock */
+#include <openssl/md5.h>
+#include <openssl/hmac.h>
+
+
+static const char rcsid[] = "$Id$";
+
+
+/*
+ * Generate the State attribute, suitable for passing to pairmake().
+ * challenge must be a null terminated string, and be sized at least
+ * as large as indicated in the function definition.
+ *
+ * Returns 0 on success, non-zero otherwise.  For successful returns,
+ * ascii_state (suitable for passing to pairmake()) and raw_state, if
+ * non-NULL, will be pointing to allocated storage.  The caller is
+ * responsible for freeing the storage.  raw_state will not be
+ * null-terminated, the caller should know the expected size (any
+ * variance in size is solely due to the length of the challenge arg).
+ *
+ * In the simplest implementation, we would just use the challenge as state.
+ * Unfortunately, the RADIUS secret protects only the User-Password
+ * attribute; an attacker that can remove packets from the wire and insert
+ * new ones can simply insert a replayed state without having to know
+ * the secret.  If not for an attacker that can remove packets from the
+ * network, I believe trivial state to be secure.
+ *
+ * So, we have to make up for that deficiency by signing our state with
+ * data unique to this specific request.  A NAS would use the Request
+ * Authenticator, but we don't know what that will be when the State is
+ * returned to us, so we'll use the time.  So our replay prevention
+ * is limited to a time interval (inst->chal_delay).  We could keep
+ * track of all challenges issued over that time interval for
+ * better protection.
+ *
+ * Our state, then, is
+ *   (challenge + flags + time + hmac(challenge + resync + time, key)),
+ * where '+' denotes concatentation, 'challenge' is ...
+ * the challenge, 'flags' is a 32-bit value that can be used to record
+ * additional info, 'time' is the 32-bit time (LSB if time_t is 64 bits)
+ * in network byte order, and 'key' is a random key, generated in
+ * otp_instantiate().  This means that only the server which generates a
+ * challenge can verify it; this should be OK if your NAS's load balance
+ * across RADIUS servers using a "first available" algorithm.  If your
+ * NAS's round-robin (ugh), you could use the RADIUS secret instead, but
+ * read RFC 2104 first, and make very sure you really want to do this.
+ *
+ * Note that putting the time in network byte order is pointless, since
+ * only "this" server will be able to verify the hmac, due to the unique
+ * key.  But I've left it in there for future consideration of sync'd
+ * keys across servers (eg, using the RADIUS secret, which is probably
+ * not a good idea; or reading from a file, which might be OK.)
+ */
+int
+otp_gen_state(char **ascii_state, unsigned char **raw_state,
+              const unsigned char challenge[OTP_MAX_CHALLENGE_LEN],
+              size_t clen,
+              int32_t flags, int32_t when, const unsigned char key[16])
+{
+  HMAC_CTX hmac_ctx;
+  unsigned char hmac[MD5_DIGEST_LENGTH];
+  char *p;
+
+  /*
+   * Generate the hmac.  We already have a dependency on openssl for
+   * DES, so we'll use it's hmac functionality also -- saves us from
+   * having to collect the data to be signed into one contiguous piece.
+   */
+  HMAC_Init(&hmac_ctx, key, sizeof(key), EVP_md5());
+  HMAC_Update(&hmac_ctx, challenge, clen);
+  HMAC_Update(&hmac_ctx, (unsigned char *) &flags, 4);
+  HMAC_Update(&hmac_ctx, (unsigned char *) &when, 4);
+  HMAC_Final(&hmac_ctx, hmac, NULL);
+  HMAC_cleanup(&hmac_ctx);
+
+  /* Fill in raw_state if requested. */
+  if (raw_state) {
+    *raw_state = rad_malloc(clen + 8 + sizeof(hmac));
+    p = *raw_state;
+    (void) memcpy(p, challenge, clen);
+    p += clen;
+    (void) memcpy(p, &flags, 4);
+    p += 4;
+    (void) memcpy(p, &when, 4);
+    p += 4;
+    (void) memcpy(p, hmac, sizeof(hmac));
+  }
+
+  /*
+   * Fill in ascii_state if requested.  (pairmake() forces us to to this.)
+   * "0x" is required for pairmake().  Note that each octet expands into
+   * 2 hex digits in ASCII (0xAA -> 0x4141).
+   */
+  if (ascii_state) {
+    *ascii_state = rad_malloc(2 +                      /* "0x"      */
+                              clen * 2 +               /* challenge */
+                              8 +                      /* flags     */
+                              8 +                      /* time      */
+                              sizeof(hmac) * 2 +       /* hmac      */
+                              1);                      /* '\0'      */
+    (void) sprintf(*ascii_state, "0x");
+    p = *ascii_state + 2;
+
+    /* Add the challenge. */
+    (void) otp_keyblock2keystring(p, challenge, clen, otp_hex_conversion);
+    p += clen * 2;
+
+    /* Add the flags and time. */
+    (void) otp_keyblock2keystring(p, (unsigned char *) &flags, 4,
+                                  otp_hex_conversion);
+    p += 8;
+    (void) otp_keyblock2keystring(p, (unsigned char *) &when, 4,
+                                  otp_hex_conversion);
+    p += 8;
+
+    /* Add the hmac. */
+    (void) otp_keyblock2keystring(p, hmac, 16, otp_hex_conversion);
+  }
+
+  return 0;
+}
diff --git a/src/modules/rlm_otp/otp_rlm.c b/src/modules/rlm_otp/otp_rlm.c
new file mode 100644 (file)
index 0000000..a138408
--- /dev/null
@@ -0,0 +1,593 @@
+/*
+ * otp_rlm.c
+ * $Id$
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * Copyright 2000,2001,2002  The FreeRADIUS server project
+ * Copyright 2001,2002  Google, Inc.
+ * Copyright 2005 TRI-D Systems, Inc.
+ */
+
+/*
+ * STRONG WARNING SECTION:
+ *
+ * ANSI X9.9 has been withdrawn as a standard, due to the weakness of DES.
+ * An attacker can learn the token's secret by observing two
+ * challenge/response pairs.  See ANSI document X9 TG-24-1999
+ * <URL:http://www.x9.org/docs/TG24_1999.pdf>.
+ *
+ * Please read the accompanying docs.
+ */
+
+/*
+ * TODO: support setting multiple auth-types in authorize()
+ * TODO: support other than ILP32 (for State)
+ */
+
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <time.h>
+#include <netinet/in.h>        /* htonl(), ntohl() */
+
+#include "otp.h"
+#ifdef FREERADIUS
+#include <freeradius-devel/modules.h>
+#endif
+
+static const char rcsid[] = "$Id$";
+
+/* Global data */
+static unsigned char hmac_key[16];     /* to protect State attribute     */
+static int ninstance = 0;              /* #instances, for global init    */
+
+/* A mapping of configuration file names to internal variables. */
+static const CONF_PARSER module_config[] = {
+  { "pwdfile", PW_TYPE_STRING_PTR, offsetof(otp_option_t, pwdfile),
+    NULL, OTP_PWDFILE },
+  { "lsmd_rp", PW_TYPE_STRING_PTR, offsetof(otp_option_t, lsmd_rp),
+    NULL, OTP_LSMD_RP },
+  { "challenge_prompt", PW_TYPE_STRING_PTR,offsetof(otp_option_t, chal_prompt),
+    NULL, OTP_CHALLENGE_PROMPT },
+  { "challenge_length", PW_TYPE_INTEGER, offsetof(otp_option_t, chal_len),
+    NULL, "6" },
+  { "challenge_delay", PW_TYPE_INTEGER, offsetof(otp_option_t, chal_delay),
+    NULL, "30" },
+  { "softfail", PW_TYPE_INTEGER, offsetof(otp_option_t, softfail),
+    NULL, "5" },
+  { "hardfail", PW_TYPE_INTEGER, offsetof(otp_option_t, hardfail),
+    NULL, "0" },
+  { "allow_sync", PW_TYPE_BOOLEAN, offsetof(otp_option_t, allow_sync),
+    NULL, "yes" },
+  { "fast_sync", PW_TYPE_BOOLEAN, offsetof(otp_option_t, fast_sync),
+    NULL, "yes" },
+  { "allow_async", PW_TYPE_BOOLEAN, offsetof(otp_option_t, allow_async),
+    NULL, "no" },
+  { "challenge_req", PW_TYPE_STRING_PTR, offsetof(otp_option_t, chal_req),
+    NULL, OTP_CHALLENGE_REQ },
+  { "resync_req", PW_TYPE_STRING_PTR, offsetof(otp_option_t, resync_req),
+    NULL, OTP_RESYNC_REQ },
+  { "prepend_pin", PW_TYPE_BOOLEAN, offsetof(otp_option_t, prepend_pin),
+    NULL, "yes" },
+  { "ewindow_size", PW_TYPE_INTEGER, offsetof(otp_option_t, ewindow_size),
+    NULL, "0" },
+  { "rwindow_size", PW_TYPE_INTEGER, offsetof(otp_option_t, rwindow_size),
+    NULL, "0" },
+  { "rwindow_delay", PW_TYPE_INTEGER, offsetof(otp_option_t, rwindow_delay),
+    NULL, "60" },
+  { "mschapv2_mppe", PW_TYPE_INTEGER,
+    offsetof(otp_option_t, mschapv2_mppe_policy), NULL, "2" },
+  { "mschapv2_mppe_bits", PW_TYPE_INTEGER,
+    offsetof(otp_option_t, mschapv2_mppe_types), NULL, "2" },
+  { "mschap_mppe", PW_TYPE_INTEGER,
+    offsetof(otp_option_t, mschap_mppe_policy), NULL, "2" },
+  { "mschap_mppe_bits", PW_TYPE_INTEGER,
+    offsetof(otp_option_t, mschap_mppe_types), NULL, "2" },
+
+  { "debug", PW_TYPE_BOOLEAN, offsetof(otp_option_t, debug),
+    NULL, "no" },
+
+  { NULL, -1, 0, NULL, NULL }          /* end the list */
+};
+
+
+/* transform otp_pw_valid() return code into an rlm return code */
+static int
+otprc2rlmrc(int rc)
+{
+  switch (rc) {
+    case OTP_RC_OK:                     return RLM_MODULE_OK;
+    case OTP_RC_USER_UNKNOWN:           return RLM_MODULE_REJECT;
+    case OTP_RC_AUTHINFO_UNAVAIL:       return RLM_MODULE_REJECT;
+    case OTP_RC_AUTH_ERR:               return RLM_MODULE_REJECT;
+    case OTP_RC_MAXTRIES:               return RLM_MODULE_USERLOCK;
+    case OTP_RC_SERVICE_ERR:            return RLM_MODULE_FAIL;
+    default:                            return RLM_MODULE_FAIL;
+  }
+}
+
+
+/* per-instance initialization */
+static int
+otp_instantiate(CONF_SECTION *conf, void **instance)
+{
+  const char *log_prefix = OTP_MODULE_NAME;
+  otp_option_t *opt;
+  char *p;
+
+  /* Set up a storage area for instance data. */
+  opt = rad_malloc(sizeof(*opt));
+  (void) memset(opt, 0, sizeof(*opt));
+
+  /* If the configuration parameters can't be parsed, then fail. */
+  if (cf_section_parse(conf, opt, module_config) < 0) {
+    free(opt);
+    return -1;
+  }
+
+  /* Onetime initialization. */
+  if (!ninstance) {
+    /* Generate a random key, used to protect the State attribute. */
+    if (otp_get_random(-1, hmac_key, sizeof(hmac_key), log_prefix) == -1) {
+      otp_log(OTP_LOG_ERR, "%s: %s: failed to obtain random data for hmac_key",
+              log_prefix, __func__);
+      free(opt);
+      return -1;
+    }
+
+    /* Initialize the passcode encoding/checking functions. */
+    otp_pwe_init();
+
+    /*
+     * Don't do this again.
+     * Only the main thread instantiates and detaches instances,
+     * so this does not need mutex protection.
+     */
+    ninstance++;
+  }
+
+  /* Verify ranges for those vars that are limited. */
+  if ((opt->chal_len < 5) || (opt->chal_len > OTP_MAX_CHALLENGE_LEN)) {
+    opt->chal_len = 6;
+    otp_log(OTP_LOG_ERR,
+            "%s: %s: invalid challenge_length, range 5-%d, using default of 6",
+            log_prefix, __func__, OTP_MAX_CHALLENGE_LEN);
+  }
+
+  /* Enforce a single "%" sequence, which must be "%s" */
+  p = strchr(opt->chal_prompt, '%');
+  if ((p == NULL) || (p != strrchr(opt->chal_prompt, '%')) ||
+      strncmp(p,"%s",2)) {
+    free(opt->chal_prompt);
+    opt->chal_prompt = strdup(OTP_CHALLENGE_PROMPT);
+    otp_log(OTP_LOG_ERR,
+            "%s: %s: invalid challenge_prompt, using default of \"%s\"",
+            log_prefix, __func__, OTP_CHALLENGE_PROMPT);
+  }
+
+  if (opt->softfail < 0) {
+    opt->softfail = 5;
+    otp_log(OTP_LOG_ERR, "%s: %s: softfail must be at least 1 "
+                         "(or 0 == infinite), using default of 5",
+            log_prefix, __func__);
+  }
+
+  if (opt->hardfail < 0) {
+    opt->hardfail = 0;
+    otp_log(OTP_LOG_ERR, "%s: %s: hardfail must be at least 1 "
+                         "(or 0 == infinite), using default of 0",
+            log_prefix, __func__);
+  }
+
+  if (!opt->hardfail && opt->hardfail <= opt->softfail) {
+    /*
+     * This is noise if the admin leaves softfail alone, so it gets
+     * the default value of 5, and sets hardfail <= to that ... but
+     * in practice that will never happen.  Anyway, it is easily
+     * overcome with a softfail setting of 0.
+     *
+     * This is because we can't tell the difference between a default
+     * [softfail] value and an admin-configured one.
+     */
+    otp_log(OTP_LOG_ERR, "%s: %s: hardfail (%d) is less than softfail (%d), "
+                         "effectively disabling softfail",
+            log_prefix, __func__, opt->hardfail, opt->softfail);
+  }
+
+  if (opt->fast_sync && !opt->allow_sync) {
+    opt->fast_sync = 0;
+    otp_log(OTP_LOG_ERR, "%s: %s: fast_sync is yes, but allow_sync is no; "
+                         "disabling fast_sync",
+            log_prefix, __func__);
+  }
+
+  if (!opt->allow_sync && !opt->allow_async) {
+    otp_log(OTP_LOG_ERR,
+            "%s: %s: at least one of {allow_async, allow_sync} must be set",
+            log_prefix, __func__);
+    free(opt);
+    return -1;
+  }
+
+  if ((opt->ewindow_size > OTP_MAX_EWINDOW_SIZE) ||
+    (opt->ewindow_size < 0)) {
+    opt->ewindow_size = 0;
+    otp_log(OTP_LOG_ERR, "%s: %s: max ewindow_size is %d, using default of 0",
+            log_prefix, __func__, OTP_MAX_EWINDOW_SIZE);
+  }
+
+  if (opt->rwindow_size && (opt->rwindow_size < opt->ewindow_size)) {
+    opt->rwindow_size = 0;
+    otp_log(OTP_LOG_ERR, "%s: %s: rwindow_size must be at least as large as "
+                         "ewindow_size, using default of 0",
+            log_prefix, __func__);
+  }
+
+  if (opt->rwindow_size && !opt->rwindow_delay) {
+    opt->rwindow_size = 0;
+    otp_log(OTP_LOG_ERR, "%s: %s: rwindow_size is non-zero, "
+                         "but rwindow_delay is zero; disabling rwindow",
+            log_prefix, __func__);
+  }
+
+  if ((opt->mschapv2_mppe_policy > 2) || (opt->mschapv2_mppe_policy < 0)) {
+    opt->mschapv2_mppe_policy = 2;
+    otp_log(OTP_LOG_ERR,
+            "%s: %s: invalid value for mschapv2_mppe, using default of 2",
+            log_prefix, __func__);
+  }
+
+  if ((opt->mschapv2_mppe_types > 2) || (opt->mschapv2_mppe_types < 0)) {
+    opt->mschapv2_mppe_types = 2;
+    otp_log(OTP_LOG_ERR,
+            "%s: %s: invalid value for mschapv2_mppe_bits, using default of 2",
+            log_prefix, __func__);
+  }
+
+  if ((opt->mschap_mppe_policy > 2) || (opt->mschap_mppe_policy < 0)) {
+    opt->mschap_mppe_policy = 2;
+    otp_log(OTP_LOG_ERR,
+            "%s: %s: invalid value for mschap_mppe, using default of 2",
+            log_prefix, __func__);
+  }
+
+  if (opt->mschap_mppe_types != 2) {
+    opt->mschap_mppe_types = 2;
+    otp_log(OTP_LOG_ERR,
+            "%s: %s: invalid value for mschap_mppe_bits, using default of 2",
+            log_prefix, __func__);
+  }
+
+  /* set the instance name (for use with authorize()) */
+  opt->name = cf_section_name2(conf);
+  if (!opt->name)
+    opt->name = cf_section_name1(conf);
+  if (!opt->name) {
+    otp_log(OTP_LOG_CRIT, "%s: %s: no instance name (this can't happen)",
+            log_prefix, __func__);
+    free(opt);
+    return -1;
+  }
+
+  /* set debug opt for portable debug output (see DEBUG definition) */
+  if (debug_flag)
+    opt->debug = 1;
+
+  *instance = opt;
+  return 0;
+}
+
+
+/* Generate a challenge to be presented to the user. */
+static int
+otp_authorize(void *instance, REQUEST *request)
+{
+  otp_option_t *inst = (otp_option_t *) instance;
+  const char *log_prefix = OTP_MODULE_NAME;
+
+  char challenge[OTP_MAX_CHALLENGE_LEN + 1];   /* +1 for '\0' terminator */
+  char *state;
+  int auth_type_found;
+  int32_t sflags = 0; /* flags for state */
+
+  struct otp_pwe_cmp_t data = {
+    .request           = request,
+    .inst              = inst,
+    .returned_vps      = NULL
+  };
+
+  /* Early exit if Auth-Type != inst->name */
+  {
+    VALUE_PAIR *vp;
+
+    auth_type_found = 0;
+    if ((vp = pairfind(request->config_items, PW_AUTHTYPE)) != NULL) {
+      auth_type_found = 1;
+      if (strcmp(vp->vp_strvalue, inst->name))
+        return RLM_MODULE_NOOP;
+    }
+  }
+
+  /* The State attribute will be present if this is a response. */
+  if (pairfind(request->packet->vps, PW_STATE) != NULL) {
+    DEBUG("rlm_otp: autz: Found response to Access-Challenge");
+    return RLM_MODULE_OK;
+  }
+
+  /* User-Name attribute required. */
+  if (!request->username) {
+    otp_log(OTP_LOG_AUTH,
+            "%s: %s: Attribute \"User-Name\" required for authentication.",
+            log_prefix, __func__);
+    return RLM_MODULE_INVALID;
+  }
+
+  if ((data.pwattr = otp_pwe_present(request, log_prefix)) == 0) {
+    otp_log(OTP_LOG_AUTH, "%s: %s: Attribute \"User-Password\" "
+                          "or equivalent required for authentication.",
+            log_prefix, __func__);
+    return RLM_MODULE_INVALID;
+  }
+
+  /* fast_sync mode (challenge only if requested) */
+  if (inst->fast_sync) {
+    if ((!otp_pwe_cmp(&data, inst->resync_req, log_prefix) &&
+        /* Set a bit indicating resync */ (sflags |= htonl(1))) ||
+        !otp_pwe_cmp(&data, inst->chal_req, log_prefix)) {
+      /*
+       * Generate a challenge if requested.  Note that we do this
+       * even if configuration doesn't allow async mode.
+       */
+      DEBUG("rlm_otp: autz: fast_sync challenge requested");
+      goto gen_challenge;
+
+    } else {
+      /* Otherwise, this is the token sync response. */
+      if (!auth_type_found)
+        pairadd(&request->config_items, pairmake("Auth-Type", "otp", T_OP_EQ));
+        return RLM_MODULE_OK;
+
+    }
+  } /* if (fast_sync && card supports sync mode) */
+
+gen_challenge:
+  /* Set the resync bit by default if the user can't choose. */
+  if (!inst->fast_sync)
+    sflags |= htonl(1);
+
+  /* Generate a random challenge. */
+  if (otp_async_challenge(-1, challenge, inst->chal_len, log_prefix) == -1) {
+    otp_log(OTP_LOG_ERR, "%s: %s: failed to obtain random challenge",
+            log_prefix, __func__);
+    return RLM_MODULE_FAIL;
+  }
+
+  /*
+   * Create the State attribute, which will be returned to us along with
+   * the response.  We will need this to verify the response.  It must
+   * be hmac protected to prevent insertion of arbitrary State by an
+   * inside attacker.  If we won't actually use the State (server config
+   * doesn't allow async), we just use a trivial State.  We always create
+   * at least a trivial State, so otp_authorize() can quickly pass on to
+   * otp_authenticate().
+   */
+  if (inst->allow_async) {
+    time_t now = time(NULL);
+
+    if (sizeof(now) != 4 || sizeof(long) != 4) {
+      otp_log(OTP_LOG_ERR, "%s: %s: only ILP32 arch is supported",
+              log_prefix, __func__);
+      return RLM_MODULE_FAIL;
+    }
+    now = htonl(now);
+
+    if (otp_gen_state(&state, NULL, challenge, inst->chal_len, sflags,
+                      now, hmac_key) != 0) {
+      otp_log(OTP_LOG_ERR, "%s: %s: failed to generate state",
+              log_prefix, __func__);
+      return RLM_MODULE_FAIL;
+    }
+  } else {
+    state = rad_malloc(5);
+    (void) sprintf(state, "0x00");
+  }
+  pairadd(&request->reply->vps, pairmake("State", state, T_OP_EQ));
+  free(state);
+
+  /* Add the challenge to the reply. */
+  {
+    char *u_challenge; /* challenge with addt'l presentation text */
+
+    u_challenge = rad_malloc(strlen(inst->chal_prompt) +
+                             OTP_MAX_CHALLENGE_LEN + 1);
+/* XXX */
+    (void) sprintf(u_challenge, inst->chal_prompt, challenge);
+    pairadd(&request->reply->vps,
+            pairmake("Reply-Message", u_challenge, T_OP_EQ));
+    free(u_challenge);
+  }
+
+  /*
+   * Mark the packet as an Access-Challenge packet.
+   * The server will take care of sending it to the user.
+   */
+  request->reply->code = PW_ACCESS_CHALLENGE;
+  DEBUG("rlm_otp: Sending Access-Challenge.");
+
+  /* TODO: support config-specific auth-type */
+  if (!auth_type_found)
+    pairadd(&request->config_items, pairmake("Auth-Type", "otp", T_OP_EQ));
+  return RLM_MODULE_HANDLED;
+}
+
+
+/* Verify the response entered by the user. */
+static int
+otp_authenticate(void *instance, REQUEST *request)
+{
+  otp_option_t *inst = (otp_option_t *) instance;
+  const char *log_prefix = OTP_MODULE_NAME;
+
+  char *username;
+  int rc;
+  int resync = 0;      /* resync flag for async mode */
+
+  unsigned char challenge[OTP_MAX_CHALLENGE_LEN];      /* cf. authorize() */
+  VALUE_PAIR *add_vps = NULL;
+
+  struct otp_pwe_cmp_t data = {
+    .request           = request,
+    .inst              = inst,
+    .returned_vps      = &add_vps
+  };
+
+  /* User-Name attribute required. */
+  if (!request->username) {
+    otp_log(OTP_LOG_AUTH,
+            "%s: %s: Attribute \"User-Name\" required for authentication.",
+            log_prefix, __func__);
+    return RLM_MODULE_INVALID;
+  }
+  username = request->username->vp_strvalue;
+
+  if ((data.pwattr = otp_pwe_present(request, log_prefix)) == 0) {
+    otp_log(OTP_LOG_AUTH, "%s: %s: Attribute \"User-Password\" "
+                          "or equivalent required for authentication.",
+            log_prefix, __func__);
+    return RLM_MODULE_INVALID;
+  }
+
+  /* Add a message to the auth log. */
+  pairadd(&request->packet->vps, pairmake("Module-Failure-Message",
+                                          OTP_MODULE_NAME, T_OP_EQ));
+  pairadd(&request->packet->vps, pairmake("Module-Success-Message",
+                                          OTP_MODULE_NAME, T_OP_EQ));
+
+  /* Retrieve the challenge (from State attribute). */
+  {
+    VALUE_PAIR *vp;
+    unsigned char      *state;
+    int32_t            sflags = 0;     /* state flags */
+    int32_t            then;           /* state timestamp */
+
+    if ((vp = pairfind(request->packet->vps, PW_STATE)) != NULL) {
+      int e_length = inst->chal_len;
+
+      /* Extend expected length if state should have been protected. */
+      if (inst->allow_async)
+        e_length += 4 + 4 + 16; /* sflags + time + hmac */
+
+      if (vp->length != e_length) {
+        otp_log(OTP_LOG_AUTH, "%s: %s: bad state for [%s]: length",
+                log_prefix, __func__, username);
+        return RLM_MODULE_INVALID;
+      }
+
+      if (inst->allow_async) {
+        /* Verify the state. */
+        (void) memcpy(challenge, vp->vp_strvalue, inst->chal_len);
+        (void) memcpy(&sflags, vp->vp_strvalue + inst->chal_len, 4);
+        (void) memcpy(&then, vp->vp_strvalue + inst->chal_len + 4, 4);
+        if (otp_gen_state(NULL, &state, challenge, inst->chal_len,
+                          sflags, then, hmac_key) != 0) {
+          otp_log(OTP_LOG_ERR, "%s: %s: failed to generate state",
+                  log_prefix, __func__);
+          return RLM_MODULE_FAIL;
+        }
+        if (memcmp(state, vp->vp_strvalue, vp->length)) {
+          otp_log(OTP_LOG_AUTH, "%s: %s: bad state for [%s]: hmac",
+                  log_prefix, __func__, username);
+          free(state);
+          return RLM_MODULE_REJECT;
+        }
+        free(state);
+
+        /* State is valid, but check expiry. */
+        then = ntohl(then);
+        if (time(NULL) - then > inst->chal_delay) {
+          otp_log(OTP_LOG_AUTH, "%s: %s: bad state for [%s]: expired",
+                  log_prefix, __func__, username);
+          return RLM_MODULE_REJECT;
+        }
+        resync = ntohl(sflags) & 1;
+      } /* if (State should have been protected) */
+    } /* if (State present) */
+  } /* code block */
+
+  /* do it */
+  rc = otprc2rlmrc(otp_pw_valid(username, challenge, NULL, resync, inst,
+                                otp_pwe_cmp, &data, log_prefix));
+
+  /* Handle any vps returned from otp_pwe_cmp(). */
+  if (rc == RLM_MODULE_OK) {
+    pairadd(&request->reply->vps, add_vps);
+  } else {
+    pairfree(&add_vps);
+  }
+  return rc;
+}
+
+
+/* per-instance destruction */
+static int
+otp_detach(void *instance)
+{
+  otp_option_t *inst = (otp_option_t *) instance;
+
+  free(inst->pwdfile);
+  free(inst->lsmd_rp);
+  free(inst->chal_prompt);
+  free(inst->chal_req);
+  free(inst->resync_req);
+  free(instance);
+  /*
+   * Only the main thread instantiates and detaches instances,
+   * so this does not need mutex protection.
+   */
+  if (--ninstance == 0)
+    memset(hmac_key, 0, sizeof(hmac_key));
+
+  return 0;
+}
+
+
+/*
+ *     If the module needs to temporarily modify it's instantiation
+ *     data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
+ *     The server will then take care of ensuring that the module
+ *     is single-threaded.
+ */
+module_t rlm_otp = {
+  RLM_MODULE_INIT,
+  "otp",
+  RLM_TYPE_THREAD_SAFE,                /* type */
+  otp_instantiate,             /* instantiation */
+  otp_detach,                  /* detach */
+  {
+    otp_authenticate,          /* authentication */
+    otp_authorize,             /* authorization */
+    NULL,                      /* preaccounting */
+    NULL,                      /* accounting */
+    NULL,                      /* checksimul */
+    NULL,                      /* pre-proxy */
+    NULL,                      /* post-proxy */
+    NULL                       /* post-auth */
+  },
+};
similarity index 77%
rename from src/modules/rlm_x99_token/x99_site.c
rename to src/modules/rlm_otp/otp_site.c
index 3252699..fc7924f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * x99_site.c
+ * otp_site.c
  * $Id$
  *
  *   This program is free software; you can redistribute it and/or modify
@@ -17,6 +17,7 @@
  *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  *
  * Copyright 2001,2002  Google, Inc.
+ * Copyright 2005 TRI-D Systems, Inc.
  */
 
 /*
  * IMPORTANT  IMPORTANT  IMPORTANT  IMPORTANT  IMPORTANT  IMPORTANT
  */
 
-#include "x99.h"
+#include "otp.h"
 #include <string.h>
 
 static const char rcsid[] = "$Id$";
 
 
-int
-x99_challenge_transform(const char *username,
-                       char challenge[MAX_CHALLENGE_LEN + 1])
+ssize_t
+otp_challenge_transform(
+#ifdef __GNUC__
+__attribute__ ((unused))
+#endif
+                        const char *username,
+                        unsigned char challenge[OTP_MAX_CHALLENGE_LEN],
+                        size_t clen)
 {
-    /* ARGSUSED */
+  unsigned i;
 
-    (void) strcpy(challenge, "DISABLED");
-    return 0;
-}
+  for (i = 0; i < clen; ++clen)
+    challenge[i] = '\0';
 
+  return clen;
+}
diff --git a/src/modules/rlm_otp/otp_state.c b/src/modules/rlm_otp/otp_state.c
new file mode 100644 (file)
index 0000000..3a9cf09
--- /dev/null
@@ -0,0 +1,617 @@
+/*
+ * otp_state.c 
+ * $Id$
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * Copyright 2005 TRI-D Systems, Inc.
+ */
+
+#ifdef FREERADIUS
+#ifndef _REENTRANT
+#define _REENTRANT
+#endif
+#include <pthread.h>
+#endif
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#ifdef __linux__
+#include <sys/un.h>
+#endif
+
+#include "otp.h"
+#include "otp_state.h"
+
+static const char rcsid[] = "$Id$";
+
+
+#if defined(PAM)
+/* a single fd (no pool) */
+static lsmd_fd_t lsmd_fd = { .fd = -1 };
+#elif defined(FREERADIUS)
+/* pointer to head of fd pool */
+static lsmd_fd_t *lsmd_fd_head;
+static pthread_mutex_t lsmd_fd_head_mutex = PTHREAD_MUTEX_INITIALIZER;
+#endif
+
+/*
+ * lock and retrieve state for a user
+ * returns 0 on success (but state may be empty!), -1 on failure
+ */
+int
+otp_state_get(const otp_option_t *opt, const char *username,
+              otp_user_state_t *user_state, const char *log_prefix)
+{
+  lsmd_fd_t *fdp;
+  char buf[1024];      /* state manager max len */
+  int buflen;
+
+  fdp = otp_state_getfd(opt, log_prefix);
+  if (!fdp || fdp->fd == -1)
+    return -1;
+
+  user_state->fdp = fdp;
+  (void) sprintf(buf, "G %s", username);       /* safe */
+  if (xwrite(fdp, buf, strlen(buf) + 1, log_prefix) == -1)
+    return -1;
+  if ((buflen = xread(fdp, buf, sizeof(buf), log_prefix)) == -1)
+    return -1;
+  if (otp_state_parse(buf, buflen, username, user_state, log_prefix) == -1)
+    return -1;
+
+  return 0;
+}
+
+
+/*
+ * update and release state for a user
+ * returns 0 on success, -1 on failure
+ */
+int
+otp_state_put(const char *username, otp_user_state_t *user_state,
+              const char *log_prefix)
+{
+  char buf[1024];      /* state manager max len */
+  int rc = 0;
+  ssize_t len;
+  size_t ulen = strlen(username);
+
+  if (!user_state->locked)
+    return 0;
+
+  if ((len = otp_state_unparse(buf, sizeof(buf), username, user_state,
+                              log_prefix)) == -1) {
+    rc = -1;
+    goto putfd;
+  }
+  if ((rc = xwrite(user_state->fdp, buf, len, log_prefix)) == -1)
+    goto putfd;
+  if ((len = xread(user_state->fdp, buf, sizeof(buf), log_prefix)) == -1) {
+    rc = -1;
+    goto putfd;
+  }
+
+  /* validate the state manager response */
+  if ((size_t) len < 3 + ulen) {
+    otp_log(OTP_LOG_ERR, "%s: %s: state manager invalid PUT response for [%s]",
+            log_prefix, __func__, username);
+    rc = -1;
+    goto putfd;
+  }
+  if (!((buf[0] == 'A' || buf[0] == 'N') &&
+        buf[1] == ' ' &&
+        !strncmp(username, &buf[2], ulen) &&
+        (buf[ulen + 2] == ' ' || buf[ulen + 2] == '\0'))) {
+    otp_log(OTP_LOG_ERR, "%s: %s: state manager invalid PUT response for [%s]",
+            log_prefix, __func__, username);
+    rc = -1;
+    goto putfd;
+  }
+  if (buf[0] == 'N') {
+    char *reason;
+
+    if (buf[ulen + 2] == '\0')
+      reason = (char *) "[no reason given]";
+    else
+      reason = &buf[ulen + 3];
+    otp_log(OTP_LOG_ERR, "%s: %s: state manager PUT rejected for [%s]: %s",
+            log_prefix, __func__, username, reason);
+    rc = -1;
+    goto putfd;
+  }
+
+  /* reset locked flag on successful PUT, to avoid further PUT's by caller */
+  user_state->locked = 0;
+
+putfd:
+  otp_state_putfd(user_state->fdp, 0, log_prefix);
+  return rc;
+}
+
+
+/*
+ * Parse the state manager response into user_state.
+ * Returns 0 on success, -1 on failure.
+ * "PUT"s state (releases lock, without update) on failure.
+ */
+static int
+otp_state_parse(const char *buf, size_t buflen, const char *username,
+                otp_user_state_t *user_state, const char *log_prefix)
+{
+  size_t i;
+  char *p, *q;
+
+  /* sanity checks */
+  if (!buflen) {
+    otp_log(OTP_LOG_ERR, "%s: %s: no state for [%s]",
+            log_prefix, __func__, username);
+    otp_state_putfd(user_state->fdp, 0, log_prefix);
+    return -1;
+  }
+  /*
+   * This guarantees there is a char after strchr(p, ':'),
+   * and that our 'q = strchr(p, ':'); *q++ = '\0', p = q;'
+   * idiom works (there is always a char after the ':').
+   */
+  if (buf[buflen - 1] != '\0') {
+    otp_log(OTP_LOG_ERR, "%s: %s: invalid state for [%s]",
+            log_prefix, __func__, username);
+    otp_state_putfd(user_state->fdp, 0, log_prefix);
+    return -1;
+  }
+
+  /* Is this an ack or a nak? */
+  if (!(buf[0] == 'A' && buf[1] == ' ')) {
+      otp_log(OTP_LOG_INFO, "%s: %s: unable to lock state for [%s]",
+            log_prefix, __func__, username);
+    otp_state_putfd(user_state->fdp, 0, log_prefix);
+    return -1;
+  }
+  user_state->locked = 1;
+  user_state->updated = 0;     /* just release lock on failures */
+
+  /*
+   * We don't do bounds checking for initial parsing,
+   * so state manager response must contain at least
+   * - ACK/NAK code + ' '
+   * - username + ' '
+   * - version + ':'
+   * - username + ':'
+   * - challenge + terminator.
+   * Beginning with the challenge we use strchr() and need
+   * no further bounds checking.
+   */
+  i = strlen(username);
+  /* 'A <username> V:<username>:C' + terminator */
+  if (buflen < 2 + i + 3 + i + 2 + 1) {
+    if (buflen < 2 + i + 1) {
+      otp_log(OTP_LOG_ERR, "%s: %s: invalid state data for [%s]",
+              log_prefix, __func__, username);
+    } else if (buflen == 2 + i + 1) {
+      otp_log(OTP_LOG_DEBUG, "%s: %s: null state data for [%s]",
+              log_prefix, __func__, username);
+      user_state->nullstate = 1;
+      return 0;
+    } else {
+      otp_log(OTP_LOG_ERR, "%s: %s: short state data for [%s]",
+              log_prefix, __func__, username);
+    }
+    (void) otp_state_put(username, user_state, log_prefix);
+    return -1;
+  } else {
+    user_state->nullstate = 0;
+  }
+  p = (char *) &buf[2];        /* username field of state manager response */
+
+  /* verify username (in state manager response, not state itself) */
+  if (!(strncmp(p, username, i) == 0 && p[i] == ' ')) {
+    otp_log(OTP_LOG_ERR, "%s: %s: state manager username mismatch for [%s]",
+            log_prefix, __func__, username);
+    (void) otp_state_put(username, user_state, log_prefix);
+    return -1;
+  }
+  p += i;      /* space after username */
+  p += 1;      /* beginning of state */
+
+  /* version */
+  if (!(p[0] == '5' && p[1] == ':')) {
+    otp_log(OTP_LOG_ERR, "%s: %s: state data unacceptable version for [%s]",
+            log_prefix, __func__, username);
+    (void) otp_state_put(username, user_state, log_prefix);
+    return -1;
+  }
+  p += 2;      /* username */
+
+  /* sanity check username */
+  if (!(strncmp(p, username, i) == 0 && p[i] == ':')) {
+    otp_log(OTP_LOG_ERR, "%s: %s: state data username mismatch for [%s]",
+            log_prefix, __func__, username);
+    (void) otp_state_put(username, user_state, log_prefix);
+    return -1;
+  }
+  p += i + 1;  /* challenge */
+
+  /* extract challenge */
+  if ((q = strchr(p, ':')) == NULL) {
+    otp_log(OTP_LOG_ERR, "%s: %s: state data invalid challenge for [%s]",
+            log_prefix, __func__, username);
+    (void) otp_state_put(username, user_state, log_prefix);
+    return -1;
+  }
+  *q++ = '\0';
+  if (strlen(p) > OTP_MAX_CHALLENGE_LEN * 2) {
+    otp_log(OTP_LOG_ERR, "%s: %s: state data challenge too long for [%s]",
+            log_prefix, __func__, username);
+    (void) otp_state_put(username, user_state, log_prefix);
+    return -1;
+  }
+  user_state->clen = otp_keystring2keyblock(p, user_state->challenge);
+  if (user_state->clen < 0) {
+    otp_log(OTP_LOG_ERR, "%s: %s: state data challenge invalid for [%s]",
+            log_prefix, __func__, username);
+    (void) otp_state_put(username, user_state, log_prefix);
+    return -1;
+  }
+  p = q;       /* csd */
+
+  /* extract csd */
+  if ((q = strchr(p, ':')) == NULL) {
+    otp_log(OTP_LOG_ERR, "%s: %s: state data invalid csd for [%s]",
+            log_prefix, __func__, username);
+    (void) otp_state_put(username, user_state, log_prefix);
+    return -1;
+  }
+  *q++ = '\0';
+  if (strlen(p) > OTP_MAX_CSD_LEN) {
+    otp_log(OTP_LOG_ERR, "%s: %s: state data csd too long for [%s]",
+            log_prefix, __func__, username);
+    (void) otp_state_put(username, user_state, log_prefix);
+    return -1;
+  }
+  (void) strcpy(user_state->csd, p);
+  p = q;       /* rd */
+
+  /* extract rd */
+  if ((q = strchr(p, ':')) == NULL) {
+    otp_log(OTP_LOG_ERR, "%s: %s: state data invalid rd for [%s]",
+            log_prefix, __func__, username);
+    (void) otp_state_put(username, user_state, log_prefix);
+    return -1;
+  }
+  *q++ = '\0';
+  if (strlen(p) > OTP_MAX_RD_LEN) {
+    otp_log(OTP_LOG_ERR, "%s: %s: state data rd too long for [%s]",
+            log_prefix, __func__, username);
+    (void) otp_state_put(username, user_state, log_prefix);
+    return -1;
+  }
+  (void) strcpy(user_state->rd, p);
+  p = q;       /* failcount */
+
+  /* extract failcount */
+  if ((q = strchr(p, ':')) == NULL) {
+    otp_log(OTP_LOG_ERR, "%s: %s: state data invalid failcount for [%s]",
+            log_prefix, __func__, username);
+    (void) otp_state_put(username, user_state, log_prefix);
+    return -1;
+  }
+  *q++ = '\0';
+  if (sscanf(p, "%" SCNx32, &user_state->failcount) != 1) {
+    otp_log(OTP_LOG_ERR, "%s: %s: state data invalid failcount for [%s]",
+            log_prefix, __func__, username);
+    (void) otp_state_put(username, user_state, log_prefix);
+    return -1;
+  }
+  p = q;       /* authtime */
+
+  /* extract authtime */
+  if ((q = strchr(p, ':')) == NULL) {
+    otp_log(OTP_LOG_ERR, "%s: %s: state data invalid authtime for [%s]",
+            log_prefix, __func__, username);
+    (void) otp_state_put(username, user_state, log_prefix);
+    return -1;
+  }
+  *q++ = '\0';
+  if (sscanf(p, "%" SCNx32, &user_state->authtime) != 1) {
+    otp_log(OTP_LOG_ERR, "%s: %s: state data invalid authtime for [%s]",
+            log_prefix, __func__, username);
+    (void) otp_state_put(username, user_state, log_prefix);
+    return -1;
+  }
+  p = q;       /* mincardtime */
+
+  /* extract mincardtime */
+  if ((q = strchr(p, ':')) == NULL) {
+    otp_log(OTP_LOG_ERR, "%s: %s: state data invalid mincardtime for [%s]",
+            log_prefix, __func__, username);
+    (void) otp_state_put(username, user_state, log_prefix);
+    return -1;
+  }
+  *q++ = '\0';
+  if (sscanf(p, "%" SCNx32, &user_state->mincardtime) != 1) {
+    otp_log(OTP_LOG_ERR, "%s: %s: state data invalid mincardtime for [%s]",
+            log_prefix, __func__, username);
+    (void) otp_state_put(username, user_state, log_prefix);
+    return -1;
+  }
+
+  return 0;
+}
+
+/*
+ * Format user_state into a state manager update request.
+ * Returns new (filled) buflen on success, -1 on failure.
+ */
+static ssize_t
+otp_state_unparse(char *buf, size_t buflen, const char *username,
+                  otp_user_state_t *user_state, const char *log_prefix)
+{
+  size_t len;
+  char s[OTP_MAX_CHALLENGE_LEN * 2 + 1];
+
+  /* perhaps this isn't our job, but it's safe */
+  if (!user_state->locked)
+    return -1;
+
+  if (user_state->updated)
+    (void) snprintf(buf, buflen, "P %s "
+                                 "5:%s:"
+                                 "%s:"
+                                 "%s:%s:"
+                                 "%" PRIx32 ":%" PRIx32 ":"
+                                 "%" PRIx32 ":",
+                    /* 'P ', */ username,
+                    /* '5:', */ username,
+                    otp_keyblock2keystring(s, user_state->challenge,
+                                           user_state->clen,
+                                           otp_hex_conversion),
+                    user_state->csd, user_state->rd,
+                    user_state->failcount, user_state->authtime,
+                    user_state->mincardtime);
+  else
+    (void) snprintf(buf, buflen, "P %s", username);
+  buf[buflen - 1] = '\0';
+
+  if ((len = strlen(buf) + 1) == buflen) {
+    /*
+     * Short by one, but the best we can do b/c of different snprintf()'s
+     * without a lot of work.  Guaranteed anyway, due to small max
+     * username, challenge, csd len's, assuming maximally (1024) sized buf.
+     */
+    otp_log(OTP_LOG_ERR, "%s: %s: state data (unparse) too long for [%s]",
+            log_prefix, __func__, username);
+    return -1;
+  }
+
+  return len;
+}
+
+
+/*
+ * Full read with logging, and close on failure.
+ * Returns nread on success, -1 on failure.
+ * buf[nread - 1] is guaranteed to be '\0'.
+ */
+static int
+xread(lsmd_fd_t *fdp, char *buf, size_t len, const char *log_prefix)
+{
+  ssize_t n;
+  int nread = 0;       /* bytes read into buf */
+
+  for (;;) {
+    if ((n = read(fdp->fd, &buf[nread], len - nread)) == -1) {
+      if (errno == EAGAIN || errno == EINTR) {
+        continue;
+      } else {
+        otp_log(OTP_LOG_ERR, "%s: %s: read from state manager: %s",
+                log_prefix, __func__, strerror(errno));
+        otp_state_putfd(fdp, 1, log_prefix);
+        return -1;
+      }
+    }
+    if (!n) {
+      otp_log(OTP_LOG_ERR, "%s: %s: state manager disconnect",
+              log_prefix, __func__);
+      otp_state_putfd(fdp, 1, log_prefix);
+      return -1;
+    }
+    nread += n;
+
+    /*
+     * was last byte a NUL? (pipelining is not possible,
+     * so we only need to check the last byte to find
+     * the message boundary)
+     */
+    if (buf[nread - 1] == '\0')
+      return nread;
+  } /* for (;;) */
+}
+
+
+/*
+ * Full write with logging, and close on failure.
+ * Returns 0 on success, -1 on failure.
+ */
+static int
+xwrite(lsmd_fd_t *fdp, const char *buf, size_t len, const char *log_prefix)
+{
+  size_t nleft = len;
+  ssize_t nwrote;
+
+  while (nleft) {
+    if ((nwrote = write(fdp->fd, &buf[len - nleft], nleft)) == -1) {
+      if (errno != EINTR) {
+        otp_log(OTP_LOG_ERR, "%s: %s: write to state manager: %s",
+                log_prefix, __func__, strerror(errno));
+        otp_state_putfd(fdp, 1, log_prefix);
+        return -1;
+      }
+    }
+    nleft -= nwrote;
+  }
+
+  return 0;
+}
+
+
+/* connect to state manager and return fd */
+static int
+otp_state_connect(const char *path, const char *log_prefix)
+{
+  int fd;
+  struct sockaddr_un sa;
+  size_t sp_len;               /* sun_path length (strlen) */
+
+  /* setup for unix domain socket */
+  sp_len = strlen(path);
+  if (sp_len > sizeof(sa.sun_path) - 1) {
+    otp_log(OTP_LOG_ERR, "%s: %s: rendezvous point name too long",
+            log_prefix, __func__);
+    return -1;
+  }
+  sa.sun_family = AF_UNIX;
+  (void) strcpy(sa.sun_path, path);
+    
+  /* connect to state manager */
+  if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) {
+    otp_log(OTP_LOG_ERR, "%s: %s: socket: %s", log_prefix, __func__,
+            strerror(errno));
+    return -1;
+  }
+  if (connect(fd, (struct sockaddr *) &sa,
+              sizeof(sa.sun_family) + sp_len) == -1) {
+    otp_log(OTP_LOG_ERR, "%s: %s: connect: %s", log_prefix, __func__,
+            strerror(errno));
+    (void) close(fd);
+    return -1;
+  }
+  return fd;
+}
+
+
+#if defined(PAM)
+/* retrieve fd (possibly opening a new one) to state manager */
+static lsmd_fd_t *
+otp_state_getfd(const otp_option_t *opt, const char *log_prefix)
+{
+  lsmd_fd_t *fdp = &lsmd_fd;
+
+  /* return existing fd if open */
+  if (fdp->fd != -1)
+    return fdp;
+
+  fdp->fd = otp_state_connect(opt->lsmd_rp, log_prefix);
+  return fdp;
+}
+
+
+/* disconnect from state manager */
+static void
+otp_state_putfd(lsmd_fd_t *fdp, int close_p, const char *log_prefix)
+{
+  /* for PAM we always close the fd; leaving it open is a leak */
+  (void) close(fdp->fd);
+  fdp->fd = -1;
+}
+
+#elif defined(FREERADIUS)
+/*
+ * Retrieve fd (from pool) to state manager.
+ * It'd be simpler to use TLS but FR can have lots of threads
+ * and we don't want to waste fd's that way.
+ * We can't have a global fd because we'd then be pipelining
+ * requests to the state manager and we have no way to demultiplex
+ * the responses.
+ */
+static lsmd_fd_t *
+otp_state_getfd(const otp_option_t *opt, const char *log_prefix)
+{
+  int rc;
+  lsmd_fd_t *fdp;
+
+  /* walk the connection pool looking for an available fd */
+  for (fdp = lsmd_fd_head; fdp; fdp = fdp->next) {
+    rc = pthread_mutex_trylock(&fdp->mutex);
+    if (!rc)
+      break;
+    if (rc != EBUSY) {
+      otp_log(OTP_LOG_ERR, "%s: %s: pthread_mutex_trylock: %s",
+              log_prefix, __func__, strerror(errno));
+      return NULL;
+    }
+  }
+
+  if (!fdp) {
+    /* no fd was available, add a new one */
+    if ((rc = pthread_mutex_lock(&lsmd_fd_head_mutex))) {
+      otp_log(OTP_LOG_ERR, "%s: %s: pthread_mutex_lock: %s",
+              log_prefix, __func__, strerror(errno));
+      return NULL;
+    }
+    fdp = rad_malloc(sizeof(*fdp));
+    if ((rc = pthread_mutex_init(&fdp->mutex, NULL))) {
+      otp_log(OTP_LOG_ERR, "%s: %s: pthread_mutex_init: %s",
+              log_prefix, __func__, strerror(errno));
+      free(fdp);
+      return NULL;
+    }
+    if ((rc = pthread_mutex_lock(&fdp->mutex))) {
+      otp_log(OTP_LOG_ERR, "%s: %s: pthread_mutex_lock: %s",
+              log_prefix, __func__, strerror(errno));
+      free(fdp);
+      return NULL;
+    }
+    fdp->next = lsmd_fd_head;
+    lsmd_fd_head = fdp;
+    if ((rc = pthread_mutex_unlock(&lsmd_fd_head_mutex))) {
+      otp_log(OTP_LOG_ERR, "%s: %s: pthread_mutex_unlock: %s",
+              log_prefix, __func__, strerror(errno));
+      /* deadlock */
+      exit(1);
+    }
+    fdp->fd = otp_state_connect(opt->lsmd_rp, log_prefix);
+  }
+
+  return fdp;
+}
+
+/* disconnect from state manager */
+static void
+otp_state_putfd(lsmd_fd_t *fdp, int close_p, const char *log_prefix)
+{
+  int rc;
+
+  /* close fd (used for errors) */
+  if (close_p) {
+    (void) close(fdp->fd);
+    fdp->fd = -1;
+  }
+  /* make connection available to another thread */
+  if ((rc = pthread_mutex_unlock(&fdp->mutex))) {
+    otp_log(OTP_LOG_ERR, "%s: %s: pthread_mutex_unlock: %s",
+            log_prefix, __func__, strerror(errno));
+    /* lost fd */
+    exit(1);
+  }
+}
+#endif /* FREERADIUS */
similarity index 51%
rename from src/modules/rlm_x99_token/x99_sync.h
rename to src/modules/rlm_otp/otp_state.h
index fb27aaf..89ccb69 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * x99_sync.h
+ * otp_state.h
  * $Id$
  *
  *   This program is free software; you can redistribute it and/or modify
  *   along with this program; if not, write to the Free Software
  *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  *
- * Copyright 2001,2002  Google, Inc.
+ * Copyright 2005 TRI-D Systems, Inc.
  */
 
-#ifndef X99_SYNC_H
-#define X99_SYNC_H
+#ifndef OTP_STATE_H
+#define OTP_STATE_H
 
-static int x99_get_failcount(const char *syncdir, const char *username,
-                            int *failcount);
-static char * x99_acquire_sd_lock(const char *syncdir, const char *username);
-static void x99_release_sd_lock(char *lockfile);
+#include "otp.h"
 
-static int x99_get_sd(const char *syncdir, const char *username,
-                     char challenge[MAX_CHALLENGE_LEN + 1], int *failures,
-                     time_t *last_async, unsigned *pos);
-static int x99_set_sd(const char *syncdir, const char *username,
-                     const char *challenge, int failures, time_t last_async,
-                     unsigned pos);
+static int otp_state_parse(const char *, size_t, const char *,
+                           otp_user_state_t *, const char *);
+static int otp_state_unparse(char *, size_t, const char *, otp_user_state_t *,
+                             const char *);
+static int xread(lsmd_fd_t *, char *, size_t, const char *);
+static int xwrite(lsmd_fd_t *, const char *, size_t, const char *);
+static int otp_state_connect(const char *, const char *);
+static lsmd_fd_t *otp_state_getfd(const otp_option_t *, const char *);
+static void otp_state_putfd(lsmd_fd_t *, int, const char *);
 
-#endif /* X99_SYNC_H */
+#endif /* OTP_STATE_H */
diff --git a/src/modules/rlm_otp/otp_util.c b/src/modules/rlm_otp/otp_util.c
new file mode 100644 (file)
index 0000000..9c5a8d1
--- /dev/null
@@ -0,0 +1,349 @@
+/*
+ * otp_util.c  
+ * $Id$
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * Copyright 2001,2002  Google, Inc.
+ * Copyright 2005 TRI-D Systems, Inc.
+ */
+
+#include "otp.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <openssl/des.h> /* des_cblock */
+
+
+static const char rcsid[] = "$Id$";
+
+
+/*
+ * Return some number of random bytes.
+ * rnd_data must be allocated by the caller.
+ * Returns 0 on success, -1 on failure, rnd_data is filled in.
+ */
+int
+otp_get_random(
+#ifdef FREERADIUS
+#ifdef __GNUC__
+__attribute__ ((unused))
+#endif
+#endif
+               int fd,
+               unsigned char *rnd_data, int req_bytes,
+#ifdef FREERADIUS
+#ifdef __GNUC__
+__attribute__ ((unused))
+#endif
+#endif
+               const char *log_prefix)
+{
+  int bytes_read = 0;
+
+  while (bytes_read < req_bytes) {
+    int n;
+#ifdef FREERADIUS
+    /* Use goofy libradius interface to avoid fd init issues. */
+    unsigned int bytes_left = req_bytes - bytes_read;
+    uint32_t r = lrad_rand();
+
+    n = sizeof(r) < bytes_left ? sizeof(r) : bytes_left;
+    memcpy(rnd_data + bytes_read, &r, n);
+#else
+    n = read(fd, rnd_data + bytes_read, req_bytes - bytes_read);
+    if (n <= 0) {
+      otp_log(OTP_LOG_ERR, "%s: %s: error reading from %s: %s",
+              log_prefix, __func__, OTP_DEVURANDOM, strerror(errno));
+      return -1;
+    }
+#endif /* !FREERADIUS */
+
+    bytes_read += n;
+  }
+
+  return 0;
+}
+
+
+/*
+ * Return a random challenge.
+ * fd must be either -1 or an open fd to the random device.
+ * challenge is filled in on successful return (must be at least size len+1).
+ * Returns 0 on success, -1 on failure.
+ * NOTE: This is really cryptocard-specific (automatic ASCII conversion
+ * and null termination).
+ */
+int
+otp_async_challenge(int fd, char challenge[OTP_MAX_CHALLENGE_LEN + 1], int len,
+                    const char *log_prefix)
+{
+  unsigned char rawchallenge[OTP_MAX_CHALLENGE_LEN];
+  int i;
+
+  if (fd == -1) {
+    if ((fd = open(OTP_DEVURANDOM, O_RDONLY)) == -1) {
+      otp_log(OTP_LOG_ERR, "%s: %s: error opening %s: %s",
+              log_prefix, __func__, OTP_DEVURANDOM, strerror(errno));
+      return -1;
+    }
+  }
+
+  if (otp_get_random(fd, rawchallenge, len, log_prefix) == -1) {
+    otp_log(OTP_LOG_ERR, "%s: %s: failed to obtain random data",
+            log_prefix, __func__);
+    return -1;
+  }
+  /* Convert the raw bytes to ASCII decimal. */
+  for (i = 0; i < len; ++i)
+    challenge[i] = '0' + rawchallenge[i] % 10;
+  challenge[len] = '\0';
+
+  return 0;
+}
+
+
+/*
+ * Convert the ASCII string representation of a key to raw octets.
+ * keyblock is filled in.  Returns keylen on success, -1 otherwise.
+ */
+ssize_t
+otp_keystring2keyblock(const char *s, unsigned char keyblock[OTP_MAX_KEY_LEN])
+{
+  unsigned i;
+  size_t l = strlen(s);
+
+  /* overflow sanity check */
+  if (l > OTP_MAX_KEY_LEN * 2)
+    return -1;
+
+  /*
+   * We could just use sscanf, but we do this a lot, and have very
+   * specific needs, and it's easy to implement, so let's go for it!
+   */
+  for (i = 0; i < l / 2; ++i) {
+    unsigned int n[2];
+    int j;
+
+    /* extract 2 nibbles */
+    n[0] = *s++;
+    n[1] = *s++;
+
+    /* verify range */
+    for (j = 0; j < 2; ++j) {
+      if ((n[j] >= '0' && n[j] <= '9') ||
+          (n[j] >= 'A' && n[j] <= 'F') ||
+          (n[j] >= 'a' && n[j] <= 'f'))
+        continue;
+      return -1;
+    }
+
+    /* convert ASCII hex digits to numeric values */
+    n[0] -= '0';
+    n[1] -= '0';
+    if (n[0] > 9) {
+      if (n[0] > 'F' - '0')
+        n[0] -= 'a' - '9' - 1;
+      else
+        n[0] -= 'A' - '9' - 1;
+    }
+    if (n[1] > 9) {
+      if (n[1] > 'F' - '0')
+        n[1] -= 'a' - '9' - 1;
+      else
+        n[1] -= 'A' - '9' - 1;
+    }
+
+    /* store as octets */
+    keyblock[i]  = n[0] << 4;
+    keyblock[i] += n[1];
+  } /* for (each octet) */
+
+  return l/2;
+}
+
+
+/* Character maps for generic hex and vendor specific decimal modes */
+const char otp_hex_conversion[]         = "0123456789abcdef";
+const char otp_cc_dec_conversion[]      = "0123456789012345";
+const char otp_snk_dec_conversion[]     = "0123456789222333";
+const char otp_sc_friendly_conversion[] = "0123456789ahcpef";
+
+/*
+ * Convert a keyblock to an ASCII string.
+ * Fills in s, which must point to at least len*2+1 bytes of space.
+ */
+char *
+otp_keyblock2keystring(char *s, const unsigned char *keyblock, size_t len,
+                       const char conversion[17])
+{
+  unsigned i;
+
+  for (i = 0; i < len; ++i) {
+    unsigned n[2];
+
+    n[0] = (keyblock[i] >> 4) & 0x0f;
+    n[1] = keyblock[i] & 0x0f;
+    s[2 * i + 0] = conversion[n[0]];
+    s[2 * i + 1] = conversion[n[1]];
+  }
+  s[2 * len] = '\0';
+
+  return s;
+}
+
+
+/*
+ * fill in card_info from our database (key file)
+ * returns 0 on success, -1 for user not found, -2 for other errors.
+ * TODO: mmap and use pointers in otp_card_info_t?
+ */
+int
+otp_get_card_info(const char *pwdfile, const char *username,
+                  otp_card_info_t *card_info, const char *log_prefix)
+{
+  FILE *fp;
+  char s[80];
+  char *p, *q;
+  int found;
+  struct stat st;
+
+  /* Verify permissions first. */
+  if (stat(pwdfile, &st) != 0) {
+    otp_log(OTP_LOG_ERR, "%s: %s: pwdfile %s error: %s",
+            log_prefix, __func__, pwdfile, strerror(errno));
+    return -2;
+  }
+  if ((st.st_mode & (S_IXUSR|S_IRWXG|S_IRWXO)) != 0) {
+    otp_log(OTP_LOG_ERR, "%s: %s: pwdfile %s has loose permissions",
+            log_prefix, __func__, pwdfile);
+    return -2;
+  }
+
+  if ((fp = fopen(pwdfile, "r")) == NULL) {
+    otp_log(OTP_LOG_ERR, "%s: %s: error opening %s: %s", log_prefix, __func__,
+            pwdfile, strerror(errno));
+    return -2;
+  }
+
+  /*
+   * Find the requested user.
+   * Add a ':' to the username to make sure we don't match shortest prefix.
+   */
+  p = malloc(strlen(username) + 2);
+  if (!p) {
+    otp_log(OTP_LOG_CRIT, "%s: %s: out of memory", log_prefix, __func__);
+    return -2;
+  }
+  (void) sprintf(p, "%s:", username);
+  found = 0;
+  while (!feof(fp)) {
+    if (fgets(s, sizeof(s), fp) == NULL) {
+      if (!feof(fp)) {
+        otp_log(OTP_LOG_ERR, "%s: %s: error reading from %s: %s",
+                log_prefix, __func__, pwdfile, strerror(errno));
+        (void) fclose(fp);
+        free(p);
+        return -2;
+      }
+    } else if (!strncmp(s, p, strlen(p))) {
+      found = 1;
+      break;
+    }
+  }
+  (void) fclose(fp);
+  free(p);
+  if (!found) {
+    otp_log(OTP_LOG_AUTH, "%s: %s: [%s] not found in %s", log_prefix, __func__,
+            username, pwdfile);
+    return -1;
+  }
+
+  /* Found him, skip to next field (card). */
+  if ((p = strchr(s, ':')) == NULL) {
+    otp_log(OTP_LOG_ERR, "%s: %s: invalid format for [%s] in %s",
+            log_prefix, __func__, username, pwdfile);
+    return -2;
+  }
+  p++;
+  if ((q = strchr(p, ':')) == NULL) {
+    otp_log(OTP_LOG_ERR, "%s: %s: invalid format for [%s] in %s",
+            log_prefix, __func__, username, pwdfile);
+    return -2;
+  }
+  *q++ = '\0';
+  /* p: card_type, q: key */
+
+  /*
+   * Unfortunately, we can't depend on having strl*, which would allow
+   * us to check for buffer overflow and copy the string in the same step.
+   * TODO: implement our own strlcpy().
+   */
+  if (strlen(p) > OTP_MAX_CARDNAME_LEN)
+    otp_log(OTP_LOG_ERR, "%s: %s: invalid format for [%s] in %s",
+            log_prefix, __func__, username, pwdfile);
+  (void) strcpy(card_info->card, p);
+
+  p = q;
+  /* optional PIN field */
+  if ((q = strchr(p, ':')) == NULL)
+    card_info->pin[0] = '\0';
+  else
+    *q++ = '\0';
+  /* p: key, q: PIN */
+
+  {
+    size_t l = strlen(p);
+
+    /* OTP_MAX_KEY_LEN keys with trailing newline won't work */
+    if (l > OTP_MAX_KEY_LEN * 2) {
+      otp_log(OTP_LOG_ERR, "%s: %s: invalid format for [%s] in %s",
+              log_prefix, __func__, username, pwdfile);
+      return -2;
+    }
+    (void) strcpy(card_info->keystring, p);
+    /* strip possible trailing newline */
+    if (l && card_info->keystring[l - 1] == '\n')
+      card_info->keystring[--l] = '\0';
+    /* check for empty key or odd len */
+    if (!l || l & 1) {
+      otp_log(OTP_LOG_ERR, "%s: %s: invalid format for [%s] in %s",
+              log_prefix, __func__, username, pwdfile);
+      return -2;
+    }
+  }
+
+  if (q) {
+    size_t l = strlen(q);
+
+    if (l > OTP_MAX_PIN_LEN) {
+      otp_log(OTP_LOG_ERR, "%s: %s: invalid format for [%s] in %s",
+              log_prefix, __func__, username, pwdfile);
+    }
+    (void) strcpy(card_info->pin, q);
+    /* strip possible trailing newline */
+    if (l && card_info->pin[l - 1] == '\n')
+      card_info->pin[--l] = '\0';
+  }
+
+  return 0;
+}
+
diff --git a/src/modules/rlm_otp/otp_x99.c b/src/modules/rlm_otp/otp_x99.c
new file mode 100644 (file)
index 0000000..5c248c9
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * otp_x99.c
+ * $Id$
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * Copyright 2001,2002  Google, Inc.
+ * Copyright 2005 TRI-D Systems, Inc.
+ */
+
+#include "otp.h"
+
+#include <string.h>
+#include <openssl/des.h>
+
+static const char rcsid[] = "$Id$";
+
+
+/*
+ * The ANSI X9.9 MAC algorithm is:
+ * 1. Perform a CBC mode DES encryption of the plaintext.  The last plaintext
+ *    block must be zero padded.
+ * 2. The MAC is the most significant 32 bits of the last cipherblock.
+ *
+ * Most tokens support a max of an 8 character challenge, but at least one
+ * (CRYPTOCard RB-1) supports performing the full CBC mode encryption
+ * of an arbitrary length challenge.  So we don't limit ourselves
+ * to just an ECB mode encryption.
+ *
+ * This routine returns the entire 64 bit last cipherblock, at least one sync
+ * mode needs this (and ANSI X9.9 states that the MAC can be 48, and 64 bit
+ * MACs should be supported).  Returns 0 on success, non-zero otherwise.
+ */
+int
+otp_x99_mac(const unsigned char *input, size_t len, unsigned char output[8],
+            const unsigned char keyblock[OTP_MAX_KEY_LEN],
+            const char *log_prefix)
+{
+  des_key_schedule ks;
+  des_cblock ivec;
+  des_cblock l_output[OTP_MAX_CHALLENGE_LEN / sizeof(des_cblock)];
+  int rc;
+
+  /*
+   * Setup and verify the key.
+   * This may be a bit expensive to do every time, but it
+   * makes more sense for calling functions to deal with
+   * the key itself, rather than the schedule.  In practice,
+   * I don't think this will amount to much, but I haven't
+   * actually profiled it.
+   * TODO: store in card_info after generating
+   */
+  if ((rc = des_set_key_checked((const_des_cblock *) keyblock, ks)) != 0) {
+    otp_log(OTP_LOG_ERR, "%s: %s: otp_x99_mac: DES key %s",
+            log_prefix, __func__,
+            rc == -1 ? "has incorrect parity" : "is weak");
+    return -1;
+  }
+
+  (void) memset(ivec, 0, sizeof(ivec));
+  des_cbc_encrypt(input, (unsigned char *) l_output, len,
+                  ks, &ivec, DES_ENCRYPT);
+  (void) memcpy(output, l_output[(len - 1) / 8], 8);
+  return 0;
+}
diff --git a/src/modules/rlm_x99_token/crcalc.c b/src/modules/rlm_x99_token/crcalc.c
deleted file mode 100644 (file)
index e353fe3..0000000
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * crcalc.c
- *
- *   This program is free software; you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License as published by
- *   the Free Software Foundation; either version 2 of the License, or
- *   (at your option) any later version.
- *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
- *
- *   You should have received a copy of the GNU General Public License
- *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- *
- * Copyright 2001,2002 Google, Inc.
- */
-
-#include <stdio.h>
-#include <string.h>
-#include <ctype.h>
-#include <openssl/des.h>
-
-/*
- * Convert the ASCII string representation of a DES key to raw octets.
- * keyblock is filled in.  Returns 0 on success, -1 otherwise.
- */
-static
-int string_to_keyblock(const char *s, des_cblock keyblock)
-{
-    int i;
-
-    if (s == NULL || strlen(s) < 16)
-       return -1;
-
-    /*
-     * We could just use sscanf, but we do this a lot, and have very
-     * specific needs, and it's easy to implement, so let's go for it!
-     */
-    for (i = 0; i < 8; ++i) {
-       unsigned int n[2];
-
-       n[0] = *s++ - '0';
-       n[1] = *s++ - '0';
-       if (n[0] > 9) {
-           n[0] -= 'a' - '9' - 1;
-       }
-       if (n[1] > 9) {
-           n[1] -= 'a' - '9' - 1;
-       }
-
-       keyblock[i]  = n[0] << 4;
-       keyblock[i] += n[1];
-    }
-    return 0;
-}
-
-
-/* Character maps for generic hex and vendor specific decimal modes */
-static const char ascii_conversion[]  = "0123456789abcdef";
-static const char cc_dec_conversion[] = "0123456789012345";
-
-/*
- * Convert a DES keyblock to an ASCII string.
- * Fills in s, which must point to at least 17 bytes of space.
- * Note that each octet expands into 2 hex digits in ASCII (0xAA -> 0x4141);
- * add a NULL string terminator and you get the 17 byte requirement.
- */
-static
-void keyblock_to_string(char *s, const des_cblock keyblock,
-                       const char conversion[17])
-{
-    int i;
-
-    for (i = 0; i < 8; ++i) {
-       unsigned n[2];
-
-       n[0] = (keyblock[i] >> 4) & 0x0f;
-       n[1] = keyblock[i] & 0x0f;
-       s[2 * i + 0] = conversion[n[0]];
-       s[2 * i + 1] = conversion[n[1]];
-    }
-    s[16] = '\0';
-}
-
-
-int
-main(int argc, char *argv[])
-{
-    /* ARGSUSED */
-    char ascii_key[17];
-    char challenge[10], response[9], response_long[17];
-    char buf[BUFSIZ];
-    des_cblock keyblock;
-    des_key_schedule ks;
-    char *p;
-    int i, rc;
-
-    memset(ascii_key, 0, sizeof(ascii_key));
-
-    /* get the key */
-    fprintf(stdout, "Enter a DES key as 16 hex digits (spaces allowed): ");
-    fgets(buf, sizeof(buf), stdin);
-    buf[strlen(buf) - 1] = '\0'; /* strip '\n' */
-    p = buf;
-
-    /* setup key */
-    if (buf[0] == '0' && (buf[1] == 'x' || buf[1] == 'X'))
-       p += 2;
-    i = 0;
-    while (*p) {
-       if (*p == ' ') {
-           p++;
-           continue;
-       }
-       if (*p < '0' || *p > '9') {
-           if (*p < 'a' || *p > 'f') {
-               if (*p < 'A' || *p > 'F') {
-                   fprintf(stderr, "bad key\n");
-                   exit(1);
-               }
-           }
-       }
-       if (i > 15) {
-           fprintf(stderr, "key too long\n");
-           exit(1);
-       }
-       ascii_key[i++] = tolower((int) *p++);
-    }
-    if (strlen(ascii_key) < 16) {
-       fprintf(stderr, "key too short\n");
-       exit(1);
-    }
-    string_to_keyblock(ascii_key, keyblock);
-
-    /* verify the key. */
-key_verify:
-    if ((rc = des_set_key_checked(&keyblock, ks)) != 0) {
-       fprintf(stderr, "key %s\n",
-              rc == -1 ? "has incorrect parity" : "is weak");
-       if (rc == -1) {
-           des_set_odd_parity(&keyblock);
-           goto key_verify;
-       }
-       else {
-           exit(1);
-       }
-    }
-
-    fprintf(stdout, "Enter the challenge: ");
-    fgets(challenge, sizeof(challenge), stdin);
-    challenge[strlen(challenge) - 1] = '\0'; /* strip '\n' */
-    /* encrypt null block if no challenge */
-
-    /*
-     * Calculate the response.  The algorithm is:
-     * 1. Convert the challenge to ASCII bytes (eg "12345" -> 0x3132333435).
-     * 2. Pad LSB of a 64-bit block w/ 0 bytes if challenge < 8 bytes (digits).
-     * 3. Encrypt w/ DES (whose block size is 64 bits).
-     * 4. Convert the most significant 32 bits of the ciphertext
-     *    to 8 hex digits as a string (eg 0x1234567f -> "1234567f").
-     */
-    {
-       des_cblock input, output;
-
-       /* Step 1, 2 (conversion is already done, just copy and pad) */
-       (void) memset(input, 0, sizeof(input));
-       (void) memcpy(input, challenge, strlen(challenge));
-
-       /* Step 3 */
-       des_ecb_encrypt(&input, &output, ks, 1);
-
-       /* Step 4, 5 */
-       keyblock_to_string(response_long, output, ascii_conversion);
-       (void) memcpy(response, response_long, 8);
-       response[8] = '\0';
-       memcpy(challenge, output, 8);
-       challenge[8] = '\0';
-    }
-
-    /* calculate the next challenge for cryptocard */
-    for (i = 0; i < 8; ++i) {
-       challenge[i] &= 0x0f;
-       if (challenge[i] > 9)
-           challenge[i] -= 10;
-       challenge[i] |= 0x30;
-    }
-
-    fprintf(stdout, "response is %s [%s]\n", response, &response_long[8]);
-    fprintf(stdout, "next challenge is %s\n", challenge);
-    exit(0);
-}
-
-
diff --git a/src/modules/rlm_x99_token/x99.h b/src/modules/rlm_x99_token/x99.h
deleted file mode 100644 (file)
index 0ebff7f..0000000
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * x99.h
- * $Id$
- *
- *   This program is free software; you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License as published by
- *   the Free Software Foundation; either version 2 of the License, or
- *   (at your option) any later version.
- *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
- *
- *   You should have received a copy of the GNU General Public License
- *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- *
- * Copyright 2001,2002  Google, Inc.
- */
-
-#ifndef X99_H
-#define X99_H
-
-#include <inttypes.h>
-#include <openssl/des.h> /* des_cblock */
-#include <time.h>        /* time_t */
-
-/*
- * Things you might like to change (although most are configurables)
- */
-
-/* Default passwd file */
-#define PWDFILE "/etc/x99passwd"
-
-/* Default sync dir */
-#define SYNCDIR "/etc/x99sync.d"
-
-/* Default prompt for presentation of challenge */
-#define CHALLENGE_PROMPT "Challenge: %s\n Response: "
-
-/* Must be a multiple of sizeof(des_cblock) (8); read docs before changing. */
-#define MAX_CHALLENGE_LEN 32
-
-/* Password that means "challenge me" in fast_sync mode */
-#define CHALLENGE_REQ "challenge"
-
-/* Password that means "challenge me and resync" in fast_sync mode */
-#define RESYNC_REQ "resync"
-
-/* Max event window size for sync modes */
-#define MAX_EWINDOW_SIZE 10
-/* Max time window size for sync modes.  More than 10 may not be usable. */
-#define MAX_TWINDOW_SIZE 10
-
-/*
- * PRNG device that does not block;
- * /dev/urandom is "merely" cryptographically strong on Linux. :-)
- */
-#define DEVURANDOM "/dev/urandom"
-
-
-/*
- * You shouldn't change anything past this point
- */
-
-
-/* struct used for instance/option data */
-typedef struct x99_token_t {
-    char *pwdfile;     /* file containing user:card_type:key entries      */
-    char *syncdir;     /* dir containing sync mode and state info         */
-    char *chal_prompt; /* text to present challenge to user, must have %s */
-    int chal_len;      /* challenge length, min 5 digits                  */
-    int chal_delay;    /* max delay time for response, in seconds         */
-    int softfail;      /* number of auth fails before time delay starts   */
-    int hardfail;      /* number of auth fails when user is locked out    */
-    int allow_sync;    /* useful to override pwdfile card_type settings   */
-    int fast_sync;     /* response-before-challenge mode                  */
-    int allow_async;   /* C/R mode allowed?                               */
-    char *chal_req;    /* keyword requesting challenge for fast_sync mode */
-    char *resync_req;  /* keyword requesting resync for fast_sync mode    */
-    int ewindow_size;  /* sync mode event window size (right side value)  */
-    int ewindow2_size; /* softfail override event window size             */
-    int ewindow2_delay;        /* softfail override max time delay                */
-#if defined(FREERADIUS)
-    /* freeradius-specific items */
-    char *name;                        /* instance name for x99_token_authorize() */
-    int mschapv2_mppe_policy;  /* whether or not do to mppe for mschapv2  */
-    int mschapv2_mppe_types;   /* key type/length for mschapv2/mppe       */
-    int mschap_mppe_policy;    /* whether or not do to mppe for mschap    */
-    int mschap_mppe_types;     /* key type/length for mschap/mppe         */
-#elif defined(PAM)
-    /* PAM specific items */
-    int debug;         /* print debug info?                               */
-    char *fast_prompt; /* fast mode prompt                                */
-#endif
-#if 0
-    int twindow_min;   /* sync mode time window left side                 */
-    int twindow_max;   /* sync mode time window right side                */
-#endif
-} x99_token_t;
-
-/* Bit maps for Card Features.  It is OK to insert values at will. */
-#define X99_CF_NONE            0
-/* Vendors */
-#define X99_CF_CRYPTOCARD      0x01 << 0  /* CRYPTOCard             */
-#define X99_CF_SNK             0x01 << 1  /* Symantec nee Axent nee */
-                                          /* AssureNet Pathways nee */
-                                          /* Digital Pathways       */
-                                          /* "SecureNet Key"        */
-#define X99_CF_ACTIVCARD       0x01 << 2  /* ActivCard              */
-#define X99_CF_SCOMPUTING      0x01 << 3  /* Secure Computing       */
-#define X99_CF_VASCO           0x01 << 4  /* Vasco                  */
-/* modes */
-#define X99_CF_AM              0x01 << 5  /* async mode (chal/resp) */
-#define X99_CF_ES              0x01 << 6  /* event synchronous      */
-#define X99_CF_TS              0x01 << 7  /* time synchronous       */
-/* display modes */
-#define X99_CF_HD              0x01 << 8  /* hex display            */
-#define X99_CF_DD              0x01 << 9  /* dec display            */
-#define X99_CF_R8              0x01 << 10 /* 8 digit response       */
-#define X99_CF_R7              0x01 << 11 /* 7 digit response       */
-#define X99_CF_R6              0x01 << 12 /* 6 digit response       */
-#define X99_CF_MAX             0x01 << 31 /* MAX placeholder        */
-
-/* mask to test for sync mode */
-#define X99_CF_SM (X99_CF_ES|X99_CF_TS)
-
-/* cards and their features */
-#define CRYPTOCARD_H8_RC (X99_CF_CRYPTOCARD|X99_CF_HD|X99_CF_R8|X99_CF_AM)
-#define CRYPTOCARD_H7_RC (X99_CF_CRYPTOCARD|X99_CF_HD|X99_CF_R7|X99_CF_AM)
-#define CRYPTOCARD_D8_RC (X99_CF_CRYPTOCARD|X99_CF_DD|X99_CF_R8|X99_CF_AM)
-#define CRYPTOCARD_D7_RC (X99_CF_CRYPTOCARD|X99_CF_DD|X99_CF_R7|X99_CF_AM)
-#define CRYPTOCARD_H8_ES (X99_CF_CRYPTOCARD|X99_CF_HD|X99_CF_R8|X99_CF_ES)
-#define CRYPTOCARD_H7_ES (X99_CF_CRYPTOCARD|X99_CF_HD|X99_CF_R7|X99_CF_ES)
-#define CRYPTOCARD_D8_ES (X99_CF_CRYPTOCARD|X99_CF_DD|X99_CF_R8|X99_CF_ES)
-#define CRYPTOCARD_D7_ES (X99_CF_CRYPTOCARD|X99_CF_DD|X99_CF_R7|X99_CF_ES)
-#define CRYPTOCARD_H8_RS (CRYPTOCARD_H8_RC|CRYPTOCARD_H8_ES)
-#define CRYPTOCARD_H7_RS (CRYPTOCARD_H7_RC|CRYPTOCARD_H7_ES)
-#define CRYPTOCARD_D8_RS (CRYPTOCARD_D8_RC|CRYPTOCARD_D8_ES)
-#define CRYPTOCARD_D7_RS (CRYPTOCARD_D7_RC|CRYPTOCARD_D7_ES)
-
-/* user-specific info */
-typedef struct x99_user_info_t {
-    uint32_t card_id;
-    des_cblock keyblock;
-} x99_user_info_t;
-
-
-/* x99_mac.c */
-extern int x99_response(const char *challenge, char response[17],
-                       uint32_t card_id, des_cblock keyblock);
-extern int x99_mac(const char *input, des_cblock output, des_cblock keyblock);
-
-/* x99_util.c */
-/* Character maps for generic hex and vendor specific decimal modes */
-extern const char x99_hex_conversion[];
-extern const char x99_cc_dec_conversion[];
-extern const char x99_snk_dec_conversion[];
-extern const char x99_sc_friendly_conversion[];
-
-extern int x99_get_challenge(int fd, char *challenge, int len);
-extern int x99_get_random(int fd, unsigned char *rnd_data, int req_bytes);
-
-extern int x99_string_to_keyblock(const char *s, des_cblock keyblock);
-extern void x99_keyblock_to_string(char *s, const des_cblock keyblock,
-                                  const char conversion[17]);
-
-extern int x99_get_user_info(const char *pwdfile, const char *username,
-                            x99_user_info_t *user_info);
-
-/* x99_sync.c */
-#define FAIL_ERR  -1
-#define FAIL_HARD -2
-#define FAIL_SOFT -3
-
-extern int x99_get_sync_data(const char *syncdir, const char *username,
-                            uint32_t card_id, int ewin, int twin,
-                            char challenge[MAX_CHALLENGE_LEN + 1],
-                            des_cblock keyblock);
-extern int x99_set_sync_data(const char *syncdir, const char *username,
-                            const char *challenge, const des_cblock keyblock);
-extern int x99_check_failcount(const char *syncdir, const x99_token_t *inst);
-extern int x99_incr_failcount(const char *syncdir, const char *username);
-extern int x99_reset_failcount(const char *syncdir, const char *username);
-extern int x99_get_last_auth(const char *syncdir, const char *username,
-                            time_t *last_auth);
-extern int x99_upd_last_auth(const char *syncdir, const char *username);
-extern unsigned x99_get_last_auth_pos(const char *syncdir,const char *username);
-extern int x99_set_last_auth_pos(const char *syncdir, const char *username,
-                                unsigned pos);
-
-/* x99_site.c */
-extern int x99_challenge_transform(const char *username,
-                                  char challenge[MAX_CHALLENGE_LEN + 1]);
-
-/* x99_log.c */
-extern void x99_log(int level, const char *format, ...);
-
-#if defined(FREERADIUS)
-#include "x99_rad.h"
-#elif defined(PAM)
-#include "x99_pam.h"
-#endif
-
-#endif /* X99_H */
-
diff --git a/src/modules/rlm_x99_token/x99_mac.c b/src/modules/rlm_x99_token/x99_mac.c
deleted file mode 100644 (file)
index e656fc4..0000000
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * x99_mac.c
- * $Id$
- *
- *   This program is free software; you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License as published by
- *   the Free Software Foundation; either version 2 of the License, or
- *   (at your option) any later version.
- *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
- *
- *   You should have received a copy of the GNU General Public License
- *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- *
- * Copyright 2001,2002  Google, Inc.
- */
-
-#ifdef FREERADIUS
-#include "radiusd.h"
-#endif
-#include "x99.h"
-
-#include <string.h>
-#include <openssl/des.h>
-
-static const char rcsid[] = "$Id$";
-
-
-/*
- * The X9.9 MAC is used by tokens in the following manner:
- *
- * 1. Possibly convert the challenge to ASCII (eg "12345" -> 0x3132333435).
- *    Note that we do this unconditionally (we don't yet support tokens
- *    that can use raw challenge bytes (SafeWord Gold/Platinum)).
- * 2. Use the possibly converted challenge as the plaintext input to
- *    the X9.9 MAC algorithm.
- * 3. Convert the 32 bit MAC to ASCII (eg 0x1234567f -> "1234567f").
- *    Note that some tokens (SafeWord Gold/Platinum) can display a 64 bit MAC;
- *    we don't support those yet.
- * 4. Apply any vendor specific transformations on chars "a" thru "f".
- * 5. Use the result as the response.
- */
-
-/* Returns 0 on success, non-zero otherwise.  response sized as indicated. */
-int
-x99_response(const char *challenge, char response[9],
-            uint32_t card_id, des_cblock keyblock)
-{
-    des_cblock output;
-    char l_response[17];
-    const char *conversion;
-
-    /* Step 1, 2, 3. */
-    if (x99_mac(challenge, output, keyblock) != 0)
-       return -1;
-
-    /* Step 4, 5 */
-    if (card_id & X99_CF_DD) {
-       if (card_id & X99_CF_CRYPTOCARD) {
-           conversion = x99_cc_dec_conversion;
-       } else {
-           /* This should not happen. */
-           x99_log(X99_LOG_ERR, "x99_response: bad card mode/vendor");
-           return -1;
-       }
-    } else {
-       /* Hex display */
-       conversion = x99_hex_conversion;
-    }
-    x99_keyblock_to_string(l_response, output, conversion);
-    (void) memcpy(response, l_response, 8);
-    response[8] = '\0';
-
-    if (card_id & X99_CF_R7) {
-       if (card_id & X99_CF_CRYPTOCARD) {
-           (void) memmove(&response[3], &response[4], 5);
-       } else {
-           /* This should not happen. */
-           x99_log(X99_LOG_ERR, "x99_response: bad card mode/vendor");
-           return -1;
-       }
-    }
-
-    return 0;
-}
-
-
-/*
- * The ANSI X9.9 MAC algorithm is:
- * 1. Perform a CBC mode DES encryption of the plaintext.  The last plaintext
- *    block must be zero padded.
- * 2. The MAC is the most significant 32 bits of the last cipherblock.
- *
- * Most tokens support a max of an 8 character challenge, but at least one
- * (CRYPTOCard RB-1) supports performing the full CBC mode encryption
- * of an arbitrary length challenge.  So we don't limit ourselves
- * to just an ECB mode encryption.
- *
- * This routine returns the entire 64 bit last cipherblock, at least one sync
- * mode needs this (and ANSI X9.9 states that the MAC can be 48 and 64 bit
- * MACs should be supported).  Returns 0 on success, non-zero otherwise.
- */
-int
-x99_mac(const char *input, des_cblock output, des_cblock keyblock)
-{
-    des_key_schedule ks;
-    des_cblock ivec;
-    des_cblock l_output[MAX_CHALLENGE_LEN / sizeof(des_cblock)];
-    int chal_len = strlen(input);
-    int rc;
-
-    /*
-     * Setup and verify the key.
-     * This may be a bit expensive to do every time, but it
-     * makes more sense for calling functions to deal with
-     * the key itself, rather than the schedule.  In practice,
-     * I don't think this will amount to much, but I haven't
-     * actually profiled it.
-     */
-    if ((rc = des_set_key_checked((const_des_cblock *) keyblock, ks)) != 0) {
-       x99_log(X99_LOG_ERR, "x99_mac: DES key %s",
-               rc == -1 ? "has incorrect parity" : "is weak");
-       return -1;
-    }
-
-    (void) memset(ivec, 0, sizeof(ivec));
-    des_cbc_encrypt(input, (unsigned char *) l_output, chal_len,
-                   ks, &ivec, DES_ENCRYPT);
-    (void) memcpy(output, l_output[(chal_len - 1) / 8], 8);
-    return 0;
-}
-
diff --git a/src/modules/rlm_x99_token/x99_pwe.c b/src/modules/rlm_x99_token/x99_pwe.c
deleted file mode 100644 (file)
index cc19557..0000000
+++ /dev/null
@@ -1,759 +0,0 @@
-/*
- * x99_pwe.c
- * $Id$
- *
- *   This program is free software; you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License as published by
- *   the Free Software Foundation; either version 2 of the License, or
- *   (at your option) any later version.
- *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
- *
- *   You should have received a copy of the GNU General Public License
- *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- *
- * Copyright 2001,2002  Google, Inc.
- */
-
-/*
- * This file implements passcode (password) checking functions for each
- * supported encoding (PAP, CHAP, etc.).  The current libradius interface
- * is not sufficient for X9.9 use.
- */
-
-#ifdef FREERADIUS
-#define _LRAD_MD4_H
-#define _LRAD_SHA1_H
-#include "libradius.h"
-#include "rad_assert.h"
-#endif
-#include "x99.h"
-#include "x99_pwe.h"
-
-#include <openssl/des.h>
-#include <openssl/md4.h>
-#include <openssl/md5.h>
-#include <openssl/sha.h>
-
-#include <string.h>
-
-static const char rcsid[] = "$Id$";
-
-
-/* Attribute IDs for supported password encodings. */
-static int pwattr[8];
-
-
-/* Initialize the pwattr array for supported password encodings. */
-void
-x99_pwe_init(void)
-{
-    DICT_ATTR *da;
-    int i = 0;
-
-    /*
-     * Setup known password types.  These are pairs.
-     * NB: Increase pwattr array size when adding a type.
-     *     It should be sized as (number of password types * 2)
-     */
-    (void) memset(pwattr, 0, sizeof(pwattr));
-
-    /* PAP */
-    if ((da = dict_attrbyname("User-Password")) != NULL) {
-       pwattr[i++] = da->attr;
-       pwattr[i++] = da->attr;
-    }
-
-    /* CHAP */
-    if ((da = dict_attrbyname("CHAP-Challenge")) != NULL) {
-       pwattr[i++] = da->attr;
-       if ((da = dict_attrbyname("CHAP-Password")) != NULL)
-           pwattr[i++] = da->attr;
-       else
-           pwattr[--i] = 0;
-    }
-
-#if 0
-    /* MS-CHAP (recommended not to use) */
-    if ((da = dict_attrbyname("MS-CHAP-Challenge")) != NULL) {
-       pwattr[i++] = da->attr;
-       if ((da = dict_attrbyname("MS-CHAP-Response")) != NULL)
-           pwattr[i++] = da->attr;
-       else
-           pwattr[--i] = 0;
-    }
-#endif /* 0 */
-
-    /* MS-CHAPv2 */
-    if ((da = dict_attrbyname("MS-CHAP-Challenge")) != NULL) {
-       pwattr[i++] = da->attr;
-       if ((da = dict_attrbyname("MS-CHAP2-Response")) != NULL)
-           pwattr[i++] = da->attr;
-       else
-           pwattr[--i] = 0;
-    }
-}
-
-
-/*
- * Test for password presence in an Access-Request packet.
- * Returns 0 for "no supported password present", or an non-zero
- * opaque value that must be used when calling x99_pw_valid.
- */
-int
-x99_pw_present(const REQUEST *request)
-{
-    int i;
-
-    for (i = 0; i < sizeof(pwattr) && pwattr[i]; i += 2) {
-       if (pairfind(request->packet->vps, pwattr[i]) &&
-           pairfind(request->packet->vps, pwattr[i + 1])) {
-           DEBUG("rlm_x99_token: pw_present: found password attributes %d, %d",
-                  pwattr[i], pwattr[i + 1]);
-           return i + 1; /* Can't return 0 (indicates failure) */
-       }
-    }
-
-    return 0;
-}
-
-
-/*
- * Test for password validity.  attr must be the return value from
- * x99_pw_present().
- * returns 1 for match, 0 for non-match.
- * If vps is non-null, then on matches, it will point to vps that
- * should be added to an Access-Accept packet.  If access is denied,
- * the caller is responsible for freeing any vps returned.
- * (vps is used for MPPE atttributes.)
- */
-int
-x99_pw_valid(const REQUEST *request, x99_token_t *inst,
-            int attr, const char *password, VALUE_PAIR **vps)
-{
-    int match = 0;
-    VALUE_PAIR *chal_vp, *resp_vp;
-
-    /*
-     * A module that does this might want to verify the presence of these.
-     * This code is self contained to x99, so I know these exist.
-     */
-    chal_vp = pairfind(request->packet->vps, pwattr[attr - 1]);
-    resp_vp = pairfind(request->packet->vps, pwattr[attr]);
-
-    /* Prepare for failure return. */
-    if (vps)
-       *vps = NULL;
-
-    /* If modular, this would actually call the authentication function. */
-    switch(pwattr[attr]) {
-    case PW_PASSWORD:
-       DEBUG("rlm_x99_token: pw_valid: handling PW_PASSWORD");
-       match = !strcmp(password, resp_vp->strvalue);
-       break;
-
-    case PW_CHAP_PASSWORD:
-    {
-       /*
-        * See RFC 1994.
-        * A CHAP password is MD5(CHAP_ID|SECRET|CHAP_CHALLENGE).
-                * CHAP_ID is a value set by the authenticator (the NAS), and used
-        * in the response calculation.  It is available as the first byte
-        * of the CHAP-Password attribute.
-        * SECRET is the password.
-        * CHAP_CHALLENGE is the challenge given to the peer (the user).
-        * The CHAP-Challenge Attribute may be missing, in which case the
-        * challenge is taken to be the Request Authenticator.  We don't
-        * handle this case.
-        */
-       /*                 ID       password    chal */
-       unsigned char input[1 + MAX_STRING_LEN + 16];
-       unsigned char output[MD5_DIGEST_LENGTH];
-
-       DEBUG("rlm_x99_token: pw_valid: handling PW_CHAP_PASSWORD");
-       if (1 + strlen(password) + chal_vp->length > sizeof(input)) {
-           DEBUG("rlm_x99_token: pw_valid: CHAP-Challenge/password too long");
-           match = 0;
-           break;
-       }
-       if (resp_vp->length != 17) {
-           x99_log(X99_LOG_AUTH, "pw_valid: CHAP-Password wrong size");
-           match = 0;
-           break;
-       }
-       input[0] = *(resp_vp->strvalue);
-       (void) memcpy(&input[1], password, strlen(password));
-       (void) memcpy(&input[1+strlen(password)], chal_vp->strvalue,
-                     chal_vp->length);
-       (void) MD5(input, 1 + strlen(password) + chal_vp->length, output);
-       match = !memcmp(output, &(resp_vp->strvalue)[1], MD5_DIGEST_LENGTH);
-    } /* case PW_CHAP_PASSWORD */
-    break;
-
-#if 0
-    case PW_MS_CHAP_RESPONSE:
-    {
-       /*
-        * See RFCs 2548, 2433, 3079.
-        * An MS-CHAP response is (IDENT|FLAGS|LM_RESPONSE|NT_RESPONSE).
-        *                 octets:   1     1       24           24
-        * IDENT is not used by RADIUS (it is the PPP MS-CHAP Identifier).
-        * FLAGS is 1 to indicate the NT_RESPONSE should be preferred.
-        * LM_RESPONSE is the LAN Manager compatible response.
-        * NT_RESPONSE is the NT compatible response.
-        * Either response may be zero-filled indicating its absence.
-        * Use of the LM response has been deprecated (RFC 2433, par. 6),
-         * so we don't handle it.
-        *
-        * The NT_RESPONSE is (DES(CHAL,K1)|DES(CHAL,K2)|DES(CHAL,K3)), where
-        * CHAL is the 8-octet challenge, and K1, K2, K3 are 7-octet pieces
-        * of MD4(unicode(password)), zero-filled to 21 octets.  Sigh.
-        */
-       unsigned char nt_keys[21]; /* sized for 3 DES keys */
-       unsigned char input[MAX_STRING_LEN * 2]; /* doubled for unicode */
-       unsigned char output[24];
-       int password_len, i;
-       VALUE_PAIR *vp;
-
-       DEBUG("rlm_x99_token: pw_valid: handling PW_MS_CHAP_RESPONSE");
-       if (chal_vp->length != 8) {
-           x99_log(X99_LOG_AUTH, "pw_valid: MS-CHAP-Challenge wrong size");
-           match = 0;
-           break;
-       }
-       if (resp_vp->length != 50) {
-           x99_log(X99_LOG_AUTH, "pw_valid: MS-CHAP-Response wrong size");
-           match = 0;
-           break;
-       }
-       if ((resp_vp->strvalue)[1] != 1) {
-           x99_log(X99_LOG_AUTH,
-                   "pw_valid: MS-CHAP-Response bad flags (LM not supported)");
-           match = 0;
-           break;
-       }
-       /* This is probably overkill. */
-       if (strlen(password) > MAX_STRING_LEN) {
-           x99_log(X99_LOG_AUTH, "pw_valid: MS-CHAP password too long");
-           match = 0;
-           break;
-       }
-
-       /*
-        * Start by hashing the unicode password.
-        * This is broken because unicode chars are machine-ordered,
-        * but the spec (RFC 2433) doesn't say how to prepare
-        * the password for md4 (other than by example values).
-        */
-       password_len = strlen(password);
-       for (i = 0; i < password_len; ++i) {
-           /* Set the high order 8 bits to 0 (little-endian) */
-           input[i * 2] = *password++;
-           input[i * 2 + 1] = 0;
-       }
-       (void) memset(nt_keys, 0, sizeof(nt_keys));
-       (void) MD4(input, 2 * password_len, nt_keys);
-
-       /* The challenge gets encrypted. */
-       (void) memcpy(input, chal_vp->strvalue, 8);
-
-       /* Convert the password hash to keys, and do the encryptions. */
-       for (i = 0; i < 3; ++i) {
-           des_cblock key;
-           des_key_schedule ks;
-
-           x99_key_from_hash(&key, &nt_keys[i * 7]);
-           des_set_key_unchecked(&key, ks);
-           des_ecb_encrypt((des_cblock *) input,
-                           (des_cblock *) &output[i * 8],
-                           ks, DES_ENCRYPT);
-       }
-
-       match = !memcmp(output, resp_vp->strvalue + 26, 24);
-       if (!match || !vps)
-           break;
-
-       /*
-        * Generate the MS-CHAP-MPPE-Keys attribute if needed.  This is not
-        * specified anywhere -- RFC 2548, par. 2.4.1 is the authority but
-        * it has typos and omissions that make this unimplementable.  The
-        * code here is based on experimental results provided by
-        * Takahiro Wagatsuma <waga@sic.shibaura-it.ac.jp>.
-        * We only support 128-bit keys derived from the NT hash; 40-bit
-        * and 56-bit keys are derived from the LM hash, which besides
-        * being deprecated, has severe security problems.
-        */
-
-       /* First, set some related attributes. */
-       vp = pairmake("MS-MPPE-Encryption-Policy",
-                     x99_mppe_policy[inst->mschap_mppe_policy], T_OP_EQ);
-       rad_assert(vp != NULL);
-       pairadd(vps, vp);
-       vp = pairmake("MS-MPPE-Encryption-Types",
-                     x99_mppe_types[inst->mschap_mppe_types], T_OP_EQ);
-       rad_assert(vp != NULL);
-       pairadd(vps, vp);
-
-       if (inst->mschap_mppe_policy) {
-           unsigned char mppe_keys[32];
-           /*                    0x    ASCII(mppe_keys)      '\0' */
-           char mppe_keys_string[2 + (2 * sizeof(mppe_keys)) + 1];
-
-           unsigned char md5_md[MD5_DIGEST_LENGTH];
-           unsigned char encode_buf[AUTH_VECTOR_LEN + MAX_STRING_LEN];
-           int secretlen;
-
-           /* Zero the LM-Key sub-field (and padding). */
-           (void) memset(mppe_keys, 0, sizeof(mppe_keys));
-           /* The NT-Key sub-field is MD4(MD4(unicode(password))). */
-           (void) MD4(nt_keys, 16, &mppe_keys[8]);
-
-#if 0 /* encoding now handled in lib/radius.c:rad_pwencode() */
-           /* Now we must encode the key as User-Password is encoded. */
-           secretlen = strlen(request->secret);
-           (void) memcpy(encode_buf, request->secret, secretlen);
-           (void) memcpy(encode_buf + secretlen, request->packet->vector,
-                         AUTH_VECTOR_LEN);
-           (void) MD5(encode_buf, secretlen + AUTH_VECTOR_LEN, md5_md);
-           for (i = 0; i < 16; ++i)
-               mppe_keys[i] ^= md5_md[i];
-           (void) memcpy(encode_buf + secretlen, mppe_keys, MD5_DIGEST_LENGTH);
-           (void) MD5(encode_buf, secretlen + MD5_DIGEST_LENGTH, md5_md);
-           for (i = 0; i < 16; ++i)
-               mppe_keys[i + 16] ^= md5_md[i];
-#endif /* 0 */
-
-           /* Whew.  Now stringify it for pairmake(). */
-           mppe_keys_string[0] = '0';
-           mppe_keys_string[1] = 'x';
-           for (i = 0; i < 32; ++i)
-               (void) sprintf(&mppe_keys_string[i*2+2], "%02X", mppe_keys[i]);
-           vp = pairmake("MS-CHAP-MPPE-Keys", mppe_keys_string, T_OP_EQ);
-           rad_assert(vp != NULL);
-           pairadd(vps, vp);
-       } /* if (doing mppe) */
-
-    } /* case PW_MS_CHAP_RESPONSE */
-    break;
-#endif /* 0 (MS_CHAP) */
-
-    case PW_MS_CHAP2_RESPONSE:
-    {
-       /*
-        * See RFCs 2548, 2759, 3079.
-        * An MS-CHAPv2 response is
-        *          (IDENT|FLAGS|PEER_CHALLENGE|RESERVED|NT_RESPONSE).
-        *   octets:   1     1         16          8        24
-        * IDENT is the PPP MS-CHAPv2 Identifier, used in MS-CHAP2-Success.
-        * FLAGS is currently unused.
-        * PEER_CHALLENGE is a random number, generated by the peer.
-        * NT_RESPONSE is (DES(CHAL,K1)|DES(CHAL,K2)|DES(CHAL,K3)), where
-        * K1, K2, K3 are 7-octet pieces of MD4(unicode(password)), zero-
-        * filled to 21 octets (just as in MS-CHAP); and CHAL is
-        * MSB8(SHA(PEER_CHALLENGE|MS_CHAP_CHALLENGE|USERNAME)).
-        */
-       unsigned char nt_keys[21]; /* aka "password_md", sized for 3 DES keys */
-       unsigned char password_md_md[MD4_DIGEST_LENGTH]; /* for mutual auth */
-       unsigned char input[MAX_STRING_LEN * 2]; /* doubled for unicode */
-       unsigned char output[24];
-       int password_len, i;
-       VALUE_PAIR *vp;
-
-       DEBUG("rlm_x99_token: pw_valid: handling PW_MS_CHAP2_RESPONSE");
-       if (chal_vp->length != 16) {
-           x99_log(X99_LOG_AUTH,"pw_valid: MS-CHAP-Challenge (v2) wrong size");
-           match = 0;
-           break;
-       }
-       if (resp_vp->length != 50) {
-           x99_log(X99_LOG_AUTH, "pw_valid: MS-CHAP2-Response wrong size");
-           match = 0;
-           break;
-       }
-       /* This is probably overkill. */
-       if (strlen(password) > MAX_STRING_LEN) {
-           x99_log(X99_LOG_AUTH, "pw_valid: MS-CHAPv2 password too long");
-           match = 0;
-           break;
-       }
-
-       /*
-        * Start by hashing the unicode password.
-        * This is broken because unicode chars are machine-ordered,
-        * but the spec (RFC 2759) doesn't say how to prepare
-        * the password for md4 (other than by example values).
-        */
-       password_len = strlen(password);
-       for (i = 0; i < password_len; ++i) {
-           /* Set the high order 8 bits to 0 (little-endian) */
-           input[i * 2] = *password++;
-           input[i * 2 + 1] = 0;
-       }
-       (void) memset(nt_keys, 0, sizeof(nt_keys));
-       (void) MD4(input, 2 * password_len, nt_keys);
-
-       /* Now calculate the CHAL value from our various inputs. */
-       {
-           SHA_CTX ctx;
-           unsigned char md[SHA_DIGEST_LENGTH];
-           char *username = request->username->strvalue;
-           int username_len = request->username->length;
-
-           SHA1_Init(&ctx);
-           SHA1_Update(&ctx, resp_vp->strvalue + 2, 16);
-           SHA1_Update(&ctx, chal_vp->strvalue, 16);
-           SHA1_Update(&ctx, username, username_len);
-           SHA1_Final(md, &ctx);
-
-           (void) memcpy(input, md, 8);
-       }
-
-       /* Convert the password hash to keys, and do the encryptions. */
-       for (i = 0; i < 3; ++i) {
-           des_cblock key;
-           des_key_schedule ks;
-
-           x99_key_from_hash(&key, &nt_keys[i * 7]);
-           des_set_key_unchecked(&key, ks);
-           des_ecb_encrypt((des_cblock *) input,
-                           (des_cblock *) &output[i * 8],
-                           ks, DES_ENCRYPT);
-       }
-
-       match = !memcmp(output, resp_vp->strvalue + 26, 24);
-       if (!match || !vps)
-           break;
-
-       /*
-        * MS-CHAPv2 requires mutual authentication; we must prove
-        * that we know the secret.  This is a bit circuitous: set
-        * MD1 = SHA(MD4(MD4(unicode(password)))|NT_RESPONSE|MAGIC1),
-        * MD2 = MSB8(SHA(PEER_CHALLENGE|MS_CHAP_CHALLENGE|USERNAME)),
-        * and finally use SHA(MD1|MD2|MAGIC2) as the authenticator.
-        * The authenticator is returned as the string "S=<auth>",
-        * <auth> is the authenticator expressed as [uppercase] ASCII.
-        * See RFC 2759.
-        */
-       {
-           SHA_CTX ctx;
-           unsigned char md1[SHA_DIGEST_LENGTH];
-           unsigned char md2[SHA_DIGEST_LENGTH];
-           unsigned char auth_md[SHA_DIGEST_LENGTH];
-           /*                  S=  (  ASCII(auth_md)   )  \0 */
-           char auth_md_string[2 + (2 * sizeof(auth_md)) + 1];
-           /*
-            * ugh.  The ASCII authenticator (auth_md_string) is sent
-            * along with a single (useless) binary byte (the ID).
-            * So we must "stringify" it again (for pairmake()) since the
-            * binary byte requires the attribute to be of type "octets".
-            */
-           /*                    0x  (ID) ( ASCII("S="ASCII(auth_md))) */
-           char auth_octet_string[2 + 2 + (2 * sizeof(auth_md_string))];
-
-           char *username = request->username->strvalue;
-           int username_len = request->username->length;
-
-           /* "Magic server to client signing constant" */
-           unsigned char magic1[39] =
-           { 0x4D, 0x61, 0x67, 0x69, 0x63, 0x20, 0x73, 0x65, 0x72, 0x76,
-             0x65, 0x72, 0x20, 0x74, 0x6F, 0x20, 0x63, 0x6C, 0x69, 0x65,
-             0x6E, 0x74, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x69, 0x6E, 0x67,
-             0x20, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74 };
-           /* "Pad to make it do more than one iteration" */
-           unsigned char magic2[41] =
-           { 0x50, 0x61, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x6D, 0x61, 0x6B,
-             0x65, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6F, 0x20, 0x6D, 0x6F,
-             0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x6E, 0x20, 0x6F, 0x6E,
-             0x65, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6F,
-             0x6E };
-
-           /* MD1 */
-           (void) MD4(nt_keys, MD4_DIGEST_LENGTH, password_md_md);
-           SHA1_Init(&ctx);
-           SHA1_Update(&ctx, password_md_md, MD4_DIGEST_LENGTH);
-           SHA1_Update(&ctx, resp_vp->strvalue + 26, 24);
-           SHA1_Update(&ctx, magic1, sizeof(magic1));
-           SHA1_Final(md1, &ctx);
-
-           /* MD2 */
-           SHA1_Init(&ctx);
-           SHA1_Update(&ctx, resp_vp->strvalue + 2, 16);
-           SHA1_Update(&ctx, chal_vp->strvalue, 16);
-           SHA1_Update(&ctx, username, username_len);
-           SHA1_Final(md2, &ctx);
-
-           /* The Authenticator */
-           SHA1_Init(&ctx);
-           SHA1_Update(&ctx, md1, SHA_DIGEST_LENGTH);
-           SHA1_Update(&ctx, md2, 8);
-           SHA1_Update(&ctx, magic2, sizeof(magic2));
-           SHA1_Final(auth_md, &ctx);
-
-           /* String conversion. */
-           auth_md_string[0] = 'S';
-           auth_md_string[1] = '=';
-           for (i = 0; i < sizeof(auth_md); ++i)
-               (void) sprintf(&auth_md_string[i * 2 + 2], "%02X", auth_md[i]);
-
-           /* And then octet conversion.  Ugh! */
-           auth_octet_string[0] = '0';
-           auth_octet_string[1] = 'x';
-           (void) sprintf(&auth_octet_string[2], "%02X", resp_vp->strvalue[0]);
-           for (i = 0; i < sizeof(auth_md_string) - 1; ++i)
-               (void) sprintf(&auth_octet_string[i * 2 + 4], "%02X",
-                              auth_md_string[i]);
-
-           vp = pairmake("MS-CHAP2-Success", auth_octet_string, T_OP_EQ);
-           rad_assert(vp != NULL);
-           pairadd(vps, vp);
-       } /* Generate mutual auth info. */
-
-       /*
-        * Generate the MPPE initial session key if needed, per RFC 3079.
-        * (Although, RFC 2548 leaves us guessing at how to generate this.)
-        * For MS-CHAPv2 we support all key lengths (40-, 56- and 128-bit),
-        * although MPPE via RADIUS supports only 40- and 128-bit keys.
-        * This is a bit more complicated than MS-CHAP.  Start by generating
-        * a "master session key"
-        *    MSB16(SHA(NTPasswordHashHash|NT_RESPONSE|MAGIC1)), where
-        * NTPasswordHashHash is MD4(MD4(unicode(password))), NT_RESPONSE
-        * is from the MS-CHAP2-Response attribute, and MAGIC1 is a
-        * constant from RFC 3079.  Then, we derive asymmetric send/receive
-        * keys from the master session key.  The "master send key" is
-        *     MSBx(SHA(MASTERKEY|SHSPAD1|MAGIC3|SHSPAD2)),
-        * and the "master receive key" is
-        *     MSBx(SHA(MASTERKEY|SHSPAD1|MAGIC2|SHSPAD2)), where
-        * MASTERKEY is the "master session key" generated above, and the
-        * other values are constants from RFC 3079.  MSBx is the x-most
-        * significant bytes, where x is 5, 7, or 16 as appropriate for
-        * the desired key length.  We always generate 16 byte (128-bit)
-        * keys, the NAS is required to truncate as needed.
-        */
-
-       /* First, set some related attributes. */
-       vp = pairmake("MS-MPPE-Encryption-Policy",
-                     x99_mppe_policy[inst->mschapv2_mppe_policy], T_OP_EQ);
-       rad_assert(vp != NULL);
-       pairadd(vps, vp);
-       vp = pairmake("MS-MPPE-Encryption-Types",
-                     x99_mppe_types[inst->mschapv2_mppe_types], T_OP_EQ);
-       rad_assert(vp != NULL);
-       pairadd(vps, vp);
-
-       if (inst->mschapv2_mppe_policy) {
-           /* These constants and key vars are named from RFC 3079. */
-           /* "This is the MPPE Master Key" */
-           unsigned char Magic1[27] =
-           { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
-             0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
-             0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
-           /* "On the client side, this is the send key; "
-              "on the server side, it is the receive key." */
-           unsigned char Magic2[84] =
-           { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
-             0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
-             0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
-             0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
-             0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
-             0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
-             0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
-             0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
-             0x6b, 0x65, 0x79, 0x2e };
-           /* "On the client side, this is the receive key; "
-              "on the server side, it is the send key." */
-           unsigned char Magic3[84] =
-           { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
-             0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
-             0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
-             0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
-             0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
-             0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
-             0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
-             0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
-             0x6b, 0x65, 0x79, 0x2e };
-           unsigned char SHSpad1[40] =
-           { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
-           unsigned char SHSpad2[40] =
-           { 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
-             0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
-             0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
-             0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 };
-           unsigned char MasterKey[16];
-           unsigned char MasterSendKey[16];
-           unsigned char MasterReceiveKey[16];
-
-           SHA_CTX ctx;
-           unsigned char sha_md[SHA_DIGEST_LENGTH];
-#if 0 /* salting/encoding now handled in lib/radius.c:tunnel_pwencode() */
-           unsigned char md5_md[MD5_DIGEST_LENGTH];
-
-           /*   From RFC 2548:           S                 R           A */
-           unsigned char encode_buf[MAX_STRING_LEN + AUTH_VECTOR_LEN + 2];
-           int secretlen;
-
-           /* A useless value required by RFC 2548. */
-           unsigned char salt[2];
-           unsigned char mppe_key[32]; /* 1 + 16 + padding */
-           /*                           0x   (   ASCII(salt)  ) */
-           unsigned char mppe_key_string[2 + (2 * sizeof(salt)) +
-           /*                            (   ASCII(mppe_key)  )  \0 */
-                                         (2 * sizeof(mppe_key)) + 1];
-#else /* 0 */
-           /*                           0x   (   ASCII(mppe_key)   )  \0 */
-           unsigned char mppe_key_string[2 + (2 * sizeof(MasterKey)) + 1];
-#endif /* 0 */
-
-           /* Generate the master session key. */
-           SHA1_Init(&ctx);
-           SHA1_Update(&ctx, password_md_md, MD4_DIGEST_LENGTH);
-           SHA1_Update(&ctx, resp_vp->strvalue + 26, 24);
-           SHA1_Update(&ctx, Magic1, sizeof(Magic1));
-           SHA1_Final(sha_md, &ctx);
-           (void) memcpy(MasterKey, sha_md, 16);
-
-           /* Generate the master send key. */
-           SHA1_Init(&ctx);
-           SHA1_Update(&ctx, MasterKey, 16);
-           SHA1_Update(&ctx, SHSpad1, 40);
-           SHA1_Update(&ctx, Magic3, sizeof(Magic3));
-           SHA1_Update(&ctx, SHSpad2, 40);
-           SHA1_Final(sha_md, &ctx);
-           (void) memcpy(MasterSendKey, sha_md, 16);
-
-           /* Generate the master receive key. */
-           SHA1_Init(&ctx);
-           SHA1_Update(&ctx, MasterKey, 16);
-           SHA1_Update(&ctx, SHSpad1, 40);
-           SHA1_Update(&ctx, Magic2, sizeof(Magic3));
-           SHA1_Update(&ctx, SHSpad2, 40);
-           SHA1_Final(sha_md, &ctx);
-           (void) memcpy(MasterReceiveKey, sha_md, 16);
-
-           /* Now, generate the MS-MPPE-Send-Key attribute. */
-
-#if 0
-           /* Setup the salt value. */
-           salt[0] = 0x80;
-           salt[1] = 0x01;
-
-           /* Encode the key. */
-           (void) memset(mppe_key, 0, sizeof(mppe_key));
-           mppe_key[0] = 16; /* length */
-           (void) memcpy(&mppe_key[1], MasterSendKey, 16);
-           secretlen = strlen(request->secret);
-           (void) memcpy(encode_buf, request->secret, secretlen);
-           (void) memcpy(encode_buf + secretlen, request->packet->vector,
-                         AUTH_VECTOR_LEN);
-           (void) memcpy(encode_buf + secretlen + 16, salt, 2);
-           (void) MD5(encode_buf, secretlen + AUTH_VECTOR_LEN + 2, md5_md);
-           for (i = 0; i < 16; ++i)
-               mppe_key[i] ^= md5_md[i];
-           (void) memcpy(encode_buf + secretlen, mppe_key, 16);
-           (void) MD5(encode_buf, secretlen + 16, md5_md);
-           for (i = 0; i < 16; ++i)
-               mppe_key[i + 16] ^= md5_md[i];
-
-           /* Whew.  Now stringify it for pairmake(). */
-           mppe_key_string[0] = '0';
-           mppe_key_string[1] = 'x';
-           (void) sprintf(&mppe_key_string[2], "%02X", salt[0]);
-           (void) sprintf(&mppe_key_string[4], "%02X", salt[1]);
-           for (i = 0; i < sizeof(mppe_key); ++i)
-               (void) sprintf(&mppe_key_string[i*2+6], "%02X", mppe_key[i]);
-#else /* 0 */
-           mppe_key_string[0] = '0';
-           mppe_key_string[1] = 'x';
-           for (i = 0; i < sizeof(MasterSendKey); ++i)
-               (void) sprintf(&mppe_key_string[i*2+2], "%02X",
-                              MasterSendKey[i]);
-#endif /* 0 */
-           vp = pairmake("MS-MPPE-Send-Key", mppe_key_string, T_OP_EQ);
-           rad_assert(vp != NULL);
-           pairadd(vps, vp);
-
-           /* Generate the MS-MPPE-Recv-Key attribute. */
-
-#if 0
-           /* Setup the salt value. */
-           salt[0] = 0x80;
-           salt[1] = 0x02;
-
-           /* Encode the key. */
-           (void) memset(mppe_key, 0, sizeof(mppe_key));
-           mppe_key[0] = 16; /* length */
-           (void) memcpy(&mppe_key[1], MasterReceiveKey, 16);
-           secretlen = strlen(request->secret);
-           (void) memcpy(encode_buf, request->secret, secretlen);
-           (void) memcpy(encode_buf + secretlen, request->packet->vector,
-                         AUTH_VECTOR_LEN);
-           (void) memcpy(encode_buf + secretlen + 16, salt, 2);
-           (void) MD5(encode_buf, secretlen + AUTH_VECTOR_LEN + 2, md5_md);
-           for (i = 0; i < 16; ++i)
-               mppe_key[i] ^= md5_md[i];
-           (void) memcpy(encode_buf + secretlen, mppe_key, 16);
-           (void) MD5(encode_buf, secretlen + 16, md5_md);
-           for (i = 0; i < 16; ++i)
-               mppe_key[i + 16] ^= md5_md[i];
-
-           /* Whew.  Now stringify it for pairmake(). */
-           mppe_key_string[0] = '0';
-           mppe_key_string[1] = 'x';
-           (void) sprintf(&mppe_key_string[2], "%02X", salt[0]);
-           (void) sprintf(&mppe_key_string[4], "%02X", salt[1]);
-           for (i = 0; i < sizeof(mppe_key); ++i)
-               (void) sprintf(&mppe_key_string[i*2+6], "%02X", mppe_key[i]);
-#else /* 0 */
-           mppe_key_string[0] = '0';
-           mppe_key_string[1] = 'x';
-           for (i = 0; i < sizeof(MasterReceiveKey); ++i)
-               (void) sprintf(&mppe_key_string[i*2+2], "%02X",
-                              MasterReceiveKey[i]);
-#endif /* 0 */
-           vp = pairmake("MS-MPPE-Recv-Key", mppe_key_string, T_OP_EQ);
-           rad_assert(vp != NULL);
-           pairadd(vps, vp);
-
-       } /* if (doing mppe) */
-
-    } /* case PW_MS_CHAP2_RESPONSE */
-    break;
-
-    default:
-       DEBUG("rlm_x99_token: pw_valid: unknown password type");
-       match = 0;
-       break;
-
-    } /* switch(pwattr[attr]) */
-
-    return match;
-}
-
-
-/*
- * #$!#@ have to convert 7 octet ranges into 8 octet keys.
- * Implementation cribbed (and slightly modified) from
- * rlm_mschap.c by Jay Miller <jaymiller@socket.net>.
- * We don't bother checking/setting parity.
- */
-static void
-x99_key_from_hash(des_cblock *key, const unsigned char hashbytes[7])
-{
-    int i;
-    unsigned char working;
-    unsigned char next = 0;
-
-    for (i = 0; i < 7; ++i) {
-       working = hashbytes[i];
-       (*key)[i] = (working >> i) | next;
-       next = (working << (7 - i));
-    }
-    (*key)[i] = next;
-}
-
diff --git a/src/modules/rlm_x99_token/x99_rad.h b/src/modules/rlm_x99_token/x99_rad.h
deleted file mode 100644 (file)
index eb6c6dc..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * x99_rad.h
- * $Id$
- *
- *   This program is free software; you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License as published by
- *   the Free Software Foundation; either version 2 of the License, or
- *   (at your option) any later version.
- *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
- *
- *   You should have received a copy of the GNU General Public License
- *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- *
- * Copyright 2001,2002  Google, Inc.
- */
-
-#ifndef X99_RAD_H
-#define X99_RAD_H
-
-#include "radiusd.h"
-#define X99_LOG_ERR  L_ERR
-#define X99_LOG_AUTH L_AUTH
-#define X99_LOG_INFO L_INFO
-#define X99_LOG_CRIT (L_ERR|L_CONS)
-
-/* x99_state.c */
-extern int x99_gen_state(char **ascii_state, unsigned char **raw_state,
-                        const char challenge[MAX_CHALLENGE_LEN + 1],
-                        int32_t flags, int32_t when,
-                        const unsigned char key[16]);
-
-/* x99_pwe.c */
-#include "libradius.h"   /* VALUE_PAIR */
-extern void x99_pwe_init(void);
-extern int x99_pw_present(const REQUEST *request);
-extern int x99_pw_valid(const REQUEST *request, x99_token_t *inst,
-                       int attr, const char *password, VALUE_PAIR **vps);
-
-#endif /* X99_RAD_H */
-
diff --git a/src/modules/rlm_x99_token/x99_state.c b/src/modules/rlm_x99_token/x99_state.c
deleted file mode 100644 (file)
index ce197d3..0000000
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * x99_state.c
- * $Id$
- *
- *   This program is free software; you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License as published by
- *   the Free Software Foundation; either version 2 of the License, or
- *   (at your option) any later version.
- *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
- *
- *   You should have received a copy of the GNU General Public License
- *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- *
- * Copyright 2001,2002  Google, Inc.
- */
-
-#ifdef FREERADIUS
-#define _LRAD_MD4_H
-#define _LRAD_SHA1_H
-#include "radiusd.h"
-#endif
-#include "x99.h"
-
-#include <string.h>
-#include <openssl/des.h> /* des_cblock */
-#include <openssl/md5.h>
-#include <openssl/hmac.h>
-
-
-static const char rcsid[] = "$Id$";
-
-
-/*
- * Generate the State attribute, suitable for passing to pairmake().
- * challenge must be a null terminated string, and be sized at least
- * as large as indicated in the function definition.
- *
- * Returns 0 on success, non-zero otherwise.  For successful returns,
- * ascii_state (suitable for passing to pairmake()) and raw_state, if
- * non-NULL, will be pointing to allocated storage.  The caller is
- * responsible for freeing the storage.  raw_state will not be
- * null-terminated, the caller should know the expected size (any
- * variance is size is solely due to the length of the challenge arg).
- *
- * In the simplest implementation, we would just use the challenge as state.
- * Unfortunately, the RADIUS secret protects only the User-Password
- * attribute; an attacker that can remove packets from the wire and insert
- * new ones can simply insert a replayed state without having to know
- * the secret.  If not for an attacker that can remove packets from the
- * network, I believe trivial state to be secure.
- *
- * So, we have to make up for that deficiency by signing our state with
- * data unique to this specific request.  A NAS would use the Request
- * Authenticator, we don't know what that will be when the State is
- * returned to us, so we'll use the time.  So our replay prevention
- * is limited to a time interval (inst->maxdelay).  We could keep
- * track of all challenges issued over that time interval for
- * better protection.
- *
- * Our state, then, is
- *   (challenge + resync + time + hmac(challenge + resync + time, key)),
- * where '+' denotes concatentation, 'challenge' is the ASCII octets of
- * the challenge, 'flags' is a 32-bit value that can be used to record
- * additional info, 'time' is the 32-bit time (LSB if time_t is 64 bits)
- * in network byte order, and 'key' is a random key, generated in
- * x99_token_init().  This means that only the server which generates
- * a challenge can verify it; this should be OK if your NAS's load balance
- * across RADIUS servers by a "first available" algorithm.  If your
- * NAS's round-robin (ugh), you could use the RADIUS secret instead, but
- * read RFC 2104 first, and make very sure you really want to do this.
- *
- * Note that putting the time in network byte order is pointless, since
- * only "this" server will be able to verify the hmac, due to the unique
- * key.  But I've left it in there for future consideration of sync'd
- * keys across servers (eg, using the RADIUS secret, which is probably
- * not a good idea, or reading from a file, which might be OK.)
- */
-int
-x99_gen_state(char **ascii_state, unsigned char **raw_state,
-             const char challenge[MAX_CHALLENGE_LEN + 1], int32_t flags,
-             int32_t when, const unsigned char key[16])
-{
-    HMAC_CTX hmac_ctx;
-    unsigned char hmac[MD5_DIGEST_LENGTH];
-    char *p;
-    int i;
-
-    /*
-     * Generate the hmac.  We already have a dependency on openssl for
-     * DES, so we'll use it's hmac functionality also -- saves us from
-     * having to collect the data to be signed into one contiguous piece.
-     */
-    HMAC_Init(&hmac_ctx, key, sizeof(key), EVP_md5());
-    HMAC_Update(&hmac_ctx, challenge, strlen(challenge));
-    HMAC_Update(&hmac_ctx, (unsigned char *) &flags, 4);
-    HMAC_Update(&hmac_ctx, (unsigned char *) &when, 4);
-    HMAC_Final(&hmac_ctx, hmac, NULL);
-    HMAC_cleanup(&hmac_ctx);
-
-    /* Fill in raw_state if requested. */
-    if (raw_state) {
-       *raw_state = rad_malloc(strlen(challenge) + 8 + sizeof(hmac));
-       p = *raw_state;
-       (void) memcpy(p, challenge, strlen(challenge));
-       p += strlen(challenge);
-       (void) memcpy(p, &flags, 4);
-       p += 4;
-       (void) memcpy(p, &when, 4);
-       p += 4;
-       (void) memcpy(p, hmac, sizeof(hmac));
-    }
-
-    /*
-     * Fill in ascii_state if requested.  (pairmake() forces us to to this.)
-     * "0x" is required for pairmake().  Note that each octet expands into
-     * 2 hex digits in ASCII (0xAA -> 0x4141).
-     */
-    if (ascii_state) {
-       *ascii_state = rad_malloc(2 +                           /* "0x"      */
-                                 strlen(challenge) * 2 +       /* challenge */
-                                 8 +                           /* flags     */
-                                 8 +                           /* time      */
-                                 sizeof(hmac) * 2 +            /* hmac      */
-                                 1);                           /* '\0'      */
-       (void) sprintf(*ascii_state, "0x");
-       p = *ascii_state + 2;
-
-       /* Add the challenge. */
-       for (i = 0; i < MAX_CHALLENGE_LEN / sizeof(des_cblock); ++i) {
-           x99_keyblock_to_string(p, challenge, x99_hex_conversion);
-           if (strlen(challenge) > sizeof(des_cblock)) {
-               challenge += sizeof(des_cblock);
-               p += 2 * sizeof(des_cblock);
-           } else {
-               p += 2 * strlen(challenge);
-               break;
-           }
-       }
-
-       /* Add the flags and time. */
-       {
-           des_cblock cblock;
-           (void) memcpy(cblock, &flags, 4);
-           (void) memcpy(&cblock[4], &when, 4);
-           x99_keyblock_to_string(p, cblock, x99_hex_conversion);
-       }
-       p += 16;
-
-       /* Add the hmac. */
-       x99_keyblock_to_string(p, hmac, x99_hex_conversion);
-       p += 16;
-       x99_keyblock_to_string(p, &hmac[8], x99_hex_conversion);
-       p += 16;
-       *p = '\0';
-    }
-
-    return 0;
-}
-
diff --git a/src/modules/rlm_x99_token/x99_sync.c b/src/modules/rlm_x99_token/x99_sync.c
deleted file mode 100644 (file)
index 85fed28..0000000
+++ /dev/null
@@ -1,680 +0,0 @@
-/*
- * x99_sync.c
- * $Id$
- *
- *   This program is free software; you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License as published by
- *   the Free Software Foundation; either version 2 of the License, or
- *   (at your option) any later version.
- *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
- *
- *   You should have received a copy of the GNU General Public License
- *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- *
- * Copyright 2001,2002  Google, Inc.
- */
-
-#ifdef FREERADIUS
-#include "radiusd.h"
-#endif
-#include "x99.h"
-#include "x99_sync.h"
-
-#include <errno.h>
-#include <limits.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <openssl/des.h> /* des_cblock */
-
-static const char rcsid[] = "$Id$";
-
-
-/*
- * Sync data fields changed slightly between v1 and v2, and were renamed.
- * These routines, however, retain the v1 names.  The name:field mapping is:
- *       *_last_auth: last_auth_t      last authentication time
- *       *_failcount: last_auth_s      number of consecutive auth failures
- *   *_last_auth_pos: last_auth_p      window pos. of last auth (not in v1)
- */
-
-
-/*
- * Get sync data for a given user.
- * Returns 0 on success, non-zero otherwise.
- *
- *  syncdir:  duh
- * username:  duh
- * card_id:   duh
- * ewin:      event window position (0 == now)
- * twin:      time window position (0 == now) (NOT IMPLEMENTED)
- * challenge: On successful return it will be filled in with the challenge
- *            expected for the given window slot.  On unsuccesful return,
- *            challenge may be overwritten and contain garbage.
- *            If ewin == 0, the stored "ewin 0" value is returned.
- *            If ewin > 0 and challenge points to a non-empty string, it
- *               will be taken as the challenge for (ewin - 1).  That is,
- *               ewin will not be used to calculate the next challenge;
- *               instead the passed in challenge is run through the sync
- *               calculation once to arrive at the next challenge.  This
- *               speeds things up since we don't have to iterate ewin times.
- *            If ewin > 0 and challenge points to an empty string, the
- *               stored "ewin 0" challenge value is run through the sync
- *               calculation ewin times.
- * keyblock:  Similar to challenge.  It may be updated for key changing
- *            sync modes. (NOT IMPLEMENTED)
- */
-int
-x99_get_sync_data(const char *syncdir, const char *username,
-                 uint32_t card_id, int ewin, int twin,
-                 char challenge[MAX_CHALLENGE_LEN + 1], des_cblock keyblock)
-{
-    /* ARGSUSED */
-    des_cblock output;
-    int i, rc = -1;
-    char *lock;
-
-    if (ewin == 0) {
-       if ((lock = x99_acquire_sd_lock(syncdir, username)) == NULL)
-           return -1;
-       rc = x99_get_sd(syncdir, username, challenge, NULL, NULL, NULL);
-       x99_release_sd_lock(lock);
-       return rc;
-
-    } else if (challenge[0]) {
-       ewin = 1; /* only iterate once */
-
-    } else {
-       /* The hard way.  Get the zeroeth challenge. */
-       rc = x99_get_sync_data(syncdir, username, card_id, 0, twin,
-                              challenge, keyblock);
-       if (rc)
-           return rc;
-    }
-
-    while (ewin--) {
-       if (card_id & X99_CF_CRYPTOCARD) {
-           if ((rc = x99_mac(challenge, output, keyblock)) == 0) {
-               for (i = 0; i < 8; ++i) {
-                   output[i] &= 0x0f;
-                   if (output[i] > 9)
-                       output[i] -= 10;
-                   output[i] |= 0x30;
-               }
-               (void) memcpy(challenge, output, 8);
-               challenge[8] = '\0';
-           } else {
-               break;
-           }
-       } else {
-           /* No other vendors implemented yet. */
-           rc = -1;
-           break;
-       }
-    }
-
-    return rc;
-}
-
-/*
- * Set sync data for a given user.
- * Returns 0 on success, non-zero otherwise.
- * Side effects:
- * - Resets failure count to 0 on successful return.
- * - Sets last auth time to "now" on successful return.
- * - Sets last auth window position to 0.
- * Because of the failure count reset, this should only be called for/after
- * successful authentications.
- *
- * username:  duh
- * challenge: The challenge to be stored.
- * keyblock:  The key to be stored.  This is for sync modes in which the
- *            key changes for successive challenges. (NOT IMPLEMENTED)
- */
-int
-x99_set_sync_data(const char *syncdir, const char *username,
-                 const char *challenge, const des_cblock keyblock)
-{
-    /* ARGSUSED */
-    int rc;
-    char *lock;
-
-    if ((lock = x99_acquire_sd_lock(syncdir, username)) == NULL)
-       return -1;
-
-    rc = x99_set_sd(syncdir, username, challenge, 0, time(NULL), 0);
-    x99_release_sd_lock(lock);
-    return rc;
-}
-
-
-/*
- * Return the last time the user authenticated.
- * Returns 0 on success, non-zero otherwise.
- */
-int
-x99_get_last_auth(const char *syncdir, const char *username, time_t *last_auth)
-{
-    int rc;
-    char *lock;
-
-    if ((lock = x99_acquire_sd_lock(syncdir, username)) == NULL)
-       return -1;
-    rc = x99_get_sd(syncdir, username, NULL, NULL, last_auth, NULL);
-    x99_release_sd_lock(lock);
-    return rc;
-}
-
-/*
- * Set the last auth time for a user to "now".
- * Returns 0 on success, non-zero otherwise.
- * Note that x99_set_sync_data() also resets the auth time.
- * This function is no longer called, (the failcount() routines do this work),
- * but I'm saving it here for reference.
- */
-int
-x99_upd_last_auth(const char *syncdir, const char *username)
-{
-    int failcount, rc;
-    char *lock;
-    char challenge[MAX_CHALLENGE_LEN + 1];
-    unsigned pos;
-
-    if ((lock = x99_acquire_sd_lock(syncdir, username)) == NULL)
-       return -1;
-
-    rc = x99_get_sd(syncdir, username, challenge, &failcount, NULL, &pos);
-    if (rc == 0)
-       rc = x99_set_sd(syncdir, username, challenge, failcount, time(NULL),
-                       pos);
-
-    x99_release_sd_lock(lock);
-    return rc;
-}
-
-
-/*
- * Atomically increment a user's failed login count.
- * Also updates last_auth.
- */
-int
-x99_incr_failcount(const char *syncdir, const char *username)
-{
-    int failcount, rc;
-    char *lock;
-    char challenge[MAX_CHALLENGE_LEN + 1];
-    unsigned pos;
-
-    if ((lock = x99_acquire_sd_lock(syncdir, username)) == NULL)
-       return -1;
-
-    /* Get current value. */
-    rc = x99_get_sd(syncdir, username, challenge, &failcount, NULL, &pos);
-    if (rc == 0) {
-       /* Increment. */
-       if (++failcount == INT_MAX)
-           failcount--;
-       rc = x99_set_sd(syncdir, username, challenge, failcount, time(NULL),
-                       pos);
-    }
-
-    x99_release_sd_lock(lock);
-    return rc;
-}
-
-/*
- * Reset failure count to 0.  Also updates last_auth and resets pos.
- * Returns 0 on success, non-zero otherwise.
- * This is almost just like x99_incr_failcount().
- * x99_set_sync_data() resets the failcount also, but that's because
- * we keep the failcount and other sync data together; we don't want
- * to necessarily make that visible to our callers (x99_rlm.c).
- */
-int
-x99_reset_failcount(const char *syncdir, const char *username)
-{
-    int rc;
-    char *lock;
-    char challenge[MAX_CHALLENGE_LEN + 1];
-
-    if ((lock = x99_acquire_sd_lock(syncdir, username)) == NULL)
-       return -1;
-
-    rc = x99_get_sd(syncdir, username, challenge, NULL, NULL, NULL);
-    if (rc == 0)
-       rc = x99_set_sd(syncdir, username, challenge, 0, time(NULL), 0);
-
-    x99_release_sd_lock(lock);
-    return rc;
-}
-
-
-/*
- * checks the failure counter.
- * returns 0 if the user is allowed to authenticate, < 0 otherwise:
- *   FAIL_ERR if the user is failed due to internal error,
- *   FAIL_HARD if the user is failed "hard",
- *   FAIL_SOFT if the user is failed "soft".
- * caller does not need to log failures, we do it (in order to be specific).
- */
-int
-x99_check_failcount(const char *username, const x99_token_t *inst)
-{
-    time_t last_auth;
-    int failcount;
-
-    if (x99_get_last_auth(inst->syncdir, username, &last_auth) != 0) {
-       x99_log(X99_LOG_ERR,
-               "auth: unable to get last auth time for [%s]", username);
-       return FAIL_ERR;
-    }
-    if (x99_get_failcount(inst->syncdir, username, &failcount) != 0) {
-       x99_log(X99_LOG_ERR,
-               "auth: unable to get failure count for [%s]", username);
-       return FAIL_ERR;
-    }
-
-    /* Check against hardfail setting. */
-    if (inst->hardfail && failcount >= inst->hardfail) {
-       x99_log(X99_LOG_AUTH,
-               "auth: %d/%d failed/max authentications for [%s]",
-               failcount, inst->hardfail, username);
-       if (x99_incr_failcount(inst->syncdir, username) != 0) {
-           x99_log(X99_LOG_ERR,
-                   "auth: unable to increment failure count for "
-                   "locked out user [%s]", username);
-       }
-       return FAIL_HARD;
-    }
-
-    /* Check against softfail setting. */
-    if (inst->softfail && failcount >= inst->softfail) {
-       time_t when;
-       int fcount;
-
-       /*
-        * Determine the next time this user can authenticate.
-        *
-        * Once we hit softfail, we introduce a 1m delay before the user
-        * can authenticate.  For each successive failed authentication,
-        * we double the delay time, up to a max of 32 minutes.  While in
-        * the "delay mode" of operation, all authentication ATTEMPTS are
-        * considered failures (we don't test if the password is correct).
-        * Also, each attempt during the delay period restarts the clock.
-        *
-        * The advantage of a delay instead of a simple lockout is that an
-        * attacker can't lock out a user as easily; the user need only wait
-        * a bit before he can authenticate.
-        */
-       fcount = failcount - inst->softfail;
-       when = last_auth + (fcount > 5 ? 32 * 60 : (1 << fcount) * 60);
-       if (time(NULL) < when) {
-           x99_log(X99_LOG_AUTH,
-                   "auth: user [%s] auth too soon while delayed, "
-                   "%d/%d failed/softfail authentications",
-                   username, failcount, inst->softfail);
-           if (x99_incr_failcount(inst->syncdir, username) != 0) {
-               x99_log(X99_LOG_ERR,
-                       "auth: unable to increment failure count for "
-                       "delayed user [%s]", username);
-           }
-           return FAIL_SOFT;
-       }
-    }
-
-    return 0;
-}
-
-/*
- * Get the last auth window position for ewindow2.
- * Returns 0 on failure (caller cannot distinguish between failure and
- * a 0 position).
- */
-unsigned
-x99_get_last_auth_pos(const char *syncdir, const char *username)
-{
-    int rc;
-    char *lock;
-    char challenge[MAX_CHALLENGE_LEN + 1];
-    unsigned pos;
-
-    if ((lock = x99_acquire_sd_lock(syncdir, username)) == NULL)
-       return -1;
-
-    rc = x99_get_sd(syncdir, username, challenge, NULL, NULL, &pos);
-
-    x99_release_sd_lock(lock);
-    return rc ? 0 : pos;
-}
-
-/*
- * Record the last auth window position (for ewindow2).
- */
-int
-x99_set_last_auth_pos(const char *syncdir, const char *username, unsigned pos)
-{
-    int rc;
-    char *lock;
-    char challenge[MAX_CHALLENGE_LEN + 1];
-    int failcount;
-    time_t last_auth;
-
-    if ((lock = x99_acquire_sd_lock(syncdir, username)) == NULL)
-       return -1;
-
-    rc = x99_get_sd(syncdir, username, challenge, &failcount, &last_auth, NULL);
-    if (rc == 0)
-       rc = x99_set_sd(syncdir, username, challenge, failcount, last_auth,
-                       pos);
-
-    x99_release_sd_lock(lock);
-    return rc;
-}
-
-
-/*
- * Return the failed login count for a user.
- * Returns 0 on success, non-zero otherwise.
- */
-static int
-x99_get_failcount(const char *syncdir, const char *username, int *failcount)
-{
-    int rc;
-    char *lock;
-
-    if ((lock = x99_acquire_sd_lock(syncdir, username)) == NULL)
-       return -1;
-    rc = x99_get_sd(syncdir, username, NULL, failcount, NULL, NULL);
-    x99_release_sd_lock(lock);
-    return rc;
-}
-
-
-/*
- * Sync data is kept in a flat file[s], only because it's easy to implement.
- * It might be worth looking at Berkeley DB, but the flat file implementation
- * gives maximal concurrency with minimal complexity.  Performance will be
- * better on filesystems like ext2fs, ffs w/ soft updates, etc, due to
- * the large number of ephemeral dot-files created/destroyed for locking.
- *
- * One file per user is created, and we typically expect that each thread
- * is handling a different user (even if a user is authenticating to
- * multiple NASs/ports, he can't really authenticate simultaneously to
- * each -- unless it's an attack), so this should give us maximal
- * concurrency.
- *
- * The file format is 'version:user:challenge:key:failures:last_auth:'.
- * Version is there to provide easy forward compatibility.  The trailing
- * colon is there for the same reason.  Future versions must add data to
- * the end.  The current version is 1.
- *
- * For performance enhancements, it might be more worthwhile to look at
- * caching the inst->pwdfile data.  Users who are disabled should also
- * be cached somehow, to reduce the impact of possible attacks.
- */
-
-
-/*
- * x99_acquire_sd_lock() returns NULL on failure, or a char *
- * which must be passed to x99_release_sd_lock() later.
- */
-static char *
-x99_acquire_sd_lock(const char *syncdir, const char *username)
-{
-    char *lockfile;
-    int i, fd = -1;
-    struct stat st;
-
-    /* Verify permissions first. */
-    if (stat(syncdir, &st) != 0) {
-       x99_log(X99_LOG_ERR, "syncdir %s error: %s",
-               syncdir, strerror(errno));
-       return NULL;
-    }
-    if (st.st_mode != (S_IFDIR|S_IRUSR|S_IWUSR|S_IXUSR)) {
-       x99_log(X99_LOG_ERR,
-               "x99_acquire_sd_lock: syncdir %s has loose permissions",
-               syncdir);
-       return NULL;
-    }
-
-    /* We use dotfile locking. */
-    lockfile = malloc(strlen(syncdir) + strlen(username) + 3);
-    if (!lockfile) {
-       x99_log(X99_LOG_CRIT, "x99_acquire_sd_lock: out of memory");
-       return NULL;
-    }
-    (void) sprintf(lockfile, "%s/.%s", syncdir, username);
-
-    /*
-     * Try to obtain exclusive access.  10 should be *plenty* of
-     * iterations, we don't expect concurrent accesses to the same file,
-     * and any accesses should be very quick.  This is broken over NFS,
-     * but you shouldn't have this data on NFS anyway.
-     */
-    for (i = 0; i < 10; ++i) {
-       if ((fd = open(lockfile, O_CREAT|O_EXCL, S_IRUSR|S_IWUSR)) != -1) {
-           break;
-       }
-       /* break stale locks (older than 60s) */
-       if (stat(lockfile, &st) == 0)
-           if (st.st_ctime  < time(NULL) - 60)
-               (void) unlink(lockfile);
-
-       usleep(500000); /* 0.5 second */
-    }
-    if (fd == -1) {
-       x99_log(X99_LOG_ERR,
-               "x99_acquire_sd_lock: unable to acquire lock for [%s]",
-               username);
-       free(lockfile);
-       return NULL;
-    }
-
-    (void) close(fd);
-    return lockfile;
-}
-
-static void
-x99_release_sd_lock(char *lockfile)
-{
-    (void) unlink(lockfile);
-    free(lockfile);
-}
-
-
-/*
- * x99_get_sd() returns 0 on success, non-zero otherwise.
- * On successful returns, challenge, failures, last_auth, pos are filled in,
- * if non-NULL.
- * On unsuccessful returns, challenge, failures, last_auth, pos may be garbage.
- * challenge should be sized as indicated (if non-NULL).
- * The caller must have obtained an exclusive lock on the sync file.
- */
-static int
-x99_get_sd(const char *syncdir, const char *username,
-          char challenge[MAX_CHALLENGE_LEN + 1], int *failures,
-          time_t *last_auth, unsigned *pos)
-{
-    char syncfile[PATH_MAX + 1];
-    FILE *fp;
-
-    char syncdata[BUFSIZ];
-    char *p, *q;
-    unsigned ver = UINT_MAX;
-
-    (void) snprintf(syncfile, PATH_MAX, "%s/%s",  syncdir, username);
-    syncfile[PATH_MAX] = '\0';
-
-    /* Open sync file. */
-    if ((fp = fopen(syncfile, "r")) == NULL) {
-       if (errno != ENOENT) {
-           x99_log(X99_LOG_ERR, "x99_get_sd: unable to open sync file %s: %s",
-                   syncfile, strerror(errno));
-           return -1;
-       }
-       /*
-        * Sync file did not exist.  If we can create it, all is well.
-        * Set the challenge to something "impossible".
-        */
-       if (failures)
-           *failures = 0;
-       return x99_set_sd(syncdir, username, "NEWSTATE", 0, 0, 0);
-    }
-
-    /* Read sync data. */
-    if ((fgets(syncdata, sizeof(syncdata), fp) == NULL) || !strlen(syncdata)) {
-       x99_log(X99_LOG_ERR, "x99_get_sd: unable to read sync data from %s: %s",
-               syncfile, strerror(errno));
-       (void) fclose(fp);
-       return -1;
-    }
-    (void) fclose(fp);
-    p = syncdata;
-
-    /* Now, parse the sync data. */
-    /* Get the version. */
-    if ((q = strchr(p, ':')) == NULL) {
-       x99_log(X99_LOG_ERR,
-               "x99_get_sd: invalid sync data for user %s", username);
-       return -1;
-    }
-    *q++ = '\0';
-    if ((sscanf(p, "%u", &ver) != 1) || (ver > 2)) {
-       x99_log(X99_LOG_ERR,
-               "x99_get_sd: invalid sync data (version) for user %s",
-               username);
-       return -1;
-    }
-    p = q;
-
-    /* Sanity check the username. */
-    if ((q = strchr(p, ':')) == NULL) {
-       x99_log(X99_LOG_ERR,
-               "x99_get_sd: invalid sync data (username) for user %s",
-               username);
-       return -1;
-    }
-    *q++ = '\0';
-    if (strcmp(p, username)) {
-       x99_log(X99_LOG_ERR,
-               "x99_get_sd: invalid sync data (user mismatch) for user %s",
-               username);
-       return -1;
-    }
-    p = q;
-
-    /* Get challenge. */
-    if ((q = strchr(p, ':')) == NULL) {
-       x99_log(X99_LOG_ERR,
-               "x99_get_sd: invalid sync data (challenge) for user %s",
-               username);
-       return -1;
-    }
-    *q++ = '\0';
-    if (strlen(p) > MAX_CHALLENGE_LEN) {
-       x99_log(X99_LOG_ERR,
-               "x99_get_sd: invalid sync data (challenge length) for user %s",
-               username);
-       return -1;
-    }
-    if (challenge)
-       strcpy(challenge, p);
-    p = q;
-
-    /* Eat key. */
-    if ((p = strchr(p, ':')) == NULL) {
-       x99_log(X99_LOG_ERR,
-               "x99_get_sd: invalid sync data (key) for user %s", username);
-       return -1;
-    }
-    p++;
-
-    /* Get failures. */
-    if ((q = strchr(p, ':')) == NULL) {
-       x99_log(X99_LOG_ERR,
-               "x99_get_sd: invalid sync data (failures) for user %s",
-               username);
-       return -1;
-    }
-    *q++ = '\0';
-    if (failures && (sscanf(p, "%d", failures) != 1)) {
-       x99_log(X99_LOG_ERR,
-               "x99_get_sd: invalid sync data (failures) for user %s",
-               username);
-       return -1;
-    }
-    p = q;
-
-    /* Get last_auth. */
-    if ((q = strchr(p, ':')) == NULL) {
-       x99_log(X99_LOG_ERR,
-               "x99_get_sd: invalid sync data (last_auth) for user %s",
-               username);
-       return -1;
-    }
-    *q++ = '\0';
-    if (last_auth && (sscanf(p, "%ld", last_auth) != 1)) {
-       x99_log(X99_LOG_ERR,
-               "x99_get_sd: invalid sync data (last_auth) for user %s",
-               username);
-       return -1;
-    }
-    p = q;
-
-    /* Get last auth position. */
-    if (pos) {
-       if (ver == 1) {
-           *pos = 0;
-       } else if (sscanf(p, "%u", pos) != 1) {
-           x99_log(X99_LOG_ERR,
-                   "x99_get_sd: invalid sync data (win. pos) for user %s",
-                   username);
-           return -1;
-       }
-    }
-    return 0;
-}
-
-
-/*
- * See x99_get_sd().
- * The caller must have obtained an exclusive lock on the sync file.
- */
-static int
-x99_set_sd(const char *syncdir, const char *username, const char *challenge,
-          int failures, time_t last_auth, unsigned pos)
-{
-    char syncfile[PATH_MAX + 1];
-    FILE *fp;
-
-    (void) snprintf(syncfile, PATH_MAX, "%s/%s",  syncdir, username);
-    syncfile[PATH_MAX] = '\0';
-
-    if ((fp = fopen(syncfile, "w")) == NULL) {
-       x99_log(X99_LOG_ERR, "x99_set_sd: unable to open sync file %s: %s",
-               syncfile, strerror(errno));
-       return -1;
-    }
-
-    /* Write our (version 2) sync data. */
-    (void) fprintf(fp, "2:%s:%s:%s:%d:%ld:%u:\n", username, challenge, "",
-                  failures, last_auth, pos);
-    if (fclose(fp) != 0) {
-       x99_log(X99_LOG_ERR, "x99_set_sd: unable to write sync file %s: %s",
-               syncfile, strerror(errno));
-       return -1;
-    }
-
-    return 0;
-}
-
diff --git a/src/modules/rlm_x99_token/x99_util.c b/src/modules/rlm_x99_token/x99_util.c
deleted file mode 100644 (file)
index 7101a08..0000000
+++ /dev/null
@@ -1,302 +0,0 @@
-/*
- * x99_util.c
- * $Id$
- *
- *   This program is free software; you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License as published by
- *   the Free Software Foundation; either version 2 of the License, or
- *   (at your option) any later version.
- *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
- *
- *   You should have received a copy of the GNU General Public License
- *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- *
- * Copyright 2001,2002  Google, Inc.
- */
-
-#ifdef FREERADIUS
-#include "radiusd.h"
-#endif
-#include "x99.h"
-
-#include <errno.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <openssl/des.h> /* des_cblock */
-
-
-static const char rcsid[] = "$Id$";
-
-
-/* Card name to int mappings */
-static struct {
-    const char *name;
-    uint32_t id;
-} card[] = {
-    { "x9.9",             CRYPTOCARD_H8_RC },
-    { "generic",          CRYPTOCARD_H8_RC },
-
-    { "cryptocard-h8-rc", CRYPTOCARD_H8_RC },
-    { "cryptocard-d8-rc", CRYPTOCARD_D8_RC },
-    { "cryptocard-h7-rc", CRYPTOCARD_H7_RC },
-    { "cryptocard-d7-rc", CRYPTOCARD_D7_RC },
-    { "cryptocard-h8-es", CRYPTOCARD_H8_ES },
-    { "cryptocard-d8-es", CRYPTOCARD_D8_ES },
-    { "cryptocard-h7-es", CRYPTOCARD_H7_ES },
-    { "cryptocard-d7-es", CRYPTOCARD_D7_ES },
-    { "cryptocard-h8-rs", CRYPTOCARD_H8_RS },
-    { "cryptocard-d8-rs", CRYPTOCARD_D8_RS },
-    { "cryptocard-h7-rs", CRYPTOCARD_H7_RS },
-    { "cryptocard-d7-rs", CRYPTOCARD_D7_RS },
-
-    { NULL, 0 }                                /* end of list */
-};
-
-
-/*
- * Return a random challenge.
- * fd must be either -1 or an open fd to the random device.
- * challenge is filled in on successful return (must be size len+1).
- * Returns 0 on success, -1 on failure.
- */
-int
-x99_get_challenge(int fd, char *challenge, int len)
-{
-    unsigned char rawchallenge[MAX_CHALLENGE_LEN];
-    int i;
-
-    if (fd == -1) {
-       if ((fd = open(DEVURANDOM, O_RDONLY)) == -1) {
-           x99_log(X99_LOG_ERR, "error opening %s: %s", DEVURANDOM,
-                   strerror(errno));
-           return -1;
-       }
-    }
-
-    if (x99_get_random(fd, rawchallenge, len) == -1) {
-       x99_log(X99_LOG_ERR, "failed to obtain random data");
-       return -1;
-    }
-    /* Convert the raw bytes to a decimal string. */
-    for (i = 0; i < len; ++i)
-       challenge[i] = '0' + rawchallenge[i] % 10;
-    challenge[i] = '\0';
-
-    return 0;
-}
-
-/*
- * Return some number of random bytes.
- * rnd_data must be allocated by the caller.
- * Returns 0 on success, -1 on failure, rnd_data is filled in.
- */
-int
-x99_get_random(int fd, unsigned char *rnd_data, int req_bytes)
-{
-    int bytes_read = 0;
-
-    while (bytes_read < req_bytes) {
-       int n;
-
-       n = read(fd, rnd_data + bytes_read, req_bytes - bytes_read);
-       if (n <= 0) {
-           x99_log(X99_LOG_ERR, "x99_get_random: error reading from %s: %s",
-                   DEVURANDOM, strerror(errno));
-           return -1;
-       }
-       bytes_read += n;
-    }
-
-    return 0;
-}
-
-
-/*
- * Convert the ASCII string representation of a DES key to raw octets.
- * keyblock is filled in.  Returns 0 on success, -1 otherwise.
- */
-int
-x99_string_to_keyblock(const char *s, des_cblock keyblock)
-{
-    int i;
-
-    if (s == NULL || strlen(s) < 16)
-       return -1;
-
-    /*
-     * We could just use sscanf, but we do this a lot, and have very
-     * specific needs, and it's easy to implement, so let's go for it!
-     */
-    for (i = 0; i < 8; ++i) {
-       unsigned int n[2];
-
-       n[0] = *s++ - '0';
-       n[1] = *s++ - '0';
-       if (n[0] > 9) {
-           n[0] -= 'a' - '9' - 1;
-       }
-       if (n[1] > 9) {
-           n[1] -= 'a' - '9' - 1;
-       }
-
-       keyblock[i]  = n[0] << 4;
-       keyblock[i] += n[1];
-    }
-    return 0;
-}
-
-
-/* Character maps for generic hex and vendor specific decimal modes */
-const char x99_hex_conversion[]         = "0123456789abcdef";
-const char x99_cc_dec_conversion[]      = "0123456789012345";
-const char x99_snk_dec_conversion[]     = "0123456789222333";
-const char x99_sc_friendly_conversion[] = "0123456789ahcpef";
-
-/*
- * Convert a DES keyblock to an ASCII string.
- * Fills in s, which must point to at least 17 bytes of space.
- * Note that each octet expands into 2 hex digits in ASCII (0xAA -> 0x4141);
- * add a NULL string terminator and you get the 17 byte requirement.
- */
-void
-x99_keyblock_to_string(char *s, const des_cblock keyblock,
-                      const char conversion[17])
-{
-    int i;
-
-    for (i = 0; i < 8; ++i) {
-       unsigned n[2];
-
-       n[0] = (keyblock[i] >> 4) & 0x0f;
-       n[1] = keyblock[i] & 0x0f;
-       s[2 * i + 0] = conversion[n[0]];
-       s[2 * i + 1] = conversion[n[1]];
-    }
-    s[16] = '\0';
-}
-
-
-/*
- * fillin user_info from our database (key file)
- * returns 0 on success, -1 for user not found, -2 for other errors.
- */
-int
-x99_get_user_info(const char *pwdfile, const char *username,
-                 x99_user_info_t *user_info)
-{
-    FILE *fp;
-    char s[80];
-    char *p, *q;
-    int found, i;
-    struct stat st;
-
-    /* Verify permissions first. */
-    if (stat(pwdfile, &st) != 0) {
-       x99_log(X99_LOG_ERR, "x99_get_user_info: pwdfile %s error: %s",
-               pwdfile, strerror(errno));
-       return -2;
-    }
-    if ((st.st_mode & (S_IXUSR|S_IRWXG|S_IRWXO)) != 0) {
-       x99_log(X99_LOG_ERR,
-               "x99_get_user_info: pwdfile %s has loose permissions", pwdfile);
-       return -2;
-    }
-
-    if ((fp = fopen(pwdfile, "r")) == NULL) {
-       x99_log(X99_LOG_ERR, "x99_get_user_info: error opening %s: %s",
-               pwdfile, strerror(errno));
-       return -2;
-    }
-
-    /*
-     * Find the requested user.
-     * Add a ':' to the username to make sure we don't match shortest prefix.
-     */
-    p = malloc(strlen(username) + 2);
-    if (!p) {
-       x99_log(X99_LOG_CRIT, "x99_get_user_info: out of memory");
-       return -2;
-    }
-    (void) sprintf(p, "%s:", username);
-    found = 0;
-    while (!feof(fp)) {
-       if (fgets(s, sizeof(s), fp) == NULL) {
-           if (!feof(fp)) {
-               x99_log(X99_LOG_ERR,
-                       "x99_get_user_info: error reading from %s: %s",
-                       pwdfile, strerror(errno));
-               (void) fclose(fp);
-               free(p);
-               return -2;
-           }
-       } else if (!strncmp(s, p, strlen(p))) {
-           found = 1;
-           break;
-       }
-    }
-    (void) fclose(fp);
-    free(p);
-    if (!found) {
-#if 0
-       /* Noisy ... let the caller report this. */
-       x99_log(X99_LOG_AUTH, "x99_get_user_info: [%s] not found in %s",
-               username, pwdfile);
-#endif
-       return -1;
-    }
-
-    /* Found him, skip to next field (card). */
-    if ((p = strchr(s, ':')) == NULL) {
-       x99_log(X99_LOG_ERR,
-               "x99_get_user_info: invalid format for [%s] in %s",
-               username, pwdfile);
-       return -2;
-    }
-    p++;
-    /* strtok() */
-    if ((q = strchr(p, ':')) == NULL) {
-       x99_log(X99_LOG_ERR,
-               "x99_get_user_info: invalid format for [%s] in %s",
-               username, pwdfile);
-       return -2;
-    }
-    *q++ = '\0';
-    /* p: card_type, q: key */
-
-    /* Match against card types. */
-    found = 0;
-    for (i = 0; card[i].name; ++i) {
-       if (!strcasecmp(p, card[i].name)) {
-           found = 1;
-           user_info->card_id = card[i].id;
-           break;
-       }
-    }
-    if (!found) {
-       x99_log(X99_LOG_ERR,
-               "x99_get_user_info: unknown card %s for [%s] in %s",
-               p, username, pwdfile);
-       return -2;
-    }
-
-    if (!(strlen(q) == 16 || (strlen(q) == 17 && q[16] == '\n'))) {
-       /* 8 octets + possible trailing newline */
-       x99_log(X99_LOG_ERR, "x99_get_user_info: invalid key for [%s] in %s",
-               username, pwdfile);
-       return -2;
-    }
-
-    /* Convert the key from ASCII to a keyblock. (+translate error code) */
-    return x99_string_to_keyblock(q, user_info->keyblock) * -2;
-}
-
index 1395429..6df1921 100644 (file)
@@ -17,6 +17,7 @@ rlm_krb5
 rlm_ldap
 rlm_mschap
 rlm_ns_mta_md5
+rlm_otp
 rlm_pam
 rlm_pap
 rlm_passwd
@@ -25,5 +26,4 @@ rlm_radutmp
 rlm_realm
 rlm_sql
 rlm_unix
-rlm_x99_token
 rlm_checkval