diff options
Diffstat (limited to 'trunk/infrastructure/com.etherpad')
-rw-r--r-- | trunk/infrastructure/com.etherpad/easysync2support.scala | 167 | ||||
-rw-r--r-- | trunk/infrastructure/com.etherpad/licensing.scala | 169 | ||||
-rw-r--r-- | trunk/infrastructure/com.etherpad/main.scala | 23 |
3 files changed, 359 insertions, 0 deletions
diff --git a/trunk/infrastructure/com.etherpad/easysync2support.scala b/trunk/infrastructure/com.etherpad/easysync2support.scala new file mode 100644 index 0000000..9f1c527 --- /dev/null +++ b/trunk/infrastructure/com.etherpad/easysync2support.scala @@ -0,0 +1,167 @@ +/** + * Copyright 2009 Google Inc. + * + * 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 com.etherpad; + +object Easysync2Support { + + def numToString(d: Int): String = java.lang.Integer.toString(d.toInt, 36); // lowercase + def stringToNum(s: String): Int = java.lang.Integer.parseInt(s, 36); + + def opAssembler() = new OpAssembler(); + + class OpAssembler() { + val buf = new StringBuilder(1000); + def append(op: Op) { + append(op.opcode, op.chars, op.lines, op.attribs); + } + def append(opcode: String, chars: Int, lines: Int, attribs: String) { + buf.append(attribs); + if (lines > 0) { + buf.append('|'); + buf.append(numToString(lines)); + } + buf.append(opcode); + buf.append(numToString(chars)); + } + override def toString(): String = buf.toString; + def clear() { buf.clear; } + } + + def isAlphanum(c: Char) = (c >= '0' && c <= '9' || c >= 'a' && c <= 'z'); + + case object OpParseError extends Error; + + def nextOpInString(str: String, startIndex: Int): Object = { + var i = startIndex; + + try { + def lookingAt(c: Char) = (i < str.length && str.charAt(i) == c); + def lookingAtAlphanum() = (i < str.length && isAlphanum(str.charAt(i))); + def atEnd() = (i >= str.length); + def readAlphanum(): Int = { + if (! lookingAtAlphanum()) { + throw OpParseError; + } + val start = i; + while (lookingAtAlphanum()) { + i += 1; + } + val end = i; + stringToNum(str.substring(start, end)); + } + + while (lookingAt('*')) { + i += 1; + if (! lookingAtAlphanum()) { + throw OpParseError; + } + while (lookingAtAlphanum()) { + i += 1; + } + } + val attribsEnd = i; + + var lines_ = 0; + if (lookingAt('|')) { + i += 1; + lines_ = readAlphanum(); + } + + if (lookingAt('?')) { + return new { val opcode = "?"; } + } + if (! (lookingAt('+') || lookingAt('-') || lookingAt('='))) { + throw OpParseError; + } + val opcode_ = str.substring(i, i+1); + i += 1; + val chars_ = readAlphanum(); + + return new Op(opcode_, chars_, lines_, str.substring(startIndex, attribsEnd)) { + val lastIndex = i; + }; + } + catch { case OpParseError => null } + } + + case class Op(var opcode: String, var chars: Int, var lines: Int, var attribs: String); + def newOp() = Op("", 0, 0, ""); + def clearOp(op: Op) { op.opcode = ""; op.chars = 0; op.lines = 0; op.attribs = ""; } + + // ported from easysync2.js + class MergingOpAssembler { + val assem = opAssembler(); + var bufOp = newOp(); + + var bufOpAdditionalCharsAfterNewline = 0; + + def flush(isEndDocument: Boolean) { + if (bufOp.opcode.length > 0) { + if (isEndDocument && bufOp.opcode == "=" && bufOp.attribs.length == 0) { + // final merged keep, leave it implicit + } + else { + assem.append(bufOp); + if (bufOpAdditionalCharsAfterNewline > 0) { + bufOp.chars = bufOpAdditionalCharsAfterNewline; + bufOp.lines = 0; + assem.append(bufOp); + bufOpAdditionalCharsAfterNewline = 0; + } + } + bufOp.opcode = ""; + } + } + def append(opcode: String, chars: Int, lines: Int, attribs: String) { + if (chars > 0) { + if (bufOp.opcode == opcode && bufOp.attribs == attribs) { + if (lines > 0) { + // bufOp and additional chars are all mergeable into a multi-line op + bufOp.chars += bufOpAdditionalCharsAfterNewline + chars; + bufOp.lines += lines; + bufOpAdditionalCharsAfterNewline = 0; + } + else if (bufOp.lines == 0) { + // both bufOp and op are in-line + bufOp.chars += chars; + } + else { + // append in-line text to multi-line bufOp + bufOpAdditionalCharsAfterNewline += chars; + } + } + else { + flush(false); + bufOp = Op(opcode, chars, lines, attribs); + } + } + } + def endDocument() { + flush(true); + } + override def toString() = { + flush(false); + assem.toString(); + } + def clear() { + assem.clear(); + clearOp(bufOp); + } + } + + def mergingOpAssembler() = new MergingOpAssembler(); +} diff --git a/trunk/infrastructure/com.etherpad/licensing.scala b/trunk/infrastructure/com.etherpad/licensing.scala new file mode 100644 index 0000000..9318f78 --- /dev/null +++ b/trunk/infrastructure/com.etherpad/licensing.scala @@ -0,0 +1,169 @@ +/** + * Copyright 2009 Google Inc. + * + * 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 com.etherpad; + +import net.appjet.oui.{Encryptomatic, config}; +import net.appjet.common.util.BetterFile; + +import java.io.{FileInputStream, FileOutputStream, ByteArrayInputStream, ByteArrayOutputStream, PrintWriter} + +import java.security._; +import java.security.spec._; + +object Licensing { + val publicKey = "s0dD94jKFjlSHIumgDQ4ldcyIyna1vMHmG5tsgkP987eBTW88XeEIUTo5JtWOjPzb252GURUrr7MReTqMz6NnsOupeJMqtXgjuVxcXbK8AnckxkxhRqMiFfBW39T9NzPgq09yBdH4tKGlPZQmgaBvjFI8cXTYa7a64LrDnrzrpDhDdJsZPZI2kE7G4vBCGijuhsZpyowK8zT5y2cKqIgIdLxUnXNFtayDi0oyuX1ywfBds2OEil9fEUQOQvkcHAt6kYfPXkE2XgQZFasAv0DPeWMUEtaHTbMaQn1U6BsfmsKjHlLhM3oWEzp0wEwUWxCC39iHYjxa5QKtxm5BNvUTTqJgkoEvk7Uu08j8jhFeCFosph6igDWPmyfAPKTnETXJobO2VON83bVHlX8UfKonnalMy0Hnw2C0I7c0UE0MtMIRtJxtwU62a311Ohp1EVrY4LwKIFfqRMVWKDP0cjXDkJyjJS58rC1DRU7SfPspgfuOy5YZo9sLKztXfzAPzNbXerQ24m2AjmYLV4JQked7MnaKQ6VfyZbFBK5th9NFcJwY1bGbIHW2EsKmiKUoNjPKRJ6VMC7odUCIXQyE9J"; + + val pkhash = "f7a3dd5940a3f79904b81e4d32a08e2efaa0b2ab"; + val keyVersion = 2.toByte; + + def thanksForStealingFromPoorHackersTryingToEkeAMeagerLivingFromThisCruelWorld = + Encryptomatic.bytesToAscii(MessageDigest.getInstance("SHA1").digest(publicKey.getBytes())) == pkhash; + def sha1(b: Array[Byte]): String = Encryptomatic.bytesToAscii(MessageDigest.getInstance("SHA1").digest(b)); + def sha1(s: String): String = sha1(s.getBytes("UTF-8")); + + def toBytes(i: Int): Array[Byte] = + Array((i >> 24).toByte, + (i >> 16).toByte, + (i >> 8).toByte, + i.toByte); + def toByte(i: Int): Array[Byte] = + Array(i.toByte); + def toBytes(l: Long): Array[Byte] = + Array((l >> 56).toByte, + (l >> 48).toByte, + (l >> 40).toByte, + (l >> 32).toByte, + (l >> 24).toByte, + (l >> 16).toByte, + (l >> 8).toByte, + l.toByte); + + def toInt(b0: Array[Byte]): Int = { + val b = b0.map(_.toInt & 0x00FF); + (b(0) << 24) | (b(1) << 16) | (b(2) << 8) | b(3); + } + def toInt(b: Byte): Int = b.toInt & 0x00FF; + + def toLong(b0: Array[Byte]): Long = { + val b = b0.map(_.toLong & 0x000000FF); + (b(0) << 56) | (b(1) << 48) | (b(2) << 40) | (b(3) << 32) | (b(4) << 24) | (b(5) << 16) | (b(6) << 8) | b(7); + } + + def generateKey(personName: String, organizationName: String, expiresDate: Long, editionId: Int, userQuota: Int, majorVersion: Int, minorVersion: Int, patchVersion: Int) = { + if (config.licenseGeneratorKey == null) { + throw new RuntimeException("No private key available to generate license key."); + } + def privateKey = Encryptomatic.readPrivateKey("DSA", new FileInputStream(config.licenseGeneratorKey)); + def clean(s: String) = s.replaceAll(":", "-"); + val keyPrefix = + List(personName, organizationName, expiresDate.toString, editionId.toString, userQuota.toString, majorVersion.toString, minorVersion.toString, patchVersion.toString).map(clean).mkString(":"); + val sig = Encryptomatic.sign(new ByteArrayInputStream(keyPrefix.getBytes("UTF-8")), privateKey) + + List(personName, organizationName).mkString(":") + ":" + + Encryptomatic.bytesToAscii( + Array.concat[Byte](Array(keyVersion), // don't want BigInt dropping bytes, that'd be sad. :( + toBytes(expiresDate), + toBytes(editionId), + toBytes(userQuota), + toByte(majorVersion), + toByte(minorVersion), + toByte(patchVersion), + sig)); + } + + def decodeKey(key: String) = try { + val Array(personName0, organizationName0, sigAndInfo) = key.split(":"); + val sigAndInfoBytes = Encryptomatic.asciiToBytes(sigAndInfo); + val thisKeyVersion = toInt(sigAndInfoBytes(0)); + val expiresDate0 = toLong(sigAndInfoBytes.slice(1, 9)); + val editionId0 = toInt(sigAndInfoBytes.slice(9, 13)); + val userQuota0 = toInt(sigAndInfoBytes.slice(13, 17)); + val (majorVersion0, minorVersion0, patchVersion0) = + if (thisKeyVersion >= 2) { + (toInt(sigAndInfoBytes(17)), toInt(sigAndInfoBytes(18)), toInt(sigAndInfoBytes(19))); + } else { + (0, 0, 0); + } + val sig = sigAndInfoBytes.drop(if (thisKeyVersion >= 2) 20 else 17); + val keyPrefix = { + var a = Seq(personName0, organizationName0, expiresDate0.toString, editionId0.toString, userQuota0.toString); + if (thisKeyVersion >= 2) { + a = a ++ Seq(majorVersion0.toString, minorVersion0.toString, patchVersion0.toString); + } + a.mkString(":"); + } + if (! Encryptomatic.verify(new ByteArrayInputStream(keyPrefix.getBytes("UTF-8")), + Encryptomatic.readPublicKey("DSA", + new ByteArrayInputStream(publicKey.getBytes())), sig)) { + null; + } else { + new { + def personName = personName0; + def organizationName = organizationName0; + def expiresDate = expiresDate0; + def editionId = editionId0; + def userQuota = userQuota0; + def majorVersion = majorVersion0; + def minorVersion = minorVersion0; + def patchVersion = patchVersion0; + } + } + } catch { + case e => null; + } + + def main(args: Array[String]) { + args(0) match { + case "genkeypair" => { + println("Generating keypair..."); + Encryptomatic.writeKeyPair(Encryptomatic.generateKeyPair("DSA"), args(1), args(2)); + println("Done."); + } + case "genmainkey" => { + println("Generating key for etherpad.com..."); + config.values("licenseGeneratorKey") = args(1); + val out = new PrintWriter(new FileOutputStream(args(2))); + out.print(generateKey("etherpad", "AppJet", -1, 0, -1, 0, 0, 0)) + out.close(); + println("Done."); + } + case "test" => { + println("Testing key generation."); + config.values("licenseGeneratorKey") = args(1); + val key = generateKey("Foo Bar", "Baz, Inc.", System.currentTimeMillis() + 86400*1000, 0, 100, 1, 2, 3); + println("Key is: "+key); + val obj = decodeKey(key); + println(List(obj.personName, obj.organizationName, obj.expiresDate, obj.editionId, obj.userQuota, obj.majorVersion, obj.minorVersion, obj.patchVersion).mkString(", ")); + } + case "parsekey" => { + println("Testing key decode."); + val obj = decodeKey(args(1)); + println("Key: "+List(obj.personName, obj.organizationName, obj.expiresDate, obj.editionId, obj.userQuota, obj.majorVersion, obj.minorVersion, obj.patchVersion).mkString(", ")); + } + case "testascii" => { + val one = 17; + val two = -1L; + val three = (Math.random*Math.pow(10, (Math.random*10).toInt)).toInt; + println(List(one, two, three).mkString(", ")); + println(List(toInt(toBytes(one)), toLong(toBytes(two)), toInt(toBytes(three))).mkString(", ")); + val bytes = Encryptomatic.asciiToBytes(Encryptomatic.bytesToAscii(Array.concat[Byte](Array(1.toByte), toBytes(one), toBytes(two), toBytes(three)))); + println("I can has bytes: "+bytes.length); + println(List(toInt(bytes.slice(1, 5)), toLong(bytes.slice(5, 13)), toInt(bytes.slice(13, 17))).mkString(", ")); + } + } + } +} diff --git a/trunk/infrastructure/com.etherpad/main.scala b/trunk/infrastructure/com.etherpad/main.scala new file mode 100644 index 0000000..5110aba --- /dev/null +++ b/trunk/infrastructure/com.etherpad/main.scala @@ -0,0 +1,23 @@ +/** + * Copyright 2009 Google Inc. + * + * 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 com.etherpad; + +object main { + def main(args: Array[String]) { + net.appjet.oui.main.main(args); + } +}
\ No newline at end of file |