Newer version, small but essential fixes.
commit 6566dd88b46a79a46c8085711337159f0e6c617f
Author: Stephen R. van den Berg <<srb[at]cuci.nl>>
Date: Fri Jan 30 02:15:22 2009 +0100
Implement an automated CAPTCHA tag as part of vform
diff --git a/server/modules/tags/vform.pike b/server/modules/tags/vform.pike
index 4a52c66..9a351ba 100644
--- a/server/modules/tags/vform.pike
+++ b/server/modules/tags/vform.pike
@@ -376,6 +376,159 @@ class TagVForm {
}
}
+ class TagCaptcha {
+ inherit RXML.Tag;
+ constant name = "captcha";
+ constant flags = RXML.FLAG_EMPTY_ELEMENT;
+
+ class Frame {
+ inherit RXML.Frame;
+
+ array do_return(RequestID id) {
+ int debug = args->debug && 1;
+ int t,tdiff;
+#define LPRIME 1151
+#define RANDRANGE (((1<<16)/2-LPRIME)/2)
+ int prim1=random(RANDRANGE)*2+LPRIME,prim2=random(RANDRANGE)*2+LPRIME,
+ prim3=random(RANDRANGE*2)+LPRIME;
+
+ string timetosess(int t,void|string seed)
+ { return MIME.encode_base64(Crypto.MD5.hash((args->seed||"")
+ +"."+(string)t+"."+(seed||"")));
+ };
+
+ string getvarname(int t)
+ { return sprintf("%s%.*s%s",args->prefix||"",args->namewidth||8,
+ timetosess(t),args->postfix||"");
+ };
+
+ int primiterate(int i)
+ { return 1<<16<i?i:primiterate((i*prim1+prim2)%(1<<16|prim1+prim2|1));
+ };
+
+ array formulas =
+ ({
+ #if 0 // These two formula's are too simple
+ lambda(string challenge)
+ { return sprintf("%d",challenge^primiterate(prim3));
+ },
+ lambda(string challenge)
+ { int i=(int)challenge^primiterate(prim3);
+ int j=random((1<<31)-2)+1;
+ return sprintf("%d^0%o",j^i,j);
+ },
+#endif
+ lambda(string challenge)
+ { int i=(int)challenge^primiterate(prim3);
+ int j=random((1<<31)-2)+1;
+ int s=random(29)+1;
+ int a=j^i;
+ return sprintf("(%d^0%o)<<0%o^0%o^0%o",
+ a>>s,j>>s,s,a&(1<<s)-1,j&(1<<s)-1);
+ },
+ lambda(string challenge)
+ { int i=(int)challenge^primiterate(prim3);
+ int j=random((1<<31)-2)+1;
+ int k=random((1<<31)-2)+1;
+ return sprintf("%d%%0%o^0%o",j,k,i^(j%k));
+ },
+ });
+
+ NOCACHE();
+ t = time(1);
+ tdiff = Roxen.time_dequantifier(args,
+ args["unix-time"] ? (int)args["unix-time"] : t) - t;
+ if(tdiff <= 0)
+ tdiff = 32;
+
+ string v2, session;
+ {
+ int t1;
+ int obfuscinterval = tdiff*4;
+ foreach(();;int ntd)
+ if(ntd>=tdiff)
+ {
+ obfuscinterval = ntd;
+ break;
+ }
+ t1 = t - t%(tdiff*obfuscinterval);
+ session = getvarname(t1);
+
+ v2 = getvarname(t1 + (tdiff*obfuscinterval)/2);
+ }
+
+ string challenge;
+ mapping sessvar;
+
+ result = "";
+
+ {
+ mixed vold;
+ vold = RXML.user_get_var(session, "form")
+ || RXML.user_get_var(v2, "form");
+
+ int ok = 0;
+
+ if(stringp(vold))
+ {
+#define SESSIONPREFIX "captcha."
+#define SESSIONWIDTH 16
+ sscanf(vold,"%[^|]|%s",session,challenge);
+ if(session && sizeof(session))
+ {
+ session = SESSIONPREFIX + session;
+ if(sessvar = cache.get_session_data(session))
+ {
+ // Clear the session immediately, so we do not allow
+ // retries; they get exactly one shot at the challenge
+ // within the allotted timeframe.
+
+ cache.clear_session(session);
+ int ntdiff = t-sessvar->t;
+ ok = ntdiff<=tdiff && ntdiff>=(int)args->minsecs
+ && sessvar->challenge==challenge;
+ if(debug)
+ result += sprintf(
+ "<br />%d<=%d && %d>%d && %s==%s<br />Next session: ",
+ ntdiff,tdiff,ntdiff,(int)args->minsecs,sessvar->challenge,
+ challenge);
+ }
+ }
+ }
+ if (!ok)
+ {
+ id->misc->vform_ok = 0;
+ id->misc->vform_failed[name]=1;
+ }
+ else
+ id->misc->vform_verified[name]=1;
+ }
+ session = timetosess(t,roxen.create_unique_id())[..SESSIONWIDTH-1];
+ challenge = sprintf("%d",random(1<<31-1));
+ sessvar = (["t":t, "challenge":challenge]);
+ cache.set_session_data(sessvar, SESSIONPREFIX+session, t+tdiff);
+ challenge = random(formulas)(challenge);
+ result +=
+ RXML.t_xml->format_tag("script",
+ ([
+ "type":"text/javascript"
+ ]), "var s='"+session+"|';"
+ "var c="+challenge+";"
+ "function g(l){"
+ +sprintf("return 1<<16<l?l:g((l*0%o+0%o)%%(8<<13|0%o|1));",
+ prim1,prim2,prim1+prim2)+
+ "};"
+ "document.write('<input name=\""+v2+"\""
+ " type=\""+(debug?"text\" size=\"32":"hidden")
+ +sprintf("\" value=\"'+s+(c^g(0%o))+'\" />",prim3)
+ +(debug?sprintf("<pre>%s^%d</pre><br />",
+ challenge,primiterate(prim3)):"")+"');")
+ ;
+ return 0;
+ }
+ }
+ }
+
class TagVerifyFail {
inherit RXML.Tag;
constant name = "verify-fail";
@@ -444,7 +597,6 @@ class TagVForm {
int eval(string ind, RequestID id) {
if(!ind || !sizeof(ind)) return !id->misc->vform_ok;
- if(!id->real_variables[ind]) return 0;
return id->misc->vform_failed[ind];
}
}
@@ -464,6 +616,7 @@ class TagVForm {
RXML.TagSet internal = RXML.TagSet (this_module(), "internal",
({ TagVInput(),
TagReload(),
+ TagCaptcha(),
TagClear(),
TagVSelect(),
TagIfVFailed(),
@@ -542,6 +695,9 @@ and <tag>roxen-automatic-charset-variable</tag>.</p>
<ex-box>
<vform>
<vinput name='mail' type='email'>&_.warning;</vinput>
+ <captcha
+ seed='&client.ip;.&client.Fullname;.&client.accept;.&client.accept-charset;'
+ minsecs='1' minutes='32' />
<input type='hidden' name='user' value='&form.userid;' />
<input type='submit' />
</vform>
@@ -598,6 +754,68 @@ and <tag>roxen-automatic-charset-variable</tag>.</p>
verified.
</p></desc>",
+"captcha":#"<desc type='tag'><p><short>
+ Creates a fully automated CAPTCHA widget. It currently relies
+ on the fact that robots are bad at evaluating complex embedded
+ Javascript.</short>
+</p></desc>
+
+<attr name='seed'><p>
+ Extra seed used to generate the session key, it is recommended to
+ include as much information about the client as possible (e.g.
+
&client.ip;.&client.Fullname;.&client.accept;.&client.accept-charset;);
the session key already uses the current time as seed.</p>
+</attr>
+
+<attr name='minsecs'><p>
+ The minimum time in seconds that has to have passed before we accept
+ a submission of the form.</p>
+</attr>
+
+<attr name='prefix'><p>
+ Optional prefix of the name of the hidden captcha variable.</p>
+</attr>
+
+<attr name='namewidth' value='number'><p>
+ Optionally specify how many characters should be used for the
+ random part of the hidden captcha variable; defaults to 8.</p>
+</attr>
+
+<attr name='postfix'><p>
+ Optional suffix of the name of the hidden captcha variable.</p>
+</attr>
+
+<attr name='unix-time' value='number'>
+ <p>The exact time of expiration, expressed as a posix time integer.</p>
+</attr>
+
+<attr name='seconds' value='number'>
+ <p>Add this number of seconds to the time the user has to answer.</p>
+</attr>
+
+<attr name='minutes' value='number'>
+ <p>Add this number of minutes to the time the user has to answer.</p>
+</attr>
+
+<attr name='hours' value='number'>
+ <p>Add this number of hours to the time the user has to answer.</p>
+</attr>
+
+<attr name='days' value='number'>
+ <p>Add this number of days to the time the user has to answer.</p>
+</attr>
+
+<attr name='weeks' value='number'>
+ <p>Add this number of weeks to the time the user has to answer.</p>
+</attr>
+
+<attr name='months' value='number'>
+ <p>Add this number of months to the time the user has to answer.</p>
+</attr>
+
+<attr name='years' value='number'>
+ <p>Add this number of years to the time the user has to answer.</p>
+</attr>",
+
"vinput":({ #"<desc type='cont'><p><short>
Creates a self-verifying input widget.</short>
</p></desc>
--
Sincerely,
Stephen R. van den Berg.
"If you make people think they're thinking, they'll love you;
but if you really make them think, they'll hate you."
|