| /*␊ |
| ␊ |
| ␉otpauthexternal - "middleware" for mod_auth_external to use TOTP One time passwords␊ |
| ␊ |
| Copyright [2013] [Nathan Adams]␊ |
| ␊ |
| Licensed under the Apache License, Version 2.0 (the "License");␊ |
| you may not use this file except in compliance with the License.␊ |
| You may obtain a copy of the License at␊ |
| ␊ |
| http://www.apache.org/licenses/LICENSE-2.0␊ |
| ␊ |
| Unless required by applicable law or agreed to in writing, software␊ |
| distributed under the License is distributed on an "AS IS" BASIS,␊ |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.␊ |
| See the License for the specific language governing permissions and␊ |
| limitations under the License.␊ |
| ␊ |
| */␊ |
| ␊ |
| #include <stdio.h>␊ |
| #include <stdlib.h>␊ |
| #include <string.h>␊ |
| #include <math.h>␊ |
| ␊ |
| #include <openssl/sha.h>␊ |
| #include <openssl/evp.h>␊ |
| #include <openssl/bio.h>␊ |
| #include <openssl/hmac.h>␊ |
| #include <openssl/md5.h>␊ |
| #include <openssl/buffer.h>␊ |
| #include <time.h>␊ |
| #include <ctype.h>␊ |
| #include <stdint.h>␊ |
| ␊ |
| #include <my_global.h>␊ |
| #include <mysql.h>␊ |
| ␊ |
| #include <signal.h>␊ |
| ␊ |
| // http://stackoverflow.com/a/14291421/195722␊ |
| unsigned int hash(const char *mode, const char* dataToHash, size_t dataSize, unsigned char* outHashed) {␊ |
| unsigned int md_len = -1;␊ |
| const EVP_MD *md = EVP_get_digestbyname(mode);␊ |
| if(NULL != md) {␊ |
| EVP_MD_CTX mdctx;␊ |
| EVP_MD_CTX_init(&mdctx);␊ |
| EVP_DigestInit_ex(&mdctx, md, NULL);␊ |
| EVP_DigestUpdate(&mdctx, dataToHash, dataSize);␊ |
| EVP_DigestFinal_ex(&mdctx, outHashed, &md_len);␊ |
| EVP_MD_CTX_cleanup(&mdctx);␊ |
| }␊ |
| return md_len;␊ |
| }␊ |
| ␊ |
| void printHash(unsigned char * hash)␊ |
| {␊ |
| ␉int i;␊ |
| ␉for(i=0; i<SHA_DIGEST_LENGTH; i++) {␊ |
| ␉␉printf("%02x", hash[i]);␊ |
| ␉}␊ |
| }␊ |
| ␊ |
| unsigned int calcB64Size(unsigned int lng)␊ |
| {␊ |
| ␉unsigned int adjustment = ( (lng % 3) ? (3 - (lng % 3)) : 0);␊ |
| ␉unsigned int code_padded_size = ( (lng + adjustment) / 3) * 4;␊ |
| ␉unsigned int newline_size = ((code_padded_size) / 72) * 2;␊ |
| ␊ |
| ␉ return code_padded_size + newline_size; //extra +1 for null terminated char␊ |
| }␊ |
| ␊ |
| char * b64encode(unsigned char * msg, int lng)␊ |
| {␊ |
| ␉ BIO *bio, *b64;␊ |
| ␉ BUF_MEM *bptr;␊ |
| ␉ //int size;␊ |
| ␉ unsigned int b64ln = calcB64Size(lng);␊ |
| ␉ char * buff;␊ |
| ␉ ␊ |
| ␉ b64 = BIO_new(BIO_f_base64());␊ |
| ␉ bio = BIO_new(BIO_s_mem());␊ |
| ␉ bio = BIO_push(b64, bio);␊ |
| ␊ |
| ␉ BIO_write(b64, msg, lng);␊ |
| ␉ BIO_flush(bio);␊ |
| ␊ |
| ␉ BIO_get_mem_ptr(b64, &bptr);␊ |
| ␊ |
| ␉ buff = (char *)malloc(bptr->length);␊ |
| ␉ memcpy(buff, bptr->data, bptr->length-1);␊ |
| ␉ buff[bptr->length-1] = 0;␊ |
| ␉ ␊ |
| ␉ BIO_free_all(b64);␊ |
| ␉ return buff;␊ |
| }␊ |
| ␊ |
| static const int powers10[] = { 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 1000000000 };␊ |
| ␊ |
| // hotp function copied from https://code.google.com/p/mod-authn-otp/source/browse/trunk/hotp.c␊ |
| ␊ |
| /*␊ |
| * otptool - HOTP/OATH one-time password utility␊ |
| *␊ |
| * Copyright 2009 Archie L. Cobbs <archie@dellroad.org>␊ |
| *␊ |
| * Licensed under the Apache License, Version 2.0 (the "License");␊ |
| * you may not use this file except in compliance with the License.␊ |
| * You may obtain a copy of the License at␊ |
| *␊ |
| * http://www.apache.org/licenses/LICENSE-2.0␊ |
| *␊ |
| * Unless required by applicable law or agreed to in writing, software␊ |
| * distributed under the License is distributed on an "AS IS" BASIS,␊ |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.␊ |
| * See the License for the specific language governing permissions and␊ |
| * limitations under the License.␊ |
| *␊ |
| * $Id$␊ |
| */␊ |
| ␊ |
| ␊ |
| void␊ |
| hotp(const unsigned char *key, size_t keylen, unsigned long counter, int ndigits, char *buf10, char *buf16, size_t buflen)␊ |
| {␊ |
| const int max10 = sizeof(powers10) / sizeof(*powers10);␊ |
| const int max16 = 8;␊ |
| const EVP_MD *sha1_md = EVP_sha1();␊ |
| unsigned char hash[EVP_MAX_MD_SIZE];␊ |
| unsigned int hash_len;␊ |
| unsigned char tosign[8];␊ |
| int offset;␊ |
| int value;␊ |
| int i;␊ |
| ␊ |
| /* Encode counter */␊ |
| for (i = sizeof(tosign) - 1; i >= 0; i--) {␊ |
| tosign[i] = counter & 0xff;␊ |
| counter >>= 8;␊ |
| }␊ |
| ␊ |
| /* Compute HMAC */␊ |
| HMAC(sha1_md, key, keylen, tosign, sizeof(tosign), hash, &hash_len);␊ |
| ␊ |
| /* Extract selected bytes to get 32 bit integer value */␊ |
| offset = hash[hash_len - 1] & 0x0f;␊ |
| value = ((hash[offset] & 0x7f) << 24) | ((hash[offset + 1] & 0xff) << 16)␊ |
| | ((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff);␊ |
| ␊ |
| /* Sanity check max # digits */␊ |
| if (ndigits < 1)␊ |
| ndigits = 1;␊ |
| ␊ |
| /* Generate decimal digits */␊ |
| if (buf10 != NULL) {␊ |
| snprintf(buf10, buflen, "%0*d", ndigits < max10 ? ndigits : max10,␊ |
| ndigits < max10 ? value % powers10[ndigits - 1] : value);␊ |
| }␊ |
| ␊ |
| /* Generate hexadecimal digits */␊ |
| if (buf16 != NULL) {␊ |
| snprintf(buf16, buflen, "%0*x", ndigits < max16 ? ndigits : max16,␊ |
| ndigits < max16 ? (value & ((1 << (4 * ndigits)) - 1)) : value);␊ |
| }␊ |
| }␊ |
| ␊ |
| ␊ |
| typedef enum {␊ |
| ␉MYSQLDB,␊ |
| ␉SQLITEDB,␊ |
| ␉UNDEFINEDDB␊ |
| } DB_TYPE;␊ |
| ␊ |
| typedef struct␊ |
| {␊ |
| ␉char dbhost[255]; ␊ |
| ␉char dbname[255];␊ |
| ␉char dbuser[255];␊ |
| ␉char dbpass[255];␊ |
| ␉char dbtable[255];␊ |
| ␉char passfield[255];␊ |
| ␉char otpfield[255];␊ |
| ␉int dbport;␊ |
| ␉DB_TYPE type;␊ |
| } DB;␊ |
| ␊ |
| typedef struct ␊ |
| {␊ |
| ␉char * user;␊ |
| ␉char * otp;␊ |
| ␉char * password;␊ |
| } User;␊ |
| ␊ |
| DB * initDBStruct()␊ |
| {␊ |
| ␉DB * d = (DB *)malloc(sizeof(*d));␊ |
| ␉memset(d->dbhost, '\0', 255);␊ |
| ␉memset(d->dbname, '\0', 255);␊ |
| ␉memset(d->dbuser, '\0', 255);␊ |
| ␉memset(d->dbpass, '\0', 255);␊ |
| ␉memset(d->dbtable, '\0', 255);␊ |
| ␉strcpy(d->passfield, "password");␊ |
| ␉strcpy(d->otpfield, "otpkey");␊ |
| ␉d->dbport = 3306;␊ |
| ␉d->type = UNDEFINEDDB;␊ |
| ␉return d;␊ |
| }␊ |
| ␊ |
| User * initUserStruct()␊ |
| {␊ |
| ␉User * u = (User *)malloc(sizeof(*u));␊ |
| ␉u->otp = NULL;␊ |
| ␉u->password = NULL;␊ |
| ␉u->user = NULL;␊ |
| ␉return u;␊ |
| }␊ |
| ␊ |
| User * getMySQLUser(char * user, DB * db)␊ |
| {␊ |
| ␉char q[512];␊ |
| ␉int strp = 0;␊ |
| ␉MYSQL_RES *result;␊ |
| ␉MYSQL_ROW row;␊ |
| ␉User * u;␊ |
| ␉MYSQL *con;␊ |
| ␉memset(q, '\0', 512);␊ |
| ␉if (db->type != MYSQLDB)␊ |
| ␉␉return NULL;␊ |
| ␉con = mysql_init(NULL);␊ |
| ␉u = initUserStruct();␊ |
| ␊ |
| ␉if (con == NULL) ␊ |
| ␉{␊ |
| ␉␉fprintf(stderr, "%s\n", mysql_error(con));␊ |
| ␉␉free(u);␊ |
| ␉␉return NULL;␊ |
| ␉}␊ |
| ␊ |
| ␉if (mysql_real_connect(con, db->dbhost, db->dbuser, db->dbpass, db->dbname,␊ |
| ␉␉db->dbport, NULL, 0) == NULL) ␊ |
| ␉{␊ |
| ␉␉fprintf(stderr, "%s\n", mysql_error(con));␊ |
| ␉␉mysql_close(con);␊ |
| ␉␉free(u);␊ |
| ␉␉return NULL;␊ |
| ␉}␊ |
| ␊ |
| ␉strncpy(q, "SELECT ", 7);␊ |
| ␉strp += 7;␊ |
| ␉strncpy(q + strp, db->passfield, strlen(db->passfield));␊ |
| ␉strp += strlen(db->passfield);␊ |
| ␉strncpy(q + strp, ",", 1);␊ |
| ␉strp += 1;␊ |
| ␉strncpy(q + strp, db->otpfield, strlen(db->otpfield));␊ |
| ␉strp += strlen(db->otpfield);␊ |
| ␉strncpy(q + strp, " FROM ", 6);␊ |
| ␉strp += 6;␊ |
| ␉strncpy(q + strp, db->dbtable, strlen(db->dbtable));␊ |
| ␉strp += strlen(db->dbtable);␊ |
| ␉strncpy(q + strp, " WHERE login = '", 16);␊ |
| ␉strp += 16;␊ |
| ␉strncpy(q + strp, user, strlen(user));␊ |
| ␉strp += strlen(user);␊ |
| ␉strncpy(q + strp, "' LIMIT 1", 9);␊ |
| ␉if (mysql_query(con, q))␊ |
| ␉{␊ |
| ␉␉return NULL;␊ |
| ␉}␊ |
| ␊ |
| ␉result = mysql_store_result(con);␊ |
| ␉␊ |
| ␉row = mysql_fetch_row(result);␊ |
| ␊ |
| ␉u->user = user;␊ |
| ␉u->password = row[0];␊ |
| ␉if (strcmp(row[1], "") != 0)␊ |
| ␉␉u->otp = row[1];␊ |
| ␊ |
| ␉mysql_free_result(result);␊ |
| ␉mysql_close(con);␊ |
| ␉return u;␊ |
| }␊ |
| ␊ |
| // Config file will have the following format:␊ |
| // config{space}value␊ |
| ␊ |
| // Sample config file:␊ |
| // dbtype mysql␊ |
| // dbhost localhost␊ |
| // dbport 3306␊ |
| // dbuser root␊ |
| // dbpass root␊ |
| // dbtable table␊ |
| // dbname db1␊ |
| ␊ |
| // optional␊ |
| // dbpassfield field␊ |
| // dbotpfield field␊ |
| ␊ |
| // future␊ |
| // linger 300␊ |
| DB * readConfig(char * config_path)␊ |
| {␊ |
| ␉FILE *ifp;␊ |
| ␉DB * d;␊ |
| ␉char config[255];␊ |
| ␉char value[255];␊ |
| ␉ifp = fopen(config_path, "r");␊ |
| ␉␊ |
| ␉d = initDBStruct();␊ |
| ␊ |
| ␉while(!feof(ifp))␊ |
| ␉{␊ |
| ␉␉if (fscanf(ifp, "%s %s", config, value) != 2)␊ |
| ␉␉{␊ |
| ␉␉␉break;␊ |
| ␉␉}␊ |
| ␊ |
| ␉␉if (strcmp(config, "dbtype") == 0)␊ |
| ␉␉{␊ |
| ␉␉␉if (strcmp(value, "mysql") == 0)␊ |
| ␉␉␉{␊ |
| ␉␉␉␉d->type = MYSQLDB;␊ |
| ␉␉␉}␊ |
| ␉␉␉continue;␊ |
| ␉␉}␊ |
| ␊ |
| ␉␉if (strcmp(config, "dbhost") == 0)␊ |
| ␉␉{␊ |
| ␉␉␉strcpy(d->dbhost, value);␊ |
| ␉␉␉continue;␊ |
| ␉␉}␊ |
| ␊ |
| ␉␉if (strcmp(config, "dbport") == 0)␊ |
| ␉␉{␊ |
| ␉␉␉sprintf(value, "%d", &d->dbport);␊ |
| ␉␉␉continue;␊ |
| ␉␉}␊ |
| ␊ |
| ␉␉if (strcmp(config, "dbuser") == 0)␊ |
| ␉␉{␊ |
| ␉␉␉strcpy(d->dbuser, value);␊ |
| ␉␉␉continue;␊ |
| ␉␉}␊ |
| ␊ |
| ␉␉if (strcmp(config, "dbpass") == 0)␊ |
| ␉␉{␊ |
| ␉␉␉strcpy(d->dbpass, value);␊ |
| ␉␉␉continue;␊ |
| ␉␉}␊ |
| ␊ |
| ␉␉if (strcmp(config, "dbtable") == 0)␊ |
| ␉␉{␊ |
| ␉␉␉strcpy(d->dbtable, value);␊ |
| ␉␉␉continue;␊ |
| ␉␉}␊ |
| ␊ |
| ␉␉if (strcmp(config, "dbname") == 0)␊ |
| ␉␉{␊ |
| ␉␉␉strcpy(d->dbname, value);␊ |
| ␉␉␉continue;␊ |
| ␉␉}␊ |
| ␊ |
| ␉␉if (strcmp(config, "dbpassfield") == 0)␊ |
| ␉␉{␊ |
| ␉␉␉strcpy(d->passfield, value);␊ |
| ␉␉␉continue;␊ |
| ␉␉}␊ |
| ␊ |
| ␉␉if (strcmp(config, "dbotpfield") == 0)␊ |
| ␉␉{␊ |
| ␉␉␉strcpy(d->otpfield, value);␊ |
| ␉␉␉continue;␊ |
| ␉␉}␊ |
| ␉}␊ |
| ␉return d;␊ |
| }␊ |
| ␊ |
| int authotp(char * user_name, char * user_passwd) //, char * config_path)␊ |
| {␊ |
| ␉DB * db;␊ |
| ␉User * user;␊ |
| ␉unsigned char outHash[20];␊ |
| ␉char * ret;␊ |
| ␉char * ret2;␊ |
| ␉int keylen;␊ |
| ␉unsigned char newkey[256];␊ |
| ␉int ik = 0;␊ |
| ␉int i;␊ |
| ␉int nibs[2];␊ |
| ␉char buf10[256];␊ |
| ␉char buf16[256];␊ |
| ␉time_t now = time(NULL);␊ |
| ␉char inotp[7];␊ |
| ␉char password[256];␊ |
| ␉OpenSSL_add_all_algorithms();␊ |
| ␉␊ |
| ␉␊ |
| ␉//db = readConfig(getenv("OTPCONFIG"));␊ |
| ␉db = readConfig("/etc/apache2/configotp");␊ |
| ␉user = getMySQLUser(user_name, db);␊ |
| ␉// if user does not have a OTP set - just verify password␊ |
| ␉if (user->otp == NULL) ␊ |
| ␉{␊ |
| ␉␉hash("SHA1", user_passwd, strlen(user_passwd), outHash);␊ |
| ␉␉ret = b64encode(outHash, 20);␊ |
| ␉␉if (strcmp(ret, user->password) == 0)␊ |
| ␉␉␉exit(0);␊ |
| ␉␉else␊ |
| ␉␉␉exit(1);␊ |
| ␉} else {␊ |
| ␉␉// password should be in the form {OTP}{PASSWORD}␊ |
| ␉␉// ie 123456password␊ |
| ␉␉if (strlen(user_passwd) < 7) // 6 OTP digits and 1 char for password␊ |
| ␉␉{␊ |
| ␉␉␉printf("password not long enough!");␊ |
| ␉␉␉exit(1);␊ |
| ␉␉}␊ |
| ␉␉for(keylen = 0; keylen < sizeof(newkey) && user->otp[ik] != '\0'; keylen++)␊ |
| ␉␉{␊ |
| ␉␉␉for(i = 0; i < 2; i++)␊ |
| ␉␉␉{␊ |
| ␉␉␉␉if (isdigit(user->otp[ik]))␊ |
| ␉␉␉␉␉nibs[i] = user->otp[ik] - '0';␊ |
| ␉␉␉␉else if (isxdigit(user->otp[ik]))␊ |
| ␉␉␉␉␉nibs[i] = tolower(user->otp[ik]) - 'a' + 10;␊ |
| ␉␉␉␉ik++;␊ |
| ␉␉␉}␊ |
| ␉␉␉newkey[keylen] = (nibs[0] << 4) | nibs[1];␊ |
| ␉␉}␊ |
| ␊ |
| ␉␉hotp(newkey, keylen, (int)now / 30, 6, buf10, buf16, 256);␊ |
| ␉␉strncpy(inotp, user_passwd, 6);␊ |
| ␉␉inotp[6] = '\0';␊ |
| ␉␉strcpy(password, user_passwd + 6);␊ |
| ␉␉hash("SHA1", password, strlen(password), outHash);␊ |
| ␉␉ret2 = b64encode(outHash, 20);␊ |
| ␉␉if (strcmp(ret2, user->password) == 0 && strcmp(buf10, inotp) == 0)␊ |
| ␉␉{␊ |
| ␉␉␉exit(0);␊ |
| ␉␉} else {␊ |
| ␉␉␉exit(1);␊ |
| ␉␉}␊ |
| ␉}␊ |
| ␊ |
| ␉free(user);␊ |
| ␉free(db);␊ |
| ␉return 0;␊ |
| }␊ |
| ␊ |
| /*char * getSQLiteUser(char * user, DB db)␊ |
| {␊ |
| ␊ |
| }*/␊ |
| ␊ |
| int main()␊ |
| {␊ |
| ␉char user[100], password[100], *p;␊ |
| ␉if (fgets(user, sizeof(user), stdin) == NULL) exit(2);␊ |
| if ((p= strchr(user, '\n')) == NULL) exit(4);␊ |
| *p= '\0';␊ |
| ␊ |
| if (fgets(password, sizeof(password), stdin) == NULL) exit(3);␊ |
| if ((p= strchr(password, '\n')) == NULL) exit(5);␊ |
| *p= '\0';␊ |
| ␊ |
| ␉authotp(user, password);␊ |
| }␊ |