All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
rlm_securid.c
Go to the documentation of this file.
1 /*
2  * This program is is free software; you can redistribute it and/or modify
3  * it under the terms of the GNU General Public License as published by
4  * the Free Software Foundation; either version 2 of the License, or (at
5  * your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software
14  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15  */
16 
17 /**
18  * $Id: a99d1f9645fe4576ec5ba1e5c02e791c04ba5b64 $
19  * @file rlm_securid.c
20  * @brief Supports auth against SecurID servers using OTP h/w tokens.
21  *
22  * Supports "next-token code" and "new-pin" modes.
23  *
24  * @copyright 2012 The FreeRADIUS server project
25  * @copyright 2012 Alan DeKok <aland@networkradius.com>
26  */
27 #include <freeradius-devel/radiusd.h>
28 #include <freeradius-devel/modules.h>
29 #include <ctype.h>
30 
31 #include "rlm_securid.h"
32 
33 typedef enum {
40 
41 
42 static const CONF_PARSER module_config[] = {
43  { FR_CONF_OFFSET("timer_expire", PW_TYPE_INTEGER, rlm_securid_t, timer_limit), .dflt = "600" },
44  { FR_CONF_OFFSET("max_sessions", PW_TYPE_INTEGER, rlm_securid_t, max_sessions), .dflt = "2048" },
45  { FR_CONF_OFFSET("max_trips_per_session", PW_TYPE_INTEGER, rlm_securid_t, max_trips_per_session) },
46  { FR_CONF_OFFSET("max_round_trips", PW_TYPE_INTEGER, rlm_securid_t, max_trips_per_session), .dflt = "6" },
48 };
49 
50 
51 static SD_CHAR empty_pin[] = "";
52 
53 /* comparison function to find session in the tree */
54 static int securid_session_cmp(void const *a, void const *b)
55 {
56  int rcode;
57  SECURID_SESSION const *one = a;
58  SECURID_SESSION const *two = b;
59 
60  rad_assert(one != NULL);
61  rad_assert(two != NULL);
62 
63  rcode = fr_ipaddr_cmp(&one->src_ipaddr, &two->src_ipaddr);
64  if (rcode != 0) return rcode;
65 
66  return memcmp(one->state, two->state, sizeof(one->state));
67 }
68 
69 
70 static SECURID_AUTH_RC securidAuth(void *instance, REQUEST *request,
71  char const *username,
72  char const *passcode,
73  char *replyMsgBuffer, size_t replyMsgBufferSize)
74 {
75  rlm_securid_t *inst = (rlm_securid_t *) instance;
76  int acm_ret;
77  SD_PIN pin_params;
78  char new_pin[10];
79  char format[30];
80  SECURID_SESSION *securid_session = NULL;
81  int rc = -1;
82 
83  SD_CHAR *securid_user, *securid_pass;
84 
85  if (!username) {
86  ERROR("SecurID username is NULL");
88  }
89 
90  if (!passcode) {
91  ERROR("SecurID passcode is NULL for %s user", username);
93  }
94 
95  memcpy(&securid_user, &username, sizeof(securid_user));
96  memcpy(&securid_pass, &passcode, sizeof(securid_pass));
97 
98  *replyMsgBuffer = '\0';
99 
100  securid_session = securid_sessionlist_find(inst, request);
101  if (!securid_session) {
102  /* securid session not found */
103  SDI_HANDLE sdiHandle = SDI_HANDLE_NONE;
104 
105  acm_ret = SD_Init(&sdiHandle);
106  if (acm_ret != ACM_OK) {
107  ERROR("Cannot communicate with the ACE/Server");
108  return -1;
109  }
110 
111  acm_ret = SD_Lock(sdiHandle, securid_user);
112  if (acm_ret != ACM_OK) {
113  ERROR("SecurID: Access denied. Name [%s] lock failed", username);
114  return -2;
115  }
116 
117  acm_ret = SD_Check(sdiHandle, securid_pass, securid_user);
118  switch (acm_ret) {
119  case ACM_OK:
120  /* we are in now */
121  RDEBUG("SecurID authentication successful for %s", username);
122  SD_Close(sdiHandle);
123 
125 
126  case ACM_ACCESS_DENIED:
127  /* not this time */
128  RDEBUG("SecurID Access denied for %s", username);
129  SD_Close(sdiHandle);
131 
132  case ACM_INVALID_SERVER:
133  ERROR("SecurID: Invalid ACE server");
135 
136  case ACM_NEW_PIN_REQUIRED:
137  RDEBUG2("SecurID new pin required for %s", username);
138 
139  /* create a new session */
140  securid_session = securid_session_alloc();
141  securid_session->sdiHandle = sdiHandle; /* save ACE handle for future use */
142  securid_session->securidSessionState = NEW_PIN_REQUIRED_STATE;
143  securid_session->identity = strdup(username);
144 
145  /* Get PIN requirements */
146  acm_ret = AceGetPinParams(sdiHandle, &pin_params);
147 
148  /* If a system-generated PIN is required */
149  if (pin_params.Selectable == CANNOT_CHOOSE_PIN) {
150  /* Prompt user to accept a system generated PIN */
151  snprintf(replyMsgBuffer, replyMsgBufferSize,
152  "\r\nAre you prepared to accept a new system-generated PIN [y/n]?");
154 
155  } else if (pin_params.Selectable == USER_SELECTABLE) { //may be returned by AM 6.x servers.
156  snprintf(replyMsgBuffer, replyMsgBufferSize,
157  "\r\nPress 'y' to generate a new PIN\r\nOR\r\n'n'to enter a new PIN yourself [y/n]");
159 
160  } else {
161  if (pin_params.Alphanumeric) {
162  strcpy(format, "alphanumeric characters");
163  } else {
164  strcpy(format, "digits");
165  }
166  snprintf(replyMsgBuffer, replyMsgBufferSize,
167  " \r\n Enter your new PIN of %d to %d %s, \r\n or\r\n <Ctrl-D> to cancel the New PIN procedure:",
168  pin_params.Min, pin_params.Max, format);
169  }
170 
171  /* insert new session in the session list */
172  securid_sessionlist_add(inst, request, securid_session);
173 
175 
176  case ACM_NEXT_CODE_REQUIRED:
177  RDEBUG2("Next securid token code required for %s",
178  username);
179 
180  /* create a new session */
181  securid_session = securid_session_alloc();
182  securid_session->sdiHandle = sdiHandle;
184  securid_session->identity = strdup(username);
185 
186  /* insert new session in the session list */
187  securid_sessionlist_add(inst, request, securid_session);
188 
189  strlcpy(replyMsgBuffer, "\r\nPlease Enter the Next Code from Your Token:", replyMsgBufferSize);
191 
192  default:
193  ERROR("SecurID: Unexpected error from ACE/Agent API acm_ret=%d", acm_ret);
194  securid_session_free(inst, request, securid_session);
196 
197 
198  }
199  } else {
200  /* existing session found */
201  RDEBUG("Continuing previous session found for user [%s]", username);
202 
203  /* continue previous session */
204  switch (securid_session->securidSessionState) {
206  DEBUG2("Securid NEXT_CODE_REQUIRED_STATE: User [%s]", username);
207  /* next token code mode */
208 
209  acm_ret = SD_Next(securid_session->sdiHandle, securid_pass);
210  if (acm_ret == ACM_OK) {
211  INFO("Next SecurID token accepted for [%s].", securid_session->identity);
213 
214  } else {
215  INFO("SecurID: Next token rejected for [%s].", securid_session->identity);
217  }
218 
219  /* deallocate session */
220  securid_session_free(inst, request, securid_session);
221  return rc;
222 
224  RDEBUG2("SecurID NEW_PIN_REQUIRED_STATE for %s",
225  username);
226 
227  /* save the previous pin */
228  if (securid_session->pin) {
229  free(securid_session->pin);
230  securid_session->pin = NULL;
231  }
232  securid_session->pin = strdup(passcode);
233 
234  strlcpy(replyMsgBuffer, "\r\n Please re-enter new PIN:", replyMsgBufferSize);
235 
236  /* set next state */
238 
239  /* insert the updated session in the session list */
240  securid_sessionlist_add(inst, request, securid_session);
242 
244  RDEBUG2("SecurID NEW_PIN_USER_CONFIRM_STATE: User [%s]", username);
245  /* compare previous pin and current pin */
246  if (!securid_session->pin || strcmp(securid_session->pin, passcode)) {
247  RDEBUG2("Pin confirmation failed. Pins do not match [%s] and [%s]",
248  SAFE_STR(securid_session->pin), securid_pass);
249  /* pins do not match */
250 
251  /* challenge the user again */
252  AceGetPinParams(securid_session->sdiHandle, &pin_params);
253  if (pin_params.Alphanumeric) {
254  strcpy(format, "alphanumeric characters");
255  } else {
256  strcpy(format, "digits");
257  }
258  snprintf(replyMsgBuffer, replyMsgBufferSize,
259  " \r\n Pins do not match--Please try again.\r\n Enter your new PIN of %d to %d %s, \r\n or\r\n <Ctrl-D> to cancel the New PIN procedure:",
260  pin_params.Min, pin_params.Max, format);
261 
262  securid_session->securidSessionState = NEW_PIN_REQUIRED_STATE;
263 
264  /* insert the updated session in the session list */
265  securid_sessionlist_add(inst, request, securid_session);
267 
268  } else {
269  /* pins match */
270  RDEBUG2("Pin confirmation succeeded. Pins match");
271  acm_ret = SD_Pin(securid_session->sdiHandle, securid_pass);
272  if (acm_ret == ACM_NEW_PIN_ACCEPTED) {
273  RDEBUG("New SecurID pin accepted for %s.", securid_session->identity);
274 
276 
277  /* insert the updated session in the session list */
278  securid_sessionlist_add(inst, request, securid_session);
279 
281  strlcpy(replyMsgBuffer, " \r\n\r\nWait for the code on your card to change, then enter new PIN and TokenCode\r\n\r\nEnter PASSCODE:", replyMsgBufferSize);
282  } else {
283  RDEBUG("SecurID: New SecurID pin rejected for %s.", securid_session->identity);
284  SD_Pin(securid_session->sdiHandle, &empty_pin[0]); /* cancel PIN */
285 
286 
288 
289  /* deallocate session */
290  securid_session_free(inst, request, securid_session);
291  }
292  }
293  return rc;
295  acm_ret = SD_Check(securid_session->sdiHandle, securid_pass, securid_user);
296  if (acm_ret == ACM_OK) {
297  RDEBUG("New SecurID passcode accepted for %s.",
298  securid_session->identity);
300 
301  } else {
302  INFO("SecurID: New passcode rejected for [%s].", securid_session->identity);
304  }
305 
306  /* deallocate session */
307  securid_session_free(inst, request, securid_session);
308 
309  return rc;
311  if (!strcmp(passcode, "y")) {
312  AceGetSystemPin(securid_session->sdiHandle, new_pin);
313 
314  /* Save the PIN for the next session
315  * continuation */
316  if (securid_session->pin) {
317  free(securid_session->pin);
318  securid_session->pin = NULL;
319  }
320  securid_session->pin = strdup(new_pin);
321 
322  snprintf(replyMsgBuffer, replyMsgBufferSize,
323  "\r\nYour new PIN is: %s\r\nDo you accept this [y/n]?",
324  new_pin);
326 
327  /* insert the updated session in the
328  * session list */
329  securid_sessionlist_add(inst, request, securid_session);
330 
332 
333  } else {
334  SD_Pin(securid_session->sdiHandle, &empty_pin[0]); //Cancel new PIN
335 
336  /* deallocate session */
337  securid_session_free(inst, request,
338  securid_session);
339 
341  }
342 
343  return rc;
344 
346  acm_ret = SD_Pin(securid_session->sdiHandle, (SD_CHAR*)securid_session->pin);
347  if (acm_ret == ACM_NEW_PIN_ACCEPTED) {
348  strlcpy(replyMsgBuffer, " \r\n\r\nPin Accepted. Wait for the code on your card to change, then enter new PIN and TokenCode\r\n\r\nEnter PASSCODE:", replyMsgBufferSize);
350  /* insert the updated session in the session list */
351  securid_sessionlist_add(inst, request, securid_session);
353 
354  } else {
355  SD_Pin(securid_session->sdiHandle, &empty_pin[0]); //Cancel new PIN
356  strlcpy(replyMsgBuffer, " \r\n\r\nPin Rejected. Wait for the code on your card to change, then try again.\r\n\r\nEnter PASSCODE:", replyMsgBufferSize);
357  /* deallocate session */
358  securid_session_free(inst, request,
359  securid_session);
361  }
362 
363  return rc;
364 
365  /* USER_SELECTABLE state should be implemented to preserve compatibility with AM 6.x servers, which can return this state */
367  if (!strcmp(passcode, "y")) {
368  /* User has opted for a system-generated PIN */
369  AceGetSystemPin(securid_session->sdiHandle, new_pin);
370  snprintf(replyMsgBuffer, replyMsgBufferSize,
371  "\r\nYour new PIN is: %s\r\nDo you accept this [y/n]?",
372  new_pin);
374 
375  /* insert the updated session in the session list */
376  securid_sessionlist_add(inst, request,
377  securid_session);
379 
380  } else {
381  /* User has opted for a user-defined PIN */
382  AceGetPinParams(securid_session->sdiHandle,
383  &pin_params);
384  if (pin_params.Alphanumeric) {
385  strcpy(format, "alphanumeric characters");
386  } else {
387  strcpy(format, "digits");
388  }
389 
390  snprintf(replyMsgBuffer, replyMsgBufferSize,
391  " \r\n Enter your new PIN of %d to %d %s, \r\n or\r\n <Ctrl-D> to cancel the New PIN procedure:",
392  pin_params.Min, pin_params.Max, format);
393  securid_session->securidSessionState = NEW_PIN_REQUIRED_STATE;
394 
395  /* insert the updated session in the session list */
396  securid_sessionlist_add(inst, request,
397  securid_session);
399  }
400 
401  return rc;
402 
403  default:
404  ERROR("rlm_securid: Invalid session state %d for user [%s]",
405  securid_session->securidSessionState,
406  username);
407  break;
408  }
409  }
410 
411  return 0;
412 
413 }
414 
415 /******************************************/
416 static int mod_detach(void *instance)
417 {
418  rlm_securid_t *inst = (rlm_securid_t *) instance;
419 
420  /* delete session tree */
421  if (inst->session_tree) {
422  rbtree_free(inst->session_tree);
423  inst->session_tree = NULL;
424  }
425 
427 
428  return 0;
429 }
430 
431 
432 static int mod_instantiate(UNUSED CONF_SECTION *conf, void *instance)
433 {
434  rlm_securid_t *inst = instance;
435 
436  /*
437  * Lookup sessions in the tree. We don't free them in
438  * the tree, as that's taken care of elsewhere...
439  */
440  inst->session_tree = rbtree_create(NULL, securid_session_cmp, NULL, 0);
441  if (!inst->session_tree) {
442  ERROR("rlm_securid: Cannot initialize session tree");
443  return -1;
444  }
445 
446  pthread_mutex_init(&(inst->session_mutex), NULL);
447  return 0;
448 }
449 
450 
451 /*
452  * Authenticate the user via one of any well-known password.
453  */
454 static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request)
455 {
456  int rcode;
457  rlm_securid_t *inst = instance;
458  char buffer[MAX_STRING_LEN]="";
459  char const *username=NULL, *password=NULL;
460  VALUE_PAIR *vp;
461 
462  /*
463  * We can only authenticate user requests which HAVE
464  * a User-Name attribute.
465  */
466  if (!request->username) {
467  AUTH("rlm_securid: Attribute \"User-Name\" is required for authentication");
468  return RLM_MODULE_INVALID;
469  }
470 
471  if (!request->password) {
472  RAUTH("Attribute \"Password\" is required for authentication");
473  return RLM_MODULE_INVALID;
474  }
475 
476  /*
477  * Clear-text passwords are the only ones we support.
478  */
479  if (request->password->da->attr != PW_USER_PASSWORD) {
480  RAUTH("Attribute \"User-Password\" is required for authentication. Cannot use \"%s\".", request->password->da->name);
481  return RLM_MODULE_INVALID;
482  }
483 
484  /*
485  * The user MUST supply a non-zero-length password.
486  */
487  if (request->password->vp_length == 0) {
488  REDEBUG("Password should not be empty");
489  return RLM_MODULE_INVALID;
490  }
491 
492  /*
493  * shortcuts
494  */
495  username = request->username->vp_strvalue;
496  password = request->password->vp_strvalue;
497 
498  if (RDEBUG_ENABLED3) {
499  RDEBUG3("Login attempt with password \"%s\"", password);
500  } else {
501  RDEBUG("Login attempt with password");
502  }
503 
504  rcode = securidAuth(inst, request, username, password,
505  buffer, sizeof(buffer));
506 
507  switch (rcode) {
509  rcode = RLM_MODULE_OK;
510  break;
511 
513  /* reply with Access-challenge message code (11) */
514 
515  /* Generate Prompt attribute */
516  vp = fr_pair_afrom_num(request->reply, 0, PW_PROMPT);
517 
518  rad_assert(vp != NULL);
519  vp->vp_integer = 0; /* no echo */
520  fr_pair_add(&request->reply->vps, vp);
521 
522  /* Mark the packet as a Acceess-Challenge Packet */
523  request->reply->code = PW_CODE_ACCESS_CHALLENGE;
524  RDEBUG("Sending Access-Challenge");
525  rcode = RLM_MODULE_HANDLED;
526  break;
527 
531  default:
532  rcode = RLM_MODULE_REJECT;
533  break;
534  }
535 
536  if (*buffer) pair_make_reply("Reply-Message", buffer, T_OP_EQ);
537 
538  return rcode;
539 }
540 
541 
542 /*
543  * The module name should be the only globally exported symbol.
544  * That is, everything else should be 'static'.
545  *
546  * If the module needs to temporarily modify it's instantiation
547  * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
548  * The server will then take care of ensuring that the module
549  * is single-threaded.
550  */
551 extern module_t rlm_securid;
552 module_t rlm_securid = {
554  .name = "securid",
555  .type = RLM_TYPE_HUP_SAFE,
556  .inst_size = sizeof(rlm_securid_t),
557  .config = module_config,
558  .instantiate = mod_instantiate,
559  .detach = mod_detach,
560  .methods = {
562  },
563 };
#define pthread_mutex_init(_x, _y)
Definition: rlm_eap.h:75
void rbtree_free(rbtree_t *tree)
Definition: rbtree.c:84
#define AUTH(fmt,...)
Definition: log.h:139
#define RDEBUG_ENABLED3
True if request debug level 1-3 messages are enabled.
Definition: log.h:239
RFC2865 - Access-Challenge.
Definition: radius.h:102
The module is OK, continue.
Definition: radiusd.h:91
Metadata exported by the module.
Definition: modules.h:134
int securid_sessionlist_add(rlm_securid_t *inst, REQUEST *request, SECURID_SESSION *session)
Definition: mem.c:93
VALUE_PAIR * fr_pair_afrom_num(TALLOC_CTX *ctx, unsigned int vendor, unsigned int attr)
Create a new valuepair.
Definition: pair.c:106
SECURID_SESSION * securid_sessionlist_find(rlm_securid_t *inst, REQUEST *request)
Definition: mem.c:181
SECURID_SESSION * securid_session_alloc(void)
Definition: mem.c:30
#define INFO(fmt,...)
Definition: log.h:143
rbtree_t * session_tree
Definition: rlm_securid.h:68
fr_ipaddr_t src_ipaddr
Definition: rlm_securid.h:46
#define UNUSED
Definition: libradius.h:134
#define RLM_MODULE_INIT
Definition: modules.h:86
#define CONF_PARSER_TERMINATOR
Definition: conffile.h:289
PUBLIC int snprintf(char *string, size_t length, char *format, va_alist)
Definition: snprintf.c:686
#define inst
Definition: token.h:46
struct rlm_securid_t rlm_securid_t
The module considers the request invalid.
Definition: radiusd.h:93
#define RLM_TYPE_HUP_SAFE
Will be restarted on HUP.
Definition: modules.h:79
static rlm_rcode_t mod_authenticate(void *instance, REQUEST *request) CC_HINT(nonnull)
Defines a CONF_PAIR to C data type mapping.
Definition: conffile.h:267
SECURID_SESSION_STATE securidSessionState
Definition: rlm_securid.h:42
#define rad_assert(expr)
Definition: rad_assert.h:38
SDI_HANDLE sdiHandle
Definition: rlm_securid.h:41
rbtree_t * rbtree_create(TALLOC_CTX *ctx, rb_comparator_t compare, rb_free_t node_free, int flags)
Create a new RED-BLACK tree.
Definition: rbtree.c:112
void fr_pair_add(VALUE_PAIR **head, VALUE_PAIR *vp)
Add a VP to the end of the list.
Definition: pair.c:659
#define DEBUG2(fmt,...)
Definition: log.h:176
SECURID_AUTH_RC
Definition: rlm_securid.c:33
Immediately reject the request.
Definition: radiusd.h:89
Stores an attribute, a value and various bits of other data.
Definition: pair.h:112
0 methods index for authenticate section.
Definition: modules.h:41
32 Bit unsigned integer.
Definition: radius.h:34
enum rlm_rcodes rlm_rcode_t
Return codes indicating the result of the module call.
static rs_t * conf
Definition: radsniff.c:46
#define SAFE_STR(s)
Definition: rlm_securid.h:10
#define RDEBUG2(fmt,...)
Definition: log.h:244
static SECURID_AUTH_RC securidAuth(void *instance, REQUEST *request, char const *username, char const *passcode, char *replyMsgBuffer, size_t replyMsgBufferSize)
Definition: rlm_securid.c:70
static SD_CHAR empty_pin[]
Definition: rlm_securid.c:51
uint64_t magic
Used to validate module struct.
Definition: modules.h:135
module_t rlm_securid
Definition: rlm_securid.c:552
#define FR_CONF_OFFSET(_n, _t, _s, _f)
Definition: conffile.h:168
#define pair_make_reply(_a, _b, _c)
Definition: radiusd.h:546
static int mod_detach(void *instance)
Definition: rlm_securid.c:416
static const CONF_PARSER module_config[]
Definition: rlm_securid.c:42
static rlm_rcode_t CC_HINT(nonnull)
Definition: rlm_securid.c:454
#define RAUTH(fmt,...)
Definition: log.h:202
#define REDEBUG(fmt,...)
Definition: log.h:254
static int mod_instantiate(UNUSED CONF_SECTION *conf, void *instance)
Definition: rlm_securid.c:432
size_t strlcpy(char *dst, char const *src, size_t siz)
Definition: strlcpy.c:38
#define MAX_STRING_LEN
Definition: libradius.h:120
#define pthread_mutex_destroy(_x)
Definition: rlm_eap.h:76
char state[SECURID_STATE_LEN]
Definition: rlm_securid.h:44
void securid_session_free(UNUSED rlm_securid_t *inst, REQUEST *request, SECURID_SESSION *session)
Definition: mem.c:42
The module handled the request, so stop.
Definition: radiusd.h:92
int fr_ipaddr_cmp(fr_ipaddr_t const *a, fr_ipaddr_t const *b)
Compare two ip addresses.
Definition: inet.c:1026
#define RDEBUG(fmt,...)
Definition: log.h:243
#define ERROR(fmt,...)
Definition: log.h:145
pthread_mutex_t session_mutex
Definition: rlm_securid.h:67
static int securid_session_cmp(void const *a, void const *b)
Definition: rlm_securid.c:54
#define RDEBUG3(fmt,...)
Definition: log.h:245