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, version 2 if the
4 * License as published by the Free Software Foundation.
6 * This program is distributed in the hope that it will be useful,
7 * but WITHOUT ANY WARRANTY; without even the implied warranty of
8 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 * GNU General Public License for more details.
11 * You should have received a copy of the GNU General Public License
12 * along with this program; if not, write to the Free Software
13 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 * @brief Supports OTP authentication using SMS.
21 * @copyright 2000,2006 The FreeRADIUS server project
22 * @copyright 2009 Siemens AG, Holger Wolff holger.wolff@siemens.com
26 #include <freeradius-devel/radiusd.h>
27 #include <freeradius-devel/modules.h>
30 typedef struct rlm_smsotp_t {
32 char const *challenge;
34 fr_connection_pool_t *pool;
37 static const CONF_PARSER module_config[] = {
38 { "socket", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_smsotp_t, socket), "/var/run/smsotp_socket" },
39 { "challenge_message", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_smsotp_t, challenge), "Enter Mobile PIN" },
40 { "challenge_type", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_smsotp_t, authtype), "smsotp-reply" },
42 { NULL, -1, 0, NULL, NULL } /* end the list */
45 static int _mod_conn_free(int *fdp)
51 static void *mod_conn_create(TALLOC_CTX *ctx, void *instance)
54 struct sockaddr_un sa;
55 rlm_smsotp_t *inst = instance;
56 socklen_t socklen = sizeof(sa);
59 sa.sun_family = AF_UNIX;
60 strlcpy(sa.sun_path, inst->socket, sizeof(sa.sun_path));
62 fd = socket(PF_UNIX, SOCK_STREAM, 0);
64 ERROR("Failed opening SMSOTP file %s: %s",
65 inst->socket, fr_syserror(errno));
69 if (connect(fd, (struct sockaddr *) &sa, socklen) < -1) {
70 ERROR("Failed connecting to SMSOTP file %s: %s",
71 inst->socket, fr_syserror(errno));
75 fdp = talloc_zero(ctx, int);
76 talloc_set_destructor(fdp, _mod_conn_free);
83 * Full read with logging, and close on failure.
84 * Returns nread on success, 0 on EOF, -1 on other failures.
86 static size_t read_all(int *fdp, char *buf, size_t len)
100 while (total < len) {
101 n = read(*fdp, &buf[total], len - total);
103 if (errno == EINTR) {
110 * Socket was closed. Don't try to re-open it.
112 if (n == 0) return 0;
116 * Check if there's more data. If not, return
119 retval = select(1, &fds, NULL, NULL, &tv);
131 * Write all of the data, taking care of EINTR, etc.
133 static int write_all(int *fdp, char const *buf, size_t len)
139 n = write(*fdp, &buf[len - left], left);
141 if ((errno == EINTR) || (errno == EPIPE)) {
154 * Do any per-module initialization that is separate to each
155 * configured instance of the module. e.g. set up connections
156 * to external databases, read configuration files, set up
157 * dictionary entries, etc.
159 * If configuration information is given in the config section
160 * that must be referenced in later calls, store a handle to it
161 * in *instance otherwise put a null pointer there.
163 static int mod_instantiate(CONF_SECTION *conf, void *instance)
165 rlm_smsotp_t *inst = instance;
166 struct sockaddr_un sa;
167 if (strlen(inst->socket) > (sizeof(sa.sun_path) - 1)) {
168 cf_log_err_cs(conf, "Socket filename is too long");
173 * Initialize the socket pool.
175 inst->pool = fr_connection_pool_init(conf, inst, mod_conn_create, NULL, NULL, NULL);
184 * Authenticate the user with the given password.
186 static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request)
188 rlm_smsotp_t *inst = instance;
192 rlm_rcode_t rcode = RLM_MODULE_FAIL;
196 fdp = fr_connection_get(inst->pool);
197 if (!fdp) return RLM_MODULE_FAIL;
200 bufsize = read_all(fdp, buffer, sizeof(buffer));
202 REDEBUG("Failed reading from socket");
207 * Look for the 'state' attribute.
209 #define WRITE_ALL(_a,_b,_c) if (write_all(_a,_b,_c) < 0) goto done;
210 state = pairfind(request->packet->vps, PW_STATE, 0, TAG_ANY);
212 RDEBUG("Found reply to access challenge");
215 snprintf(output, sizeof(output), "check otp for %s\n",
216 request->username->vp_strvalue);
217 WRITE_ALL(fdp, output, strlen(output));
219 (void) read_all(fdp, buffer, sizeof(buffer));
222 snprintf(output, sizeof(output), "user otp is %s\n",
223 request->password->vp_strvalue);
224 WRITE_ALL(fdp, output, strlen(output));
226 (void) read_all(fdp, buffer, sizeof(buffer));
229 snprintf(output, sizeof(output), "otp id is %s\n",
231 WRITE_ALL(fdp, output, strlen(output));
233 (void) read_all(fdp, buffer, sizeof(buffer));
235 /* now check the otp */
236 WRITE_ALL(fdp, "get check result\n", 17);
238 (void) read_all(fdp, buffer, sizeof(buffer));
240 /* end the sesssion */
241 WRITE_ALL(fdp, "quit\n", 5);
243 RDEBUG("answer is %s", buffer);
244 if (strcmp(buffer,"OK") == 0) {
245 rcode = RLM_MODULE_OK;
251 RDEBUG("Generating OTP");
254 snprintf(output, sizeof(output), "generate otp for %s\n",
255 request->username->vp_strvalue);
256 WRITE_ALL(fdp, output, strlen(output));
258 (void) read_all(fdp, buffer, sizeof(buffer));
260 /* end the sesssion */
261 WRITE_ALL(fdp, "quit\n", 5);
263 RDEBUG("Unique ID is %s", buffer);
265 /* check the return string */
266 if (strcmp(buffer,"FAILED") == 0) { /* smsotp script returns a error */
271 * Create the challenge, and add it to the reply.
274 pairmake_reply("Reply-Message", inst->challenge, T_OP_EQ);
275 pairmake_reply("State", buffer, T_OP_EQ);
278 * Mark the packet as an Access-Challenge packet.
280 * The server will take care of sending it to the user.
282 request->reply->code = PW_CODE_ACCESS_CHALLENGE;
283 DEBUG("rlm_smsotp: Sending Access-Challenge");
285 rcode = RLM_MODULE_HANDLED;
288 fr_connection_release(inst->pool, fdp);
293 * Find the named user in this modules database. Create the set
294 * of attribute-value pairs to check and reply with for this user
295 * from the database. The authentication code only needs to check
296 * the password, the rest is done here.
298 static rlm_rcode_t CC_HINT(nonnull) mod_authorize(UNUSED void *instance, UNUSED REQUEST *request)
301 rlm_smsotp_t *inst = instance;
304 * Look for the 'state' attribute.
306 state = pairfind(request->packet->vps, PW_STATE, 0, TAG_ANY);
308 DEBUG("rlm_smsotp: Found reply to access challenge (AUTZ), Adding Auth-Type '%s'",inst->authtype);
310 pairdelete(&request->config_items, PW_AUTH_TYPE, 0, TAG_ANY); /* delete old auth-type */
311 pairmake_config("Auth-Type", inst->authtype, T_OP_SET);
314 return RLM_MODULE_OK;
319 * The module name should be the only globally exported symbol.
320 * That is, everything else should be 'static'.
322 * If the module needs to temporarily modify it's instantiation
323 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
324 * The server will then take care of ensuring that the module
325 * is single-threaded.
327 module_t rlm_smsotp = {
330 RLM_TYPE_THREAD_SAFE, /* type */
331 sizeof(rlm_smsotp_t),
333 mod_instantiate, /* instantiation */
336 mod_authenticate, /* authentication */
337 mod_authorize, /* authorization */
338 NULL, /* preaccounting */
339 NULL, /* accounting */
340 NULL, /* checksimul */
341 NULL, /* pre-proxy */
342 NULL, /* post-proxy */