diff -r 0000000000000000000000000000000000000000 -r a141f344305bfe24b29481084c9195debfb41576 otp.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/otp.c Sat Aug 10 20:21:43 2013 -0500 @@ -0,0 +1,468 @@ +/* + + 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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +// 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; ilength); + 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 + * + * 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); +}