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
--- /dev/null
+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>.
+
+++ /dev/null
-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>.
-
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:
#
-# 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"
# 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
# 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
# (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
# 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}
--- /dev/null
+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
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
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
}
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.
# 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'
+++ /dev/null
-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.)
-
# 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
--- /dev/null
+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)
--- /dev/null
+/*
+ * 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");
+}
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+#! /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!
--- /dev/null
+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!
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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;
+}
/*
- * 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);
}
-
--- /dev/null
+/*
+ * 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;
+}
/*
- * 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 */
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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 */
+ },
+};
/*
- * x99_site.c
+ * otp_site.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 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;
+}
--- /dev/null
+/*
+ * 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 */
/*
- * 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 */
--- /dev/null
+/*
+ * 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;
+}
+
--- /dev/null
+/*
+ * 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;
+}
+++ /dev/null
-/*
- * 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);
-}
-
-
+++ /dev/null
-/*
- * 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 */
-
+++ /dev/null
-/*
- * 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;
-}
-
+++ /dev/null
-/*
- * 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;
-}
-
+++ /dev/null
-/*
- * 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 */
-
+++ /dev/null
-/*
- * 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;
-}
-
+++ /dev/null
-/*
- * 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;
-}
-
+++ /dev/null
-/*
- * 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;
-}
-
rlm_ldap
rlm_mschap
rlm_ns_mta_md5
+rlm_otp
rlm_pam
rlm_pap
rlm_passwd
rlm_realm
rlm_sql
rlm_unix
-rlm_x99_token
rlm_checkval