librecaptcha/src/main/scala/lc/Main.scala

237 lines
8.3 KiB
Scala

package lc
import com.sksamuel.scrimage._
import java.io.ByteArrayInputStream
import java.util.concurrent._
import java.util.UUID
import java.sql.{Blob, ResultSet}
import java.util.concurrent.atomic.AtomicInteger
import java.io._
import java.sql.Statement
case class Size(height: Int, width: Int)
case class Parameters(level: String, media: String, input_type: String, size: Option[Size])
case class Id(id: String)
case class Answer(answer: String, id: String)
case class ProviderSecret(provider: String, secret: String)
object CaptchaProviders {
val providers = Map(
"FilterChallenge" -> new FilterChallenge,
// "FontFunCaptcha" -> new FontFunCaptcha,
"GifCaptcha" -> new GifCaptcha,
"ShadowTextCaptcha" -> new ShadowTextCaptcha,
"RainDropsCaptcha" -> new RainDropsCP,
// "LabelCaptcha" -> new LabelCaptcha
)
def generateChallengeSamples() = {
providers.map {case (key, provider) =>
(key, provider.returnChallenge())
}
}
}
class Statements(dbConn: DBConn) {
val insertPstmt = dbConn.con.prepareStatement("INSERT INTO challenge(id, secret, provider, contentType, image) VALUES (?, ?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS )
val mapPstmt = dbConn.con.prepareStatement("INSERT INTO mapId(uuid, token, lastServed) VALUES (?, ?, CURRENT_TIMESTAMP)")
val selectPstmt = dbConn.con.prepareStatement("SELECT c.secret, c.provider FROM challenge c, mapId m WHERE m.token=c.token AND DATEDIFF(MINUTE, CURRENT_TIMESTAMP, DATEADD(MINUTE, 1, m.lastServed)) > 0 AND m.uuid = ?")
val imagePstmt = dbConn.con.prepareStatement("SELECT image FROM challenge c, mapId m WHERE c.token=m.token AND m.uuid = ?")
val updateAttemptedPstmt = dbConn.con.prepareStatement("UPDATE challenge SET attempted = attempted+1 WHERE token = (SELECT m.token FROM mapId m, challenge c WHERE m.token=c.token AND m.uuid = ?)")
val tokenPstmt = dbConn.con.prepareStatement("SELECT token FROM challenge WHERE attempted < 10 ORDER BY RAND() LIMIT 1")
val deleteAnswerPstmt = dbConn.con.prepareStatement("DELETE FROM mapId WHERE uuid = ?")
val challengeGCPstmt = dbConn.con.prepareStatement("DELETE FROM challenge WHERE attempted >= 10 AND token NOT IN (SELECT token FROM mapId)")
val mapIdGCPstmt = dbConn.con.prepareStatement("DELETE FROM mapId WHERE DATEDIFF(MINUTE, CURRENT_TIMESTAMP, DATEADD(MINUTE, 1, lastServed)) < 0")
}
object Statements {
var dbConn: DBConn = _
val tlStmts = ThreadLocal.withInitial(() => new Statements(dbConn))
}
class Captcha(throttle: Int, dbConn: DBConn) {
import CaptchaProviders._
private val stmt = dbConn.getStatement()
stmt.execute("CREATE TABLE IF NOT EXISTS challenge(token int auto_increment, id varchar, secret varchar, provider varchar, contentType varchar, image blob, attempted int default 0, PRIMARY KEY(token))")
stmt.execute("CREATE TABLE IF NOT EXISTS mapId(uuid varchar, token int, lastServed timestamp, PRIMARY KEY(uuid), FOREIGN KEY(token) REFERENCES challenge(token) ON DELETE CASCADE)")
private val seed = System.currentTimeMillis.toString.substring(2,6).toInt
private val random = new scala.util.Random(seed)
def getNextRandomInt(max: Int) = random.synchronized {
random.nextInt(max)
}
def getProvider(): String = {
val keys = providers.keys
val providerIndex = keys.toVector(getNextRandomInt(keys.size))
providerIndex
}
def getCaptcha(id: Id): Array[Byte] = {
var image :Array[Byte] = null
var blob: Blob = null
try {
val imagePstmt = Statements.tlStmts.get.imagePstmt
imagePstmt.setString(1, id.id)
val rs: ResultSet = imagePstmt.executeQuery()
if(rs.next()){
blob = rs.getBlob("image")
if(blob != null){
image = blob.getBytes(1, blob.length().toInt)
}
}
image
} catch { case e: Exception =>
println(e)
image
}
}
private val uniqueIntCount = new AtomicInteger()
def generateChallenge(param: Parameters): Int = {
//TODO: eval params to choose a provider
val providerMap = getProvider()
val provider = providers(providerMap)
val challenge = provider.returnChallenge()
val blob = new ByteArrayInputStream(challenge.content)
val insertPstmt = Statements.tlStmts.get.insertPstmt
insertPstmt.setString(1, provider.getId)
insertPstmt.setString(2, challenge.secret)
insertPstmt.setString(3, providerMap)
insertPstmt.setString(4, challenge.contentType)
insertPstmt.setBlob(5, blob)
insertPstmt.executeUpdate()
val rs: ResultSet = insertPstmt.getGeneratedKeys()
val token = if(rs.next()){
rs.getInt("token")
}
println("Added new challenge: "+ token.toString)
token.asInstanceOf[Int]
}
val task = new Runnable {
def run(): Unit = {
try {
val mapIdGCPstmt = Statements.tlStmts.get.mapIdGCPstmt
mapIdGCPstmt.executeUpdate()
val challengeGCPstmt = Statements.tlStmts.get.challengeGCPstmt
challengeGCPstmt.executeUpdate()
val imageNum = stmt.executeQuery("SELECT COUNT(*) AS total FROM challenge")
var throttleIn = (throttle*1.1).toInt
if(imageNum.next())
throttleIn = (throttleIn-imageNum.getInt("total"))
while(0 < throttleIn){
generateChallenge(Parameters("","","",Option(Size(0,0))))
throttleIn -= 1
}
} catch { case e: Exception => println(e) }
}
}
def beginThread(delay: Int) : Unit = {
val ex = new ScheduledThreadPoolExecutor(1)
val thread = ex.scheduleWithFixedDelay(task, 1, delay, TimeUnit.SECONDS)
}
def getChallenge(param: Parameters): Id = {
try {
val tokenPstmt = Statements.tlStmts.get.tokenPstmt
val rs = tokenPstmt.executeQuery()
val tokenOpt = if(rs.next()) {
Some(rs.getInt("token"))
} else {
None
}
val updateAttemptedPstmt = Statements.tlStmts.get.updateAttemptedPstmt
val uuid = getUUID(tokenOpt.getOrElse(generateChallenge(param)))
updateAttemptedPstmt.setString(1, uuid)
updateAttemptedPstmt.executeUpdate()
Id(uuid)
} catch {case e: Exception =>
println(e)
Id(getUUID(-1))
}
}
def getUUID(id: Int): String = {
val uuid = UUID.randomUUID().toString
val mapPstmt = Statements.tlStmts.get.mapPstmt
mapPstmt.setString(1,uuid)
mapPstmt.setInt(2,id)
mapPstmt.executeUpdate()
uuid
}
def checkAnswer(answer: Answer): String = {
val selectPstmt = Statements.tlStmts.get.selectPstmt
selectPstmt.setString(1, answer.id)
val rs: ResultSet = selectPstmt.executeQuery()
val psOpt = if (rs.first()) {
val secret = rs.getString("secret")
val provider = rs.getString("provider")
val check = providers(provider).checkAnswer(secret, answer.answer)
val result = if(check) "TRUE" else "FALSE"
result
} else {
"EXPIRED"
}
val deleteAnswerPstmt = Statements.tlStmts.get.deleteAnswerPstmt
deleteAnswerPstmt.setString(1, answer.id)
deleteAnswerPstmt.executeUpdate()
psOpt
}
def display(): Unit = {
val rs: ResultSet = stmt.executeQuery("SELECT * FROM challenge")
println("token\t\tid\t\tsecret\t\tattempted")
while(rs.next()) {
val token = rs.getInt("token")
val id = rs.getString("id")
val secret = rs.getString("secret")
val attempted = rs.getString("attempted")
println(s"${token}\t\t${id}\t\t${secret}\t\t${attempted}\n\n")
}
val rss: ResultSet = stmt.executeQuery("SELECT * FROM mapId")
println("uuid\t\ttoken\t\tlastServed")
while(rss.next()){
val uuid = rss.getString("uuid")
val token = rss.getInt("token")
val lastServed = rss.getTimestamp("lastServed")
println(s"${uuid}\t\t${token}\t\t${lastServed}\n\n")
}
}
}
object LCFramework{
def main(args: scala.Array[String]) {
val dbConn = new DBConn()
Statements.dbConn = dbConn
val captcha = new Captcha(2, dbConn)
val server = new Server(8888, captcha, dbConn)
captcha.beginThread(2)
server.start()
}
}
object MakeSamples {
def main(args: scala.Array[String]) {
val samples = CaptchaProviders.generateChallengeSamples()
samples.foreach {case (key, sample) =>
val extensionMap = Map("image/png" -> "png", "image/gif" -> "gif")
println(key + ": " + sample)
val outStream = new java.io.FileOutputStream("samples/"+key+"."+extensionMap(sample.contentType))
outStream.write(sample.content)
outStream.close
}
}
}