private static ByteString Encode(ByteString payload, int fieldOffset, int fieldLength, ByteOrder byteOrder) { var h = new ByteStringBuilder().PutInt(payload.Count, byteOrder).Result(); var header = byteOrder == ByteOrder.LittleEndian ? h.Take(fieldLength) : h.Drop(4 - fieldLength); return(ByteString.Create(new byte[fieldOffset]) + header + payload); }
public void TestParseLargeLiteral() { var literal = new ByteStringBuilder(1 * 1024 * 1024); for (var i = 0; i < literal.Capacity; i += 16) { literal.Append("0123456789abcdef"); } WriteReceived(string.Format("{{{0}}}\r\n{1}\r\n", literal.Length, literal.ToString())); var data = receiver.Parse(); Assert.IsNull(data); data = receiver.Parse(); Assert.IsNotNull(data); Assert.AreEqual(1, data.Length); Assert.AreEqual(ImapDataFormat.Text, data[0].Format); var stream = data[0].GetTextAsStream(); Assert.IsNotInstanceOfType(typeof(MemoryStream), stream); Assert.AreEqual(literal.ToByteArray(), Smdn.IO.StreamExtensions.ReadToEnd(stream)); }
private void HandleWrite(object message) { if (message is ByteString) { var bs = message as ByteString; var buffer = ByteString.Unsafe.GetBuffer(bs); var builder = new ByteStringBuilder(); builder.PutInt(buffer.Length, ByteOrder.BigEndian); builder.PutBytes(buffer); _connection.Tell(Tcp.Write.Create(builder.Result())); } else { Unhandled(message); } }
protected override SaslExchangeStatus Exchange(ByteString serverChallenge, out ByteString clientResponse) { if (Credential == null) throw new SaslException("Credential property must be set"); clientResponse = null; if (string.IsNullOrEmpty(Credential.UserName) || string.IsNullOrEmpty(Credential.Password)) return SaslExchangeStatus.Failed; var responseBuilder = new ByteStringBuilder(0x300); // 255(authcid) + 1(NUL) + 255(authzid) + 1(NUL) + 255(passwd) responseBuilder.Append(Encoding.UTF8.GetBytes(Credential.Domain ?? string.Empty)); // authcid responseBuilder.Append(Smdn.Formats.Octets.NUL); responseBuilder.Append(Encoding.UTF8.GetBytes(Credential.UserName)); // authzid responseBuilder.Append(Smdn.Formats.Octets.NUL); responseBuilder.Append(Encoding.UTF8.GetBytes(Credential.Password)); // passwd clientResponse = responseBuilder.ToByteString(); return SaslExchangeStatus.Succeeded; }
private static ImapSearchCriteria FromUri(Uri uri, bool convertLiteral, bool synchronizedLiteral, bool splitCharset, out bool containsLiteral, out string charset) { if (uri == null) throw new ArgumentNullException("uri"); containsLiteral = false; charset = null; var q = uri.Query; if (q.Length == 0) return null; else if (q.Length == 1) // '?' return new ImapSearchCriteria(string.Empty); /* * http://tools.ietf.org/html/rfc5092 * RFC 5092 - IMAP URL Scheme * * Note that quoted strings and non-synchronizing literals [LITERAL+] * are allowed in the <enc-search> content; however, synchronizing * literals are not allowed, as their presence would effectively mean * that the agent interpreting IMAP URLs needs to parse an <enc-search> * content, find all synchronizing literals, and perform proper command * continuation request handling (see Sections 4.3 and 7 of [IMAP4]). */ var query = PercentEncoding.Decode(q.Substring(1), false); var len = query.Length; var convertedQuery = new ByteStringBuilder(len); if (splitCharset) { var queryString = new ByteString(query); if (queryString.StartsWithIgnoreCase(charsetSpecification)) { // CHARSET<SP>astring<SP> var posEndOfCharset = queryString.IndexOf(Octets.SP, charsetSpecification.Length); if (posEndOfCharset < 0) { throw new ArgumentException("search criteria contains invalid charset specification", "uri"); } else { charset = queryString.Substring(charsetSpecification.Length, posEndOfCharset - charsetSpecification.Length).ToString(); query = queryString.Substring(posEndOfCharset + 1).ByteArray; len = query.Length; } } } for (var i = 0; i < len;) { if (query[i] == ImapOctets.DQuote) { /* * quoted */ var start = i; for (;;) { if (++i == len) throw new ArgumentException("search criteria contains unclosed quoted string", "uri"); if (query[i] == ImapOctets.DQuote) { break; } else if (query[i] == ImapOctets.BackSlash) { if (++i == len || !(query[i] == ImapOctets.DQuote || query[i] == ImapOctets.BackSlash)) throw new ArgumentException("search criteria contains invalid quoted string", "uri"); } } i++; convertedQuery.Append(query, start, i - start); } else if (query[i] == ImapOctets.OpenBrace) { /* * literal */ var start = i; var isLiteralSynchronizing = false; var literalLength = 0; for (;;) { if (++i == len) throw new ArgumentException("search criteria contains incomplete literal", "uri"); if (Octets.IsDecimalNumber(query[i])) { literalLength = literalLength * 10 + (query[i] - 0x30 /* '0' */); // TODO: check length } else if (query[i] == ImapOctets.CloseBrace) { // {xxx} isLiteralSynchronizing = true; break; } else if (query[i] == ImapOctets.Plus) { // {xxx+} if (++i == len || query[i] != ImapOctets.CloseBrace) throw new ArgumentException("search criteria contains incomplete non-synchronized literal", "uri"); isLiteralSynchronizing = false; break; } else { throw new ArgumentException("search criteria contains invalid literal", "uri"); } } if (++i == len || query[i] != Octets.CR) throw new ArgumentException("search criteria contains incomplete literal (CR not found)", "uri"); if (++i == len || query[i] != Octets.LF) throw new ArgumentException("search criteria contains incomplete literal (LF not found)", "uri"); if (++i == len && 0 < literalLength) throw new ArgumentException("search criteria contains incomplete literal (unexpected EOL)", "uri"); containsLiteral = true; if (convertLiteral) { if (synchronizedLiteral) convertedQuery.Append(string.Format("{{{0}}}\x0d\x0a", literalLength)); else convertedQuery.Append(string.Format("{{{0}+}}\x0d\x0a", literalLength)); convertedQuery.Append(query, i, literalLength); } else { if (synchronizedLiteral != isLiteralSynchronizing) throw new ArgumentException(synchronizedLiteral ? "search criteria contains non-synchronizing literal" : "search criteria contains synchronizing literal", "uri"); convertedQuery.Append(query, start, i - start + literalLength); } i += literalLength; } else { convertedQuery.Append(query[i++]); } } return new ImapSearchCriteria(new ImapPreformattedString(convertedQuery.ToByteArray())); }
protected override SaslExchangeStatus Exchange(ByteString serverChallenge, out ByteString clientResponse) { if (md5 == null) throw new ObjectDisposedException(GetType().FullName); if (Credential == null) throw new SaslException("Credential property must be set"); if (string.IsNullOrEmpty(ServiceName)) throw new SaslException("ServiceName property must be set"); var pairs = ParseChallenge(serverChallenge); if (step == 3) { /* * 2.1.3 Step Three */ if (pairs.ContainsKey("rspauth")) { clientResponse = ByteString.CreateEmpty(); return SaslExchangeStatus.Succeeded; } else { clientResponse = null; return SaslExchangeStatus.Failed; } } /* * 2.1.1 Step One */ step = 1; clientResponse = null; if (string.IsNullOrEmpty(Credential.UserName) || string.IsNullOrEmpty(Credential.Password)) return SaslExchangeStatus.Failed; // algorithm // This directive is required for backwards compatibility with HTTP // Digest., which supports other algorithms. . This directive is // required and MUST appear exactly once; if not present, or if // multiple instances are present, the client should abort the // authentication exchange. // // algorithm = "algorithm" "=" "md5-sess" if (!(pairs.ContainsKey("algorithm") && pairs["algorithm"].Equals("md5-sess"))) return SaslExchangeStatus.Failed; // "algorithm" is missing or unsupported algorithm // charset // This directive, if present, specifies that the server supports // UTF-8 encoding for the username and password. If not present, the // username and password must be encoded in ISO 8859-1 (of which // US-ASCII is a subset). The directive is needed for backwards // compatibility with HTTP Digest, which only supports ISO 8859-1. // This directive may appear at most once; if multiple instances are // present, the client should abort the authentication exchange. // // charset = "charset" "=" "utf-8" var charset = (pairs.ContainsKey("charset") && pairs["charset"].Equals("utf-8")) ? Encoding.UTF8 : Encoding.GetEncoding("iso-8859-1"); ByteString realm, nonce, qop; if (!pairs.TryGetValue("realm", out realm)) return SaslExchangeStatus.Failed; if (!pairs.TryGetValue("nonce", out nonce)) return SaslExchangeStatus.Failed; if (!pairs.TryGetValue("qop", out qop)) return SaslExchangeStatus.Failed; qop = new ByteString("auth"); // ignore qop-options /* * 2.1.2 Step Two */ step = 2; // cnonce // It is RECOMMENDED that it contain at least 64 bits of entropy. var cnonce = new ByteString(Cnonce ?? Nonce.Generate(64, true)); // nonce-count // The nc-value is the hexadecimal count of the number of requests // (including the current request) that the client has sent with the // nonce value in this request. For example, in the first request // sent in response to a given nonce value, the client sends // "nc=00000001" var nc = new ByteString((1).ToString("X8")); // digest-uri // Indicates the principal name of the service with which the client // wishes to connect, formed from the serv-type, host, and serv-name. // For example, the FTP service on "ftp.example.com" would have a // "digest-uri" value of "ftp/ftp.example.com"; the SMTP server from // the example above would have a "digest-uri" value of // "smtp/mail3.example.com/example.com". var digestUri = new ByteString(string.Format("{0}/{1}", ServiceName, Credential.Domain ?? string.Empty)); // response-value = // HEX( KD ( HEX(H(A1)), // { nonce-value, ":" nc-value, ":", // cnonce-value, ":", qop-value, ":", HEX(H(A2)) })) var responseValue = HexKD(md5, HexHA1(md5, charset, Credential.UserName, realm, Credential.Password, nonce, cnonce, null), nonce, nc, cnonce, qop, HexHA2(md5, qop, digestUri)); var responseBuilder = new ByteStringBuilder(0x200); if (charset == Encoding.UTF8) responseBuilder.Append("charset=utf-8,"); responseBuilder.Append("username=\""); responseBuilder.Append(Credential.UserName); responseBuilder.Append("\",realm=\""); responseBuilder.Append(realm); responseBuilder.Append("\",nonce=\""); responseBuilder.Append(nonce); responseBuilder.Append("\",nc="); responseBuilder.Append(nc); responseBuilder.Append(",cnonce=\""); responseBuilder.Append(cnonce); responseBuilder.Append("\",digest-uri=\""); responseBuilder.Append(digestUri); responseBuilder.Append("\",response="); responseBuilder.Append(responseValue); responseBuilder.Append(",qop="); responseBuilder.Append(qop); step = 3; clientResponse = responseBuilder.ToByteString(); return SaslExchangeStatus.Continuing; }
private static ByteString HexKD(MD5 h, ByteString hexHA1, ByteString nonce, ByteString nc, ByteString cnonce, ByteString qop, ByteString hexHA2) { // response-value = // HEX( KD ( HEX(H(A1)), // { nonce-value, ":" nc-value, ":", // cnonce-value, ":", qop-value, ":", HEX(H(A2)) })) // Let KD(k, s) be H({k, ":", s}), i.e., the 16 octet hash of the string // k, a colon and the string s. var tmp = new ByteStringBuilder(0x100); tmp.Append(hexHA1); tmp.Append(0x3a); tmp.Append(nonce); tmp.Append(0x3a); tmp.Append(nc); tmp.Append(0x3a); tmp.Append(cnonce); tmp.Append(0x3a); tmp.Append(qop); tmp.Append(0x3a); tmp.Append(hexHA2); return HexH(h, tmp.ToByteString()); }
private static ByteString HexHA2(MD5 h, ByteString qop, ByteString digestUri) { // If the "qop" directive's value is "auth", then A2 is: // // A2 = { "AUTHENTICATE:", digest-uri-value } // // If the "qop" value is "auth-int" or "auth-conf" then A2 is: // // A2 = { "AUTHENTICATE:", digest-uri-value, // ":00000000000000000000000000000000" } var a2 = new ByteStringBuilder(0x80); a2.Append("AUTHENTICATE:"); a2.Append(digestUri); if (qop.EqualsIgnoreCase("auth-int") || qop.EqualsIgnoreCase("auth-conf")) a2.Append(":00000000000000000000000000000000"); return HexH(h, a2.ToByteString()); }
private static ByteString HexHA1(MD5 h, Encoding charset, string username, ByteString realm, string password, ByteString nonce, ByteString cnonce, ByteString authzid) { // If authzid is specified, then A1 is // // A1 = { H( { username-value, ":", realm-value, ":", passwd } ), // ":", nonce-value, ":", cnonce-value, ":", authzid-value } // // If authzid is not specified, then A1 is // // A1 = { H( { username-value, ":", realm-value, ":", passwd } ), // ":", nonce-value, ":", cnonce-value } var a1 = new ByteStringBuilder(0x100); a1.Append(h.ComputeHash(ArrayExtensions.Concat(charset.GetBytes(username), new byte[] {0x3a}, realm.ByteArray, new byte[] {0x3a}, charset.GetBytes(password)))); a1.Append(0x3a); a1.Append(nonce); a1.Append(0x3a); a1.Append(cnonce); if (authzid != null) { a1.Append(0x3a); a1.Append(authzid); } return HexH(h, a1.ToByteString()); }