Merge branch 'libs/jetty/upstream' into libs/jetty/local
authorFelix Dörre <felix@dogcraft.de>
Thu, 30 Jun 2016 15:46:33 +0000 (17:46 +0200)
committerFelix Dörre <felix@dogcraft.de>
Thu, 30 Jun 2016 15:46:33 +0000 (17:46 +0200)
Change-Id: If35dc415d7d6b78338052a41aebb71ad0ccea0bd

106 files changed:
.classpath
.settings/org.eclipse.core.runtime.prefs [new file with mode: 0644]
Gigi.MF [new file with mode: 0644]
README.md
build.xml [new file with mode: 0644]
config/.gitignore [new file with mode: 0644]
config/gigi.properties.template [new file with mode: 0644]
config/test.properties.template [new file with mode: 0644]
doc/.gitignore [new file with mode: 0644]
doc/beforeYouStart.txt [new file with mode: 0644]
doc/jenkinsJob/README.txt [new file with mode: 0644]
doc/jenkinsJob/config.xml [new file with mode: 0644]
doc/scripts/.gitignore [new file with mode: 0644]
doc/scripts/generateKeys.sh [new file with mode: 0755]
doc/scripts/generateTruststore.sh [new file with mode: 0755]
doc/scripts/getJetty.sh [moved from doc/getJetty.sh with 93% similarity]
doc/scripts/selfsign.config [new file with mode: 0644]
doc/tableStructure.sql [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/ssl/SslConnection.java
lib/jetty/org/eclipse/jetty/io/ssl/SslReconfigurator.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/SslConnectionFactory.java
lib/jetty/org/eclipse/jetty/server/handler/ContextHandler.java
lib/jtar/org/kamranzafar/jtar/Octal.java [new file with mode: 0644]
lib/jtar/org/kamranzafar/jtar/TarConstants.java [new file with mode: 0644]
lib/jtar/org/kamranzafar/jtar/TarEntry.java [new file with mode: 0644]
lib/jtar/org/kamranzafar/jtar/TarHeader.java [new file with mode: 0644]
lib/jtar/org/kamranzafar/jtar/TarInputStream.java [new file with mode: 0644]
lib/jtar/org/kamranzafar/jtar/TarOutputStream.java [new file with mode: 0644]
lib/jtar/org/kamranzafar/jtar/TarUtils.java [new file with mode: 0644]
locale/.gitignore [new file with mode: 0644]
natives/.gitignore [new file with mode: 0644]
natives/Makefile [new file with mode: 0644]
natives/org_cacert_gigi_natives_SetUID.c [new file with mode: 0644]
src/org/cacert/gigi/DevelLauncher.java [new file with mode: 0644]
src/org/cacert/gigi/Gigi.java [new file with mode: 0644]
src/org/cacert/gigi/GigiConfig.java [new file with mode: 0644]
src/org/cacert/gigi/Language.java [new file with mode: 0644]
src/org/cacert/gigi/Launcher.java [new file with mode: 0644]
src/org/cacert/gigi/Name.java [new file with mode: 0644]
src/org/cacert/gigi/PolicyRedirector.java [new file with mode: 0644]
src/org/cacert/gigi/TestServlet.java [new file with mode: 0644]
src/org/cacert/gigi/User.java [new file with mode: 0644]
src/org/cacert/gigi/database/DatabaseConnection.java [new file with mode: 0644]
src/org/cacert/gigi/email/CommandlineEmailProvider.java [new file with mode: 0644]
src/org/cacert/gigi/email/EmailProvider.java [new file with mode: 0644]
src/org/cacert/gigi/email/Sendmail.java [new file with mode: 0644]
src/org/cacert/gigi/email/TestEmailProvider.java [new file with mode: 0644]
src/org/cacert/gigi/natives/SetUID.java [new file with mode: 0644]
src/org/cacert/gigi/output/CertificateTable.java [new file with mode: 0644]
src/org/cacert/gigi/output/DataTable.java [new file with mode: 0644]
src/org/cacert/gigi/output/DateSelector.java [new file with mode: 0644]
src/org/cacert/gigi/output/Form.java [new file with mode: 0644]
src/org/cacert/gigi/output/MailTable.java [new file with mode: 0644]
src/org/cacert/gigi/output/Outputable.java [new file with mode: 0644]
src/org/cacert/gigi/output/Template.java [new file with mode: 0644]
src/org/cacert/gigi/pages/LoginPage.java [new file with mode: 0644]
src/org/cacert/gigi/pages/MainPage.java [new file with mode: 0644]
src/org/cacert/gigi/pages/Page.java [new file with mode: 0644]
src/org/cacert/gigi/pages/TestSecure.java [new file with mode: 0644]
src/org/cacert/gigi/pages/Verify.java [new file with mode: 0644]
src/org/cacert/gigi/pages/account/MailAdd.java [new file with mode: 0644]
src/org/cacert/gigi/pages/account/MailCertificates.java [new file with mode: 0644]
src/org/cacert/gigi/pages/account/MailOverview.java [new file with mode: 0644]
src/org/cacert/gigi/pages/account/MyDetails.java [new file with mode: 0644]
src/org/cacert/gigi/pages/account/MyDetails.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/main/RegisterPage.java [new file with mode: 0644]
src/org/cacert/gigi/pages/main/RegisterPage.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/main/Signup.java [new file with mode: 0644]
src/org/cacert/gigi/pages/main/Signup.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/wot/AssuranceForm.java [new file with mode: 0644]
src/org/cacert/gigi/pages/wot/AssuranceForm.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/wot/AssurePage.java [new file with mode: 0644]
src/org/cacert/gigi/pages/wot/AssureeSearch.templ [new file with mode: 0644]
src/org/cacert/gigi/util/CipherInfo.java [new file with mode: 0644]
src/org/cacert/gigi/util/HTMLEncoder.java [new file with mode: 0644]
src/org/cacert/gigi/util/Notary.java [new file with mode: 0644]
src/org/cacert/gigi/util/PasswordHash.java [new file with mode: 0644]
src/org/cacert/gigi/util/PasswordStrengthChecker.java [new file with mode: 0644]
src/org/cacert/gigi/util/RandomToken.java [new file with mode: 0644]
src/org/cacert/gigi/util/ServerConstants.java [new file with mode: 0644]
static/default.css [new file with mode: 0644]
static/menu.js [new file with mode: 0644]
static/policy/AssurancePolicy.html [new file with mode: 0644]
static/policy/CAcertCommunityAgreement.html [new file with mode: 0644]
static/policy/CertificationPracticeStatement.html [new file with mode: 0644]
static/policy/DisputeResolutionPolicy.html [new file with mode: 0644]
static/policy/NRPDisclaimerAndLicence.html [new file with mode: 0644]
static/policy/OrganisationAssurancePolicy.html [new file with mode: 0644]
static/policy/PolicyOnPolicy.html [new file with mode: 0644]
static/policy/PrivacyPolicy.html [new file with mode: 0644]
static/policy/RootDistributionLicense.html [new file with mode: 0644]
static/policy/cacert-draft.png [new file with mode: 0644]
templates/base.html [new file with mode: 0644]
tests/org/cacert/gigi/LoginTest.java [new file with mode: 0644]
tests/org/cacert/gigi/TestSSL.java [new file with mode: 0644]
tests/org/cacert/gigi/pages/main/RegisterPageTest.java [new file with mode: 0644]
tests/org/cacert/gigi/pages/wot/TestAssurance.java [new file with mode: 0644]
tests/org/cacert/gigi/testUtils/IOUtils.java [new file with mode: 0644]
tests/org/cacert/gigi/testUtils/InitTruststore.java [new file with mode: 0644]
tests/org/cacert/gigi/testUtils/ManagedTest.java [new file with mode: 0644]
tests/org/cacert/gigi/testUtils/TestEmailReciever.java [new file with mode: 0644]
tests/org/cacert/gigi/util/TestHTMLEncoder.java [new file with mode: 0644]
tests/org/cacert/gigi/util/TestPasswordHash.java [new file with mode: 0644]
tests/org/cacert/gigi/util/TestPasswordStrengthChecker.java [new file with mode: 0644]
util/org/cacert/gigi/util/DatabaseManager.java [new file with mode: 0644]
util/org/cacert/gigi/util/FetchLocales.java [new file with mode: 0644]

index 0fba86b..c9c9607 100644 (file)
@@ -1,8 +1,13 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
-       <classpathentry kind="src" path="src"/>
        <classpathentry kind="src" path="lib/servlet-api"/>
        <classpathentry kind="src" path="lib/jetty"/>
+       <classpathentry kind="src" path="lib/jtar"/>
+       <classpathentry kind="src" path="src"/>
+       <classpathentry kind="src" path="util"/>
+       <classpathentry kind="src" path="tests"/>
        <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+       <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
+       <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/MySQL"/>
        <classpathentry kind="output" path="bin"/>
 </classpath>
diff --git a/.settings/org.eclipse.core.runtime.prefs b/.settings/org.eclipse.core.runtime.prefs
new file mode 100644 (file)
index 0000000..5a0ad22
--- /dev/null
@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+line.separator=\n
diff --git a/Gigi.MF b/Gigi.MF
new file mode 100644 (file)
index 0000000..614d595
--- /dev/null
+++ b/Gigi.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Main-Class: org.cacert.gigi.Launcher
+
index a4c3425..8f4be3b 100644 (file)
--- a/README.md
+++ b/README.md
@@ -2,3 +2,6 @@ Gigi
 =================
 
 Webserver Module for CAcert
+
+
+Contains source from jetty 9.1.0.RC0
diff --git a/build.xml b/build.xml
new file mode 100644 (file)
index 0000000..ff8c6d6
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,136 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<project basedir="." default="all" name="cacert-gigi">
+       <property environment="env"/>
+       <property name="junit.output.dir" value="junit"/>
+       <property name="debuglevel" value="source,lines,vars"/>
+       <property name="target" value="1.8"/>
+       <property name="source" value="1.8"/>
+       <property name="mysqlconnector" value="mysql-connector-java-5.1.31-bin.jar"/>
+       <property name="juintexec" value="."/>
+       <path id="JUnit 4.libraryclasspath">
+               <pathelement location="${juintexec}/junit.jar"/>
+               <pathelement location="${juintexec}/org.hamcrest.core.jar"/>
+       </path>
+       <path id="cacert-gigi.classpath">
+               <pathelement location="bin"/>
+               <pathelement location="${mysqlconnector}"/>
+       </path>
+       <path id="cacert-gigi.test.classpath">
+               <pathelement location="bin"/>
+               <pathelement location="bintest"/>
+               <path refid="JUnit 4.libraryclasspath"/>
+               <pathelement location="${mysqlconnector}"/>
+       </path>
+       <target name="init">
+               <mkdir dir="bin"/>
+               <mkdir dir="bintest"/>
+
+               <copy includeemptydirs="false" todir="bin">
+                       <fileset dir="lib/servlet-api">
+                               <exclude name="**/*.launch"/>
+                               <exclude name="**/*.java"/>
+                       </fileset>
+               </copy>
+               <copy includeemptydirs="false" todir="bin">
+                       <fileset dir="lib/jetty">
+                               <exclude name="**/*.launch"/>
+                               <exclude name="**/*.java"/>
+                       </fileset>
+               </copy>
+               <copy includeemptydirs="false" todir="bin">
+                       <fileset dir="src">
+                               <exclude name="**/*.launch"/>
+                               <exclude name="**/*.java"/>
+                       </fileset>
+               </copy>
+               <copy includeemptydirs="false" todir="bin">
+                       <fileset dir="util">
+                               <exclude name="**/*.launch"/>
+                               <exclude name="**/*.java"/>
+                       </fileset>
+               </copy>
+               <copy includeemptydirs="false" todir="bintest">
+                       <fileset dir="tests">
+                               <exclude name="**/*.launch"/>
+                               <exclude name="**/*.java"/>
+                       </fileset>
+               </copy>
+       </target>
+       <target name="clean">
+               <delete dir="bin"/>
+       </target>
+       <target depends="clean" name="cleanall"/>
+       <target depends="build-project, native" name="build"/>
+       <target depends="init" name="build-project">
+               <echo message="${ant.project.name}: ${ant.file}"/>
+               <javac debug="true" debuglevel="${debuglevel}" destdir="bin" includeantruntime="false" source="${source}" target="${target}">
+                       <src path="lib/servlet-api"/>
+                       <src path="lib/jetty"/>
+                       <src path="lib/jtar"/>
+                       <src path="src"/>
+                       <src path="util"/>
+                       <classpath refid="cacert-gigi.classpath"/>
+               </javac>
+       </target>
+
+       <target name="native">
+               <exec executable="make" dir="natives"/>
+       </target>
+
+       <target depends="build" name="pack">
+               <jar destfile="gigi.jar" basedir="bin" manifest="Gigi.MF"/>
+       </target>
+
+       <target depends="test,native" name="bundle">
+               <zip destfile="gigi-linux_amd64.zip" basedir="." includes="gigi.jar,native/*.so,doc/tableStructure.sql,static/**,templates/**"/>
+       </target>
+       <target name="static-bundle">
+               <tar destfile="static.tar.gz" compression="gzip" basedir="." includes="doc/tableStructure.sql,static/**,templates/**"/>
+       </target>
+
+       <target name="all" depends="bundle,static-bundle"/>
+
+
+       <target depends="init,build-project" name="build-project-test">
+               <echo message="${ant.project.name}: ${ant.file}"/>
+               <javac debug="true" debuglevel="${debuglevel}" destdir="bintest" includeantruntime="false" source="${source}" target="${target}">
+                       <src path="tests"/>
+                       <classpath refid="cacert-gigi.test.classpath"/>
+               </javac>
+       </target>
+       <target name="FetchLocales">
+               <java classname="org.cacert.gigi.util.FetchLocales" failonerror="true" fork="yes">
+                       <classpath refid="cacert-gigi.classpath"/>
+               </java>
+       </target>
+       <target name="check-generateKeys">
+               <available file="config/keystore.pkcs12" property="keystore.present"/>
+       </target>
+       <target name="generateKeys" depends="check-generateKeys" unless="keystore.present">
+               <exec executable="./generateKeys.sh" dir="doc/scripts"/>
+               <exec executable="./generateTruststore.sh" dir="doc/scripts">
+                       <arg value="-noprompt"/>
+               </exec>
+       </target>
+       <target name="test" depends="build-project-test,generateKeys,FetchLocales,pack">
+               <mkdir dir="${junit.output.dir}"/>
+               <junit fork="yes" printsummary="withOutAndErr">
+                       <formatter type="xml"/>
+                       <batchtest fork="yes" todir="${junit.output.dir}">
+                               <fileset dir="tests">
+                                       <include name="**/*.java"/>
+                                       <exclude name="**/testUtils/**"/>
+                               </fileset>
+                       </batchtest>
+                       <classpath refid="cacert-gigi.test.classpath"/>
+               </junit>
+       </target>
+       <target name="junitreport">
+               <junitreport todir="${junit.output.dir}">
+                       <fileset dir="${junit.output.dir}">
+                               <include name="TEST-*.xml"/>
+                       </fileset>
+                       <report format="frames" todir="${junit.output.dir}"/>
+               </junitreport>
+       </target>
+</project>
diff --git a/config/.gitignore b/config/.gitignore
new file mode 100644 (file)
index 0000000..59f2d87
--- /dev/null
@@ -0,0 +1,5 @@
+
+keystore.pkcs12
+cacerts.jks
+gigi.properties
+test.properties
diff --git a/config/gigi.properties.template b/config/gigi.properties.template
new file mode 100644 (file)
index 0000000..474d677
--- /dev/null
@@ -0,0 +1,8 @@
+host=127.0.0.1
+port=443
+#emailProvider=org.cacert.gigi.email.Sendmail
+emailProvider=org.cacert.gigi.email.CommandlineEmailProvider
+sql.driver=com.mysql.jdbc.Driver
+sql.url=jdbc:mysql://
+sql.user=
+sql.password=
diff --git a/config/test.properties.template b/config/test.properties.template
new file mode 100644 (file)
index 0000000..1bd8a58
--- /dev/null
@@ -0,0 +1,18 @@
+type=local
+server=localhost:443
+mail=localhost:8474
+
+# ==== OR ===
+type=autonomous
+java=java -cp bin;/path/to/mysqlConnector.jar org.cacert.gigi.Launcher
+serverPort=4443
+mailPort=8473
+
+
+
+
+# ==== ALL ===
+sql.driver=com.mysql.jdbc.Driver
+sql.url=jdbc:mysql://localhost:3306/cacert
+sql.user=cacert
+sql.password=<password>
diff --git a/doc/.gitignore b/doc/.gitignore
new file mode 100644 (file)
index 0000000..becafc3
--- /dev/null
@@ -0,0 +1 @@
+sampleData.sql
diff --git a/doc/beforeYouStart.txt b/doc/beforeYouStart.txt
new file mode 100644 (file)
index 0000000..bfa3f39
--- /dev/null
@@ -0,0 +1,12 @@
+Before you start using you might want to:
+
+- create a keypair for the server (scripts/generateKeys.sh)
+- create a truststore for the server (scripts/generateTruststore.sh)
+
+- download locales (util/ org.cacert.gigi.util.FetchLocales)
+- write your sql connection properties: config/gigi.properties.template -> config/gigi.properties
+- add the corresponding jdbc connector to your path.
+
+- on unix-like systems: to securely run on privileged ports <= 1024 build the native setuid library (run the makefile in natives/).
+  This expects JAVA_HOME to be set.
+
diff --git a/doc/jenkinsJob/README.txt b/doc/jenkinsJob/README.txt
new file mode 100644 (file)
index 0000000..c5651bf
--- /dev/null
@@ -0,0 +1,10 @@
+/path/to/mysql-connector.jar
+a Path to the mysql-jdbc-connector
+
+<yourSqlPassword>
+a Password to the sql database to test in.
+
+/path/to/folder/with/junit/
+folder Containing:
+- junit.jar
+- org.hamcrest.core.jar
diff --git a/doc/jenkinsJob/config.xml b/doc/jenkinsJob/config.xml
new file mode 100644 (file)
index 0000000..61a2e0c
--- /dev/null
@@ -0,0 +1,113 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<project>
+  <actions/>
+  <description></description>
+  <keepDependencies>false</keepDependencies>
+  <properties>
+    <hudson.security.AuthorizationMatrixProperty>
+      <permission>hudson.model.Item.Read:anonymous</permission>
+    </hudson.security.AuthorizationMatrixProperty>
+    <hudson.model.ParametersDefinitionProperty>
+      <parameterDefinitions>
+        <hudson.model.TextParameterDefinition>
+          <name>JAVA_HOME</name>
+          <description></description>
+          <defaultValue>/usr/lib/jvm/java-8-openjdk/</defaultValue>
+        </hudson.model.TextParameterDefinition>
+        <hudson.model.TextParameterDefinition>
+          <name>BRANCH</name>
+          <description>The branch to build from.</description>
+          <defaultValue>master</defaultValue>
+        </hudson.model.TextParameterDefinition>
+      </parameterDefinitions>
+    </hudson.model.ParametersDefinitionProperty>
+  </properties>
+  <scm class="hudson.plugins.git.GitSCM" plugin="git@1.5.0">
+    <configVersion>2</configVersion>
+    <userRemoteConfigs>
+      <hudson.plugins.git.UserRemoteConfig>
+        <name></name>
+        <refspec></refspec>
+        <url>https://github.com/yellowant/cacert-gigi.git</url>
+      </hudson.plugins.git.UserRemoteConfig>
+    </userRemoteConfigs>
+    <branches>
+      <hudson.plugins.git.BranchSpec>
+        <name>$BRANCH</name>
+      </hudson.plugins.git.BranchSpec>
+    </branches>
+    <disableSubmodules>false</disableSubmodules>
+    <recursiveSubmodules>false</recursiveSubmodules>
+    <doGenerateSubmoduleConfigurations>false</doGenerateSubmoduleConfigurations>
+    <authorOrCommitter>false</authorOrCommitter>
+    <clean>false</clean>
+    <wipeOutWorkspace>false</wipeOutWorkspace>
+    <pruneBranches>false</pruneBranches>
+    <remotePoll>false</remotePoll>
+    <ignoreNotifyCommit>false</ignoreNotifyCommit>
+    <useShallowClone>false</useShallowClone>
+    <buildChooser class="hudson.plugins.git.util.DefaultBuildChooser"/>
+    <gitTool>Default</gitTool>
+    <submoduleCfg class="list"/>
+    <relativeTargetDir></relativeTargetDir>
+    <reference></reference>
+    <excludedRegions></excludedRegions>
+    <excludedUsers></excludedUsers>
+    <gitConfigName></gitConfigName>
+    <gitConfigEmail></gitConfigEmail>
+    <skipTag>false</skipTag>
+    <includedRegions></includedRegions>
+    <scmName></scmName>
+  </scm>
+  <canRoam>true</canRoam>
+  <disabled>false</disabled>
+  <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
+  <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
+  <jdk>Java 8 OpenJDK</jdk>
+  <triggers class="vector">
+    <hudson.triggers.SCMTrigger>
+      <spec>@midnight</spec>
+      <ignorePostCommitHooks>false</ignorePostCommitHooks>
+    </hudson.triggers.SCMTrigger>
+  </triggers>
+  <concurrentBuild>false</concurrentBuild>
+  <builders>
+    <hudson.tasks.Shell>
+      <command>cat &lt;&lt;EOT &gt;config/test.properties
+type=autonomous
+java=java -cp gigi.jar:/path/to/mysql-connector.jar org.cacert.gigi.Launcher
+serverPort=4448
+mailPort=8473
+sql.driver=com.mysql.jdbc.Driver
+sql.url=jdbc:mysql://localhost:3306/cacert
+sql.user=cacert
+sql.password=<yourSqlPassword>
+EOT
+</command>
+    </hudson.tasks.Shell>
+    <hudson.tasks.Ant plugin="ant@1.2">
+      <targets></targets>
+      <antOpts>-Dfile.encoding=UTF-8</antOpts>
+      <buildFile>build.xml</buildFile>
+      <properties>juintexec=/path/to/folder/with/junit/
+mysqlconnector=/path/to/mysql-connector.jar</properties>
+    </hudson.tasks.Ant>
+  </builders>
+  <publishers>
+    <hudson.tasks.junit.JUnitResultArchiver>
+      <testResults>junit/*.xml</testResults>
+      <keepLongStdio>false</keepLongStdio>
+      <testDataPublishers/>
+    </hudson.tasks.junit.JUnitResultArchiver>
+    <hudson.tasks.ArtifactArchiver>
+      <artifacts>natives/*.so,gigi.jar,gigi-linux_amd64.zip,static.tar.gz</artifacts>
+      <latestOnly>false</latestOnly>
+      <allowEmptyArchive>false</allowEmptyArchive>
+    </hudson.tasks.ArtifactArchiver>
+    <hudson.tasks.Fingerprinter>
+      <targets></targets>
+      <recordBuildArtifacts>true</recordBuildArtifacts>
+    </hudson.tasks.Fingerprinter>
+  </publishers>
+  <buildWrappers/>
+</project>
\ No newline at end of file
diff --git a/doc/scripts/.gitignore b/doc/scripts/.gitignore
new file mode 100644 (file)
index 0000000..3574a98
--- /dev/null
@@ -0,0 +1,3 @@
+*.crt
+jetty.csr
+jetty.key
diff --git a/doc/scripts/generateKeys.sh b/doc/scripts/generateKeys.sh
new file mode 100755 (executable)
index 0000000..26a01ce
--- /dev/null
@@ -0,0 +1,7 @@
+#!/bin/sh
+# this script generates a simple self-signed keypair
+
+openssl genrsa -out jetty.key 4096
+openssl req -new -key jetty.key -out jetty.csr -subj "/CN=localhost" -config selfsign.config
+openssl x509 -req -days 365 -in jetty.csr -signkey jetty.key -out jetty.crt
+openssl pkcs12 -inkey jetty.key -in jetty.crt -export -passout pass: -out ../../config/keystore.pkcs12
diff --git a/doc/scripts/generateTruststore.sh b/doc/scripts/generateTruststore.sh
new file mode 100755 (executable)
index 0000000..1295294
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/sh
+# this script imports the cacert root certs
+
+wget -N http://www.cacert.org/certs/root.crt
+wget -N http://www.cacert.org/certs/class3.crt
+
+keytool -importcert -keystore ../../config/cacerts.jks -file root.crt -alias root -storepass "changeit" $1
+keytool -importcert -keystore ../../config/cacerts.jks -file class3.crt -alias class3 -storepass "changeit" $1
+keytool -importcert -keystore ../../config/cacerts.jks -file jetty.crt -alias own -storepass "changeit" $1
+
+keytool -list -keystore ../../config/cacerts.jks -storepass "changeit"
similarity index 93%
rename from doc/getJetty.sh
rename to doc/scripts/getJetty.sh
index 2b1bd95..784a6f1 100644 (file)
@@ -2,7 +2,7 @@
 set -e
 JETTY=C:/jars/jetty-distribution-9.1.0.RC0/org.eclipse.jetty.project
 
-pushd ../lib/jetty/org/eclipse/jetty
+pushd ../../lib/jetty/org/eclipse/jetty
 rm -fR *
 
 pushd $JETTY
diff --git a/doc/scripts/selfsign.config b/doc/scripts/selfsign.config
new file mode 100644 (file)
index 0000000..4962f72
--- /dev/null
@@ -0,0 +1,9 @@
+[req]
+distinguished_name=dn
+#req_extensions=ext
+
+[dn]
+commonName = cn
+
+[ext]
+subjectAltName=
diff --git a/doc/tableStructure.sql b/doc/tableStructure.sql
new file mode 100644 (file)
index 0000000..88ba34e
--- /dev/null
@@ -0,0 +1,169 @@
+DROP TABLE IF EXISTS `users`;
+CREATE TABLE `users` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `email` varchar(255) NOT NULL DEFAULT '',
+  `password` varchar(255) NOT NULL DEFAULT '',
+  `fname` varchar(255) NOT NULL DEFAULT '',
+  `mname` varchar(255) NOT NULL DEFAULT '',
+  `lname` varchar(255) NOT NULL DEFAULT '',
+  `suffix` varchar(50) NOT NULL DEFAULT '',
+  `dob` date NOT NULL DEFAULT '0000-00-00',
+  `verified` int(1) NOT NULL DEFAULT '0',
+  `ccid` int(3) NOT NULL DEFAULT '0',
+  `regid` int(5) NOT NULL DEFAULT '0',
+  `locid` int(7) NOT NULL DEFAULT '0',
+  `listme` int(1) NOT NULL DEFAULT '0',
+  `admin` tinyint(1) NOT NULL DEFAULT '0',
+  `language` varchar(5) NOT NULL DEFAULT '',
+  `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+  `modified` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+  `deleted` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+  `locked` tinyint(1) NOT NULL DEFAULT '0',
+  `assurer_blocked` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  KEY `ccid` (`ccid`),
+  KEY `regid` (`regid`),
+  KEY `locid` (`locid`),
+  KEY `email` (`email`),
+  KEY `stats_users_created` (`created`),
+  KEY `stats_users_verified` (`verified`),
+  KEY `userverified` (`verified`)
+) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;
+
+
+DROP TABLE IF EXISTS `email`;
+CREATE TABLE `email` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `memid` int(11) NOT NULL DEFAULT '0',
+  `email` varchar(255) NOT NULL DEFAULT '',
+  `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+  `modified` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+  `deleted` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+  `hash` varchar(50) NOT NULL DEFAULT '',
+  `attempts` int(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  KEY `memid` (`memid`),
+  KEY `stats_email_hash` (`hash`),
+  KEY `stats_email_deleted` (`deleted`),
+  KEY `email` (`email`)
+) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=latin1;
+
+DROP TABLE IF EXISTS `pinglog`;
+CREATE TABLE `pinglog` (
+  `when` datetime NOT NULL,
+  `uid` int(11) NOT NULL,
+  `email` varchar(255) NOT NULL,
+  `result` varchar(255) NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+
+DROP TABLE IF EXISTS `baddomains`;
+CREATE TABLE `baddomains` (
+  `domain` varchar(255) NOT NULL DEFAULT ''
+) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+
+
+DROP TABLE IF EXISTS `alerts`;
+CREATE TABLE `alerts` (
+  `memid` int(11) NOT NULL DEFAULT '0',
+  `general` tinyint(1) NOT NULL DEFAULT '0',
+  `country` tinyint(1) NOT NULL DEFAULT '0',
+  `regional` tinyint(1) NOT NULL DEFAULT '0',
+  `radius` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`memid`)
+) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+
+DROP TABLE IF EXISTS `user_agreements`;
+CREATE TABLE `user_agreements` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `memid` int(11) NOT NULL,
+  `secmemid` int(11) DEFAULT NULL,
+  `document` varchar(50) DEFAULT NULL,
+  `date` datetime DEFAULT NULL,
+  `active` int(1) NOT NULL,
+  `method` varchar(100) NOT NULL,
+  `comment` varchar(100) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=latin1;
+
+DROP TABLE IF EXISTS `emailcerts`;
+CREATE TABLE `emailcerts` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `memid` int(11) NOT NULL DEFAULT '0',
+  `serial` varchar(50) NOT NULL DEFAULT '',
+  `CN` varchar(255) NOT NULL DEFAULT '',
+  `subject` text NOT NULL,
+  `keytype` char(2) NOT NULL DEFAULT 'NS',
+  `codesign` tinyint(1) NOT NULL DEFAULT '0',
+  `csr_name` varchar(255) NOT NULL DEFAULT '',
+  `crt_name` varchar(255) NOT NULL DEFAULT '',
+  `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+  `modified` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+  `revoked` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+  `expire` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+  `warning` tinyint(1) NOT NULL DEFAULT '0',
+  `renewed` tinyint(1) NOT NULL DEFAULT '0',
+  `rootcert` int(2) NOT NULL DEFAULT '1',
+  `md` enum('md5','sha1','sha256','sha512') NOT NULL DEFAULT 'sha512',
+  `type` tinyint(4) DEFAULT NULL,
+  `disablelogin` int(1) NOT NULL DEFAULT '0',
+  `pkhash` char(40) DEFAULT NULL,
+  `certhash` char(40) DEFAULT NULL,
+  `coll_found` tinyint(1) NOT NULL,
+  `description` varchar(100) NOT NULL DEFAULT '',
+  PRIMARY KEY (`id`),
+  KEY `emailcerts_pkhash` (`pkhash`),
+  KEY `revoked` (`revoked`),
+  KEY `created` (`created`),
+  KEY `memid` (`memid`),
+  KEY `serial` (`serial`),
+  KEY `stats_emailcerts_expire` (`expire`),
+  KEY `emailcrt` (`crt_name`)
+) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=latin1;
+
+DROP TABLE IF EXISTS `notary`;
+CREATE TABLE `notary` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `from` int(11) NOT NULL DEFAULT '0',
+  `to` int(11) NOT NULL DEFAULT '0',
+  `awarded` int(3) NOT NULL DEFAULT '0',
+  `points` int(3) NOT NULL DEFAULT '0',
+  `method` enum('Face to Face Meeting','Trusted Third Parties','Thawte Points Transfer','Administrative Increase','CT Magazine - Germany','Temporary Increase','Unknown','TOPUP','TTP-Assisted') NOT NULL DEFAULT 'Face to Face Meeting',
+  `location` varchar(255) NOT NULL DEFAULT '',
+  `date` varchar(255) NOT NULL DEFAULT '',
+  `when` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+  `expire` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+  `sponsor` int(11) NOT NULL DEFAULT '0',
+  `deleted` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+  PRIMARY KEY (`id`),
+  KEY `from` (`from`),
+  KEY `to` (`to`),
+  KEY `from_2` (`from`),
+  KEY `to_2` (`to`),
+  KEY `stats_notary_when` (`when`),
+  KEY `stats_notary_method` (`method`)
+) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=latin1;
+
+
+DROP TABLE IF EXISTS `cats_passed`;
+CREATE TABLE `cats_passed` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `user_id` int(11) NOT NULL,
+  `variant_id` int(11) NOT NULL,
+  `pass_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `test_passed` (`user_id`,`variant_id`,`pass_date`)
+) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=latin1;
+
+# --------------------------------------------------------
+
+#
+# Table structure for table `cats_type`
+#
+
+DROP TABLE IF EXISTS `cats_type`;
+CREATE TABLE `cats_type` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `type_text` varchar(255) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `type_text` (`type_text`)
+) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=latin1;
index c68cc06..21c8bf3 100644 (file)
@@ -83,7 +83,8 @@ public class SslConnection extends AbstractConnection
     private static final ByteBuffer __FILL_CALLED_FLUSH= BufferUtil.allocate(0);
     private static final ByteBuffer __FLUSH_CALLED_FILL= BufferUtil.allocate(0);
     private final ByteBufferPool _bufferPool;
-    private final SSLEngine _sslEngine;
+    private SSLEngine _sslEngine;
+    private final SslReconfigurator _sslFactory;
     private final DecryptedEndPoint _decryptedEndPoint;
     private ByteBuffer _decryptedInput;
     private ByteBuffer _encryptedInput;
@@ -101,12 +102,17 @@ public class SslConnection extends AbstractConnection
     private boolean _renegotiationAllowed;
 
     public SslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine sslEngine)
+    {
+       this(byteBufferPool, executor, endPoint, sslEngine, null);
+    }
+    public SslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine sslEngine, SslReconfigurator fact)
     {
         // This connection does not execute calls to onfillable, so they will be called by the selector thread.
         // onfillable does not block and will only wakeup another thread to do the actual reading and handling.
         super(endPoint, executor, !EXECUTE_ONFILLABLE);
         this._bufferPool = byteBufferPool;
         this._sslEngine = sslEngine;
+        this._sslFactory = fact;
         this._decryptedEndPoint = newDecryptedEndPoint();
     }
 
@@ -246,6 +252,7 @@ public class SslConnection extends AbstractConnection
         private boolean _cannotAcceptMoreAppDataToFlush;
         private boolean _handshaken;
         private boolean _underFlown;
+        private boolean _peeking = _sslFactory != null;
 
         private final Callback _writeCallback = new Callback()
         {
@@ -489,7 +496,7 @@ public class SslConnection extends AbstractConnection
                 // We will need a network buffer
                 if (_encryptedInput == null)
                     _encryptedInput = _bufferPool.acquire(_sslEngine.getSession().getPacketBufferSize(), _encryptedDirectBuffers);
-                else
+                else if(!_peeking)
                     BufferUtil.compact(_encryptedInput);
 
                 // We also need an app buffer, but can use the passed buffer if it is big enough
@@ -618,6 +625,13 @@ public class SslConnection extends AbstractConnection
                                     case NEED_TASK:
                                     {
                                         _sslEngine.getDelegatedTask().run();
+                                        if(_peeking)
+                                        {
+                                               _sslEngine = _sslFactory.restartSSL(_sslEngine.getHandshakeSession());
+                                               _encryptedInput.position(0);
+                                               _peeking = false;
+                                               continue decryption;
+                                        }
                                         continue;
                                     }
                                     case NEED_WRAP:
diff --git a/lib/jetty/org/eclipse/jetty/io/ssl/SslReconfigurator.java b/lib/jetty/org/eclipse/jetty/io/ssl/SslReconfigurator.java
new file mode 100644 (file)
index 0000000..b393d88
--- /dev/null
@@ -0,0 +1,29 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io.ssl;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLSession;
+
+public interface SslReconfigurator {
+    public boolean shouldRestartSSL();
+
+    public SSLEngine restartSSL(SSLSession sslSession);
+
+}
index eafa594..fe3ea14 100644 (file)
@@ -27,10 +27,11 @@ import org.eclipse.jetty.http.HttpVersion;
 import org.eclipse.jetty.io.Connection;
 import org.eclipse.jetty.io.EndPoint;
 import org.eclipse.jetty.io.ssl.SslConnection;
+import org.eclipse.jetty.io.ssl.SslReconfigurator;
 import org.eclipse.jetty.util.annotation.Name;
 import org.eclipse.jetty.util.ssl.SslContextFactory;
 
-public class SslConnectionFactory extends AbstractConnectionFactory
+public class SslConnectionFactory extends AbstractConnectionFactory implements SslReconfigurator
 {
     private final SslContextFactory _sslContextFactory;
     private final String _nextProtocol;
@@ -91,7 +92,15 @@ public class SslConnectionFactory extends AbstractConnectionFactory
 
     protected SslConnection newSslConnection(Connector connector, EndPoint endPoint, SSLEngine engine)
     {
-        return new SslConnection(connector.getByteBufferPool(), connector.getExecutor(), endPoint, engine);
+        return new SslConnection(connector.getByteBufferPool(), connector.getExecutor(), endPoint, engine, shouldRestartSSL()?this:null);
+    }
+    
+    public boolean shouldRestartSSL(){
+       return false;
+    }
+    
+    public SSLEngine restartSSL(SSLSession sslSession){
+       throw new UnsupportedOperationException();
     }
 
     @Override
index 928f850..23be9f4 100644 (file)
@@ -2213,7 +2213,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
             try
             {
                 @SuppressWarnings("unchecked")
-                Class<? extends EventListener> clazz = _classLoader==null?Loader.loadClass(ContextHandler.class,className):_classLoader.loadClass(className);
+                Class<? extends EventListener> clazz = (Class<? extends EventListener>) (_classLoader==null?Loader.loadClass(ContextHandler.class,className):_classLoader.loadClass(className));
                 addListener(clazz);
             }
             catch (ClassNotFoundException e)
diff --git a/lib/jtar/org/kamranzafar/jtar/Octal.java b/lib/jtar/org/kamranzafar/jtar/Octal.java
new file mode 100644 (file)
index 0000000..7a40ea1
--- /dev/null
@@ -0,0 +1,141 @@
+/**
+ * Copyright 2012 Kamran Zafar 
+ * 
+ * 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. 
+ * 
+ */
+
+package org.kamranzafar.jtar;
+
+/**
+ * @author Kamran Zafar
+ * 
+ */
+public class Octal {
+
+    /**
+     * Parse an octal string from a header buffer. This is used for the file
+     * permission mode value.
+     * 
+     * @param header
+     *            The header buffer from which to parse.
+     * @param offset
+     *            The offset into the buffer from which to parse.
+     * @param length
+     *            The number of header bytes to parse.
+     * 
+     * @return The long value of the octal string.
+     */
+    public static long parseOctal(byte[] header, int offset, int length) {
+        long result = 0;
+        boolean stillPadding = true;
+
+        int end = offset + length;
+        for (int i = offset; i < end; ++i) {
+            if (header[i] == 0)
+                break;
+
+            if (header[i] == (byte) ' ' || header[i] == '0') {
+                if (stillPadding)
+                    continue;
+
+                if (header[i] == (byte) ' ')
+                    break;
+            }
+
+            stillPadding = false;
+
+            result = ( result << 3 ) + ( header[i] - '0' );
+        }
+
+        return result;
+    }
+
+    /**
+     * Parse an octal integer from a header buffer.
+     * 
+     * @param value
+     * @param buf
+     *            The header buffer from which to parse.
+     * @param offset
+     *            The offset into the buffer from which to parse.
+     * @param length
+     *            The number of header bytes to parse.
+     * 
+     * @return The integer value of the octal bytes.
+     */
+    public static int getOctalBytes(long value, byte[] buf, int offset, int length) {
+        int idx = length - 1;
+
+        buf[offset + idx] = 0;
+        --idx;
+        buf[offset + idx] = (byte) ' ';
+        --idx;
+
+        if (value == 0) {
+            buf[offset + idx] = (byte) '0';
+            --idx;
+        } else {
+            for (long val = value; idx >= 0 && val > 0; --idx) {
+                buf[offset + idx] = (byte) ( (byte) '0' + (byte) ( val & 7 ) );
+                val = val >> 3;
+            }
+        }
+
+        for (; idx >= 0; --idx) {
+            buf[offset + idx] = (byte) ' ';
+        }
+
+        return offset + length;
+    }
+
+    /**
+     * Parse the checksum octal integer from a header buffer.
+     * 
+     * @param value
+     * @param buf
+     *            The header buffer from which to parse.
+     * @param offset
+     *            The offset into the buffer from which to parse.
+     * @param length
+     *            The number of header bytes to parse.
+     * @return The integer value of the entry's checksum.
+     */
+    public static int getCheckSumOctalBytes(long value, byte[] buf, int offset, int length) {
+        getOctalBytes( value, buf, offset, length );
+        buf[offset + length - 1] = (byte) ' ';
+        buf[offset + length - 2] = 0;
+        return offset + length;
+    }
+
+    /**
+     * Parse an octal long integer from a header buffer.
+     * 
+     * @param value
+     * @param buf
+     *            The header buffer from which to parse.
+     * @param offset
+     *            The offset into the buffer from which to parse.
+     * @param length
+     *            The number of header bytes to parse.
+     * 
+     * @return The long value of the octal bytes.
+     */
+    public static int getLongOctalBytes(long value, byte[] buf, int offset, int length) {
+        byte[] temp = new byte[length + 1];
+        getOctalBytes( value, temp, 0, length + 1 );
+        System.arraycopy( temp, 0, buf, offset, length );
+        return offset + length;
+    }
+
+}
diff --git a/lib/jtar/org/kamranzafar/jtar/TarConstants.java b/lib/jtar/org/kamranzafar/jtar/TarConstants.java
new file mode 100644 (file)
index 0000000..c85d0a7
--- /dev/null
@@ -0,0 +1,28 @@
+/**
+ * Copyright 2012 Kamran Zafar 
+ * 
+ * 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. 
+ * 
+ */
+
+package org.kamranzafar.jtar;
+
+/**
+ * @author Kamran Zafar
+ * 
+ */
+public class TarConstants {
+    public static final int EOF_BLOCK = 1024;
+    public static final int DATA_BLOCK = 512;
+    public static final int HEADER_BLOCK = 512;
+}
diff --git a/lib/jtar/org/kamranzafar/jtar/TarEntry.java b/lib/jtar/org/kamranzafar/jtar/TarEntry.java
new file mode 100644 (file)
index 0000000..c91eac6
--- /dev/null
@@ -0,0 +1,322 @@
+/**
+ * Copyright 2012 Kamran Zafar 
+ * 
+ * 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. 
+ * 
+ */
+
+package org.kamranzafar.jtar;
+
+import java.io.File;
+import java.util.Date;
+
+/**
+ * @author Kamran Zafar
+ * 
+ */
+public class TarEntry {
+       protected File file;
+       protected TarHeader header;
+
+       private TarEntry() {
+               this.file = null;
+               header = new TarHeader();
+       }
+
+       public TarEntry(File file, String entryName) {
+               this();
+               this.file = file;
+               this.extractTarHeader(entryName);
+       }
+
+       public TarEntry(byte[] headerBuf) {
+               this();
+               this.parseTarHeader(headerBuf);
+       }
+
+       /**
+        * Constructor to create an entry from an existing TarHeader object.
+        * 
+        * This method is useful to add new entries programmatically (e.g. for
+        * adding files or directories that do not exist in the file system).
+        * 
+        * @param header
+        * 
+        */
+       public TarEntry(TarHeader header) {
+               this.file = null;
+               this.header = header;
+       }
+
+       @Override
+       public boolean equals(Object it) {
+               if (!(it instanceof TarEntry)) {
+                       return false;
+               }
+               return header.name.toString().equals(
+                               ((TarEntry) it).header.name.toString());
+       }
+
+       @Override
+       public int hashCode() {
+               return header.name.hashCode();
+       }
+
+       public boolean isDescendent(TarEntry desc) {
+               return desc.header.name.toString().startsWith(header.name.toString());
+       }
+
+       public TarHeader getHeader() {
+               return header;
+       }
+
+       public String getName() {
+               String name = header.name.toString();
+               if (header.namePrefix != null
+                               && !header.namePrefix.toString().equals("")) {
+                       name = header.namePrefix.toString() + "/" + name;
+               }
+
+               return name;
+       }
+
+       public void setName(String name) {
+               header.name = new StringBuffer(name);
+       }
+
+       public int getUserId() {
+               return header.userId;
+       }
+
+       public void setUserId(int userId) {
+               header.userId = userId;
+       }
+
+       public int getGroupId() {
+               return header.groupId;
+       }
+
+       public void setGroupId(int groupId) {
+               header.groupId = groupId;
+       }
+
+       public String getUserName() {
+               return header.userName.toString();
+       }
+
+       public void setUserName(String userName) {
+               header.userName = new StringBuffer(userName);
+       }
+
+       public String getGroupName() {
+               return header.groupName.toString();
+       }
+
+       public void setGroupName(String groupName) {
+               header.groupName = new StringBuffer(groupName);
+       }
+
+       public void setIds(int userId, int groupId) {
+               this.setUserId(userId);
+               this.setGroupId(groupId);
+       }
+
+       public void setModTime(long time) {
+               header.modTime = time / 1000;
+       }
+
+       public void setModTime(Date time) {
+               header.modTime = time.getTime() / 1000;
+       }
+
+       public Date getModTime() {
+               return new Date(header.modTime * 1000);
+       }
+
+       public File getFile() {
+               return this.file;
+       }
+
+       public long getSize() {
+               return header.size;
+       }
+
+       public void setSize(long size) {
+               header.size = size;
+       }
+
+       /**
+        * Checks if the org.kamrazafar.jtar entry is a directory
+        * 
+        * @return
+        */
+       public boolean isDirectory() {
+               if (this.file != null) {
+                       return this.file.isDirectory();
+               }
+
+               if (header != null) {
+                       if (header.linkFlag == TarHeader.LF_DIR) {
+                               return true;
+                       }
+
+                       if (header.name.toString().endsWith("/")) {
+                               return true;
+                       }
+               }
+
+               return false;
+       }
+
+       /**
+        * Extract header from File
+        * 
+        * @param entryName
+        */
+       public void extractTarHeader(String entryName) {
+               header = TarHeader.createHeader(entryName, file.length(),
+                               file.lastModified() / 1000, file.isDirectory());
+       }
+
+       /**
+        * Calculate checksum
+        * 
+        * @param buf
+        * @return
+        */
+       public long computeCheckSum(byte[] buf) {
+               long sum = 0;
+
+               for (int i = 0; i < buf.length; ++i) {
+                       sum += 255 & buf[i];
+               }
+
+               return sum;
+       }
+
+       /**
+        * Writes the header to the byte buffer
+        * 
+        * @param outbuf
+        */
+       public void writeEntryHeader(byte[] outbuf) {
+               int offset = 0;
+
+               offset = TarHeader.getNameBytes(header.name, outbuf, offset,
+                               TarHeader.NAMELEN);
+               offset = Octal.getOctalBytes(header.mode, outbuf, offset,
+                               TarHeader.MODELEN);
+               offset = Octal.getOctalBytes(header.userId, outbuf, offset,
+                               TarHeader.UIDLEN);
+               offset = Octal.getOctalBytes(header.groupId, outbuf, offset,
+                               TarHeader.GIDLEN);
+
+               long size = header.size;
+
+               offset = Octal.getLongOctalBytes(size, outbuf, offset,
+                               TarHeader.SIZELEN);
+               offset = Octal.getLongOctalBytes(header.modTime, outbuf, offset,
+                               TarHeader.MODTIMELEN);
+
+               int csOffset = offset;
+               for (int c = 0; c < TarHeader.CHKSUMLEN; ++c) {
+                       outbuf[offset++] = (byte) ' ';
+               }
+
+               outbuf[offset++] = header.linkFlag;
+
+               offset = TarHeader.getNameBytes(header.linkName, outbuf, offset,
+                               TarHeader.NAMELEN);
+               offset = TarHeader.getNameBytes(header.magic, outbuf, offset,
+                               TarHeader.USTAR_MAGICLEN);
+               offset = TarHeader.getNameBytes(header.userName, outbuf, offset,
+                               TarHeader.USTAR_USER_NAMELEN);
+               offset = TarHeader.getNameBytes(header.groupName, outbuf, offset,
+                               TarHeader.USTAR_GROUP_NAMELEN);
+               offset = Octal.getOctalBytes(header.devMajor, outbuf, offset,
+                               TarHeader.USTAR_DEVLEN);
+               offset = Octal.getOctalBytes(header.devMinor, outbuf, offset,
+                               TarHeader.USTAR_DEVLEN);
+               offset = TarHeader.getNameBytes(header.namePrefix, outbuf, offset,
+                               TarHeader.USTAR_FILENAME_PREFIX);
+
+               for (; offset < outbuf.length;) {
+                       outbuf[offset++] = 0;
+               }
+
+               long checkSum = this.computeCheckSum(outbuf);
+
+               Octal.getCheckSumOctalBytes(checkSum, outbuf, csOffset,
+                               TarHeader.CHKSUMLEN);
+       }
+
+       /**
+        * Parses the tar header to the byte buffer
+        * 
+        * @param header
+        * @param bh
+        */
+       public void parseTarHeader(byte[] bh) {
+               int offset = 0;
+
+               header.name = TarHeader.parseName(bh, offset, TarHeader.NAMELEN);
+               offset += TarHeader.NAMELEN;
+
+               header.mode = (int) Octal.parseOctal(bh, offset, TarHeader.MODELEN);
+               offset += TarHeader.MODELEN;
+
+               header.userId = (int) Octal.parseOctal(bh, offset, TarHeader.UIDLEN);
+               offset += TarHeader.UIDLEN;
+
+               header.groupId = (int) Octal.parseOctal(bh, offset, TarHeader.GIDLEN);
+               offset += TarHeader.GIDLEN;
+
+               header.size = Octal.parseOctal(bh, offset, TarHeader.SIZELEN);
+               offset += TarHeader.SIZELEN;
+
+               header.modTime = Octal.parseOctal(bh, offset, TarHeader.MODTIMELEN);
+               offset += TarHeader.MODTIMELEN;
+
+               header.checkSum = (int) Octal.parseOctal(bh, offset,
+                               TarHeader.CHKSUMLEN);
+               offset += TarHeader.CHKSUMLEN;
+
+               header.linkFlag = bh[offset++];
+
+               header.linkName = TarHeader.parseName(bh, offset, TarHeader.NAMELEN);
+               offset += TarHeader.NAMELEN;
+
+               header.magic = TarHeader
+                               .parseName(bh, offset, TarHeader.USTAR_MAGICLEN);
+               offset += TarHeader.USTAR_MAGICLEN;
+
+               header.userName = TarHeader.parseName(bh, offset,
+                               TarHeader.USTAR_USER_NAMELEN);
+               offset += TarHeader.USTAR_USER_NAMELEN;
+
+               header.groupName = TarHeader.parseName(bh, offset,
+                               TarHeader.USTAR_GROUP_NAMELEN);
+               offset += TarHeader.USTAR_GROUP_NAMELEN;
+
+               header.devMajor = (int) Octal.parseOctal(bh, offset,
+                               TarHeader.USTAR_DEVLEN);
+               offset += TarHeader.USTAR_DEVLEN;
+
+               header.devMinor = (int) Octal.parseOctal(bh, offset,
+                               TarHeader.USTAR_DEVLEN);
+               offset += TarHeader.USTAR_DEVLEN;
+
+               header.namePrefix = TarHeader.parseName(bh, offset,
+                               TarHeader.USTAR_FILENAME_PREFIX);
+       }
+}
\ No newline at end of file
diff --git a/lib/jtar/org/kamranzafar/jtar/TarHeader.java b/lib/jtar/org/kamranzafar/jtar/TarHeader.java
new file mode 100644 (file)
index 0000000..deecaa0
--- /dev/null
@@ -0,0 +1,243 @@
+/**
+ * Copyright 2012 Kamran Zafar 
+ * 
+ * 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. 
+ * 
+ */
+
+package org.kamranzafar.jtar;
+
+import java.io.File;
+
+/**
+ * Header
+ * 
+ * <pre>
+ * Offset  Size     Field
+ * 0       100      File name
+ * 100     8        File mode
+ * 108     8        Owner's numeric user ID
+ * 116     8        Group's numeric user ID
+ * 124     12       File size in bytes
+ * 136     12       Last modification time in numeric Unix time format
+ * 148     8        Checksum for header block
+ * 156     1        Link indicator (file type)
+ * 157     100      Name of linked file
+ * </pre>
+ * 
+ * 
+ * File Types
+ * 
+ * <pre>
+ * Value        Meaning
+ * '0'          Normal file
+ * (ASCII NUL)  Normal file (now obsolete)
+ * '1'          Hard link
+ * '2'          Symbolic link
+ * '3'          Character special
+ * '4'          Block special
+ * '5'          Directory
+ * '6'          FIFO
+ * '7'          Contigous
+ * </pre>
+ * 
+ * 
+ * 
+ * Ustar header
+ * 
+ * <pre>
+ * Offset  Size    Field
+ * 257     6       UStar indicator "ustar"
+ * 263     2       UStar version "00"
+ * 265     32      Owner user name
+ * 297     32      Owner group name
+ * 329     8       Device major number
+ * 337     8       Device minor number
+ * 345     155     Filename prefix
+ * </pre>
+ */
+
+public class TarHeader {
+
+       /*
+        * Header
+        */
+       public static final int NAMELEN = 100;
+       public static final int MODELEN = 8;
+       public static final int UIDLEN = 8;
+       public static final int GIDLEN = 8;
+       public static final int SIZELEN = 12;
+       public static final int MODTIMELEN = 12;
+       public static final int CHKSUMLEN = 8;
+       public static final byte LF_OLDNORM = 0;
+
+       /*
+        * File Types
+        */
+       public static final byte LF_NORMAL = (byte) '0';
+       public static final byte LF_LINK = (byte) '1';
+       public static final byte LF_SYMLINK = (byte) '2';
+       public static final byte LF_CHR = (byte) '3';
+       public static final byte LF_BLK = (byte) '4';
+       public static final byte LF_DIR = (byte) '5';
+       public static final byte LF_FIFO = (byte) '6';
+       public static final byte LF_CONTIG = (byte) '7';
+
+       /*
+        * Ustar header
+        */
+
+       public static final String USTAR_MAGIC = "ustar"; // POSIX
+
+       public static final int USTAR_MAGICLEN = 8;
+       public static final int USTAR_USER_NAMELEN = 32;
+       public static final int USTAR_GROUP_NAMELEN = 32;
+       public static final int USTAR_DEVLEN = 8;
+       public static final int USTAR_FILENAME_PREFIX = 155;
+
+       // Header values
+       public StringBuffer name;
+       public int mode;
+       public int userId;
+       public int groupId;
+       public long size;
+       public long modTime;
+       public int checkSum;
+       public byte linkFlag;
+       public StringBuffer linkName;
+       public StringBuffer magic; // ustar indicator and version
+       public StringBuffer userName;
+       public StringBuffer groupName;
+       public int devMajor;
+       public int devMinor;
+       public StringBuffer namePrefix;
+
+       public TarHeader() {
+               this.magic = new StringBuffer(TarHeader.USTAR_MAGIC);
+
+               this.name = new StringBuffer();
+               this.linkName = new StringBuffer();
+
+               String user = System.getProperty("user.name", "");
+
+               if (user.length() > 31)
+                       user = user.substring(0, 31);
+
+               this.userId = 0;
+               this.groupId = 0;
+               this.userName = new StringBuffer(user);
+               this.groupName = new StringBuffer("");
+               this.namePrefix = new StringBuffer();
+       }
+
+       /**
+        * Parse an entry name from a header buffer.
+        * 
+        * @param name
+        * @param header
+        *            The header buffer from which to parse.
+        * @param offset
+        *            The offset into the buffer from which to parse.
+        * @param length
+        *            The number of header bytes to parse.
+        * @return The header's entry name.
+        */
+       public static StringBuffer parseName(byte[] header, int offset, int length) {
+               StringBuffer result = new StringBuffer(length);
+
+               int end = offset + length;
+               for (int i = offset; i < end; ++i) {
+                       if (header[i] == 0)
+                               break;
+                       result.append((char) header[i]);
+               }
+
+               return result;
+       }
+
+       /**
+        * Determine the number of bytes in an entry name.
+        * 
+        * @param name
+        * @param header
+        *            The header buffer from which to parse.
+        * @param offset
+        *            The offset into the buffer from which to parse.
+        * @param length
+        *            The number of header bytes to parse.
+        * @return The number of bytes in a header's entry name.
+        */
+       public static int getNameBytes(StringBuffer name, byte[] buf, int offset, int length) {
+               int i;
+
+               for (i = 0; i < length && i < name.length(); ++i) {
+                       buf[offset + i] = (byte) name.charAt(i);
+               }
+
+               for (; i < length; ++i) {
+                       buf[offset + i] = 0;
+               }
+
+               return offset + length;
+       }
+
+       /**
+        * Creates a new header for a file/directory entry.
+        * 
+        * 
+        * @param name
+        *            File name
+        * @param size
+        *            File size in bytes
+        * @param modTime
+        *            Last modification time in numeric Unix time format
+        * @param dir
+        *            Is directory
+        * 
+        * @return
+        */
+       public static TarHeader createHeader(String entryName, long size, long modTime, boolean dir) {
+               String name = entryName;
+               name = TarUtils.trim(name.replace(File.separatorChar, '/'), '/');
+
+               TarHeader header = new TarHeader();
+               header.linkName = new StringBuffer("");
+
+               if (name.length() > 100) {
+                       header.namePrefix = new StringBuffer(name.substring(0, name.lastIndexOf('/')));
+                       header.name = new StringBuffer(name.substring(name.lastIndexOf('/') + 1));
+               } else {
+                       header.name = new StringBuffer(name);
+               }
+
+               if (dir) {
+                       header.mode = 040755;
+                       header.linkFlag = TarHeader.LF_DIR;
+                       if (header.name.charAt(header.name.length() - 1) != '/') {
+                               header.name.append("/");
+                       }
+                       header.size = 0;
+               } else {
+                       header.mode = 0100644;
+                       header.linkFlag = TarHeader.LF_NORMAL;
+                       header.size = size;
+               }
+
+               header.modTime = modTime;
+               header.checkSum = 0;
+               header.devMajor = 0;
+               header.devMinor = 0;
+
+               return header;
+       }
+}
\ No newline at end of file
diff --git a/lib/jtar/org/kamranzafar/jtar/TarInputStream.java b/lib/jtar/org/kamranzafar/jtar/TarInputStream.java
new file mode 100644 (file)
index 0000000..cd48ae0
--- /dev/null
@@ -0,0 +1,249 @@
+/**
+ * Copyright 2012 Kamran Zafar 
+ * 
+ * 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. 
+ * 
+ */
+
+package org.kamranzafar.jtar;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * @author Kamran Zafar
+ * 
+ */
+public class TarInputStream extends FilterInputStream {
+
+       private static final int SKIP_BUFFER_SIZE = 2048;
+       private TarEntry currentEntry;
+       private long currentFileSize;
+       private long bytesRead;
+       private boolean defaultSkip = false;
+
+       public TarInputStream(InputStream in) {
+               super(in);
+               currentFileSize = 0;
+               bytesRead = 0;
+       }
+
+       @Override
+       public boolean markSupported() {
+               return false;
+       }
+
+       /**
+        * Not supported
+        * 
+        */
+       @Override
+       public synchronized void mark(int readlimit) {
+       }
+
+       /**
+        * Not supported
+        * 
+        */
+       @Override
+       public synchronized void reset() throws IOException {
+               throw new IOException("mark/reset not supported");
+       }
+
+       /**
+        * Read a byte
+        * 
+        * @see java.io.FilterInputStream#read()
+        */
+       @Override
+       public int read() throws IOException {
+               byte[] buf = new byte[1];
+
+               int res = this.read(buf, 0, 1);
+
+               if (res != -1) {
+                       return 0xFF & buf[0];
+               }
+
+               return res;
+       }
+
+       /**
+        * Checks if the bytes being read exceed the entry size and adjusts the byte
+        * array length. Updates the byte counters
+        * 
+        * 
+        * @see java.io.FilterInputStream#read(byte[], int, int)
+        */
+       @Override
+       public int read(byte[] b, int off, int len) throws IOException {
+               if (currentEntry != null) {
+                       if (currentFileSize == currentEntry.getSize()) {
+                               return -1;
+                       } else if ((currentEntry.getSize() - currentFileSize) < len) {
+                               len = (int) (currentEntry.getSize() - currentFileSize);
+                       }
+               }
+
+               int br = super.read(b, off, len);
+
+               if (br != -1) {
+                       if (currentEntry != null) {
+                               currentFileSize += br;
+                       }
+
+                       bytesRead += br;
+               }
+
+               return br;
+       }
+
+       /**
+        * Returns the next entry in the tar file
+        * 
+        * @return TarEntry
+        * @throws IOException
+        */
+       public TarEntry getNextEntry() throws IOException {
+               closeCurrentEntry();
+
+               byte[] header = new byte[TarConstants.HEADER_BLOCK];
+               byte[] theader = new byte[TarConstants.HEADER_BLOCK];
+               int tr = 0;
+
+               // Read full header
+               while (tr < TarConstants.HEADER_BLOCK) {
+                       int res = read(theader, 0, TarConstants.HEADER_BLOCK - tr);
+
+                       if (res < 0) {
+                               break;
+                       }
+
+                       System.arraycopy(theader, 0, header, tr, res);
+                       tr += res;
+               }
+
+               // Check if record is null
+               boolean eof = true;
+               for (byte b : header) {
+                       if (b != 0) {
+                               eof = false;
+                               break;
+                       }
+               }
+
+               if (!eof) {
+                       currentEntry = new TarEntry(header);
+               }
+
+               return currentEntry;
+       }
+
+       /**
+        * Returns the current offset (in bytes) from the beginning of the stream. 
+        * This can be used to find out at which point in a tar file an entry's content begins, for instance. 
+        */
+       public long getCurrentOffset() {
+               return bytesRead;
+       }
+       
+       /**
+        * Closes the current tar entry
+        * 
+        * @throws IOException
+        */
+       protected void closeCurrentEntry() throws IOException {
+               if (currentEntry != null) {
+                       if (currentEntry.getSize() > currentFileSize) {
+                               // Not fully read, skip rest of the bytes
+                               long bs = 0;
+                               while (bs < currentEntry.getSize() - currentFileSize) {
+                                       long res = skip(currentEntry.getSize() - currentFileSize - bs);
+
+                                       if (res == 0 && currentEntry.getSize() - currentFileSize > 0) {
+                                               // I suspect file corruption
+                                               throw new IOException("Possible tar file corruption");
+                                       }
+
+                                       bs += res;
+                               }
+                       }
+
+                       currentEntry = null;
+                       currentFileSize = 0L;
+                       skipPad();
+               }
+       }
+
+       /**
+        * Skips the pad at the end of each tar entry file content
+        * 
+        * @throws IOException
+        */
+       protected void skipPad() throws IOException {
+               if (bytesRead > 0) {
+                       int extra = (int) (bytesRead % TarConstants.DATA_BLOCK);
+
+                       if (extra > 0) {
+                               long bs = 0;
+                               while (bs < TarConstants.DATA_BLOCK - extra) {
+                                       long res = skip(TarConstants.DATA_BLOCK - extra - bs);
+                                       bs += res;
+                               }
+                       }
+               }
+       }
+
+       /**
+        * Skips 'n' bytes on the InputStream<br>
+        * Overrides default implementation of skip
+        * 
+        */
+       @Override
+       public long skip(long n) throws IOException {
+               if (defaultSkip) {
+                       // use skip method of parent stream
+                       // may not work if skip not implemented by parent
+                       long bs = super.skip(n);
+                       bytesRead += bs;
+
+                       return bs;
+               }
+
+               if (n <= 0) {
+                       return 0;
+               }
+
+               long left = n;
+               byte[] sBuff = new byte[SKIP_BUFFER_SIZE];
+
+               while (left > 0) {
+                       int res = read(sBuff, 0, (int) (left < SKIP_BUFFER_SIZE ? left : SKIP_BUFFER_SIZE));
+                       if (res < 0) {
+                               break;
+                       }
+                       left -= res;
+               }
+
+               return n - left;
+       }
+
+       public boolean isDefaultSkip() {
+               return defaultSkip;
+       }
+
+       public void setDefaultSkip(boolean defaultSkip) {
+               this.defaultSkip = defaultSkip;
+       }
+}
diff --git a/lib/jtar/org/kamranzafar/jtar/TarOutputStream.java b/lib/jtar/org/kamranzafar/jtar/TarOutputStream.java
new file mode 100644 (file)
index 0000000..e17413c
--- /dev/null
@@ -0,0 +1,163 @@
+/**
+ * Copyright 2012 Kamran Zafar 
+ * 
+ * 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. 
+ * 
+ */
+
+package org.kamranzafar.jtar;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+
+/**
+ * @author Kamran Zafar
+ * 
+ */
+public class TarOutputStream extends OutputStream {
+       private final OutputStream out;
+    private long bytesWritten;
+    private long currentFileSize;
+    private TarEntry currentEntry;
+
+    public TarOutputStream(OutputStream out) {
+        this.out = out;
+        bytesWritten = 0;
+        currentFileSize = 0;
+    }
+
+       public TarOutputStream(final File fout) throws FileNotFoundException {
+               this.out = new BufferedOutputStream(new FileOutputStream(fout));
+               bytesWritten = 0;
+               currentFileSize = 0;
+       }
+
+       /**
+        * Opens a file for writing. 
+        */
+       public TarOutputStream(final File fout, final boolean append) throws IOException {
+               @SuppressWarnings("resource")
+               RandomAccessFile raf = new RandomAccessFile(fout, "rw");
+               final long fileSize = fout.length();
+               if (append && fileSize > TarConstants.EOF_BLOCK) {
+                       raf.seek(fileSize - TarConstants.EOF_BLOCK);
+               }
+               out = new BufferedOutputStream(new FileOutputStream(raf.getFD()));
+       }
+
+    /**
+     * Appends the EOF record and closes the stream
+     * 
+     * @see java.io.FilterOutputStream#close()
+     */
+    @Override
+    public void close() throws IOException {
+        closeCurrentEntry();
+        write( new byte[TarConstants.EOF_BLOCK] );
+        out.close();
+    }
+    /**
+     * Writes a byte to the stream and updates byte counters
+     * 
+     * @see java.io.FilterOutputStream#write(int)
+     */
+    @Override
+    public void write(int b) throws IOException {
+        out.write( b );
+        bytesWritten += 1;
+
+        if (currentEntry != null) {
+            currentFileSize += 1;
+        }
+    }
+
+    /**
+     * Checks if the bytes being written exceed the current entry size.
+     * 
+     * @see java.io.FilterOutputStream#write(byte[], int, int)
+     */
+    @Override
+    public void write(byte[] b, int off, int len) throws IOException {
+        if (currentEntry != null && !currentEntry.isDirectory()) {
+            if (currentEntry.getSize() < currentFileSize + len) {
+                throw new IOException( "The current entry[" + currentEntry.getName() + "] size["
+                        + currentEntry.getSize() + "] is smaller than the bytes[" + ( currentFileSize + len )
+                        + "] being written." );
+            }
+        }
+
+        out.write( b, off, len );
+        
+        bytesWritten += len;
+
+        if (currentEntry != null) {
+            currentFileSize += len;
+        }        
+    }
+
+    /**
+     * Writes the next tar entry header on the stream
+     * 
+     * @param entry
+     * @throws IOException
+     */
+    public void putNextEntry(TarEntry entry) throws IOException {
+        closeCurrentEntry();
+
+        byte[] header = new byte[TarConstants.HEADER_BLOCK];
+        entry.writeEntryHeader( header );
+
+        write( header );
+
+        currentEntry = entry;
+    }
+
+    /**
+     * Closes the current tar entry
+     * 
+     * @throws IOException
+     */
+    protected void closeCurrentEntry() throws IOException {
+        if (currentEntry != null) {
+            if (currentEntry.getSize() > currentFileSize) {
+                throw new IOException( "The current entry[" + currentEntry.getName() + "] of size["
+                        + currentEntry.getSize() + "] has not been fully written." );
+            }
+
+            currentEntry = null;
+            currentFileSize = 0;
+
+            pad();
+        }
+    }
+
+    /**
+     * Pads the last content block
+     * 
+     * @throws IOException
+     */
+    protected void pad() throws IOException {
+        if (bytesWritten > 0) {
+            int extra = (int) ( bytesWritten % TarConstants.DATA_BLOCK );
+
+            if (extra > 0) {
+                write( new byte[TarConstants.DATA_BLOCK - extra] );
+            }
+        }
+    }
+}
diff --git a/lib/jtar/org/kamranzafar/jtar/TarUtils.java b/lib/jtar/org/kamranzafar/jtar/TarUtils.java
new file mode 100644 (file)
index 0000000..8dccc37
--- /dev/null
@@ -0,0 +1,96 @@
+/**
+ * Copyright 2012 Kamran Zafar 
+ * 
+ * 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. 
+ * 
+ */
+
+package org.kamranzafar.jtar;
+
+import java.io.File;
+
+/**
+ * @author Kamran
+ * 
+ */
+public class TarUtils {
+       /**
+        * Determines the tar file size of the given folder/file path
+        * 
+        * @param path
+        * @return
+        */
+       public static long calculateTarSize(File path) {
+               return tarSize(path) + TarConstants.EOF_BLOCK;
+       }
+
+       private static long tarSize(File dir) {
+               long size = 0;
+
+               if (dir.isFile()) {
+                       return entrySize(dir.length());
+               } else {
+                       File[] subFiles = dir.listFiles();
+
+                       if (subFiles != null && subFiles.length > 0) {
+                               for (File file : subFiles) {
+                                       if (file.isFile()) {
+                                               size += entrySize(file.length());
+                                       } else {
+                                               size += tarSize(file);
+                                       }
+                               }
+                       } else {
+                               // Empty folder header
+                               return TarConstants.HEADER_BLOCK;
+                       }
+               }
+
+               return size;
+       }
+
+       private static long entrySize(long fileSize) {
+               long size = 0;
+               size += TarConstants.HEADER_BLOCK; // Header
+               size += fileSize; // File size
+
+               long extra = size % TarConstants.DATA_BLOCK;
+
+               if (extra > 0) {
+                       size += (TarConstants.DATA_BLOCK - extra); // pad
+               }
+
+               return size;
+       }
+
+       public static String trim(String s, char c) {
+               StringBuffer tmp = new StringBuffer(s);
+               for (int i = 0; i < tmp.length(); i++) {
+                       if (tmp.charAt(i) != c) {
+                               break;
+                       } else {
+                               tmp.deleteCharAt(i);
+                       }
+               }
+
+               for (int i = tmp.length() - 1; i >= 0; i--) {
+                       if (tmp.charAt(i) != c) {
+                               break;
+                       } else {
+                               tmp.deleteCharAt(i);
+                       }
+               }
+
+               return tmp.toString();
+       }
+}
diff --git a/locale/.gitignore b/locale/.gitignore
new file mode 100644 (file)
index 0000000..917eade
--- /dev/null
@@ -0,0 +1,3 @@
+*
+!.gitignore
+a
\ No newline at end of file
diff --git a/natives/.gitignore b/natives/.gitignore
new file mode 100644 (file)
index 0000000..0b5c1b3
--- /dev/null
@@ -0,0 +1,2 @@
+/libsetuid.so
+*.h
diff --git a/natives/Makefile b/natives/Makefile
new file mode 100644 (file)
index 0000000..b58e400
--- /dev/null
@@ -0,0 +1,12 @@
+SYSTEM= $(shell uname | awk '{print tolower($$0)}')
+
+
+all: libsetuid.so
+
+libsetuid.so:
+       javah -classpath ../bin/ -jni org.cacert.gigi.natives.SetUID    
+       gcc -fPIC -o libsetuid.so -shared -I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/$(SYSTEM) org_cacert_gigi_natives_SetUID.c
+
+clean:
+       rm -f *.so
+       rm -f *.h
diff --git a/natives/org_cacert_gigi_natives_SetUID.c b/natives/org_cacert_gigi_natives_SetUID.c
new file mode 100644 (file)
index 0000000..f0ae7cb
--- /dev/null
@@ -0,0 +1,36 @@
+#include <jni.h>  
+#include <sys/types.h>
+#include <unistd.h> 
+  
+#ifndef _Included_org_cacert_natives_SetUID  
+#define _Included_org_cacert_natives_SetUID  
+#ifdef __cplusplus  
+extern "C" {  
+#endif  
+  
+jobject getStatus(JNIEnv *env, int successCode, const char * message) {  
+  
+   jstring message_str = (*env)->NewStringUTF(env, message);
+   jboolean success = successCode;  
+   jclass cls = (*env)->FindClass(env, "Lorg/cacert/gigi/natives/SetUID$Status;");  
+   jmethodID constructor = (*env)->GetMethodID(env, cls, "<init>", "(ZLjava/lang/String;)V");  
+   return (*env)->NewObject(env, cls, constructor, success, message_str);  
+}  
+  
+JNIEXPORT jobject JNICALL Java_org_cacert_gigi_natives_SetUID_setUid  
+  (JNIEnv *env, jobject obj, jint uid, jint gid) {  
+         if(setgid((int)gid)) {  
+         return (jobject)getStatus(env, 0, "Error while setting GID.");  
+      } 
+  
+      if(setuid((int)uid)) {
+         return (jobject)getStatus(env, 0, "Error while setting UID.");  
+      }  
+  
+      return (jobject)getStatus(env, 1, "Successfully set uid/gid.");  
+}  
+  
+#ifdef __cplusplus  
+}  
+#endif  
+#endif  
diff --git a/src/org/cacert/gigi/DevelLauncher.java b/src/org/cacert/gigi/DevelLauncher.java
new file mode 100644 (file)
index 0000000..74a4ae6
--- /dev/null
@@ -0,0 +1,77 @@
+package org.cacert.gigi;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Properties;
+
+import org.kamranzafar.jtar.TarEntry;
+import org.kamranzafar.jtar.TarHeader;
+import org.kamranzafar.jtar.TarOutputStream;
+
+public class DevelLauncher {
+       public static void main(String[] args) throws Exception {
+               Properties mainProps = new Properties();
+               mainProps.load(new FileInputStream("config/gigi.properties"));
+               for (int i = 0; i < args.length; i++) {
+                       if (args[i].equals("--port")) {
+                               mainProps.setProperty("port", args[i + 1]);
+                       }
+                       i++;
+               }
+
+               ByteArrayOutputStream chunkConfig = new ByteArrayOutputStream();
+               DataOutputStream dos = new DataOutputStream(chunkConfig);
+               byte[] cacerts = Files.readAllBytes(Paths.get("config/cacerts.jks"));
+               byte[] keystore = Files.readAllBytes(Paths
+                               .get("config/keystore.pkcs12"));
+
+               DevelLauncher.writeGigiConfig(dos, new byte[]{}, "changeit".getBytes(),
+                               mainProps, cacerts, keystore);
+               dos.flush();
+               InputStream oldin = System.in;
+               System.setIn(new ByteArrayInputStream(chunkConfig.toByteArray()));
+               Launcher.main(args);
+               System.setIn(oldin);
+       }
+       public static void writeGigiConfig(OutputStream target, byte[] keystorepw,
+                       byte[] truststorepw, Properties mainprop, byte[] cacerts,
+                       byte[] keystore) throws IOException {
+               TarOutputStream tos = new TarOutputStream(target);
+               ByteArrayOutputStream baos = new ByteArrayOutputStream();
+               mainprop.store(baos, "");
+
+               putTarEntry(baos.toByteArray(), tos, "gigi.properties");
+               putTarEntry(keystorepw, tos, "keystorepw");
+               putTarEntry(truststorepw, tos, "truststorepw");
+               putTarEntry(keystore, tos, "keystore.pkcs12");
+               putTarEntry(cacerts, tos, "cacerts.jks");
+               tos.close();
+
+       }
+       private static void putTarEntry(byte[] data, TarOutputStream tos,
+                       String name) throws IOException {
+               TarHeader th = new TarHeader();
+               th.name = new StringBuffer(name);
+               th.size = data.length;
+               tos.putNextEntry(new TarEntry(th));
+               tos.write(data);
+       }
+       public static void writeChunk(DataOutputStream dos, byte[] chunk)
+                       throws IOException {
+               dos.writeInt(chunk.length);
+               dos.write(chunk);
+       }
+       public static void launch(Properties props, File cacerts, File keystore)
+                       throws IOException {
+               ByteArrayOutputStream config = new ByteArrayOutputStream();
+               props.store(config, "");
+       }
+}
diff --git a/src/org/cacert/gigi/Gigi.java b/src/org/cacert/gigi/Gigi.java
new file mode 100644 (file)
index 0000000..cef1834
--- /dev/null
@@ -0,0 +1,157 @@
+package org.cacert.gigi;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Properties;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.email.EmailProvider;
+import org.cacert.gigi.pages.LoginPage;
+import org.cacert.gigi.pages.MainPage;
+import org.cacert.gigi.pages.Page;
+import org.cacert.gigi.pages.TestSecure;
+import org.cacert.gigi.pages.Verify;
+import org.cacert.gigi.pages.account.MailAdd;
+import org.cacert.gigi.pages.account.MailCertificates;
+import org.cacert.gigi.pages.account.MailOverview;
+import org.cacert.gigi.pages.account.MyDetails;
+import org.cacert.gigi.pages.main.RegisterPage;
+import org.cacert.gigi.pages.wot.AssurePage;
+import org.eclipse.jetty.util.log.Log;
+
+public class Gigi extends HttpServlet {
+       public static final String LOGGEDIN = "loggedin";
+       public static final String USER = "user";
+       private static final long serialVersionUID = -6386785421902852904L;
+       private String[] baseTemplate;
+       private HashMap<String, Page> pages = new HashMap<String, Page>();
+
+       public Gigi(Properties conf) {
+               EmailProvider.init(conf);
+               DatabaseConnection.init(conf);
+       }
+       @Override
+       public void init() throws ServletException {
+               pages.put("/login", new LoginPage("CACert - Login"));
+               pages.put("/", new MainPage("CACert - Home"));
+               pages.put("/secure", new TestSecure());
+               pages.put(Verify.PATH, new Verify());
+               pages.put(AssurePage.PATH + "/*", new AssurePage());
+               pages.put(MailCertificates.PATH, new MailCertificates());
+               pages.put(MyDetails.PATH, new MyDetails());
+               pages.put(RegisterPage.PATH, new RegisterPage());
+               pages.put(MailOverview.DEFAULT_PATH, new MailOverview(
+                               "My email addresses"));
+               pages.put(MailAdd.DEFAULT_PATH, new MailAdd("Add new email"));
+               String templ = "";
+               try (BufferedReader reader = new BufferedReader(new InputStreamReader(
+                               new FileInputStream(new File("templates/base.html"))))) {
+                       String tmp;
+                       while ((tmp = reader.readLine()) != null) {
+                               templ += tmp + "\n";
+                       }
+                       baseTemplate = templ.split("\\$content\\$");
+               } catch (Exception e) {
+                       Log.getLogger(Gigi.class).warn("Error loading template!", e);
+               }
+               super.init();
+
+       }
+       @Override
+       protected void service(HttpServletRequest req, HttpServletResponse resp)
+                       throws ServletException, IOException {
+               addXSSHeaders(resp);
+               if (req.getHeader("Origin") != null) {
+                       resp.getWriter().println("No cross domain access allowed.");
+                       return;
+               }
+               HttpSession hs = req.getSession();
+               if (req.getPathInfo() != null && req.getPathInfo().equals("/logout")) {
+                       if (hs != null) {
+                               hs.setAttribute(LOGGEDIN, null);
+                               hs.invalidate();
+                       }
+                       resp.sendRedirect("/");
+                       return;
+               }
+
+               Page p = getPage(req.getPathInfo());
+               if (p != null) {
+
+                       if (p.needsLogin() && hs.getAttribute("loggedin") == null) {
+                               String request = req.getPathInfo();
+                               request = request.split("\\?")[0];
+                               hs.setAttribute(LoginPage.LOGIN_RETURNPATH, request);
+                               resp.sendRedirect("/login");
+                               return;
+                       }
+                       if (p.beforeTemplate(req, resp)) {
+                               return;
+                       }
+
+                       String b0 = baseTemplate[0];
+                       b0 = makeDynTempl(b0, p);
+                       resp.setContentType("text/html; charset=utf-8");
+                       resp.getWriter().print(b0);
+                       if (req.getMethod().equals("POST")) {
+                               p.doPost(req, resp);
+                       } else {
+                               p.doGet(req, resp);
+                       }
+                       String b1 = baseTemplate[1];
+                       b1 = makeDynTempl(b1, p);
+                       resp.getWriter().print(b1);
+               } else {
+                       resp.sendError(404, "Page not found.");
+               }
+
+       }
+       private Page getPage(String pathInfo) {
+               if (pathInfo.endsWith("/") && !pathInfo.equals("/")) {
+                       pathInfo = pathInfo.substring(0, pathInfo.length() - 1);
+               }
+               Page page = pages.get(pathInfo);
+               if (page != null) {
+                       return page;
+               }
+               page = pages.get(pathInfo + "/*");
+               if (page != null) {
+                       return page;
+               }
+               int idx = pathInfo.lastIndexOf('/');
+               pathInfo = pathInfo.substring(0, idx);
+
+               page = pages.get(pathInfo + "/*");
+               if (page != null) {
+                       return page;
+               }
+               return null;
+
+       }
+       private String makeDynTempl(String in, Page p) {
+               int year = Calendar.getInstance().get(Calendar.YEAR);
+               in = in.replaceAll("\\$title\\$", p.getTitle());
+               in = in.replaceAll("\\$year\\$", year + "");
+               return in;
+       }
+       public static void addXSSHeaders(HttpServletResponse hsr) {
+               hsr.addHeader("Access-Control-Allow-Origin",
+                               "http://cacert.org https://localhost");
+               hsr.addHeader("Access-Control-Max-Age", "60");
+               hsr.addHeader("Content-Security-Policy",
+                               "default-src 'self' https://www.cacert.org/*;frame-ancestors 'none'");
+               // ;report-uri https://felix.dogcraft.de/report.php
+
+       }
+}
diff --git a/src/org/cacert/gigi/GigiConfig.java b/src/org/cacert/gigi/GigiConfig.java
new file mode 100644 (file)
index 0000000..69c95bb
--- /dev/null
@@ -0,0 +1,87 @@
+package org.cacert.gigi;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.util.Properties;
+
+import org.kamranzafar.jtar.TarEntry;
+import org.kamranzafar.jtar.TarInputStream;
+
+public class GigiConfig {
+       public static final String GIGI_CONFIG_VERSION = "GigiConfigV1.0";
+       byte[] cacerts;
+       byte[] keystore;
+       Properties mainProps = new Properties();
+       private char[] keystorpw;
+       private char[] truststorepw;
+
+       private GigiConfig() {
+       }
+       public byte[] getCacerts() {
+               return cacerts;
+       }
+       public byte[] getKeystore() {
+               return keystore;
+       }
+       public Properties getMainProps() {
+               return mainProps;
+       }
+
+       public static GigiConfig parse(InputStream input) throws IOException {
+               TarInputStream tis = new TarInputStream(input);
+               TarEntry t;
+               GigiConfig gc = new GigiConfig();
+               while ((t = tis.getNextEntry()) != null) {
+                       if (t.getName().equals("gigi.properties")) {
+                               gc.mainProps.load(tis);
+                       } else if (t.getName().equals("cacerts.jks")) {
+                               gc.cacerts = readFully(tis);
+                       } else if (t.getName().equals("keystore.pkcs12")) {
+                               gc.keystore = readFully(tis);
+                       } else if (t.getName().equals("keystorepw")) {
+                               gc.keystorpw = transformSafe(readFully(tis));
+                       } else if (t.getName().equals("truststorepw")) {
+                               gc.truststorepw = transformSafe(readFully(tis));
+                       } else {
+                               System.out.println("Unknown config: " + t.getName());
+                       }
+               }
+               tis.close();
+               return gc;
+       }
+       public static byte[] readFully(InputStream is) throws IOException {
+               ByteArrayOutputStream baos = new ByteArrayOutputStream();
+               byte[] buffer = new byte[1024];
+               int len = 0;
+               while ((len = is.read(buffer)) > 0) {
+                       baos.write(buffer, 0, len);
+               }
+               baos.close();
+               return baos.toByteArray();
+       }
+       private static char[] transformSafe(byte[] readChunk) {
+               char[] res = new char[readChunk.length];
+               for (int i = 0; i < res.length; i++) {
+                       res[i] = (char) readChunk[i];
+                       readChunk[i] = 0;
+               }
+               return res;
+       }
+
+       public KeyStore getPrivateStore() throws GeneralSecurityException,
+                       IOException {
+               KeyStore ks1 = KeyStore.getInstance("pkcs12");
+               ks1.load(new ByteArrayInputStream(keystore), keystorpw);
+               return ks1;
+       }
+       public KeyStore getTrustStore() throws GeneralSecurityException,
+                       IOException {
+               KeyStore ks1 = KeyStore.getInstance("jks");
+               ks1.load(new ByteArrayInputStream(cacerts), truststorepw);
+               return ks1;
+       }
+}
diff --git a/src/org/cacert/gigi/Language.java b/src/org/cacert/gigi/Language.java
new file mode 100644 (file)
index 0000000..5841c3c
--- /dev/null
@@ -0,0 +1,73 @@
+package org.cacert.gigi;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Locale;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+public class Language {
+       private static HashMap<String, Language> langs = new HashMap<String, Language>();
+       HashMap<String, String> translations = new HashMap<String, String>();
+       Locale l;
+       private Language(String language) throws ParserConfigurationException,
+                       IOException, SAXException {
+               if (language.contains("_")) {
+                       String[] parts = language.split("_");
+                       l = new Locale(parts[0], parts[1]);
+               } else {
+                       l = new Locale(language);
+               }
+
+               DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+               DocumentBuilder db = dbf.newDocumentBuilder();
+               Document d = db.parse(new FileInputStream(new File("locale", language
+                               + ".xml")));
+               NodeList nl = d.getDocumentElement().getChildNodes();
+               for (int i = 0; i < nl.getLength(); i++) {
+                       if (!(nl.item(i) instanceof Element)) {
+                               continue;
+                       }
+                       Element e = (Element) nl.item(i);
+                       Element id = (Element) e.getElementsByTagName("id").item(0);
+                       Element msg = (Element) e.getElementsByTagName("msg").item(0);
+                       translations.put(id.getTextContent(), msg.getTextContent());
+               }
+               System.out.println(translations.size() + " strings loaded.");
+       }
+       public String getTranslation(String text) {
+               String string = translations.get(text);
+               if (string == null || string.equals("")) {
+                       return text;
+               }
+               return string;
+       }
+       public static Language getInstance(String language) {
+               Language l = langs.get(language);
+               if (l == null) {
+                       try {
+                               l = new Language(language);
+                               langs.put(language, l);
+                       } catch (ParserConfigurationException e) {
+                               e.printStackTrace();
+                       } catch (IOException e) {
+                               e.printStackTrace();
+                       } catch (SAXException e) {
+                               e.printStackTrace();
+                       }
+               }
+               return l;
+       }
+       public Locale getLocale() {
+               return l;
+       }
+
+}
diff --git a/src/org/cacert/gigi/Launcher.java b/src/org/cacert/gigi/Launcher.java
new file mode 100644 (file)
index 0000000..6c23490
--- /dev/null
@@ -0,0 +1,121 @@
+package org.cacert.gigi;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.util.Properties;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.TrustManagerFactory;
+import org.cacert.gigi.natives.SetUID;
+import org.cacert.gigi.util.CipherInfo;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.SecureRequestCustomizer;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SessionManager;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.HandlerList;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.server.handler.ResourceHandler;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+public class Launcher {
+       public static void main(String[] args) throws Exception {
+               GigiConfig conf = GigiConfig.parse(System.in);
+
+               Server s = new Server();
+               // === SSL HTTP Configuration ===
+               HttpConfiguration https_config = new HttpConfiguration();
+               https_config.setSendServerVersion(false);
+               https_config.setSendXPoweredBy(false);
+
+               // for client-cert auth
+               https_config.addCustomizer(new SecureRequestCustomizer());
+
+               ServerConnector connector = new ServerConnector(s,
+                               new SslConnectionFactory(generateSSLContextFactory(conf),
+                                               "http/1.1"), new HttpConnectionFactory(https_config));
+               connector.setHost(conf.getMainProps().getProperty("host"));
+               connector.setPort(Integer.parseInt(conf.getMainProps().getProperty(
+                               "port")));
+               s.setConnectors(new Connector[]{connector});
+
+               HandlerList hl = new HandlerList();
+               hl.setHandlers(new Handler[]{generateStaticContext(),
+                               generateGigiContext(conf.getMainProps())});
+               s.setHandler(hl);
+               s.start();
+               if (connector.getPort() <= 1024
+                               && !System.getProperty("os.name").toLowerCase().contains("win")) {
+                       SetUID uid = new SetUID();
+                       if (!uid.setUid(65536 - 2, 65536 - 2).getSuccess()) {
+                               Log.getLogger(Launcher.class).warn("Couldn't set uid!");
+                       }
+               }
+       }
+
+       private static ServletContextHandler generateGigiContext(Properties conf) {
+               ServletContextHandler servlet = new ServletContextHandler(
+                               ServletContextHandler.SESSIONS);
+               servlet.setInitParameter(SessionManager.__SessionCookieProperty,
+                               "CACert-Session");
+               servlet.addServlet(new ServletHolder(new Gigi(conf)), "/*");
+               return servlet;
+       }
+
+       private static Handler generateStaticContext() {
+               final ResourceHandler rh = new ResourceHandler();
+               rh.setResourceBase("static");
+               HandlerWrapper hw = new PolicyRedirector();
+               hw.setHandler(rh);
+
+               ContextHandler ch = new ContextHandler();
+               ch.setContextPath("/static");
+               ch.setHandler(hw);
+
+               return ch;
+       }
+
+       private static SslContextFactory generateSSLContextFactory(GigiConfig conf)
+                       throws GeneralSecurityException, IOException {
+               TrustManagerFactory tmFactory = TrustManagerFactory.getInstance("PKIX");
+               tmFactory.init((KeyStore) null);
+
+               SslContextFactory scf = new SslContextFactory() {
+
+                       String[] ciphers = null;
+
+                       @Override
+                       public void customize(SSLEngine sslEngine) {
+                               super.customize(sslEngine);
+
+                               SSLParameters ssl = sslEngine.getSSLParameters();
+                               ssl.setUseCipherSuitesOrder(true);
+                               if (ciphers == null) {
+                                       ciphers = CipherInfo.filter(sslEngine
+                                                       .getSupportedCipherSuites());
+                               }
+
+                               ssl.setCipherSuites(ciphers);
+                               sslEngine.setSSLParameters(ssl);
+
+                       }
+
+               };
+               scf.setRenegotiationAllowed(false);
+               scf.setWantClientAuth(true);
+
+               scf.setProtocol("TLS");
+               scf.setTrustStore(conf.getTrustStore());
+               scf.setKeyStore(conf.getPrivateStore());
+               return scf;
+       }
+}
diff --git a/src/org/cacert/gigi/Name.java b/src/org/cacert/gigi/Name.java
new file mode 100644 (file)
index 0000000..933982e
--- /dev/null
@@ -0,0 +1,56 @@
+package org.cacert.gigi;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+import org.cacert.gigi.output.Outputable;
+
+public class Name implements Outputable {
+       String fname;
+       String mname;
+       String lname;
+       String suffix;
+
+       public Name(String fname, String lname) {
+               this.fname = fname;
+               this.lname = lname;
+       }
+
+       @Override
+       public void output(PrintWriter out, Language l, Map<String, Object> vars) {
+               out.println("<span class=\"accountdetail\">");
+               out.print("<span class=\"fname\">");
+               out.print(fname);
+               out.print("</span> ");
+               out.print("<span class=\"lname\">");
+               out.print(lname);
+               out.print("</span>");
+               out.println("</span>");
+       }
+       @Override
+       public boolean equals(Object obj) {
+               if (!(obj instanceof Name)) {
+                       return false;
+               }
+               Name n = (Name) obj;
+               if (!(n.fname.equals(fname) && n.lname.equals(lname))) {
+                       return false;
+               }
+               if (mname == null) {
+                       if (n.mname != null) {
+                               return false;
+                       }
+               } else if (!mname.equals(n.mname)) {
+                       return false;
+               }
+               if (suffix == null) {
+                       if (n.suffix != null) {
+                               return false;
+                       }
+               } else if (!suffix.equals(n.suffix)) {
+                       return false;
+               }
+               return true;
+
+       }
+}
diff --git a/src/org/cacert/gigi/PolicyRedirector.java b/src/org/cacert/gigi/PolicyRedirector.java
new file mode 100644 (file)
index 0000000..8714d95
--- /dev/null
@@ -0,0 +1,25 @@
+package org.cacert.gigi;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+
+public class PolicyRedirector extends HandlerWrapper {
+       @Override
+       public void handle(String target, Request baseRequest,
+                       HttpServletRequest request, HttpServletResponse response)
+                       throws IOException, ServletException {
+               if (target.startsWith("/policy/") && target.endsWith(".php")) {
+                       target = target.replace(".php", ".html");
+                       response.sendRedirect("/static" + target);
+                       baseRequest.setHandled(true);
+                       return;
+               }
+               super.handle(target, baseRequest, request, response);
+       }
+}
diff --git a/src/org/cacert/gigi/TestServlet.java b/src/org/cacert/gigi/TestServlet.java
new file mode 100644 (file)
index 0000000..82d8795
--- /dev/null
@@ -0,0 +1,48 @@
+package org.cacert.gigi;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.security.cert.X509Certificate;
+import java.util.Enumeration;
+
+import javax.net.ssl.SSLEngine;
+import javax.security.auth.x500.X500Principal;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.Request;
+
+public class TestServlet extends HttpServlet {
+       @Override
+       protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+                       throws ServletException, IOException {
+               Request r = (Request) req;
+               HttpChannel<?> hc = r.getHttpChannel();
+               EndPoint ep = hc.getEndPoint();
+               SSLEngine se;
+               Enumeration<String> names = req.getAttributeNames();
+               X509Certificate[] cert = (X509Certificate[]) req
+                               .getAttribute("javax.servlet.request.X509Certificate");
+               int keySize = (Integer) req
+                               .getAttribute("javax.servlet.request.key_size");
+               String ciphers = (String) req
+                               .getAttribute("javax.servlet.request.cipher_suite");
+               String sid = (String) req
+                               .getAttribute("javax.servlet.request.ssl_session_id");
+               PrintWriter out = resp.getWriter();
+               out.println("KeySize: " + keySize);
+               out.println("cipher: " + ciphers);
+               X509Certificate c1 = cert[0];
+               out.println("Serial:" + c1.getSerialNumber());
+               X500Principal client = c1.getSubjectX500Principal();
+               out.println("Name " + client.getName());
+               out.println(client.getName(X500Principal.RFC1779));
+               out.println(client.getName(X500Principal.RFC2253));
+               out.println("signature: " + c1.getSigAlgName());
+               out.println("issuer: " + c1.getSubjectX500Principal());
+               out.println("certCount: " + cert.length);
+       }
+}
diff --git a/src/org/cacert/gigi/User.java b/src/org/cacert/gigi/User.java
new file mode 100644 (file)
index 0000000..96e0a11
--- /dev/null
@@ -0,0 +1,179 @@
+package org.cacert.gigi;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Date;
+
+import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.util.PasswordHash;
+
+public class User {
+
+       private int id;
+       Name name = new Name(null, null);
+
+       Date dob;
+       String email;
+
+       public User(int id) {
+               this.id = id;
+               try {
+                       PreparedStatement ps = DatabaseConnection
+                                       .getInstance()
+                                       .prepare(
+                                                       "SELECT `fname`, `lname`, `dob`, `email` FROM `users` WHERE id=?");
+                       ps.setInt(1, id);
+                       ResultSet rs = ps.executeQuery();
+                       if (rs.next()) {
+                               name = new Name(rs.getString(1), rs.getString(2));
+                               dob = rs.getDate(3);
+                               email = rs.getString(4);
+                       }
+                       rs.close();
+               } catch (SQLException e) {
+                       e.printStackTrace();
+               }
+       }
+       public User() {
+       }
+       public int getId() {
+               return id;
+       }
+       public String getFname() {
+               return name.fname;
+       }
+       public String getLname() {
+               return name.lname;
+       }
+       public String getMname() {
+               return name.mname;
+       }
+       public Name getName() {
+               return name;
+       }
+       public void setMname(String mname) {
+               this.name.mname = mname;
+       }
+       public String getSuffix() {
+               return name.suffix;
+       }
+       public void setSuffix(String suffix) {
+               this.name.suffix = suffix;
+       }
+       public Date getDob() {
+               return dob;
+       }
+       public void setDob(Date dob) {
+               this.dob = dob;
+       }
+       public String getEmail() {
+               return email;
+       }
+       public void setEmail(String email) {
+               this.email = email;
+       }
+       public void setId(int id) {
+               this.id = id;
+       }
+       public void setFname(String fname) {
+               this.name.fname = fname;
+       }
+       public void setLname(String lname) {
+               this.name.lname = lname;
+       }
+       public void insert(String password) throws SQLException {
+               if (id != 0) {
+                       throw new Error("refusing to insert");
+               }
+               PreparedStatement query = DatabaseConnection.getInstance().prepare(
+                               "insert into `users` set `email`=?, `password`=?, "
+                                               + "`fname`=?, `mname`=?, `lname`=?, "
+                                               + "`suffix`=?, `dob`=?, `created`=NOW(), locked=0");
+               query.setString(1, email);
+               query.setString(2, PasswordHash.hash(password));
+               query.setString(3, name.fname);
+               query.setString(4, name.mname);
+               query.setString(5, name.lname);
+               query.setString(6, name.suffix);
+               query.setDate(7, new java.sql.Date(dob.getTime()));
+               query.execute();
+               id = DatabaseConnection.lastInsertId(query);
+               System.out.println("Inserted: " + id);
+       }
+
+       public boolean canAssure() throws SQLException {
+               if (getAssurancePoints() < 100) {
+                       return false;
+               }
+
+               return hasPassedCATS();
+
+       }
+       public boolean hasPassedCATS() throws SQLException {
+               PreparedStatement query = DatabaseConnection.getInstance().prepare(
+                               "SELECT 1 FROM `cats_passed` where `user_id`=?");
+               query.setInt(1, id);
+               ResultSet rs = query.executeQuery();
+               if (rs.next()) {
+                       return true;
+               } else {
+                       return false;
+               }
+       }
+       public int getAssurancePoints() throws SQLException {
+               PreparedStatement query = DatabaseConnection
+                               .getInstance()
+                               .prepare(
+                                               "SELECT sum(points) FROM `notary` where `to`=? AND `deleted`=0");
+               query.setInt(1, id);
+               ResultSet rs = query.executeQuery();
+               int points = 0;
+               if (rs.next()) {
+                       points = rs.getInt(1);
+               }
+               rs.close();
+               return points;
+       }
+       public int getExperiencePoints() throws SQLException {
+               PreparedStatement query = DatabaseConnection.getInstance().prepare(
+                               "SELECT count(*) FROM `notary` where `from`=? AND `deleted`=0");
+               query.setInt(1, id);
+               ResultSet rs = query.executeQuery();
+               int points = 0;
+               if (rs.next()) {
+                       points = rs.getInt(1) * 2;
+               }
+               rs.close();
+               return points;
+       }
+       @Override
+       public boolean equals(Object obj) {
+               if (!(obj instanceof User)) {
+                       return false;
+               }
+               User s = (User) obj;
+               return name.equals(s.name) && email.equals(s.email)
+                               && dob.equals(s.dob);
+       }
+       public int getMaxAssurePoints() throws SQLException {
+               int exp = getExperiencePoints();
+               int points = 10;
+               if (exp >= 10) {
+                       points += 5;
+               }
+               if (exp >= 20) {
+                       points += 5;
+               }
+               if (exp >= 30) {
+                       points += 5;
+               }
+               if (exp >= 40) {
+                       points += 5;
+               }
+               if (exp >= 50) {
+                       points += 5;
+               }
+               return points;
+       }
+}
diff --git a/src/org/cacert/gigi/database/DatabaseConnection.java b/src/org/cacert/gigi/database/DatabaseConnection.java
new file mode 100644 (file)
index 0000000..6bed8bd
--- /dev/null
@@ -0,0 +1,110 @@
+package org.cacert.gigi.database;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Properties;
+import java.sql.Statement;
+
+public class DatabaseConnection {
+       public static final int CONNECTION_TIMEOUT = 24 * 60 * 60;
+       Connection c;
+       HashMap<String, PreparedStatement> statements = new HashMap<String, PreparedStatement>();
+       private static Properties credentials;
+       Statement adHoc;
+       public DatabaseConnection() {
+               try {
+                       Class.forName(credentials.getProperty("sql.driver"));
+               } catch (ClassNotFoundException e) {
+                       e.printStackTrace();
+               }
+               tryConnect();
+
+       }
+       private void tryConnect() {
+               try {
+                       c = DriverManager.getConnection(credentials.getProperty("sql.url")
+                                       + "?zeroDateTimeBehavior=convertToNull",
+                                       credentials.getProperty("sql.user"),
+                                       credentials.getProperty("sql.password"));
+                       PreparedStatement ps = c
+                                       .prepareStatement("SET SESSION wait_timeout=?;");
+                       ps.setInt(1, CONNECTION_TIMEOUT);
+                       ps.execute();
+                       ps.close();
+                       adHoc = c.createStatement();
+               } catch (SQLException e) {
+                       e.printStackTrace();
+               }
+       }
+       public PreparedStatement prepare(String query) throws SQLException {
+               ensureOpen();
+               PreparedStatement statement = statements.get(query);
+               if (statement == null) {
+                       statement = c.prepareStatement(query,
+                                       Statement.RETURN_GENERATED_KEYS);
+                       statements.put(query, statement);
+               }
+               return statement;
+       }
+       long lastAction = System.currentTimeMillis();
+       private void ensureOpen() {
+               if (System.currentTimeMillis() - lastAction > CONNECTION_TIMEOUT * 1000L) {
+                       try {
+                               ResultSet rs = adHoc.executeQuery("SELECT 1");
+                               rs.close();
+                               lastAction = System.currentTimeMillis();
+                               return;
+                       } catch (SQLException e) {
+                       }
+                       statements.clear();
+                       tryConnect();
+               }
+               lastAction = System.currentTimeMillis();
+       }
+       public static int lastInsertId(PreparedStatement query) throws SQLException {
+               ResultSet rs = query.getGeneratedKeys();
+               rs.next();
+               int id = rs.getInt(1);
+               rs.close();
+               return id;
+       }
+       static ThreadLocal<DatabaseConnection> instances = new ThreadLocal<DatabaseConnection>() {
+               @Override
+               protected DatabaseConnection initialValue() {
+                       return new DatabaseConnection();
+               }
+       };
+       public static DatabaseConnection getInstance() {
+               return instances.get();
+       }
+       public static boolean isInited() {
+               return credentials != null;
+       }
+       public static void init(Properties conf) {
+               if (credentials != null) {
+                       throw new Error("Re-initiaizing is forbidden.");
+               }
+               credentials = conf;
+       }
+       public void beginTransaction() throws SQLException {
+               c.setAutoCommit(false);
+       }
+       public void commitTransaction() throws SQLException {
+               c.commit();
+               c.setAutoCommit(true);
+       }
+       public void quitTransaction() {
+               try {
+                       if (!c.getAutoCommit()) {
+                               c.rollback();
+                               c.setAutoCommit(true);
+                       }
+               } catch (SQLException e) {
+                       e.printStackTrace();
+               }
+       }
+}
diff --git a/src/org/cacert/gigi/email/CommandlineEmailProvider.java b/src/org/cacert/gigi/email/CommandlineEmailProvider.java
new file mode 100644 (file)
index 0000000..2f7502b
--- /dev/null
@@ -0,0 +1,32 @@
+package org.cacert.gigi.email;
+
+import java.io.IOException;
+import java.util.Properties;
+
+public class CommandlineEmailProvider extends EmailProvider {
+       public CommandlineEmailProvider(Properties p) {
+       }
+
+       @Override
+       public void sendmail(String to, String subject, String message,
+                       String from, String replyto, String toname, String fromname,
+                       String errorsto, boolean extra) throws IOException {
+               synchronized (System.out) {
+                       System.out.println("== MAIL ==");
+                       System.out.println("To: " + to);
+                       System.out.println("Subject: " + subject);
+                       System.out.println("From: " + from);
+                       System.out.println("Errors-To: " + errorsto);
+                       System.out.println("Extra: " + extra);
+                       System.out.println(message);
+               }
+
+       }
+       @Override
+       public String checkEmailServer(int forUid, String address)
+                       throws IOException {
+               System.out.println("checkMailBox: " + address);
+               return OK;
+       }
+
+}
diff --git a/src/org/cacert/gigi/email/EmailProvider.java b/src/org/cacert/gigi/email/EmailProvider.java
new file mode 100644 (file)
index 0000000..644c462
--- /dev/null
@@ -0,0 +1,140 @@
+package org.cacert.gigi.email;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.net.Socket;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.LinkedList;
+import java.util.Properties;
+import java.util.regex.Pattern;
+
+import org.cacert.gigi.database.DatabaseConnection;
+
+public abstract class EmailProvider {
+       public abstract void sendmail(String to, String subject, String message,
+                       String from, String replyto, String toname, String fromname,
+                       String errorsto, boolean extra) throws IOException;
+       private static EmailProvider instance;
+       public static EmailProvider getInstance() {
+               return instance;
+       }
+       public static void init(Properties conf) {
+               try {
+                       Class<?> c = Class.forName(conf.getProperty("emailProvider"));
+                       instance = (EmailProvider) c.getDeclaredConstructor(
+                                       Properties.class).newInstance(conf);
+               } catch (ReflectiveOperationException e) {
+                       e.printStackTrace();
+               }
+       }
+
+       public static final String OK = "OK";
+       public static final String FAIL = "FAIL";
+       private static final Pattern MAIL = Pattern
+                       .compile("^([a-zA-Z0-9])+([a-zA-Z0-9\\+\\._-])*@([a-zA-Z0-9_-])+([a-zA-Z0-9\\._-]+)+$");
+
+       public String checkEmailServer(int forUid, String address)
+                       throws IOException {
+               if (MAIL.matcher(address).matches()) {
+                       String[] parts = address.split("@", 2);
+                       String domain = parts[1];
+
+                       LinkedList<String> mxhosts = getMxHosts(domain);
+
+                       for (String host : mxhosts) {
+                               try (Socket s = new Socket(host, 25);
+                                               BufferedReader br = new BufferedReader(
+                                                               new InputStreamReader(s.getInputStream()));
+                                               PrintWriter pw = new PrintWriter(s.getOutputStream())) {
+                                       String line;
+                                       while ((line = br.readLine()) != null
+                                                       && line.startsWith("220-")) {
+                                       }
+                                       if (line == null || !line.startsWith("220")) {
+                                               continue;
+                                       }
+
+                                       pw.print("HELO www.cacert.org\r\n");
+                                       pw.flush();
+
+                                       while ((line = br.readLine()) != null
+                                                       && line.startsWith("220")) {
+                                       }
+
+                                       if (line == null || !line.startsWith("250")) {
+                                               continue;
+                                       }
+                                       pw.print("MAIL FROM: <returns@cacert.org>\r\n");
+                                       pw.flush();
+
+                                       line = br.readLine();
+
+                                       if (line == null || !line.startsWith("250")) {
+                                               continue;
+                                       }
+                                       pw.print("RCPT TO: <" + address + ">\r\n");
+                                       pw.flush();
+
+                                       line = br.readLine();
+                                       pw.print("QUIT\r\n");
+                                       pw.flush();
+
+                                       try {
+                                               PreparedStatement statmt = DatabaseConnection
+                                                               .getInstance()
+                                                               .prepare(
+                                                                               "insert into `pinglog` set `when`=NOW(), `email`=?, `result`=?, `uid`=?");
+                                               statmt.setString(1, address);
+                                               statmt.setString(2, line);
+                                               statmt.setInt(3, forUid);
+                                               statmt.execute();
+                                       } catch (SQLException e) {
+                                               e.printStackTrace();
+                                       }
+
+                                       if (line == null || !line.startsWith("250")) {
+                                               return line;
+                                       } else {
+                                               return OK;
+                                       }
+                               }
+
+                       }
+               }
+               try {
+                       PreparedStatement statmt = DatabaseConnection
+                                       .getInstance()
+                                       .prepare(
+                                                       "insert into `pinglog` set `when`=NOW(), `email`=?, `result`=?, `uid`=?");
+                       statmt.setString(1, address);
+                       statmt.setString(2,
+                                       "Failed to make a connection to the mail server");
+                       statmt.setInt(3, forUid);
+                       statmt.execute();
+               } catch (SQLException e) {
+                       e.printStackTrace();
+               }
+               return FAIL;
+       }
+       private static LinkedList<String> getMxHosts(String domain)
+                       throws IOException {
+               LinkedList<String> mxhosts = new LinkedList<String>();
+               Process dig = Runtime.getRuntime().exec(
+                               new String[]{"dig", "+short", "MX", domain});
+               try (BufferedReader br = new BufferedReader(new InputStreamReader(
+                               dig.getInputStream()))) {
+                       String line;
+                       while ((line = br.readLine()) != null) {
+                               String[] mxparts = line.split(" ", 2);
+                               if (mxparts.length != 2) {
+                                       continue;
+                               }
+                               mxhosts.add(mxparts[1].substring(0, mxparts[1].length() - 1));
+                       }
+               }
+               return mxhosts;
+       }
+}
diff --git a/src/org/cacert/gigi/email/Sendmail.java b/src/org/cacert/gigi/email/Sendmail.java
new file mode 100644 (file)
index 0000000..c61494b
--- /dev/null
@@ -0,0 +1,108 @@
+package org.cacert.gigi.email;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.net.Socket;
+import java.text.SimpleDateFormat;
+import java.util.Base64;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Properties;
+import java.util.regex.Pattern;
+
+public class Sendmail extends EmailProvider {
+       protected Sendmail(Properties props) {
+       }
+       private static final Pattern NON_ASCII = Pattern
+                       .compile("[^a-zA-Z0-9 .-\\[\\]!_@]");
+
+       @Override
+       public void sendmail(String to, String subject, String message,
+                       String from, String replyto, String toname, String fromname,
+                       String errorsto, boolean extra) throws IOException {
+
+               String[] bits = from.split(",");
+
+               Socket smtp = new Socket("dogcraft.de", 25);
+               PrintWriter out = new PrintWriter(smtp.getOutputStream());
+               BufferedReader in = new BufferedReader(new InputStreamReader(
+                               smtp.getInputStream()));
+               readResponse(in);
+               out.print("HELO www.cacert.org\r\n");
+               out.flush();
+               readResponse(in);
+               out.print("MAIL FROM:<returns@cacert.org>\r\n");
+               out.flush();
+               readResponse(in);
+               bits = to.split(",");
+               for (String user : bits) {
+                       out.print("RCPT TO:<" + user.trim() + ">\r\n");
+                       out.flush();
+                       readResponse(in);
+               }
+               out.print("DATA\r\n");
+               out.flush();
+               readResponse(in);
+               out.print("X-Mailer: CAcert.org Website\r\n");
+               // if (array_key_exists("REMOTE_ADDR", $_SERVER)) {
+               // out.print("X-OriginatingIP: ".$_SERVER["REMOTE_ADDR"]."\r\n");
+               // }
+               // TODO
+               SimpleDateFormat emailDate = new SimpleDateFormat(
+                               "E, d MMM yyyy HH:mm:ss ZZZZ (z)", Locale.ENGLISH);
+               out.print("Date: "
+                               + emailDate.format(new Date(System.currentTimeMillis()))
+                               + "\r\n");
+               out.print("Sender: " + errorsto + "\r\n");
+               out.print("Errors-To: " + errorsto + "\r\n");
+               if (replyto != null) {
+                       out.print("Reply-To: " + replyto + "\r\n");
+               } else {
+                       out.print("Reply-To: " + from + "\r\n");
+               }
+               out.print("From: " + from + "\r\n");
+               out.print("To: " + to + "\r\n");
+               if (NON_ASCII.matcher(subject).matches()) {
+
+                       out.print("Subject: =?utf-8?B?"
+                                       + Base64.getEncoder().encodeToString(subject.getBytes())
+                                       + "?=\r\n");
+               } else {
+                       out.print("Subject: " + subject + "\r\n");
+               }
+               out.print("Mime-Version: 1.0\r\n");
+               if (!extra) {
+                       out.print("Content-Type: text/plain; charset=\"utf-8\"\r\n");
+                       out.print("Content-Transfer-Encoding: 8bit\r\n");
+               } else {
+                       out.print("Content-Type: text/plain; charset=\"iso-8859-1\"\r\n");
+                       out.print("Content-Transfer-Encoding: quoted-printable\r\n");
+                       out.print("Content-Disposition: inline\r\n");
+               }
+               // out.print("Content-Transfer-Encoding: BASE64\r\n");
+               out.print("\r\n");
+               // out.print(chunk_split(base64_encode(recode("html..utf-8",
+               // $message)))."\r\n.\r\n");
+               message = message + "\r\n";
+
+               String sendM = message.replace("\r", "").replace("\n.\n", "\n")
+                               .replace("\n.\n", "\n").replace("\n", "\r\n")
+                               + ".\r\n";
+               out.print(sendM);
+               out.flush();
+               readResponse(in);
+               out.print("QUIT\n");
+               out.flush();
+               readResponse(in);
+               smtp.close();
+       }
+       private static void readResponse(BufferedReader in) throws IOException {
+               String line;
+               while ((line = in.readLine()) != null && line.matches("\\d+-")) {
+               }
+
+       }
+
+}
diff --git a/src/org/cacert/gigi/email/TestEmailProvider.java b/src/org/cacert/gigi/email/TestEmailProvider.java
new file mode 100644 (file)
index 0000000..0734350
--- /dev/null
@@ -0,0 +1,82 @@
+package org.cacert.gigi.email;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.Properties;
+
+class TestEmailProvider extends EmailProvider {
+       ServerSocket servs;
+       Socket client;
+       DataOutputStream out;
+       DataInputStream in;
+       protected TestEmailProvider(Properties props) {
+               try {
+                       servs = new ServerSocket(Integer.parseInt(props
+                                       .getProperty("emailProvider.port")), 10,
+                                       InetAddress.getByName("127.0.0.1"));
+               } catch (IOException e) {
+                       e.printStackTrace();
+               }
+       }
+       @Override
+       public synchronized void sendmail(String to, String subject,
+                       String message, String from, String replyto, String toname,
+                       String fromname, String errorsto, boolean extra) throws IOException {
+               while (true) {
+                       assureLocalConnection();
+                       try {
+                               out.writeUTF("mail");
+                               write(to);
+                               write(subject);
+                               write(message);
+                               write(from);
+                               write(replyto);
+                               out.flush();
+                               return;
+                       } catch (IOException e) {
+                               client = null;
+                       }
+               }
+       }
+       private void assureLocalConnection() throws IOException {
+               if (out != null) {
+                       try {
+                               out.writeUTF("ping");
+                       } catch (IOException e) {
+                               client = null;
+                       }
+               }
+               if (client == null || client.isClosed()) {
+                       client = servs.accept();
+                       out = new DataOutputStream(client.getOutputStream());
+                       in = new DataInputStream(client.getInputStream());
+               }
+       }
+       @Override
+       public synchronized String checkEmailServer(int forUid, String address)
+                       throws IOException {
+               while (true) {
+                       assureLocalConnection();
+                       try {
+                               out.writeUTF("challengeAddrBox");
+                               out.writeUTF(address);
+                               return in.readUTF();
+                       } catch (IOException e) {
+                               client = null;
+                       }
+               }
+       }
+
+       private void write(String to) throws IOException {
+               if (to == null) {
+                       out.writeUTF("<null>");
+               } else {
+                       out.writeUTF(to);
+               }
+       }
+
+}
diff --git a/src/org/cacert/gigi/natives/SetUID.java b/src/org/cacert/gigi/natives/SetUID.java
new file mode 100644 (file)
index 0000000..e6b0f7c
--- /dev/null
@@ -0,0 +1,36 @@
+package org.cacert.gigi.natives;
+
+import java.io.File;
+
+/**
+ * Native to use privileged ports on unix-like hosts.
+ * 
+ * @author janis
+ * 
+ */
+public class SetUID {
+       static {
+               System.load(new File("natives/libsetuid.so").getAbsolutePath());
+       }
+
+       public native Status setUid(int uid, int gid);
+
+       public static class Status {
+
+               private boolean success;
+               private String message;
+
+               public Status(boolean success, String message) {
+                       this.success = success;
+                       this.message = message;
+               }
+
+               public boolean getSuccess() {
+                       return success;
+               }
+
+               public String getMessage() {
+                       return message;
+               }
+       }
+}
diff --git a/src/org/cacert/gigi/output/CertificateTable.java b/src/org/cacert/gigi/output/CertificateTable.java
new file mode 100644 (file)
index 0000000..836f344
--- /dev/null
@@ -0,0 +1,56 @@
+package org.cacert.gigi.output;
+
+import java.io.PrintWriter;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.LinkedList;
+import java.util.Map;
+
+import org.cacert.gigi.Language;
+import org.cacert.gigi.output.DataTable.Cell;
+
+public class CertificateTable implements Outputable {
+       String resultSet;
+       public CertificateTable(String resultSet) {
+               this.resultSet = resultSet;
+       }
+
+       @Override
+       public void output(PrintWriter out, Language l, Map<String, Object> vars) {
+               ResultSet rs = (ResultSet) vars.get(resultSet);
+               try {
+                       out.println("<form method=\"post\" action=\"account.php\">");
+                       final LinkedList<Cell> cells = new LinkedList<>();
+                       cells.add(new Cell("Renew/Revoke/Delete", true));
+                       cells.add(new Cell("Status", true));
+                       cells.add(new Cell("Email Address", true));
+                       cells.add(new Cell("SerialNumber", true));
+                       cells.add(new Cell("Revoked", true));
+                       cells.add(new Cell("Expires", true));
+                       cells.add(new Cell("Login", true));
+                       cells.add(new Cell("Comment *", true, 2));
+                       rs.beforeFirst();
+                       while (rs.next()) {
+                               // out.println(rs.getString("id"));
+                               cells.add(new Cell());
+                               cells.add(new Cell("State", false));
+                               cells.add(new Cell(rs.getString("CN"), false));
+                               cells.add(new Cell(rs.getString("serial"), false));
+                               if (rs.getString("revoked") == null) {
+                                       cells.add(new Cell("N/A", false));
+                               } else {
+                                       cells.add(new Cell(rs.getString("revoked"), false));
+                               }
+                               cells.add(new Cell(rs.getString("expire"), false));
+                               cells.add(new Cell(rs.getString("a"), false));
+                               cells.add(new Cell(rs.getString("a"), false));
+                       }
+                       DataTable t = new DataTable(9, cells);
+                       t.output(out, l, vars);
+                       out.println("</form>");
+               } catch (SQLException e) {
+                       e.printStackTrace();
+               }
+
+       }
+}
diff --git a/src/org/cacert/gigi/output/DataTable.java b/src/org/cacert/gigi/output/DataTable.java
new file mode 100644 (file)
index 0000000..0a30180
--- /dev/null
@@ -0,0 +1,95 @@
+package org.cacert.gigi.output;
+
+import java.io.PrintWriter;
+import java.util.LinkedList;
+import java.util.Map;
+
+import org.cacert.gigi.Language;
+
+public class DataTable implements Outputable {
+       private LinkedList<Cell> cells;
+       private int columnCount;
+
+       public DataTable(int coloumnCount, LinkedList<Cell> content) {
+               this.columnCount = coloumnCount;
+               this.cells = content;
+       }
+
+       public void output(PrintWriter out, Language l) {
+               float mesCells = cells.size();
+               for (Cell c : cells) {
+                       if (c.getColSpan() > 1) {
+                               mesCells += c.getColSpan();
+                       }
+               }
+               out.println("<table align=\"center\" valign=\"middle\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\" class=\"wrapper\">");
+               int cellsRendered = 0;
+               for (int i = 0; i < Math.ceil(mesCells / columnCount) - 1; i++) {
+                       out.println("<tr>");
+                       for (int j = 0; j < columnCount;) {
+                               Cell current = cells.get(cellsRendered++);
+                               j += current.getColSpan();
+                               out.println("<td ");
+                               out.print(current.getHtmlAttribs());
+                               out.print(" >");
+                               out.print(current.shouldTranslate() ? l.getTranslation(current
+                                               .getText()) : current.getText());
+                               out.print("</td>");
+                       }
+                       out.println("</tr>");
+               }
+               out.println("</table>");
+       }
+
+       public static class Cell {
+               private String text, htmlAttribs;
+               private boolean translate;
+               private int colSpan;
+
+               public Cell() {
+                       this("&nbsp;", false);
+               }
+
+               public Cell(String text, boolean translate, int colSpan,
+                               String htmlAttribs) {
+                       this.text = text;
+                       this.translate = translate;
+                       this.htmlAttribs = htmlAttribs;
+                       if (colSpan > 1) {
+                               this.htmlAttribs += " colspan=\"" + colSpan + "\"";
+                       }
+                       this.colSpan = colSpan;
+               }
+
+               public Cell(String text, boolean translate) {
+                       this(text, translate, 1, "class=\"DataTD\"");
+               }
+
+               public Cell(String text, boolean translate, int colSpan) {
+                       this(text, translate, colSpan, "class=\"DataTD\"");
+               }
+
+               public boolean shouldTranslate() {
+                       return translate;
+               }
+
+               public String getText() {
+                       return text;
+               }
+
+               public int getColSpan() {
+                       return colSpan;
+               }
+
+               public String getHtmlAttribs() {
+                       return htmlAttribs;
+               }
+
+       }
+
+       @Override
+       public void output(PrintWriter out, Language l, Map<String, Object> vars) {
+               output(out, l);
+       }
+
+}
diff --git a/src/org/cacert/gigi/output/DateSelector.java b/src/org/cacert/gigi/output/DateSelector.java
new file mode 100644 (file)
index 0000000..a21e38b
--- /dev/null
@@ -0,0 +1,102 @@
+package org.cacert.gigi.output;
+
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.cacert.gigi.Language;
+
+public class DateSelector implements Outputable {
+       String[] names;
+       public DateSelector(String day, String month, String year) {
+               this.names = new String[]{day, month, year};
+       }
+       int day;
+       int month;
+       int year;
+       @Override
+       public void output(PrintWriter out, Language l, Map<String, Object> vars) {
+               out.print("<nobr><select name=\"");
+               out.print(names[0]);
+               out.println("\">");
+               for (int i = 1; i <= 31; i++) {
+                       out.print("<option");
+                       if (i == day) {
+                               out.print(" selected=\"selected\"");
+                       }
+                       out.println(">" + i + "</option>");
+               }
+               out.println("</select>");
+               SimpleDateFormat sdf = new SimpleDateFormat("MMMM", l.getLocale());
+               out.print("<select name=\"");
+               out.print(names[1]);
+               out.println("\">");
+               Calendar c = sdf.getCalendar();
+               for (int i = 1; i <= 12; i++) {
+                       c.set(Calendar.MONTH, i - 1);
+                       out.print("<option value='" + i + "'");
+                       if (i == month) {
+                               out.print(" selected=\"selected\"");
+                       }
+                       out.println(">" + sdf.format(c.getTime()) + " (" + i + ")</option>");
+               }
+               out.println("</select>");
+               out.print("<input type=\"text\" name=\"");
+               out.print(names[2]);
+               out.print("\" value=\"");
+               if (year != 0) {
+                       out.print(year);
+               }
+               out.print("\" size=\"4\" autocomplete=\"off\"></nobr>");
+       }
+
+       public void update(HttpServletRequest r) {
+               String dayS = r.getParameter(names[0]);
+               if (dayS != null) {
+                       day = parseIntSafe(dayS);
+               }
+
+               String monthS = r.getParameter(names[1]);
+               if (monthS != null) {
+                       month = parseIntSafe(monthS);
+               }
+
+               String yearS = r.getParameter(names[2]);
+               if (yearS != null) {
+                       year = parseIntSafe(yearS);
+               }
+       }
+       private int parseIntSafe(String dayS) {
+               try {
+                       return Integer.parseInt(dayS);
+               } catch (NumberFormatException e) {
+
+               }
+               return 0;
+       }
+       public boolean isValid() {
+               if (!(1900 < year && 1 <= month && month <= 12 && 1 <= day && day <= 32)) {
+                       return false;
+               }
+               return true; // TODO checkdate
+       }
+
+       @Override
+       public String toString() {
+               return "DateSelector [names=" + Arrays.toString(names) + ", day=" + day
+                               + ", month=" + month + ", year=" + year + "]";
+       }
+
+       public Date getDate() {
+               Calendar gc = GregorianCalendar.getInstance();
+               gc.set(year, month - 1, day);
+               return gc.getTime();
+       }
+
+}
diff --git a/src/org/cacert/gigi/output/Form.java b/src/org/cacert/gigi/output/Form.java
new file mode 100644 (file)
index 0000000..9a27127
--- /dev/null
@@ -0,0 +1,19 @@
+package org.cacert.gigi.output;
+
+import java.io.PrintWriter;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+
+import org.cacert.gigi.pages.Page;
+
+public abstract class Form implements Outputable {
+       public abstract boolean submit(PrintWriter out, HttpServletRequest req);
+
+       protected void outputError(PrintWriter out, ServletRequest req, String text) {
+               out.print("<div>");
+               out.print(Page.translate(req, text));
+               out.println("</div>");
+       }
+
+}
diff --git a/src/org/cacert/gigi/output/MailTable.java b/src/org/cacert/gigi/output/MailTable.java
new file mode 100644 (file)
index 0000000..5e310b7
--- /dev/null
@@ -0,0 +1,61 @@
+package org.cacert.gigi.output;
+
+import java.io.PrintWriter;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.LinkedList;
+import java.util.Map;
+
+import org.cacert.gigi.Language;
+import org.cacert.gigi.output.DataTable.Cell;
+
+public class MailTable implements Outputable {
+       private String resultSet, userMail;
+
+       public MailTable(String key, String userMail) {
+               this.resultSet = key;
+               this.userMail = userMail;
+       }
+
+       @Override
+       public void output(PrintWriter out, Language l, Map<String, Object> vars) {
+               ResultSet rs = (ResultSet) vars.get(resultSet);
+               String userMail = (String) vars.get(this.userMail);
+               LinkedList<Cell> cells = new LinkedList<>();
+               cells.add(new Cell("Email Accounts", true, 4, "class=\"title\""));
+               cells.add(new Cell("Default", true));
+               cells.add(new Cell("Status", true));
+               cells.add(new Cell("Delete", true));
+               cells.add(new Cell("Address", true));
+               try {
+                       rs.beforeFirst();
+                       while (rs.next()) {
+                               cells.add(new Cell());
+                               cells.add(new Cell(
+                                               rs.getString("hash").trim().isEmpty() ? "Verified"
+                                                               : "Unverified", true));
+                               if (rs.getString("email").equals(userMail)) {
+                                       cells.add(new Cell(
+                                                       "N/A"
+                                                                       , true));
+                               } else {
+                                       cells.add(new Cell("<input type=\"checkbox\" name=\"delid[]\" value=\""
+                                                                                       + rs.getInt("id") + "\">", false));
+                               }
+                               cells.add(new Cell(rs.getString("email"), false));
+                       }
+               } catch (SQLException e) {
+                       e.printStackTrace();
+               }
+               String trans = l.getTranslation("Make Default");
+               cells.add(new Cell(
+                               "<input type=\"submit\" name=\"makedefault\" value=\"" + trans
+                                               + "\">", false, 2));
+               trans = l.getTranslation("Delete");
+               cells.add(new Cell("<input type=\"submit\" name=\"process\" value=\""
+                               + trans + "\">", false, 2));
+               DataTable t = new DataTable(4, cells);
+               t.output(out, l, vars);
+       }
+
+}
diff --git a/src/org/cacert/gigi/output/Outputable.java b/src/org/cacert/gigi/output/Outputable.java
new file mode 100644 (file)
index 0000000..4d5978e
--- /dev/null
@@ -0,0 +1,10 @@
+package org.cacert.gigi.output;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+import org.cacert.gigi.Language;
+
+public interface Outputable {
+       public void output(PrintWriter out, Language l, Map<String, Object> vars);
+}
diff --git a/src/org/cacert/gigi/output/Template.java b/src/org/cacert/gigi/output/Template.java
new file mode 100644 (file)
index 0000000..225560c
--- /dev/null
@@ -0,0 +1,111 @@
+package org.cacert.gigi.output;
+
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Scanner;
+import java.util.regex.Pattern;
+
+import org.cacert.gigi.Language;
+
+public class Template implements Outputable {
+       String[] contents;
+       Outputable[] vars;
+
+       public Template(Reader r) {
+               LinkedList<String> splitted = new LinkedList<String>();
+               LinkedList<Outputable> commands = new LinkedList<Outputable>();
+               Scanner sc = new Scanner(r);
+               Pattern p1 = Pattern.compile("([^<]|<[^?])*<\\?");
+               Pattern p2 = Pattern.compile("([^<]|<[^?])*\\?>");
+               while (true) {
+                       String s1 = sc.findWithinHorizon(p1, 0);
+                       if (s1 == null) {
+                               break;
+                       }
+                       s1 = s1.substring(0, s1.length() - 2);
+                       splitted.add(s1);
+                       String s2 = sc.findWithinHorizon(p2, 0);
+                       s2 = s2.substring(0, s2.length() - 2);
+                       commands.add(parseCommand(s2));
+               }
+               sc.useDelimiter("\0");
+               if (sc.hasNext()) {
+                       splitted.add(sc.next());
+               }
+               sc.close();
+               contents = splitted.toArray(new String[splitted.size()]);
+               vars = commands.toArray(new Outputable[commands.size()]);
+       }
+       private Outputable parseCommand(String s2) {
+               if (s2.startsWith("=_")) {
+                       final String raw = s2.substring(2);
+                       return new Outputable() {
+
+                               @Override
+                               public void output(PrintWriter out, Language l,
+                                               Map<String, Object> vars) {
+                                       out.print(l.getTranslation(raw));
+                               }
+                       };
+               } else if (s2.startsWith("=$")) {
+                       final String raw = s2.substring(2);
+                       return new Outputable() {
+
+                               @Override
+                               public void output(PrintWriter out, Language l,
+                                               Map<String, Object> vars) {
+                                       outputVar(out, l, vars, raw);
+                               }
+                       };
+               } else if (s2.startsWith("=s,")) {
+                       String command = s2.substring(3);
+                       final LinkedList<String> store = new LinkedList<String>();
+                       while (command.startsWith("$")) {
+                               int idx = command.indexOf(",");
+                               store.add(command.substring(0, idx));
+                               command = command.substring(idx + 1);
+                       }
+                       final String text = command;
+                       return new Outputable() {
+
+                               @Override
+                               public void output(PrintWriter out, Language l,
+                                               Map<String, Object> vars) {
+                                       String[] parts = l.getTranslation(text).split("%s");
+                                       String[] myvars = store.toArray(new String[store.size()]);
+                                       out.print(parts[0]);
+                                       for (int j = 1; j < parts.length; j++) {
+                                               outputVar(out, l, vars, myvars[j - 1].substring(1));
+                                               out.print(parts[j]);
+                                       }
+                               }
+                       };
+               } else {
+                       System.out.println("Unknown processing instruction: " + s2);
+               }
+               return null;
+       }
+       public void output(PrintWriter out, Language l, Map<String, Object> vars) {
+               for (int i = 0; i < contents.length; i++) {
+                       out.print(contents[i]);
+                       if (i < this.vars.length) {
+                               this.vars[i].output(out, l, vars);
+                       }
+               }
+       }
+       private void outputVar(PrintWriter out, Language l,
+                       Map<String, Object> vars, String varname) {
+               Object s = vars.get(varname);
+
+               if (s == null) {
+                       System.out.println("Empty variable: " + varname);
+               }
+               if (s instanceof Outputable) {
+                       ((Outputable) s).output(out, l, vars);
+               } else {
+                       out.print(s);
+               }
+       }
+}
diff --git a/src/org/cacert/gigi/pages/LoginPage.java b/src/org/cacert/gigi/pages/LoginPage.java
new file mode 100644 (file)
index 0000000..acfc8f5
--- /dev/null
@@ -0,0 +1,118 @@
+package org.cacert.gigi.pages;
+
+import static org.cacert.gigi.Gigi.LOGGEDIN;
+import static org.cacert.gigi.Gigi.USER;
+
+import java.io.IOException;
+import java.security.cert.X509Certificate;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.cacert.gigi.User;
+import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.util.PasswordHash;
+
+public class LoginPage extends Page {
+       public static final String LOGIN_RETURNPATH = "login-returnpath";
+
+       public LoginPage(String title) {
+               super(title);
+       }
+
+       @Override
+       public void doGet(HttpServletRequest req, HttpServletResponse resp)
+                       throws IOException {
+               resp.getWriter()
+                               .println(
+                                               "<form method='POST' action='/login'>"
+                                                               + "<input type='text' name='username'>"
+                                                               + "<input type='password' name='password'> <input type='submit' value='login'></form>");
+       }
+
+       @Override
+       public boolean beforeTemplate(HttpServletRequest req,
+                       HttpServletResponse resp) throws IOException {
+               if (req.getSession().getAttribute("loggedin") == null) {
+                       X509Certificate[] cert = (X509Certificate[]) req
+                                       .getAttribute("javax.servlet.request.X509Certificate");
+                       if (cert != null && cert[0] != null) {
+                               tryAuthWithCertificate(req, cert[0]);
+                       }
+                       if (req.getMethod().equals("POST")) {
+                               tryAuthWithUnpw(req);
+                       }
+               }
+
+               if (req.getSession().getAttribute("loggedin") != null) {
+                       String s = (String) req.getSession().getAttribute(LOGIN_RETURNPATH);
+                       if (s != null) {
+                               if (!s.startsWith("/")) {
+                                       s = "/" + s;
+                               }
+                               resp.sendRedirect(s);
+                       } else {
+                               resp.sendRedirect("/");
+                       }
+                       return true;
+               }
+               return false;
+       }
+       @Override
+       public boolean needsLogin() {
+               return false;
+       }
+       private void tryAuthWithUnpw(HttpServletRequest req) {
+               String un = req.getParameter("username");
+               String pw = req.getParameter("password");
+               try {
+                       PreparedStatement ps = DatabaseConnection
+                                       .getInstance()
+                                       .prepare(
+                                                       "SELECT `password`, `id` FROM `users` WHERE `email`=? AND locked='0' AND verified='1'");
+                       ps.setString(1, un);
+                       ResultSet rs = ps.executeQuery();
+                       if (rs.next()) {
+                               if (PasswordHash.verifyHash(pw, rs.getString(1))) {
+                                       req.getSession().invalidate();
+                                       HttpSession hs = req.getSession();
+                                       hs.setAttribute(LOGGEDIN, true);
+                                       hs.setAttribute(USER, new User(rs.getInt(2)));
+                               }
+                       }
+                       rs.close();
+               } catch (SQLException e) {
+                       e.printStackTrace();
+               }
+       }
+       public static User getUser(HttpServletRequest req) {
+               return (User) req.getSession().getAttribute(USER);
+       }
+       private void tryAuthWithCertificate(HttpServletRequest req,
+                       X509Certificate x509Certificate) {
+               String serial = x509Certificate.getSerialNumber().toString(16)
+                               .toUpperCase();
+               try {
+                       PreparedStatement ps = DatabaseConnection
+                                       .getInstance()
+                                       .prepare(
+                                                       "SELECT `memid` FROM `emailcerts` WHERE `serial`=? AND `disablelogin`='0' AND `revoked` = "
+                                                                       + "'0000-00-00 00:00:00'");
+                       ps.setString(1, serial);
+                       ResultSet rs = ps.executeQuery();
+                       if (rs.next()) {
+                               req.getSession().invalidate();
+                               HttpSession hs = req.getSession();
+                               hs.setAttribute(LOGGEDIN, true);
+                               hs.setAttribute(USER, new User(rs.getInt(1)));
+                       }
+                       rs.close();
+               } catch (SQLException e) {
+                       e.printStackTrace();
+               }
+       }
+}
diff --git a/src/org/cacert/gigi/pages/MainPage.java b/src/org/cacert/gigi/pages/MainPage.java
new file mode 100644 (file)
index 0000000..adeaa80
--- /dev/null
@@ -0,0 +1,22 @@
+package org.cacert.gigi.pages;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class MainPage extends Page {
+       public MainPage(String title) {
+               super(title);
+       }
+
+       @Override
+       public void doGet(HttpServletRequest req, HttpServletResponse resp)
+                       throws IOException {
+               resp.getWriter().println("Access granted.");
+       }
+       @Override
+       public boolean needsLogin() {
+               return false;
+       }
+}
diff --git a/src/org/cacert/gigi/pages/Page.java b/src/org/cacert/gigi/pages/Page.java
new file mode 100644 (file)
index 0000000..04f1dc6
--- /dev/null
@@ -0,0 +1,121 @@
+package org.cacert.gigi.pages;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.cacert.gigi.Language;
+import org.cacert.gigi.output.Template;
+
+/**
+ * This class encapsulates a sub page of Gigi. A template residing nearby this
+ * class with name &lt;className&gt;.templ will be loaded automatically.
+ */
+public abstract class Page {
+       private String title;
+       private Template defaultTemplate;
+
+       public Page(String title) {
+               this.title = title;
+               try {
+                       InputStream resource = getClass().getResourceAsStream(
+                                       getClass().getSimpleName() + ".templ");
+                       if (resource != null) {
+                               defaultTemplate = new Template(new InputStreamReader(resource,
+                                               "UTF-8"));
+                       }
+               } catch (UnsupportedEncodingException e) {
+                       e.printStackTrace();
+               }
+       }
+
+       /**
+        * Retrives the default template (&lt;className&gt;.templ) which has already
+        * been loaded.
+        * 
+        * @return the default template.
+        */
+       public Template getDefaultTemplate() {
+               return defaultTemplate;
+       }
+
+       /**
+        * This method can be overridden to execute code and do stuff before the
+        * default template is applied.
+        * 
+        * @param req
+        *            the request to handle.
+        * @param resp
+        *            the response to write to
+        * @return true, iff the request is consumed and the default template should
+        *         not be applied.
+        * @throws IOException
+        *             if output goes wrong.
+        */
+       public boolean beforeTemplate(HttpServletRequest req,
+                       HttpServletResponse resp) throws IOException {
+               return false;
+       }
+
+       /**
+        * This method is called to generate the content inside the default
+        * template.
+        * 
+        * @param req
+        *            the request to handle.
+        * @param resp
+        *            the response to write to
+        * @throws IOException
+        *             if output goes wrong.
+        */
+       public abstract void doGet(HttpServletRequest req, HttpServletResponse resp)
+                       throws IOException;
+
+       /**
+        * Same as {@link #doGet(HttpServletRequest, HttpServletResponse)} but for
+        * POST requests. By default they are redirected to
+        * {@link #doGet(HttpServletRequest, HttpServletResponse)};
+        * 
+        * @param req
+        *            the request to handle.
+        * @param resp
+        *            the response to write to
+        * @throws IOException
+        *             if output goes wrong.
+        */
+       public void doPost(HttpServletRequest req, HttpServletResponse resp)
+                       throws IOException {
+               doGet(req, resp);
+       }
+
+       /**
+        * Returns true, iff this page requires login. Default is <code>true</code>
+        * 
+        * @return iff the page needs login.
+        */
+       public boolean needsLogin() {
+               return true;
+       }
+
+       public String getTitle() {
+               return title;
+       }
+
+       public void setTitle(String title) {
+               this.title = title;
+       }
+       public static Language getLanguage(ServletRequest req) {
+               return Language.getInstance("de");
+       }
+
+       public static String translate(ServletRequest req, String string) {
+               Language l = getLanguage(req);
+               return l.getTranslation(string);
+       }
+
+}
diff --git a/src/org/cacert/gigi/pages/TestSecure.java b/src/org/cacert/gigi/pages/TestSecure.java
new file mode 100644 (file)
index 0000000..e3d2f3c
--- /dev/null
@@ -0,0 +1,20 @@
+package org.cacert.gigi.pages;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class TestSecure extends Page {
+
+       public TestSecure() {
+               super("Secure testpage");
+       }
+
+       @Override
+       public void doGet(HttpServletRequest req, HttpServletResponse resp)
+                       throws IOException {
+               resp.getWriter().println("This page is secure.");
+       }
+
+}
diff --git a/src/org/cacert/gigi/pages/Verify.java b/src/org/cacert/gigi/pages/Verify.java
new file mode 100644 (file)
index 0000000..8d29266
--- /dev/null
@@ -0,0 +1,72 @@
+package org.cacert.gigi.pages;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.cacert.gigi.database.DatabaseConnection;
+
+public class Verify extends Page {
+       public static final String PATH = "/verify";
+       public Verify() {
+               super("Verify email");
+       }
+       @Override
+       public boolean needsLogin() {
+               return false;
+       }
+       @Override
+       public void doGet(HttpServletRequest req, HttpServletResponse resp)
+                       throws IOException {
+               PrintWriter out = resp.getWriter();
+               String hash = req.getParameter("hash");
+               String type = req.getParameter("type");
+               String id = req.getParameter("id");
+               if ("email".equals(type)) {
+                       try {
+                               PreparedStatement ps = DatabaseConnection
+                                               .getInstance()
+                                               .prepare(
+                                                               "select email, memid from `email` where `id`=? and `hash`=? and `hash` != '' and `deleted` = 0");
+                               ps.setString(1, id);
+                               ps.setString(2, hash);
+                               ResultSet rs = ps.executeQuery();
+                               rs.last();
+                               if (rs.getRow() == 1) {
+                                       PreparedStatement ps1 = DatabaseConnection
+                                                       .getInstance()
+                                                       .prepare(
+                                                                       "update `email` set `hash`='', `modified`=NOW() where `id`=?");
+                                       ps1.setString(1, id);
+                                       ps1.execute();
+                                       PreparedStatement ps2 = DatabaseConnection
+                                                       .getInstance()
+                                                       .prepare(
+                                                                       "update `users` set `verified`='1' where `id`=? and `email`=? and `verified`='0'");
+                                       ps2.setString(1, rs.getString(2));
+                                       ps2.setString(2, rs.getString(1));
+                                       ps2.execute();
+                                       out.println("Your email is good.");
+                               } else {
+                                       out.println("Your request is invalid");
+                               }
+                       } catch (SQLException e) {
+                               e.printStackTrace();
+                       }
+               }
+       }
+       @Override
+       public void doPost(HttpServletRequest req, HttpServletResponse resp)
+                       throws IOException {
+               String hash = req.getParameter("hash");
+               String type = req.getParameter("type");
+               if ("email".equals(type)) {
+
+               }
+       }
+}
diff --git a/src/org/cacert/gigi/pages/account/MailAdd.java b/src/org/cacert/gigi/pages/account/MailAdd.java
new file mode 100644 (file)
index 0000000..e311cbc
--- /dev/null
@@ -0,0 +1,42 @@
+package org.cacert.gigi.pages.account;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.LinkedList;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.cacert.gigi.Language;
+import org.cacert.gigi.output.DataTable;
+import org.cacert.gigi.output.DataTable.Cell;
+import org.cacert.gigi.pages.Page;
+
+public class MailAdd extends Page{
+       public static final String DEFAULT_PATH = "/account/mails/add";
+       public MailAdd(String title) {
+               super(title);
+       }
+
+       @Override
+       public void doGet(HttpServletRequest req, HttpServletResponse resp)
+                       throws IOException {
+               LinkedList<Cell> cells = new LinkedList<>();
+               cells.add(new Cell("Add Email", true, 2, "class=\"title\""));
+               cells.add(new Cell("Email Address", true));
+               cells.add(new Cell("<input type=\"text\" name=\"newemail\">", false));
+               Language language = getLanguage(req);
+               String trans = language.getTranslation("I own or am authorised to control this email address");
+               cells.add(new Cell("<input type=\"submit\" name=\"process\" value=\""
+                               + trans + "\">", false, 2));
+               DataTable dt = new DataTable(2, cells);
+               dt.output(resp.getWriter(), language);
+               PrintWriter out = resp.getWriter();
+               out.println("<p>");
+               out.println(language
+                               .getTranslation(
+                                               "Currently we only issue certificates for Punycode domains if the person requesting them has code signing attributes attached to their account, as these have potentially slightly higher security risk."));
+               out.println("</p>");
+       }
+
+}
diff --git a/src/org/cacert/gigi/pages/account/MailCertificates.java b/src/org/cacert/gigi/pages/account/MailCertificates.java
new file mode 100644 (file)
index 0000000..72952d4
--- /dev/null
@@ -0,0 +1,46 @@
+package org.cacert.gigi.pages.account;
+
+import java.io.IOException;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.cacert.gigi.User;
+import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.output.CertificateTable;
+import org.cacert.gigi.pages.LoginPage;
+import org.cacert.gigi.pages.Page;
+
+public class MailCertificates extends Page {
+       CertificateTable myTable = new CertificateTable("mailcerts");
+       public static final String PATH = "/account/certs/email";
+
+       public MailCertificates() {
+               super("Email Certificates");
+       }
+
+       @Override
+       public void doGet(HttpServletRequest req, HttpServletResponse resp)
+                       throws IOException {
+               HashMap<String, Object> vars = new HashMap<String, Object>();
+               User us = LoginPage.getUser(req);
+               try {
+                       PreparedStatement ps = DatabaseConnection
+                                       .getInstance()
+                                       .prepare(
+                                                       "SELECT `id`, `CN`, `serial`, `revoked`, `expire`, `disablelogin` FROM `emailcerts` WHERE `memid`=?");
+                       ps.setInt(1, us.getId());
+                       ResultSet rs = ps.executeQuery();
+                       vars.put("mailcerts", rs);
+                       myTable.output(resp.getWriter(), getLanguage(req), vars);
+                       rs.close();
+               } catch (SQLException e) {
+                       e.printStackTrace();
+               }
+       }
+
+}
diff --git a/src/org/cacert/gigi/pages/account/MailOverview.java b/src/org/cacert/gigi/pages/account/MailOverview.java
new file mode 100644 (file)
index 0000000..e574438
--- /dev/null
@@ -0,0 +1,53 @@
+package org.cacert.gigi.pages.account;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.cacert.gigi.Language;
+import org.cacert.gigi.User;
+import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.output.MailTable;
+import org.cacert.gigi.pages.LoginPage;
+import org.cacert.gigi.pages.Page;
+
+public class MailOverview extends Page {
+       public static final String DEFAULT_PATH = "/account/mails";
+       private MailTable table = new MailTable("mails", "userMail");
+       public MailOverview(String title) {
+               super(title);
+       }
+
+       @Override
+       public void doGet(HttpServletRequest req, HttpServletResponse resp)
+                       throws IOException {
+               HashMap<String, Object> vars = new HashMap<String, Object>();
+               User us = LoginPage.getUser(req);
+               int id = us.getId();
+               try {
+                       PreparedStatement ps = DatabaseConnection.getInstance().prepare(
+                                       "SELECT * from `email` WHERE `memid`=? AND `deleted`=0");
+                       ps.setInt(1, id);
+                       ResultSet rs = ps.executeQuery();
+                       vars.put("mails", rs);
+                       vars.put("userMail", us.getEmail());
+
+               } catch (SQLException e) {
+                       e.printStackTrace();
+               }
+               Language language = getLanguage(req);
+               table.output(resp.getWriter(), language, vars);
+               PrintWriter wri = resp.getWriter();
+               wri.println("<p>");
+               wri.println(language
+                               .getTranslation("Please Note: You can not set an unverified account as a default account, and you can not remove a default account. To remove the default account you must set another verified account as the default."));
+               wri.println("</p>");
+       }
+
+}
diff --git a/src/org/cacert/gigi/pages/account/MyDetails.java b/src/org/cacert/gigi/pages/account/MyDetails.java
new file mode 100644 (file)
index 0000000..6bce47b
--- /dev/null
@@ -0,0 +1,47 @@
+package org.cacert.gigi.pages.account;
+
+import static org.cacert.gigi.Gigi.USER;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashMap;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.cacert.gigi.User;
+import org.cacert.gigi.output.DateSelector;
+import org.cacert.gigi.pages.Page;
+import org.cacert.gigi.util.HTMLEncoder;
+
+public class MyDetails extends Page {
+
+       public MyDetails() {
+               super("My Details");
+       }
+
+       public static final String PATH = "/account/details";
+
+       @Override
+       public void doGet(HttpServletRequest req, HttpServletResponse resp)
+                       throws IOException {
+               User u = (User) req.getSession().getAttribute(USER);
+
+               PrintWriter out = resp.getWriter();
+               HashMap<String, Object> map = new HashMap<String, Object>();
+               map.put("fname", HTMLEncoder.encodeHTML(u.getFname()));
+               map.put("mname",
+                               u.getMname() == null
+                                               ? ""
+                                               : HTMLEncoder.encodeHTML(u.getMname()));
+               map.put("lname", HTMLEncoder.encodeHTML(u.getLname()));
+               map.put("suffix",
+                               u.getSuffix() == null ? "" : HTMLEncoder.encodeHTML(u
+                                               .getSuffix()));
+               DateSelector ds = new DateSelector("day", "month", "year");
+               map.put("DoB", ds);
+               map.put("details", "");
+               getDefaultTemplate().output(out, getLanguage(req), map);
+
+       }
+}
diff --git a/src/org/cacert/gigi/pages/account/MyDetails.templ b/src/org/cacert/gigi/pages/account/MyDetails.templ
new file mode 100644 (file)
index 0000000..4a75843
--- /dev/null
@@ -0,0 +1,40 @@
+<form method="post" action="/account/myDetails">
+<table class="wrapper" width="400">
+  <tr>
+    <td colspan="2" class="title"><?=_My Details?></td>
+  </tr>
+  <tr>
+    <td class="DataTD" width="125"><?=_First Name?>: </td>
+    <td class="DataTD" width="125"><input type="text" name="fname" value="<?=$fname?>"></td>
+  </tr>
+  <tr>
+    <td class="DataTD" valign="top"><?=_Middle Name(s)?><br>
+      (<?=_optional?>)
+    </td>
+    <td class="DataTD"><input type="text" name="mname" value="<?=$mname?>"></td>
+  </tr>
+  <tr>
+    <td class="DataTD"><?=_Last Name?>: </td>
+    <td class="DataTD"><input type="text" name="lname" value="<?=$lname?>"></td>
+  </tr>
+  <tr>
+    <td class="DataTD"><?=_Suffix?><br>
+      (<?=_optional?>)</td>
+    <td class="DataTD"><input type="text" name="suffix" value="<?=$suffix?>"></td>
+  </tr>
+  <tr>
+    <td class="DataTD"><?=_Date of Birth?><br>
+           (<?=_dd/mm/yyyy?>)</td>
+    <td class="DataTD"><?=$DoB?></td>
+  </tr>
+  <tr>
+    <td colspan="2" class="title"><?=_('Show account history')?></td>
+  </tr>
+  <tr>
+    <td colspan="2" class="title"><?=_View secret question & answers and OTP phrases?></td>
+  </tr>
+  <?=$details?>
+  <tr><td class="DataTD" colspan="2"><input type="submit" name="process" value="<?=_Update?>"></td>
+  </tr>
+</table>
+</form>
diff --git a/src/org/cacert/gigi/pages/main/RegisterPage.java b/src/org/cacert/gigi/pages/main/RegisterPage.java
new file mode 100644 (file)
index 0000000..b381949
--- /dev/null
@@ -0,0 +1,65 @@
+package org.cacert.gigi.pages.main;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashMap;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.cacert.gigi.pages.Page;
+
+public class RegisterPage extends Page {
+
+       private static final String SIGNUP_PROCESS = "signupProcess";
+       public static final String PATH = "/register";
+
+       public RegisterPage() {
+               super("Register");
+       }
+
+       @Override
+       public void doGet(HttpServletRequest req, HttpServletResponse resp)
+                       throws IOException {
+               PrintWriter out = resp.getWriter();
+               HashMap<String, Object> vars = new HashMap<String, Object>();
+               getDefaultTemplate().output(out, getLanguage(req), vars);
+               Signup s = getForm(req);
+               s.output(out, getLanguage(req), vars);
+       }
+       public Signup getForm(HttpServletRequest req) {
+               HttpSession hs = req.getSession();
+               Signup s = (Signup) hs.getAttribute(SIGNUP_PROCESS);
+               if (s == null) {
+                       s = new Signup();
+                       hs.setAttribute(SIGNUP_PROCESS, s);
+               }
+               return s;
+
+       }
+       @Override
+       public void doPost(HttpServletRequest req, HttpServletResponse resp)
+                       throws IOException {
+               Signup s = getForm(req);
+               if (s.submit(resp.getWriter(), req)) {
+                       HttpSession hs = req.getSession();
+                       hs.setAttribute(SIGNUP_PROCESS, null);
+                       resp.getWriter()
+                                       .println(
+                                                       translate(
+                                                                       req,
+                                                                       "Your information has been submitted"
+                                                                                       + " into our system. You will now be sent an email with a web link,"
+                                                                                       + " you need to open that link in your web browser within 24 hours"
+                                                                                       + " or your information will be removed from our system!"));
+                       return;
+               }
+
+               super.doPost(req, resp);
+       }
+       @Override
+       public boolean needsLogin() {
+               return false;
+       }
+}
diff --git a/src/org/cacert/gigi/pages/main/RegisterPage.templ b/src/org/cacert/gigi/pages/main/RegisterPage.templ
new file mode 100644 (file)
index 0000000..4fbb41d
--- /dev/null
@@ -0,0 +1,11 @@
+<p><?=_By joining CAcert and becoming a member, you agree to the CAcert Community Agreement. Please take a moment now to read that and agree to it; this will be required to complete the process of joining.?></p>
+<p><?=_Warning! This site requires cookies to be enabled to ensure your privacy and security. This site uses session cookies to store temporary values to prevent people from copying and pasting the session ID to someone else exposing their account, personal details and identity theft as a result.?></p>
+<p style="border:dotted 1px #900;padding:0.3em;background-color:#ffe;">
+<b><?=_Note: Please enter your date of birth and names as they are written in your official documents.?></b><br /><br />
+<?=_Because CAcert is a certificate authority (CA) people rely on us knowing about the identity of the users of our certificates. So even as we value privacy very much, we need to collect at least some basic information about our members. This is especially the case for everybody who wants to take part in our web of trust.?>
+<?=_Your private information will be used for internal procedures only and will not be shared with third parties.?>
+</p>
+<p style="border:dotted 1px #900;padding:0.3em;background-color:#ffe;">
+<?=_A proper password wouldn't match your name or email at all, it contains at least 1 lower case letter, 1 upper case letter, a number, white space and a misc symbol. You get additional security for being over 15 characters and a second additional point for having it over 30. The system starts reducing security if you include any section of your name, or password or email address or if it matches a word from the english dictionary...?><br><br>
+<b><?=_Note: White spaces at the beginning and end of a password will be removed.?></b>
+</p>
diff --git a/src/org/cacert/gigi/pages/main/Signup.java b/src/org/cacert/gigi/pages/main/Signup.java
new file mode 100644 (file)
index 0000000..3d07444
--- /dev/null
@@ -0,0 +1,282 @@
+package org.cacert.gigi.pages.main;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.cacert.gigi.Language;
+import org.cacert.gigi.User;
+import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.email.EmailProvider;
+import org.cacert.gigi.output.DateSelector;
+import org.cacert.gigi.output.Form;
+import org.cacert.gigi.output.Template;
+import org.cacert.gigi.pages.Page;
+import org.cacert.gigi.util.HTMLEncoder;
+import org.cacert.gigi.util.Notary;
+import org.cacert.gigi.util.PasswordStrengthChecker;
+import org.cacert.gigi.util.RandomToken;
+import org.cacert.gigi.util.ServerConstants;
+
+public class Signup extends Form {
+       User buildup = new User();
+       Template t;
+       boolean general = true, country = true, regional = true, radius = true;
+       public Signup() {
+               try {
+                       t = new Template(new InputStreamReader(
+                                       Signup.class.getResourceAsStream("Signup.templ"), "UTF-8"));
+               } catch (UnsupportedEncodingException e) {
+                       e.printStackTrace();
+               }
+               buildup.setFname("");
+               buildup.setMname("");
+               buildup.setLname("");
+               buildup.setSuffix("");
+               buildup.setEmail("");
+               buildup.setDob(new Date(0));
+       }
+       DateSelector myDoB = new DateSelector("day", "month", "year");
+
+       public void output(PrintWriter out, Language l,
+                       Map<String, Object> outerVars) {
+               HashMap<String, Object> vars = new HashMap<String, Object>();
+               vars.put("fname", HTMLEncoder.encodeHTML(buildup.getFname()));
+               vars.put("mname", HTMLEncoder.encodeHTML(buildup.getMname()));
+               vars.put("lname", HTMLEncoder.encodeHTML(buildup.getLname()));
+               vars.put("suffix", HTMLEncoder.encodeHTML(buildup.getSuffix()));
+               vars.put("dob", myDoB);
+               vars.put("email", HTMLEncoder.encodeHTML(buildup.getEmail()));
+               vars.put("general", general ? " checked=\"checked\"" : "");
+               vars.put("country", country ? " checked=\"checked\"" : "");
+               vars.put("regional", regional ? " checked=\"checked\"" : "");
+               vars.put("radius", radius ? " checked=\"checked\"" : "");
+               vars.put(
+                               "helpOnNames",
+                               String.format(
+                                               l.getTranslation("Help on Names %sin the wiki%s"),
+                                               "<a href=\"//wiki.cacert.org/FAQ/HowToEnterNamesInJoinForm\" target=\"_blank\">",
+                                               "</a>"));
+               t.output(out, l, vars);
+       }
+       private void update(HttpServletRequest r) {
+               if (r.getParameter("fname") != null) {
+                       buildup.setFname(r.getParameter("fname"));
+               }
+               if (r.getParameter("lname") != null) {
+                       buildup.setLname(r.getParameter("lname"));
+               }
+               if (r.getParameter("mname") != null) {
+                       buildup.setMname(r.getParameter("mname"));
+               }
+               if (r.getParameter("suffix") != null) {
+                       buildup.setSuffix(r.getParameter("suffix"));
+               }
+               if (r.getParameter("email") != null) {
+                       buildup.setEmail(r.getParameter("email"));
+               }
+               general = "1".equals(r.getParameter("general"));
+               country = "1".equals(r.getParameter("country"));
+               regional = "1".equals(r.getParameter("regional"));
+               radius = "1".equals(r.getParameter("radius"));
+               myDoB.update(r);
+       }
+
+       @Override
+       public synchronized boolean submit(PrintWriter out, HttpServletRequest req) {
+               update(req);
+               boolean failed = false;
+               out.println("<div class='formError'>");
+               if (buildup.getFname().equals("") || buildup.getLname().equals("")) {
+                       outputError(out, req, "First and/or last names were blank.");
+                       failed = true;
+               }
+               if (!myDoB.isValid()) {
+                       outputError(out, req, "Invalid date of birth");
+                       failed = true;
+               }
+               if (!"1".equals(req.getParameter("cca_agree"))) {
+                       outputError(out, req,
+                                       "You have to agree to the CAcert Community agreement.");
+                       failed = true;
+               }
+               if (buildup.getEmail().equals("")) {
+                       outputError(out, req, "Email Address was blank");
+                       failed = true;
+               }
+               String pw1 = req.getParameter("pword1");
+               String pw2 = req.getParameter("pword2");
+               if (pw1 == null || pw1.equals("")) {
+                       outputError(out, req, "Pass Phrases were blank");
+                       failed = true;
+               } else if (!pw1.equals(pw2)) {
+                       outputError(out, req, "Pass Phrases don't match");
+                       failed = true;
+               }
+               int pwpoints = PasswordStrengthChecker.checkpw(pw1, buildup);
+               if (pwpoints < 3) {
+                       outputError(
+                                       out,
+                                       req,
+                                       "The Pass Phrase you submitted failed to contain enough"
+                                                       + " differing characters and/or contained words from"
+                                                       + " your name and/or email address.");
+                       failed = true;
+               }
+               if (failed) {
+                       out.println("</div>");
+                       return false;
+               }
+               try {
+                       PreparedStatement q1 = DatabaseConnection.getInstance().prepare(
+                                       "select * from `email` where `email`=? and `deleted`=0");
+                       PreparedStatement q2 = DatabaseConnection.getInstance().prepare(
+                                       "select * from `users` where `email`=? and `deleted`=0");
+                       q1.setString(1, buildup.getEmail());
+                       q2.setString(1, buildup.getEmail());
+                       ResultSet r1 = q1.executeQuery();
+                       ResultSet r2 = q2.executeQuery();
+                       if (r1.next() || r2.next()) {
+                               outputError(out, req,
+                                               "This email address is currently valid in the system.");
+                               failed = true;
+                       }
+                       r1.close();
+                       r2.close();
+                       PreparedStatement q3 = DatabaseConnection
+                                       .getInstance()
+                                       .prepare(
+                                                       "select `domain` from `baddomains` where `domain`=RIGHT(?, LENGTH(`domain`))");
+                       q3.setString(1, buildup.getEmail());
+
+                       ResultSet r3 = q3.executeQuery();
+                       if (r3.next()) {
+                               String domain = r3.getString(1);
+                               out.print("<div>");
+                               out.print(String.format(
+                                               Page.translate(req,
+                                                               "We don't allow signups from people using email addresses from %s"),
+                                               domain));
+                               out.println("</div>");
+                               failed = true;
+                       }
+                       r3.close();
+               } catch (SQLException e) {
+                       e.printStackTrace();
+                       failed = true;
+               }
+               String mailResult = EmailProvider.FAIL;
+               try {
+                       mailResult = EmailProvider.getInstance().checkEmailServer(0,
+                                       buildup.getEmail());
+               } catch (IOException e) {
+               }
+               if (!mailResult.equals(EmailProvider.OK)) {
+                       if (mailResult.startsWith("4")) {
+                               outputError(
+                                               out,
+                                               req,
+                                               "The mail server responsible for your domain indicated"
+                                                               + " a temporary failure. This may be due to anti-SPAM measures, such"
+                                                               + " as greylisting. Please try again in a few minutes.");
+                       } else {
+                               outputError(
+                                               out,
+                                               req,
+                                               "Email Address given was invalid, or a test connection"
+                                                               + " couldn't be made to your server, or the server"
+                                                               + " rejected the email address as invalid");
+                       }
+                       if (mailResult.equals(EmailProvider.FAIL)) {
+                               outputError(out, req,
+                                               "Failed to make a connection to the mail server");
+                       } else {
+                               out.print("<div>");
+                               out.print(mailResult);
+                               out.println("</div>");
+                       }
+                       failed = true;
+               }
+
+               out.println("</div>");
+               if (failed) {
+                       return false;
+               }
+               try {
+                       run(req, pw1);
+               } catch (SQLException e) {
+                       e.printStackTrace();
+               }
+               return true;
+       }
+
+       private void run(HttpServletRequest req, String password)
+                       throws SQLException {
+               try {
+                       DatabaseConnection.getInstance().beginTransaction();
+                       String hash = RandomToken.generateToken(16);
+
+                       buildup.setDob(myDoB.getDate());
+                       buildup.insert(password);
+                       int memid = buildup.getId();
+                       PreparedStatement ps = DatabaseConnection.getInstance().prepare(
+                                       "insert into `email` set `email`=?,"
+                                                       + " `hash`=?, `created`=NOW(),`memid`=?");
+                       ps.setString(1, buildup.getEmail());
+                       ps.setString(2, hash);
+                       ps.setInt(3, memid);
+                       ps.execute();
+                       int emailid = DatabaseConnection.lastInsertId(ps);
+                       ps = DatabaseConnection
+                                       .getInstance()
+                                       .prepare(
+                                                       "insert into `alerts` set `memid`=?,"
+                                                                       + " `general`=?, `country`=?, `regional`=?, `radius`=?");
+                       ps.setInt(1, memid);
+                       ps.setString(2, general ? "1" : "0");
+                       ps.setString(3, country ? "1" : "0");
+                       ps.setString(4, regional ? "1" : "0");
+                       ps.setString(5, radius ? "1" : "0");
+                       ps.execute();
+                       Notary.writeUserAgreement(memid, "CCA", "account creation", "",
+                                       true, 0);
+
+                       StringBuffer body = new StringBuffer();
+                       body.append(Page
+                                       .translate(
+                                                       req,
+                                                       "Thanks for signing up with CAcert.org, below is the link you need to open to verify your account. Once your account is verified you will be able to start issuing certificates till your hearts' content!"));
+                       body.append("\n\n");
+                       body.append(ServerConstants.NORMAL_HOST_NAME);
+                       body.append("/verify?type=email&id=");
+                       body.append(emailid);
+                       body.append("&hash=");
+                       body.append(hash);
+                       body.append("\n\n");
+                       body.append(Page.translate(req, "Best regards"));
+                       body.append("\n");
+                       body.append(Page.translate(req, "CAcert.org Support!"));
+                       try {
+                               EmailProvider.getInstance().sendmail(buildup.getEmail(),
+                                               "[CAcert.org] " + Page.translate(req, "Mail Probe"),
+                                               body.toString(), "support@cacert.org", null, null,
+                                               null, null, false);
+                       } catch (IOException e) {
+                               e.printStackTrace();
+                       }
+                       DatabaseConnection.getInstance().commitTransaction();
+               } finally {
+                       DatabaseConnection.getInstance().quitTransaction();
+               }
+
+       }
+}
diff --git a/src/org/cacert/gigi/pages/main/Signup.templ b/src/org/cacert/gigi/pages/main/Signup.templ
new file mode 100644 (file)
index 0000000..631215e
--- /dev/null
@@ -0,0 +1,83 @@
+<form method="post" action="/register" autocomplete="off">
+<table align="center" valign="middle" border="0" cellspacing="0" cellpadding="0" class="wrapper" width="400">
+  <tr>
+    <td colspan="3" class="title"><?=_My Details?></td>
+  </tr>
+
+  <tr>
+    <td class="DataTD" width="125"><?=_First Name?>: </td>
+    <td class="DataTD" width="125"><input type="text" name="fname" size="30" value="<?=$fname?>" autocomplete="off"></td>
+    <td rowspan="4" class="DataTD" width="125"><?=$helpOnNames?></td>
+  </tr>
+
+  <tr>
+    <td class="DataTD" valign="top"><?=_Middle Name(s)?><br>
+      (<?=_optional?>)
+    </td>
+    <td class="DataTD"><input type="text" name="mname" size="30" value="<?=$mname?>" autocomplete="off"></td>
+  </tr>
+
+  <tr>
+    <td class="DataTD"><?=_Last Name?>: </td>
+    <td class="DataTD"><input type="text" name="lname" size="30" value="<?=$lname?>" autocomplete="off"></td>
+  </tr>
+
+  <tr>
+    <td class="DataTD"><?=_Suffix?><br>
+      (<?=_optional?>)</td>
+    <td class="DataTD"><input type="text" name="suffix" size="30" value="<?=$suffix?>" autocomplete="off"><br><?=_Please only write Name Suffixes into this field.?></td>
+  </tr>
+
+  <tr>
+    <td class="DataTD"><?=_Date of Birth?><br>
+           (<?=_dd/mm/yyyy?>)</td>
+    <td class="DataTD"><?=$dob?></td>
+    <td class="DataTD">&nbsp;</td>
+  </tr>
+
+  <tr>
+    <td class="DataTD"><?=_Email Address?>: </td>
+    <td class="DataTD"><input type="text" name="email" size="30" value="<?=$email?>" autocomplete="off"></td>
+    <td class="DataTD"><?=_I own or am authorised to control this email address?></td>
+  </tr>
+
+  <tr>
+    <td class="DataTD"><?=_Pass Phrase?><font color="red">*</font>: </td>
+    <td class="DataTD"><input type="password" name="pword1" size="30" autocomplete="off"></td>
+    <td class="DataTD" rowspan="2">&nbsp;</td>
+  </tr>
+  <tr>
+    <td class="DataTD"><?=_Pass Phrase Again?><font color="red">*</font>: </td>
+    <td class="DataTD"><input type="password" name="pword2" size="30" autocomplete="off"></td>
+  </tr>
+
+  <tr>
+    <td class="DataTD" colspan="3"><font color="red">*</font><?=_Please note, in the interests of good security, the pass phrase must be made up of an upper case letter, lower case letter, number and symbol.?></td>
+  </tr>
+  <tr>
+    <td class="DataTD" colspan="3"><?=_It's possible to get notifications of up and coming events and even just general announcements, untick any notifications you don't wish to receive. For country, regional and radius notifications to work you must choose your location once you've verified your account and logged in.?></td>
+  </tr>
+
+  <tr>
+    <td class="DataTD" valign="top"><?=_Alert me if?>: </td>
+    <td class="DataTD" align="left">
+        <input type="checkbox" name="general" value="1"<?=$general?>><?=_General Announcements?><br>
+       <input type="checkbox" name="country" value="1"<?=$country?>><?=_Country Announcements?><br>
+       <input type="checkbox" name="regional" value="1"<?=$regional?>><?=_Regional Announcements?><br>
+       <input type="checkbox" name="radius" value="1"<?=$radius?>><?=_Within 200km Announcements?></td>
+    <td class="DataTD">&nbsp;</td>
+  </tr>
+
+  <tr>
+    <td class="DataTD" colspan="3"><?=_When you click on next, we will send a confirmation email to the email address you have entered above.?></td>
+  </tr>
+  <tr>
+    <td class="DataTD" colspan="3"><input type="checkbox" name="cca_agree" value="1"><?=_I agree to the terms and conditions of the CAcert Community Agreement?>: <a href="/policy/CAcertCommunityAgreement.php">http://www.cacert.org/policy/CAcertCommunityAgreement.php</a></td>
+  </tr>
+
+  <tr>
+    <td class="DataTD" colspan="3"><input type="submit" name="process" value="<?=_Next?>"></td>
+  </tr>
+
+</table>
+</form>
diff --git a/src/org/cacert/gigi/pages/wot/AssuranceForm.java b/src/org/cacert/gigi/pages/wot/AssuranceForm.java
new file mode 100644 (file)
index 0000000..b3546fb
--- /dev/null
@@ -0,0 +1,119 @@
+package org.cacert.gigi.pages.wot;
+
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.sql.SQLException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.cacert.gigi.Language;
+import org.cacert.gigi.User;
+import org.cacert.gigi.output.Form;
+import org.cacert.gigi.output.Template;
+import org.cacert.gigi.pages.LoginPage;
+import org.cacert.gigi.util.Notary;
+
+public class AssuranceForm extends Form {
+       User assuree;
+       static final Template templ;
+       static {
+               templ = new Template(new InputStreamReader(
+                               AssuranceForm.class.getResourceAsStream("AssuranceForm.templ")));
+       }
+
+       public AssuranceForm(int assuree) {
+               this.assuree = new User(assuree);
+       }
+       SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+
+       @Override
+       public void output(PrintWriter out, Language l, Map<String, Object> vars) {
+               HashMap<String, Object> res = new HashMap<String, Object>();
+               res.putAll(vars);
+               res.put("name", assuree.getName());
+               try {
+                       res.put("maxpoints", assuree.getMaxAssurePoints());
+               } catch (SQLException e) {
+                       e.printStackTrace();
+               }
+               res.put("dob", sdf.format(assuree.getDob()));
+               templ.output(out, l, res);
+       }
+
+       @Override
+       public boolean submit(PrintWriter out, HttpServletRequest req) {
+               out.println("<div class='formError'>");
+               boolean failed = false;
+
+               if (!"1".equals(req.getParameter("certify"))
+                               || !"1".equals(req.getParameter("rules"))
+                               || !"1".equals(req.getParameter("CCAAgreed"))
+                               || !"1".equals(req.getParameter("assertion"))) {
+                       outputError(out, req, "You failed to check all boxes to validate"
+                                       + " your adherence to the rules and policies of CAcert");
+                       failed = true;
+
+               }
+               if (req.getParameter("date") == null
+                               || req.getParameter("date").equals("")) {
+                       outputError(out, req,
+                                       "You must enter the date when you met the assuree.");
+                       failed = true;
+               } else {
+                       try {
+                               Date d = sdf.parse(req.getParameter("date"));
+                               if (d.getTime() > System.currentTimeMillis()) {
+                                       outputError(out, req,
+                                                       "You must not enter a date in the future.");
+                                       failed = true;
+                               }
+                       } catch (ParseException e) {
+                               outputError(out, req,
+                                               "You must enter the date in this format: YYYY-MM-DD.");
+                               failed = true;
+                       }
+               }
+               // check location, min 3 characters
+               if (req.getParameter("location") == null
+                               || req.getParameter("location").equals("")) {
+                       outputError(out, req,
+                                       "You failed to enter a location of your meeting.");
+                       failed = true;
+               } else if (req.getParameter("location").length() <= 2) {
+                       outputError(out, req,
+                                       "You must enter a location with at least 3 characters eg town and country.");
+                       failed = true;
+               }
+               // TODO checkPoints
+               String points = req.getParameter("points");
+               if (points == null || "".equals(points)) {
+                       // TODO message
+                       failed = true;
+               }
+               if (failed) {
+                       out.println("</div>");
+                       return false;
+               }
+               try {
+                       boolean success = Notary.assure(LoginPage.getUser(req), assuree,
+                                       Integer.parseInt(req.getParameter("points")),
+                                       req.getParameter("location"), req.getParameter("date"));
+                       if (!success) {
+                               outputError(out, req,
+                                               "Assurance failed. Maybe user data changed.");
+                       }
+                       out.println("</div>");
+                       return success;
+               } catch (SQLException e) {
+                       e.printStackTrace();
+               }
+
+               out.println("</div>");
+               return false;
+       }
+}
diff --git a/src/org/cacert/gigi/pages/wot/AssuranceForm.templ b/src/org/cacert/gigi/pages/wot/AssuranceForm.templ
new file mode 100644 (file)
index 0000000..c3df60c
--- /dev/null
@@ -0,0 +1,58 @@
+<form method="POST">
+<table class="wrapper" width="600">
+<tr><td colspan="2" class="title"><?=_Assurance Confirmation?></td></tr>
+<tr><td colspan="2" class="DataTD"><?=s,$name,Please check the following details match against what you witnessed when you met %s in person. You MUST NOT proceed unless you are sure the details are correct. You may be held responsible by the CAcert Arbitrator for any issues with this Assurance.?>
+</td></tr>
+
+       <tr>
+               <td class="DataTD"><?=_Name?>: </td>
+               <td class="DataTD"><span class="accountdetail"><?=$name?></span></td>
+       </tr>
+       <tr>
+               <td class="DataTD"><?=_Date of Birth?>: </td>
+               <td class="DataTD"><span class="accountdetail dob"><?=$dob?></span></td>
+       </tr>
+       <tr>
+               <td class="DataTD"><input type="checkbox" name="certify" value="1"></td>
+               <td class="DataTD"><?=s,$name,I certify that %s has appeared in person.?></td>
+       </tr>
+       <tr>
+               <td class="DataTD"><input type="checkbox" name="CCAAgreed" value="1"></td>
+               <td class="DataTD"><?=s,$name,I verify that %s has accepted the CAcert Community Agreement.?></td>
+       </tr>
+       <tr>
+               <td class="DataTD"><?=_Location?></td>
+               <td class="DataTD"><input type="text" name="location"></td>
+       </tr>
+       <tr>
+               <td class="DataTD"><?=_Date?></td>
+               <td class="DataTD"><input type="text" name="date"><br/><?=_The date when the assurance took place. Please adjust the date if you assured the person on a different day (YYYY-MM-DD).?></td>
+       </tr>
+       <tr>
+               <td class="DataTD"><input type="checkbox" name="assertion" value="1"></td>
+               <td class="DataTD"><?=_I believe that the assertion of identity I am making is correct, complete and verifiable. I have seen original documentation attesting to this identity. I accept that the CAcert Arbitrator may call upon me to provide evidence in any dispute, and I may be held responsible.?></td>
+       </tr>
+       <tr>
+               <td class="DataTD"><input type="checkbox" name="rules" value="1"></td>
+               <td class="DataTD"><?=_I have read and understood the CAcert Community Agreement (CCA), Assurance Policy and the Assurance Handbook. I am making this Assurance subject to and in compliance with the CCA, Assurance policy and handbook.?></td>
+       </tr>
+       <tr>
+               <td class="DataTD"><?=_Policy?>: </td>
+               <td class="DataTD">
+                       <a href="/policy/CAcertCommunityAgreement.php" target="_blank"><?=_CAcert Community Agreement?></a>
+                        - <a href="/policy/AssurancePolicy.php" target="_blank"><?=_Assurance Policy?></a>
+                        - <a href="http://wiki.cacert.org/AssuranceHandbook2" target="_blank"><?=_Assurance Handbook?></a>
+               </td>
+       </tr>
+       <tr>
+               <td class="DataTD"><?=_Points?></td>
+               <td class="DataTD"><input type="text" name="points"><br/>(Max. <?=$maxpoints?>)</td>
+       </tr>
+       <tr>
+               <td class="DataTD" colspan="2">
+                       <input type="submit" name="process" value="<?=_I confirm this Assurance?>" />
+                       <input type="submit" name="cancel" value="<?=_Cancel?>" />
+               </td>
+       </tr>
+</table>
+</form>
diff --git a/src/org/cacert/gigi/pages/wot/AssurePage.java b/src/org/cacert/gigi/pages/wot/AssurePage.java
new file mode 100644 (file)
index 0000000..8862535
--- /dev/null
@@ -0,0 +1,136 @@
+package org.cacert.gigi.pages.wot;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.cacert.gigi.User;
+import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.output.DateSelector;
+import org.cacert.gigi.output.Template;
+import org.cacert.gigi.pages.LoginPage;
+import org.cacert.gigi.pages.Page;
+import org.cacert.gigi.util.Notary;
+
+public class AssurePage extends Page {
+       public static final String PATH = "/wot/assure";
+       public static final String SESSION = "/wot/assure/FORM";
+       DateSelector ds = new DateSelector("day", "month", "year");
+       Template t;
+
+       public AssurePage() {
+               super("Assure someone");
+               t = new Template(new InputStreamReader(
+                               AssuranceForm.class.getResourceAsStream("AssureeSearch.templ")));
+
+       }
+
+       @Override
+       public void doGet(HttpServletRequest req, HttpServletResponse resp)
+                       throws IOException {
+
+               PrintWriter out = resp.getWriter();
+               String pi = req.getPathInfo().substring(PATH.length());
+               if (pi.length() > 1) {
+                       User myself = LoginPage.getUser(req);
+                       int mid = Integer.parseInt(pi.substring(1));
+
+                       if (!Notary.checkAssuranceIsPossible(myself, new User(mid), out)) {
+                               return;
+                       }
+                       HttpSession hs = req.getSession();
+                       AssuranceForm form = (AssuranceForm) hs.getAttribute(SESSION);
+                       if (form == null || form.assuree.getId() != mid) {
+                               form = new AssuranceForm(mid);
+                               hs.setAttribute(SESSION, form);
+                       }
+
+                       form.output(out, getLanguage(req), new HashMap<String, Object>());;
+               } else {
+                       HashMap<String, Object> vars = new HashMap<String, Object>();
+                       vars.put("DoB", ds);
+                       t.output(out, getLanguage(req), vars);
+               }
+       }
+       @Override
+       public void doPost(HttpServletRequest req, HttpServletResponse resp)
+                       throws IOException {
+               PrintWriter out = resp.getWriter();
+               String pi = req.getPathInfo().substring(PATH.length());
+               if (pi.length() > 1) {
+                       User myself = LoginPage.getUser(req);
+                       int mid = Integer.parseInt(pi.substring(1));
+                       if (mid == myself.getId()) {
+                               out.println("Cannot assure myself.");
+                               return;
+                       }
+
+                       AssuranceForm form = (AssuranceForm) req.getSession().getAttribute(
+                                       SESSION);
+                       if (form == null) {
+                               out.println("No form found. This is an Error. Fill in the form again.");
+                               return;
+                       }
+                       form.submit(out, req);
+
+                       return;
+               }
+
+               System.out.println("searching for");
+               ResultSet rs = null;
+               try {
+                       PreparedStatement ps = DatabaseConnection
+                                       .getInstance()
+                                       .prepare(
+                                                       "SELECT id, verified FROM users WHERE email=? AND dob=? AND deleted=0");
+                       ps.setString(1, req.getParameter("email"));
+                       String day = req.getParameter("year") + "-"
+                                       + req.getParameter("month") + "-" + req.getParameter("day");
+                       ps.setString(2, day);
+                       rs = ps.executeQuery();
+                       int id = 0;
+                       if (rs.next()) {
+                               id = rs.getInt(1);
+                               int verified = rs.getInt(2);
+                               if (rs.next()) {
+                                       out.println("Error, ambigous user. Please contact support@cacert.org.");
+                               } else {
+                                       if (verified == 0) {
+                                               out.println(translate(req,
+                                                               "User is not yet verified. Please try again in 24 hours!"));
+                                       }
+                                       resp.sendRedirect(PATH + "/" + id);
+                               }
+                       } else {
+                               out.print("<div class='formError'>");
+
+                               out.println(translate(
+                                               req,
+                                               "I'm sorry, there was no email and date of birth matching"
+                                                               + " what you entered in the system. Please double check"
+                                                               + " your information."));
+                               out.print("</div>");
+                       }
+
+                       rs.close();
+               } catch (SQLException e) {
+                       e.printStackTrace();
+               } finally {
+                       try {
+                               if (rs != null) {
+                                       rs.close();
+                               }
+                       } catch (SQLException e) {
+                               e.printStackTrace();
+                       }
+               }
+       }
+}
diff --git a/src/org/cacert/gigi/pages/wot/AssureeSearch.templ b/src/org/cacert/gigi/pages/wot/AssureeSearch.templ
new file mode 100644 (file)
index 0000000..cd1cb28
--- /dev/null
@@ -0,0 +1,19 @@
+<form method="POST">
+<table class="wrapper" width="300">
+  <tr>
+    <td colspan="2" class="title"><?=_Assure Someone?></td>
+  </tr>
+  <tr>
+    <td class="DataTD" width="125"><?=_Email?>: </td>
+    <td class="DataTD" width="125"><input type="text" name="email"></td>
+  </tr>
+  <tr>
+    <td class="DataTD" width="125"><?=_Date of Birth?><br>
+           (<?=_dd/mm/yyyy?>)</td>
+    <td class="DataTD" width="125"><?=$DoB?></td>
+  </tr>
+  <tr>
+    <td class="DataTD" colspan="2"><input type="submit" name="process" value="<?=_Next?>"></td>
+  </tr>
+</table>
+</form>
diff --git a/src/org/cacert/gigi/util/CipherInfo.java b/src/org/cacert/gigi/util/CipherInfo.java
new file mode 100644 (file)
index 0000000..5860c11
--- /dev/null
@@ -0,0 +1,288 @@
+package org.cacert.gigi.util;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.TreeSet;
+
+import sun.security.ssl.SSLContextImpl;
+
+public class CipherInfo implements Comparable<CipherInfo> {
+       private static class CipherInfoGenerator {
+               private Class<?> cipherSuite;
+               private Field cipherSuiteNameMap;
+               private Field exchange;
+               private Field cipher;
+               private Field keySize;
+               private Field algortihm;
+               private Field transformation;
+               private HashMap<?, ?> names;
+               private Field macAlg;
+               private Field macName;
+               private Field macSize;
+
+               public CipherInfoGenerator() throws ReflectiveOperationException {
+                       SSLContextImpl sc = new SSLContextImpl.TLS12Context();
+                       Method m = SSLContextImpl.class
+                                       .getDeclaredMethod("getSupportedCipherSuiteList");
+                       m.setAccessible(true);
+                       Object o = m.invoke(sc);
+                       Class<?> cipherSuiteList = o.getClass();
+                       Method collection = cipherSuiteList.getDeclaredMethod("collection");
+                       collection.setAccessible(true);
+                       Collection<?> suites = (Collection<?>) collection.invoke(o);
+                       Object oneSuite = suites.iterator().next();
+                       cipherSuite = oneSuite.getClass();
+                       cipherSuiteNameMap = cipherSuite.getDeclaredField("nameMap");
+                       cipherSuiteNameMap.setAccessible(true);
+                       names = (HashMap<?, ?>) cipherSuiteNameMap.get(null);
+                       exchange = cipherSuite.getDeclaredField("keyExchange");
+                       exchange.setAccessible(true);
+                       cipher = cipherSuite.getDeclaredField("cipher");
+                       cipher.setAccessible(true);
+                       Class<?> bulkCipher = cipher.getType();
+                       keySize = bulkCipher.getDeclaredField("keySize");
+                       keySize.setAccessible(true);
+                       algortihm = bulkCipher.getDeclaredField("algorithm");
+                       algortihm.setAccessible(true);
+                       transformation = bulkCipher.getDeclaredField("transformation");
+                       transformation.setAccessible(true);
+
+                       macAlg = cipherSuite.getDeclaredField("macAlg");
+                       macAlg.setAccessible(true);
+                       Class<?> mac = macAlg.getType();
+                       macName = mac.getDeclaredField("name");
+                       macName.setAccessible(true);
+                       macSize = mac.getDeclaredField("size");
+                       macSize.setAccessible(true);
+               }
+               public CipherInfo generateInfo(String suiteName)
+                               throws IllegalArgumentException, IllegalAccessException {
+                       Object suite = names.get(suiteName);
+                       String keyExchange = exchange.get(suite).toString();
+                       Object bulkCipher = cipher.get(suite);
+                       Object mac = macAlg.get(suite);
+
+                       String transform = (String) transformation.get(bulkCipher);
+                       String[] transformationParts = transform.split("/");
+                       int keysize = keySize.getInt(bulkCipher);
+
+                       String macNam = (String) macName.get(mac);
+                       int macSiz = macSize.getInt(mac);
+
+                       String chaining = null;
+                       String padding = null;
+                       if (transformationParts.length > 1) {
+                               chaining = transformationParts[1];
+                               padding = transformationParts[2];
+                       }
+
+                       return new CipherInfo(suiteName, keyExchange,
+                                       transformationParts[0], keysize * 8, chaining, padding,
+                                       macNam, macSiz * 8);
+
+               }
+       }
+       String keyExchange;
+       String cipher;
+       int keySize;
+       String cipherChaining;
+       String cipherPadding;
+       String macName;
+       int macSize;
+       String suiteName;
+
+       private CipherInfo(String suiteName, String keyExchange, String cipher,
+                       int keySize, String cipherChaining, String cipherPadding,
+                       String macName, int macSize) {
+               this.suiteName = suiteName;
+               this.keyExchange = keyExchange;
+               this.cipher = cipher;
+               this.keySize = keySize;
+               this.cipherChaining = cipherChaining;
+               this.cipherPadding = cipherPadding;
+               this.macName = macName;
+               this.macSize = macSize;
+       }
+
+       static CipherInfoGenerator cig;
+       static {
+               try {
+                       cig = new CipherInfoGenerator();
+               } catch (ReflectiveOperationException e) {
+                       e.printStackTrace();
+               }
+       }
+
+       public static CipherInfo generateInfo(String name) {
+               if (cig == null) {
+                       return null;
+               }
+               try {
+                       return cig.generateInfo(name);
+               } catch (IllegalArgumentException e) {
+                       e.printStackTrace();
+               } catch (IllegalAccessException e) {
+                       e.printStackTrace();
+               }
+               return null;
+       }
+       public String getSuiteName() {
+               return suiteName;
+       }
+       /**
+        * 5: ECDHE, AES||CAMELLIA, keysize >=256 <br>
+        * 4: DHE, AES||CAMELLIA, keysize >= 256<br>
+        * 3: ECDHE|| DHE, AES||CAMELLIA<br>
+        * 2: ECDHE||DHE<br>
+        * 1: RSA||DSA <br>
+        * 0: Others
+        * 
+        * @return the strength
+        */
+       public int getStrength() {
+               if (cipher.equals("NULL") || cipher.equals("RC4")
+                               || cipher.contains("DES")) {
+                       return 0;
+               }
+               boolean ecdhe = keyExchange.startsWith("ECDHE");
+               boolean dhe = keyExchange.startsWith("DHE");
+               boolean pfs = ecdhe || dhe;
+               boolean goodCipher = cipher.equals("AES") || cipher.equals("CAMELLIA");
+               if (ecdhe && goodCipher && keySize >= 256) {
+                       return 5;
+               }
+               if (dhe && goodCipher && keySize >= 256) {
+                       return 4;
+               }
+               if (pfs && goodCipher) {
+                       return 3;
+               }
+               if (pfs) {
+                       return 2;
+               }
+               if (keyExchange.equals("RSA") || keyExchange.equals("DSA")) {
+                       return 1;
+               }
+               return 0;
+       }
+       private static final String[] CIPHER_RANKING = new String[]{"CAMELLIA",
+                       "AES", "RC4", "3DES", "DES", "DES40"};
+
+       @Override
+       public String toString() {
+               return "CipherInfo [keyExchange=" + keyExchange + ", cipher=" + cipher
+                               + ", keySize=" + keySize + ", cipherChaining=" + cipherChaining
+                               + ", cipherPadding=" + cipherPadding + ", macName=" + macName
+                               + ", macSize=" + macSize + "]";
+       }
+       /**
+        * ECDHE<br>
+        * GCM<br>
+        * Cipher {@link #CIPHER_RANKING}<br>
+        * Cipher {@link #keySize}<br>
+        * HMAC<br>
+        * HMAC size<br>
+        * 
+        * @return
+        */
+       @Override
+       public int compareTo(CipherInfo o) {
+               int myStrength = getStrength();
+               int oStrength = o.getStrength();
+               if (myStrength > oStrength) {
+                       return -1;
+               }
+               if (myStrength < oStrength) {
+                       return 1;
+               }
+               // TODO sort SSL/TLS
+               boolean myEcdhe = keyExchange.startsWith("ECDHE");
+               boolean oEcdhe = o.keyExchange.startsWith("ECDHE");
+               if (myEcdhe && !oEcdhe) {
+                       return -1;
+               }
+               if (!myEcdhe && oEcdhe) {
+                       return 1;
+               }
+               boolean myGCM = "GCM".equals(cipherChaining);
+               boolean oGCM = "GCM".equals(o.cipherChaining);
+               if (myGCM && !oGCM) {
+                       return -1;
+               }
+               if (!myGCM && oGCM) {
+                       return 1;
+               }
+               if (!cipher.equals(o.cipher)) {
+
+                       for (String testCipher : CIPHER_RANKING) {
+                               if (cipher.equals(testCipher)) {
+                                       return -1;
+                               }
+                               if (o.cipher.equals(testCipher)) {
+                                       return 1;
+                               }
+                       }
+                       if (cipher.equals("NULL")) {
+                               return 1;
+                       }
+                       if (o.cipher.equals("NULL")) {
+                               return -1;
+                       }
+               }
+               if (keySize > o.keySize) {
+                       return -1;
+               }
+               if (keySize < o.keySize) {
+                       return 1;
+               }
+               boolean mySHA = macName.startsWith("SHA");
+               boolean oSHA = o.macName.startsWith("SHA");
+               if (mySHA && !oSHA) {
+                       return -1;
+               }
+               if (mySHA && !oSHA) {
+                       return 1;
+               }
+               if (macSize > o.macSize) {
+                       return -1;
+               }
+               if (macSize < o.macSize) {
+                       return 1;
+               }
+
+               return suiteName.compareTo(o.suiteName);
+       }
+       static String[] cipherRanking = null;
+       public static String[] getCompleteRanking() {
+               if (cipherRanking == null) {
+                       String[] ciphers = filterCiphers((Iterable<String>) cig.names
+                                       .keySet());
+                       cipherRanking = ciphers;
+               }
+               return cipherRanking;
+       }
+       private static String[] filterCiphers(Iterable<String> toFilter) {
+               TreeSet<CipherInfo> chosenCiphers = new TreeSet<CipherInfo>();
+               for (String o : toFilter) {
+                       String s = o;
+                       CipherInfo info = CipherInfo.generateInfo(s);
+                       if (info != null) {
+                               if (info.getStrength() > 1) {
+                                       chosenCiphers.add(info);
+                               }
+                       }
+               }
+               String[] ciphers = new String[chosenCiphers.size()];
+               int counter = 0;
+               for (CipherInfo i : chosenCiphers) {
+                       ciphers[counter++] = i.getSuiteName();
+               }
+               return ciphers;
+       }
+       public static String[] filter(String[] supportedCipherSuites) {
+               return filterCiphers(Arrays.asList(supportedCipherSuites));
+       }
+}
diff --git a/src/org/cacert/gigi/util/HTMLEncoder.java b/src/org/cacert/gigi/util/HTMLEncoder.java
new file mode 100644 (file)
index 0000000..9303d8d
--- /dev/null
@@ -0,0 +1,12 @@
+package org.cacert.gigi.util;
+
+public class HTMLEncoder {
+       public static String encodeHTML(String s) {
+               s = s.replace("&", "&amp;");
+               s = s.replace("<", "&lt;");
+               s = s.replace(">", "&gt;");
+               s = s.replace("\"", "&quot;");
+               s = s.replace("'", "&#39;");
+               return s;
+       }
+}
diff --git a/src/org/cacert/gigi/util/Notary.java b/src/org/cacert/gigi/util/Notary.java
new file mode 100644 (file)
index 0000000..657264a
--- /dev/null
@@ -0,0 +1,91 @@
+package org.cacert.gigi.util;
+
+import java.io.PrintWriter;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.cacert.gigi.User;
+import org.cacert.gigi.database.DatabaseConnection;
+
+public class Notary {
+       public static void writeUserAgreement(int memid, String document,
+                       String method, String comment, boolean active, int secmemid)
+                       throws SQLException {
+               PreparedStatement q = DatabaseConnection
+                               .getInstance()
+                               .prepare(
+                                               "insert into `user_agreements` set `memid`=?, `secmemid`=?,"
+                                                               + " `document`=?,`date`=NOW(), `active`=?,`method`=?,`comment`=?");
+               q.setInt(1, memid);
+               q.setInt(2, secmemid);
+               q.setString(3, document);
+               q.setInt(4, active ? 1 : 0);
+               q.setString(5, method);
+               q.setString(6, comment);
+               q.execute();
+       }
+
+       public static boolean checkAssuranceIsPossible(User assurer, User target,
+                       PrintWriter errOut) {
+               if (assurer.getId() == target.getId()) {
+                       if (errOut != null) {
+                               errOut.println("Cannot assure myself.");
+                       }
+                       return false;
+               }
+               try {
+                       PreparedStatement ps = DatabaseConnection
+                                       .getInstance()
+                                       .prepare(
+                                                       "SELECT 1 FROM `notary` where `to`=? and `from`=? AND `deleted`=0");
+                       ps.setInt(1, target.getId());
+                       ps.setInt(2, assurer.getId());
+                       ResultSet rs = ps.executeQuery();
+                       if (rs.next()) {
+                               if (errOut != null) {
+                                       errOut.println("You already assured this person.");
+                               }
+                               rs.close();
+                               return false;
+                       }
+                       rs.close();
+                       if (!assurer.canAssure()) {
+                               if (errOut != null) {
+                                       errOut.println("You cannot assure.");
+                               }
+                               return false;
+                       }
+               } catch (SQLException e) {
+                       e.printStackTrace();
+               }
+               return true;
+       }
+
+       public synchronized static boolean assure(User assurer, User target,
+                       int awarded, String location, String date) throws SQLException {
+               if (!checkAssuranceIsPossible(assurer, target, null)) {
+                       return false;
+               }
+               User u = new User(target.getId());
+               if (!u.equals(target)) {
+                       return false;
+               }
+               System.out.println("Would now assure.");
+               if (awarded > assurer.getMaxAssurePoints()) {
+                       return false;
+               }
+
+               PreparedStatement ps = DatabaseConnection
+                               .getInstance()
+                               .prepare(
+                                               "INSERT INTO `notary` SET `from`=?, `to`=?, `points`=?, `location`=?, `date`=?");
+               ps.setInt(1, assurer.getId());
+               ps.setInt(2, target.getId());
+               ps.setInt(3, awarded);
+               ps.setString(4, location);
+               ps.setString(5, date);
+               ps.execute();
+               return true;
+       }
+}
diff --git a/src/org/cacert/gigi/util/PasswordHash.java b/src/org/cacert/gigi/util/PasswordHash.java
new file mode 100644 (file)
index 0000000..edc1ad5
--- /dev/null
@@ -0,0 +1,30 @@
+package org.cacert.gigi.util;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+public class PasswordHash {
+       public static boolean verifyHash(String password, String hash) {
+               String newhash = sha1(password);
+               return newhash.equals(hash);
+       }
+
+       private static String sha1(String password) {
+               try {
+                       MessageDigest md = MessageDigest.getInstance("SHA1");
+                       byte[] digest = md.digest(password.getBytes());
+                       StringBuffer res = new StringBuffer(digest.length * 2);
+                       for (int i = 0; i < digest.length; i++) {
+                               res.append(Integer.toHexString((digest[i] & 0xF0) >> 4));
+                               res.append(Integer.toHexString(digest[i] & 0xF));
+                       }
+                       return res.toString();
+               } catch (NoSuchAlgorithmException e) {
+                       throw new Error(e);
+               }
+       }
+
+       public static String hash(String password) {
+               return sha1(password);
+       }
+}
diff --git a/src/org/cacert/gigi/util/PasswordStrengthChecker.java b/src/org/cacert/gigi/util/PasswordStrengthChecker.java
new file mode 100644 (file)
index 0000000..07898f2
--- /dev/null
@@ -0,0 +1,81 @@
+package org.cacert.gigi.util;
+
+import java.util.regex.Pattern;
+
+import org.cacert.gigi.User;
+
+public class PasswordStrengthChecker {
+       static Pattern digits = Pattern.compile("\\d");
+       static Pattern lower = Pattern.compile("[a-z]");
+       static Pattern upper = Pattern.compile("[A-Z]");
+       static Pattern whitespace = Pattern.compile("\\s");
+       static Pattern special = Pattern.compile("(?!\\s)\\W");
+       private PasswordStrengthChecker() {
+       }
+       private static int checkpwlight(String pw) {
+               int points = 0;
+               if (pw.length() > 15) {
+                       points++;
+               }
+               if (pw.length() > 20) {
+                       points++;
+               }
+               if (pw.length() > 25) {
+                       points++;
+               }
+               if (pw.length() > 30) {
+                       points++;
+               }
+               if (digits.matcher(pw).find()) {
+                       points++;
+               }
+               if (lower.matcher(pw).find()) {
+                       points++;
+               }
+               if (upper.matcher(pw).find()) {
+                       points++;
+               }
+               if (special.matcher(pw).find()) {
+                       points++;
+               }
+               if (whitespace.matcher(pw).find()) {
+                       points++;
+               }
+               return points;
+       }
+       public static int checkpw(String pw, User u) {
+               if (pw == null) {
+                       return 0;
+               }
+               int light = checkpwlight(pw);
+               if (contained(pw, u.getEmail())) {
+                       light -= 2;
+               }
+               if (contained(pw, u.getFname())) {
+                       light -= 2;
+               }
+               if (contained(pw, u.getLname())) {
+                       light -= 2;
+               }
+               if (contained(pw, u.getMname())) {
+                       light -= 2;
+               }
+               if (contained(pw, u.getSuffix())) {
+                       light -= 2;
+               }
+               // TODO dictionary check
+               return light;
+       }
+       private static boolean contained(String pw, String check) {
+               if (check == null || check.equals("")) {
+                       return false;
+               }
+               if (pw.contains(check)) {
+                       return true;
+               }
+               if (check.contains(pw)) {
+                       return true;
+               }
+               return false;
+       }
+}
diff --git a/src/org/cacert/gigi/util/RandomToken.java b/src/org/cacert/gigi/util/RandomToken.java
new file mode 100644 (file)
index 0000000..7d87b7b
--- /dev/null
@@ -0,0 +1,25 @@
+package org.cacert.gigi.util;
+
+import java.security.SecureRandom;
+
+public class RandomToken {
+       static SecureRandom sr = new SecureRandom();
+       public static String generateToken(int length) {
+               StringBuffer token = new StringBuffer();
+               for (int i = 0; i < length; i++) {
+                       int rand = sr.nextInt(26 * 2 + 10);
+                       if (rand < 10) {
+                               token.append((char) ('0' + rand));
+                               continue;
+                       }
+                       rand -= 10;
+                       if (rand < 26) {
+                               token.append((char) ('a' + rand));
+                               continue;
+                       }
+                       rand -= 26;
+                       token.append((char) ('A' + rand));
+               }
+               return token.toString();
+       }
+}
diff --git a/src/org/cacert/gigi/util/ServerConstants.java b/src/org/cacert/gigi/util/ServerConstants.java
new file mode 100644 (file)
index 0000000..82b124c
--- /dev/null
@@ -0,0 +1,5 @@
+package org.cacert.gigi.util;
+
+public class ServerConstants {
+       public static final String NORMAL_HOST_NAME = "http://www.cacert.org";
+}
diff --git a/static/default.css b/static/default.css
new file mode 100644 (file)
index 0000000..ac122d6
--- /dev/null
@@ -0,0 +1,737 @@
+/***********************************************/
+/* emx_nav_right.css                           */
+/* Use with template Halo_rightNav.html        */
+/***********************************************/
+
+/***********************************************/
+/* HTML tag styles                             */
+/***********************************************/
+
+body {
+       font-family: Arial,sans-serif;
+       color: #333333;
+       line-height: 1.166;
+       margin: 0px;
+       padding: 0px;
+       background: #cccccc;
+/*     url("/siteimages/bg_grad.jpg") fixed;   */
+}
+
+
+/******* hyperlink and anchor tag styles *******/
+
+a:link, a:visited {
+       color: #005fa9;
+       text-decoration: none;
+}
+
+a:hover {
+       text-decoration: underline;
+}
+
+
+/************** header tag styles **************/
+
+h1 {
+       font: bold 120% Arial ,sans-serif;
+       color: #334d55;
+       margin: 0px;
+       padding: 0px;
+}
+
+h2 {
+       font: bold 114% Arial ,sans-serif;
+       color: #006699;
+       margin: 0px;
+       padding: 0px;
+}
+
+h3 {
+       font: bold 100% Arial ,sans-serif;
+       color: #334d55;
+       margin: 0px;
+       padding: 0px;
+}
+
+h3.pointer {
+       cursor: pointer;
+       /* cursor: hand; */
+}
+
+h4 {
+       font: bold 100% Arial ,sans-serif;
+       color: #333333;
+       margin: 0px;
+       padding: 0px;
+}
+
+h5 {
+       font: 100% Arial ,sans-serif;
+       color: #334d55;
+       margin: 0px;
+       padding: 0px;
+}
+
+
+/*************** list tag styles ***************/
+
+ul.menu {
+       list-style: none;
+       margin: 0px 0px 0px 15px;
+       padding-left: 5px;
+       border-left: 1px dotted #000;
+}
+
+ul.top {
+       list-style: none;
+       margin: 0px 0px 0px 15px;
+       padding-left: 5px;
+       border-left: 0px;
+}
+
+ul.no_indent {
+       list-style: none;
+       padding: 0px;
+}
+
+.attach_ul {
+       margin-bottom: 0px;
+}
+
+.attach_ul + ul {
+       margin-top: 0px;
+}
+
+
+/***********************************************/
+/* Layout Divs                                 */
+/***********************************************/
+
+#pagecell1 {
+       position: absolute;
+       top: 2%;
+       left: 2%;
+       right: 2%;
+       width: 96%;
+       background-color: #ffffff;
+}
+
+#tl {
+       position: absolute;
+       top: -1px;
+       left: -1px;
+       margin: 0px;
+       padding: 0px;
+       z-index: 100;
+}
+
+#tr {
+       position: absolute;
+       top: -1px;
+       right: -1px;
+       margin: 0px;
+       padding: 0px;
+       z-index: 100;
+}
+
+#masthead {
+       position: absolute;
+       top: 0px;
+       left: 2%;
+       right: 2%;
+       width: 95.6%;
+}
+
+#pageNav {
+       float: right;
+       width: 178px;
+       padding: 0px;
+       background-color: #F5f7f7;
+       border-left: 1px solid #cccccc;
+       font: small Verdana,sans-serif;
+}
+
+#content {
+       padding: 0px 10px 0px 0px;
+       margin: 0px 178px 0px 0px;
+}
+
+
+/***********************************************/
+/* Component Divs                              */
+/***********************************************/
+#siteName {
+       margin: 0px;
+       padding: 16px 0px 8px 0px;
+       color: #ffffff;
+       font-weight: normal;
+}
+
+
+/************** utility styles *****************/
+
+#utility {
+       font: 75% Verdana,sans-serif;
+       position: absolute;
+       top: 16px;
+       right: 0px;
+       color: #919999;
+}
+
+#utility a {
+       color: #ffffff;
+}
+
+#utility a:hover {
+       text-decoration: underline;
+}
+
+
+/************** pageName styles ****************/
+
+#pageName {
+       padding: 0px 0px 14px 10px;
+       margin: 0px;
+       border-bottom: 1px solid #ccd2d2;
+       z-index: 2;
+}
+
+#pageName h2 {
+       font: bold 175% Arial,sans-serif;
+       color: #000000;
+       margin: 0px;
+       padding: 0px;
+}
+
+/*
+#pageLogo {
+       position: absolute;
+       top: 8px;
+       left: 10px;
+       z-index: 5;
+}
+*/
+
+
+/************* globalNav styles ****************/
+
+#globalNav {
+       position: relative;
+       width: 100%;
+       min-width: 640px;
+       height: 32px;
+       color: #cccccc;
+       padding: 0px;
+       margin: 0px;
+       background-image: url("siteimages/glbnav_background.gif");
+}
+
+#globalNav img {
+       margin-bottom: -4px;
+}
+
+#gnl {
+       position: absolute;
+       top: 0px;
+       left:0px;
+}
+
+#gnr {
+       position: absolute;
+       top: 0px;
+       right:0px;
+}
+
+#globalLink {
+       position: absolute;
+       top: 6px;
+       height: 22px;
+       min-width: 640px;
+       padding: 0px;
+       margin: 0px;
+       left: 10px;
+       z-index: 100;
+}
+
+
+a.glink, a.glink:visited {
+       font-size: small;
+       color: #000000;
+       font-weight: bold;
+       margin: 0px;
+       padding: 2px 5px 4px 5px;
+       border-right: 1px solid #8fb8bc;
+}
+
+a.glink:hover {
+       background-image: url("siteimages/glblnav_selected.gif");
+       text-decoration: none;
+}
+
+.skipLinks {
+       display: none;
+}
+
+
+/************ subglobalNav styles **************/
+
+.subglobalNav {
+       position: absolute;
+       top: 84px;
+       left: 0px;
+       /*width: 100%;*/
+       min-width: 640px;
+       height: 20px;
+       padding: 0px 0px 0px 10px;
+       visibility: hidden;
+       color: #ffffff;
+}
+
+.subglobalNav a:link, .subglobalNav a:visited {
+       font-size: 80%;
+       color: #ffffff;
+}
+
+.subglobalNav a:hover {
+       color: #cccccc;
+}
+
+
+/*************** search styles *****************/
+/*
+#listshow {
+       z-order: 101;
+}
+*/
+
+#search {
+       position: absolute;
+       top: 125px;
+       right: 0px;
+}
+
+#search form {
+       position: absolute;
+       top: 125px;
+       right: 300px;
+}
+#search input {
+       font-size: 11px;
+}
+
+#search1 {
+       position: absolute;
+       top: 85px;
+       right: 300px;
+}
+
+#search2 {
+       position: absolute;
+       top: 100px;
+       right: 300px;
+}
+
+#search3 {
+       position: absolute;
+       top: 85px;
+       right: 240px;
+}
+
+#search4 {
+       position: absolute;
+       top: 100px;
+       right: 226px;
+}
+
+#googlead {
+       position: absolute;
+       top: 5px;
+       right: 0px;
+       z-index: -10;
+}
+
+#search input {
+       font-size: 70%;
+       margin: 0px 0px 0px 10px;
+}
+
+#search a:link, #search a:visited {
+       font-size: 80%;
+       font-weight: bold;
+
+}
+
+#search a:hover {
+       margin: 0px;
+}
+
+
+/************* breadCrumb styles ***************/
+
+#breadCrumb {
+       padding: 5px 0px 5px 10px;
+       font: small Verdana,sans-serif;
+       color: #aaaaaa;
+}
+
+#breadCrumb a {
+       color: #aaaaaa;
+}
+
+#breadCrumb a:hover {
+       color: #005fa9;
+       text-decoration: underline;
+}
+
+
+/************** feature styles *****************/
+
+.feature {
+       padding: 0px 0px 10px 10px;
+       font-size: 80%;
+       min-height: 200px;
+       height: 200px;
+}
+
+.feature {
+       height: auto;
+}
+
+.feature h3 {
+       font: bold 175% Arial,sans-serif;
+       color: #000000;
+       padding: 30px 0px 5px 0px;
+}
+
+.feature img {
+       float: left;
+       padding: 0px 10px 0px 0px;
+}
+
+
+/*************** story styles ******************/
+
+.story {
+       padding: 10px 0px 0px 10px;
+       font-size: 80%;
+       min-height: 450px;
+}
+
+.story h3 {
+       font: bold 125% Arial,sans-serif;
+       color: #000000;
+}
+
+.story a.capsule {
+       font: bold 1em Arial,sans-serif;
+       color: #005FA9;
+       display: block;
+       padding-bottom: 5px;
+}
+
+.story a.capsule:hover {
+       text-decoration: underline;
+}
+
+td.storyLeft {
+       padding-right: 12px;
+}
+
+
+/************** siteInfo styles ****************/
+
+#siteInfo {
+       clear: both;
+       border-top: 1px solid #cccccc;
+       font-size: small;
+       color: #cccccc;
+       padding: 10px 10px 10px 10px;
+}
+
+
+/************ sectionLinks styles **************/
+
+#sectionLinks {
+       margin: 0px;
+       padding: 0px;
+}
+
+#sectionLinks h3 {
+       padding: 10px 0px 2px 10px;
+       border-bottom: 1px solid #cccccc;
+}
+
+#sectionLinks a:link, #sectionLinks a:visited {
+       display: block;
+       border-top: 1px solid #ffffff;
+       border-bottom: 1px solid #cccccc;
+       background-image: url("siteimages/bg_nav.jpg");
+       font-weight: bold;
+       padding: 3px 0px 3px 10px;
+       color: #21536A;
+}
+
+#sectionLinks a:hover {
+       border-top: 1px solid #cccccc;
+       background-color: #DDEEFF;
+       background-image: none;
+       font-weight: bold;
+       text-decoration: none;
+}
+
+
+/************* relatedLinks styles **************/
+
+#pageNav div {
+       margin: 0px;
+       padding: 0px 0px 10px 10px;
+       border-bottom: 1px solid #cccccc;
+}
+
+#pageNav div h3 {
+       padding: 10px 0px 2px 0px;
+}
+
+#pageNav div a {
+       display: block;
+}
+
+
+/**************** advert styles *****************/
+
+#advert {
+       padding: 10px;
+}
+
+#advert img {
+       display: block;
+}
+
+
+/********************* end **********************/
+
+.DataTD input, .DataTD textarea {
+       font-size: 92%;
+}
+
+.DataTD select, .DataTD option {
+       font-size: 92%;
+}
+
+.DataTD {
+       background-color: #e2e2e2;
+       border-style: inset;
+       border-width: 1px;
+       font-size: 8pt;
+       color: #000000;
+       font-family: Arial, Tahoma, Verdana, Helvetica, sans-serif;
+
+       background: #ffffff;
+       padding: 1px 5px 1px 5px;
+       border: 1px #cfcfcf solid;
+       border-left: 1px #cfcfcf dotted;
+       border-right: 1px #cfcfcf dotted;
+}
+
+.DataTDGrey {
+       background-color: #EFEDED;
+       border-style: inset;
+       border-width: 1px;
+       font-size: 8pt;
+       color: #000000;
+       font-family: Arial, Tahoma, Verdana, Helvetica, sans-serif;
+
+       padding: 1px 5px 1px 5px;
+       border: 1px #CFCFCF solid;
+       border-left: 1px #cfcfcf dotted;
+       border-right: 1px #cfcfcf dotted;
+}
+
+.DataTDNotDotted {
+       background-color: #e2e2e2;
+       border-style: inset;
+       border-width: 1px;
+       font-size: 8pt;
+       color: #000000;
+       font-family: Arial, Tahoma, Verdana, Helvetica, sans-serif;
+
+       background: #ffffff;
+       padding: 1px 5px 1px 5px;
+       border: 1px #CFCFCF solid;
+       border-left: 1px #cfcfcf solid;
+       border-right: 1px #cfcfcf solid;
+}
+
+.DataTDError {
+    border-style: inset;
+    border-width: 1px;
+    font-size: 8pt;
+    color: #ff0000;
+    font-family: Arial, Tahoma, Verdana, Helvetica, sans-serif;
+
+    background: #ffffff;
+    padding: 1px 5px 1px 5px;
+    border: 1px #cfcfcf solid;
+    border-left: 1px #cfcfcf dotted;
+    border-right: 1px #cfcfcf dotted;
+}
+.wrapper {
+       border-collapse: collapse;
+       font-family: verdana, sans-serif;
+       font-size: 11px;
+       text-align: center;
+       margin-left:auto;
+       margin-right:auto;
+}
+
+td.greytxt {
+       color: #cccccc;
+       font-size: smaller;
+       text-align: right;
+       vertical-align: bottom;
+}
+.bold, .primaryemailaddress {
+       font-weight:bold;
+}
+.italic, .deletedemailaddress {
+       font-style:italic;
+}
+.title {
+       background: #e2e2e2;
+       font-weight: bold;
+       padding: 1px 5px 1px 5px;
+       border: 1px solid #cfcfcf;
+       border-bottom: 3px double #cfcfcf;
+       border-top: 1px solid #656565;
+       text-align: center;
+}
+
+.errmsg {
+       font-weight: bold;
+       color: #FF0000;
+}
+
+.ac_menu {
+       border: 1px solid black
+}
+
+.ac_normal {
+       background-color: #ffffff;
+       cursor: pointer;
+}
+
+.ac_highlight {
+       background-color: #3366cc;
+       color: white;
+       cursor: pointer;
+}
+
+.ac_normal .a {
+       font-size: 13px;
+       color: black;
+}
+
+.ac_highlight .a {
+       font-size: 13px;
+}
+
+.ac_normal .d {
+       float: right;
+       font-size: 10px;
+       color: green;
+}
+
+.ac_highlight .d {
+       float: right;
+       font-size: 10px;
+}
+
+
+/************** sponsorInfo styles ****************/
+
+div.sponsorinfo {
+       clear: both;
+       border-top: 1px solid #cccccc;
+       font-size: small;
+       color: #000000;
+       padding: 10px 10px 10px 10px;
+}
+
+img.sponsorlogo {
+       margin-left: 10px;
+       margin-right: 10px;
+       border: 0px none;
+       vertical-align: middle;
+}
+
+
+/************ Newsbox *************/
+
+#lnews {       /* class for the text "Latest News" */
+       font-size: small;
+       font-variant: small-caps;
+}
+
+div.newsbox {
+       border-top: 1px solid #cccccc;
+       color: #101010;
+       padding: 10px 10px 10px 10px;
+}
+
+
+/************ SQL Performance ***********/
+
+div.footerbar {
+       clear: both;
+       border-top: 1px solid #cccccc;
+       font-size: small;
+       color: black;
+       padding: 10px 10px 10px 10px;
+}
+
+
+/************ Honeypot  ***********/
+
+.robotic {
+       display: none;
+}
+
+
+/************  unicode fallbacks ***********/
+
+/* Some embedding of font */
+@font-face {
+       font-family: 'Source Code Pro';
+       src: local('Source Code Pro');
+/*  src: url(/res/fonts/SourceCodePro-Medium.ttf); */
+}
+
+@font-face {
+       font-family: 'Last Resort';
+       src: local('LastResort');
+/*  src: url(/res/fonts/LastResort.ttf); */
+}
+
+.accountdetail {
+       font-family: 'Source Code Pro', 'Lucida Console', 'Arial Unicode MS', monospace, 'Last Resort';
+       font-size: 1.1em;
+}
+
+.accountdetail.fname, .accountdetail .fname {
+}
+
+.accountdetail.mname {
+}
+
+.accountdetail.lname, .accountdetail .lname {
+       font-weight: bold;
+}
+
+.accountdetail.suffix {
+}
+ul.menu.hidden{
+       display: none;
+}
+img{
+       border: 0;
+}
\ No newline at end of file
diff --git a/static/menu.js b/static/menu.js
new file mode 100644 (file)
index 0000000..b73827e
--- /dev/null
@@ -0,0 +1,35 @@
+(function() {
+       function explodeMenu(e) {
+               if (document.getElementById(e).className == 'menu hidden') {
+                       document.getElementById(e).className = 'menu';
+               } else {
+                       document.getElementById(e).className = 'menu hidden';
+               }
+       }
+
+       function initMenu() {
+               var Nodes = document.getElementsByTagName('ul');
+               var max = Nodes.length;
+               for (var i = 0; i < max; i++) {
+                       var nodeObj = Nodes.item(i);
+                       if (nodeObj.className.indexOf("menu") > -1 && nodeObj.id != "recom") {
+                               nodeObj.previousSibling.previousSibling.onclick = (function(nid) {
+                                       return function() {
+                                               explodeMenu(nid);
+                                       };
+                               })(nodeObj.id);
+                       }
+               }
+       }
+       (function(oldLoad) {
+               if (oldLoad == undefined) {
+                       window.onload = initMenu;
+               } else {
+                       window.onload = function() {
+                               initMenu();
+                               oldLoad();
+                       }
+               }
+       })(window.onload);
+
+})();
\ No newline at end of file
diff --git a/static/policy/AssurancePolicy.html b/static/policy/AssurancePolicy.html
new file mode 100644 (file)
index 0000000..752da2d
--- /dev/null
@@ -0,0 +1,723 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<html><head>
+<title>Assurance Policy</title>
+
+<meta name="CREATED" content="20080530;0">
+<meta name="CHANGEDBY" content="Teus Hagen">
+<meta name="CHANGED" content="20080709;12381800">
+<meta name="CREATEDBY" content="Ian Grigg">
+<meta name="CHANGEDBY" content="Teus Hagen">
+<meta name="CHANGEDBY" content="Robert Cruikshank">
+<meta name="CHANGEDBY" content="Teus Hagen">
+<style type="text/css">
+<!--
+P { color: #000000 }
+TD P { color: #000000 }
+H1 { color: #000000 }
+H2 { color: #000000 }
+DT { color: #000000 }
+DD { color: #000000 }
+H3 { color: #000000 }
+TH P { color: #000000 }
+-->
+</style></head>
+<body style="direction: ltr; color: rgb(0, 0, 0);" lang="en-GB">
+<h1>Assurance Policy for CAcert Community Members</h1>
+<p><a href="PolicyOnPolicy.html"><img src="/images/cacert-policy.png" id="graphics1" alt="CAcert Policy Status == POLICY" align="bottom" border="0" height="33" width="90"></a>
+<br>
+Editor: Teus Hagen<br>
+Creation date: 2008-05-30<br>
+Last change by: Iang<br>
+Last change date: 2009-01-08<br>
+Status: POLICY p20090105.2
+</p>
+
+<h2><a name="0">0.</a> Preamble</h2>
+<h3><a name="0.1">0.1.</a> Definition of Terms</h3>
+<dl>
+<dt><i>Member</i> </dt>
+<dd> A Member is an individual who has agreed to the CAcert
+Community Agreement
+(<a href="http://www.cacert.org/policy/CAcertCommunityAgreement.html" target="_blank">CCA</a>)
+and has created successfully
+a CAcert login account on the CAcert web site. </dd>
+<dt> <i>Assurance</i> </dt>
+<dd> Assurance is the process by which a Member of CAcert
+Community (Assurer) identifies an individual (<span lang="en-US">Assuree</span>).
+</dd>
+<dt> <i>Prospective Member</i> </dt>
+<dd> An individual who participates in the process of Assurance,
+but has not yet created a CAcert login account. </dd>
+<dt> <i>Name</i> </dt>
+<dd> A Name is the full name of an individual.
+</dd>
+<dt> <i>Secondary Distinguishing Feature</i>
+</dt>
+<dd> An additional personal data item of the Member
+that assists discrimination from Members with similar full names.
+(Currently this is the Date of Birth (DoB).)
+</dd>
+</dl>
+
+<h3><a name="0.2">0.2.</a> The CAcert Web of Trust</h3>
+<p>
+In face-to-face meetings,
+an Assurer allocates a number of Assurance Points
+to the Member being Assured.
+CAcert combines the Assurance Points
+into a global <i>Web-of-Trust</i> (or "WoT").
+</p>
+<p>
+CAcert explicitly chooses to meet its various goals by
+construction of a Web-of-Trust of all Members.
+</p>
+
+<h3><a name="0.3">0.3.</a> Related Documentation</h3>
+<p>
+Documentation on Assurance is split between this
+Assurance Policy (AP) and the
+<a href="http://wiki.cacert.org/wiki/AssuranceHandbook2" target="_blank">Assurance
+Handbook</a>. The policy is controlled by Configuration Control
+Specification
+(<a href="http://wiki.cacert.org/wiki/PolicyDrafts/ConfigurationControlSpecification" target="_blank">CCS</a>)
+under Policy on Policy
+(<a href="http://www.cacert.org/policy/PolicyOnPolicy.html" target="_blank">PoP</a>)
+policy document regime.  Because Assurance is an active area, much
+of the practice is han