Rename NDesk.DBus namespace to just Wv.
[versaplex.git] / dbus-sharp / Authentication.cs
blob406441a0ba95eb8e46ddc3c34205a992f8b02974
1 // Copyright 2006 Alp Toker <alp@atoker.com>
2 // Copyright 2007 Versabanq (Adrian Dewhurst <adewhurst@versabanq.com>)
3 // This software is made available under the MIT License
4 // See COPYING for details
6 using System;
7 using System.Collections.Generic;
8 using System.IO;
9 using System.Text;
10 using System.Globalization;
12 namespace Wv.Authentication
14 enum ClientState
16 WaitingForData,
17 WaitingForOK,
18 WaitingForReject,
19 Terminate
22 enum ServerState
24 WaitingForAuth,
25 WaitingForData,
26 WaitingForBegin,
27 Terminate,
28 DoneAuth
31 public class ExternalAuthClient : SaslProcess
33 public override bool SupportNonBlocking { get { return false; } }
35 bool done = false;
36 public override bool Done { get { return done; } }
37 bool ok = false;
38 public override bool OK { get { return ok; } }
40 public ExternalAuthClient (Connection conn) : base(conn)
44 public override void Run ()
46 StreamReader sr = new StreamReader (conn.ns, Encoding.ASCII);
47 StreamWriter sw = new StreamWriter (conn.ns, Encoding.ASCII);
49 sw.NewLine = "\r\n";
51 string str = conn.Transport.AuthString ();
52 byte[] bs = Encoding.ASCII.GetBytes (str);
54 string authStr = ToHex (bs);
56 sw.WriteLine ("AUTH EXTERNAL {0}", authStr);
57 sw.Flush ();
59 string ok_rep = sr.ReadLine ();
61 string[] parts;
62 parts = ok_rep.Split (' ');
64 if (parts.Length < 1 || parts[0] != "OK") {
65 done = true;
66 throw new Exception ("Authentication error: AUTH EXTERNAL was not OK: \"" + ok_rep + "\"");
70 string guid = parts[1];
71 byte[] guidData = FromHex (guid);
72 uint unixTime = BitConverter.ToUInt32 (guidData, 0);
73 Console.Error.WriteLine ("guid: " + guid + ", " + "unixTime: " + unixTime + " (" + UnixToDateTime (unixTime) + ")");
76 sw.WriteLine ("BEGIN");
77 sw.Flush ();
79 done = true;
80 ok = true;
84 public enum SaslMechResponse {
85 Continue,
86 OK,
87 Reject
90 public abstract class SaslProcess {
91 protected Connection conn;
93 public abstract bool SupportNonBlocking { get; }
94 public abstract bool Done { get; }
95 public abstract bool OK { get; }
97 public virtual void Run()
99 while (ProcessLine()) {
103 public virtual bool ProcessLine()
105 throw new NotImplementedException();
108 protected SaslProcess(Connection conn)
110 this.conn = conn;
113 protected static string GetLine (Stream s)
115 const int MAX_BUFFER = 16384; // From real dbus client
117 // FIXME: There must be a better way to do this that
118 // doesn't run the risk of eating bytes after the
119 // BEGIN\r\n
121 // XXX: This is just generally horrible and
122 // inefficient. Sorry.
124 Encoding e = Encoding.ASCII;
125 StringBuilder sb = new StringBuilder();
127 while (sb.Length < MAX_BUFFER) {
128 int r = s.ReadByte();
130 // End of stream... no line to get
131 if (r < 0)
132 return null;
134 byte[] b = new byte[1];
135 b[0] = (byte)r;
137 sb.Append(e.GetString(b));
139 // Look for \r\n
140 if (sb.Length >= 2) {
141 // For some reason I can't use
142 // sb.Chars[i] (mono problem?)
143 string str = sb.ToString();
145 if (str.EndsWith("\r\n"))
146 return str;
150 // Line shouldn't be this big
151 return null;
154 protected static void PutLine (Stream s, string line)
156 Encoding e = Encoding.ASCII;
158 byte[] outbuf = e.GetBytes(line);
159 s.Write(outbuf, 0, outbuf.Length);
162 //From Mono.Unix.Native.NativeConvert
163 //should these methods use long or (u)int?
164 public static DateTime UnixToDateTime (long time)
166 DateTime LocalUnixEpoch = new DateTime (1970, 1, 1);
167 TimeSpan LocalUtcOffset = TimeZone.CurrentTimeZone.GetUtcOffset (DateTime.UtcNow);
168 return LocalUnixEpoch.AddSeconds ((double) time + LocalUtcOffset.TotalSeconds);
171 public static long DateTimeToUnix (DateTime time)
173 DateTime LocalUnixEpoch = new DateTime (1970, 1, 1);
174 TimeSpan LocalUtcOffset = TimeZone.CurrentTimeZone.GetUtcOffset (DateTime.UtcNow);
175 TimeSpan unixTime = time.Subtract (LocalUnixEpoch) - LocalUtcOffset;
177 return (long) unixTime.TotalSeconds;
180 //From Mono.Security.Cryptography
181 //Modified to output lowercase hex
182 static public string ToHex (byte[] input)
184 if (input == null)
185 return null;
187 StringBuilder sb = new StringBuilder (input.Length * 2);
188 foreach (byte b in input) {
189 sb.Append (b.ToString ("x2", CultureInfo.InvariantCulture));
191 return sb.ToString ();
194 //From Mono.Security.Cryptography
195 static private byte FromHexChar (char c)
197 if ((c >= 'a') && (c <= 'f'))
198 return (byte) (c - 'a' + 10);
199 if ((c >= 'A') && (c <= 'F'))
200 return (byte) (c - 'A' + 10);
201 if ((c >= '0') && (c <= '9'))
202 return (byte) (c - '0');
203 throw new ArgumentException ("Invalid hex char");
206 //From Mono.Security.Cryptography
207 static public byte[] FromHex (string hex)
209 if (hex == null)
210 return null;
211 if ((hex.Length & 0x1) == 0x1)
212 throw new ArgumentException ("Length must be a multiple of 2");
214 byte[] result = new byte [hex.Length >> 1];
215 int n = 0;
216 int i = 0;
217 while (n < result.Length) {
218 result [n] = (byte) (FromHexChar (hex [i++]) << 4);
219 result [n++] += FromHexChar (hex [i++]);
221 return result;
225 public abstract class SaslAuthCtx
227 protected Connection conn;
229 protected SaslAuthCtx (Connection conn)
231 this.conn = conn;
234 public abstract SaslMechResponse Data (byte[] response,
235 out byte[] challenge);
237 public abstract bool Accepted { get; }
239 public virtual void Aborted()
243 public virtual void Completed()
248 public delegate SaslMechResponse SaslAuthCtxFactory (byte[] initialData,
249 out SaslAuthCtx ctx, out byte[] challenge);
251 public sealed class SaslServer : SaslProcess {
252 private ServerState state = ServerState.WaitingForAuth;
253 private SaslAuthCtx authctx = null;
255 public override bool SupportNonBlocking { get { return true; } }
257 public override bool Done {
258 get {
259 return (state == ServerState.Terminate
260 || state == ServerState.DoneAuth);
264 public override bool OK {
265 get { return state == ServerState.DoneAuth; }
268 private IDictionary<string,SaslAuthCtxFactory> mechs;
270 public SaslServer (IDictionary<string,SaslAuthCtxFactory> mechs,
271 Connection conn) : base(conn)
273 if (mechs.Keys.Count == 0)
274 throw new Exception("SaslServer requires at "
275 +"least one authentication "
276 +"mechanism");
278 this.mechs = new Dictionary<string,SaslAuthCtxFactory>(mechs);
281 public override bool ProcessLine()
283 string line = GetLine(conn.Transport.Stream);
285 if (line == null) {
286 // Didn't get a complete line
287 state = ServerState.Terminate;
288 return true;
291 if (line.Substring(line.Length - 2) != "\r\n") {
292 // Actually, this isn't a big deal at the
293 // moment because GetLine ensures that there
294 // is a \r\n, so this block will never run.
296 // FIXME
297 // Log("Line didn't end with CRLF");
298 state = ServerState.Terminate;
299 return true;
302 line = line.Substring(0, line.Length - 2);
304 string[] tokens = line.Split(' ');
306 string response = null;
308 if (tokens.Length < 1) {
309 response = "ERROR No command provided";
310 } else switch (state) {
311 case ServerState.WaitingForAuth:
312 ProcessWaitAuth(tokens, out response);
313 break;
314 case ServerState.WaitingForData:
315 ProcessWaitData(tokens, out response);
316 break;
317 case ServerState.WaitingForBegin:
318 ProcessWaitBegin(tokens, out response);
319 break;
320 case ServerState.Terminate:
321 case ServerState.DoneAuth:
322 throw new Exception("No further authentication"
323 +" processing should be done");
326 if (response != null) {
327 PutLine(conn.Transport.Stream, response + "\r\n");
328 return false;
329 } else switch (state) {
330 case ServerState.Terminate:
331 case ServerState.DoneAuth:
332 return true;
333 default:
334 throw new Exception("Auth mechanism "
335 +"didn't provide a "
336 +"response");
340 private void ProcessWaitAuth(string[] tokens,
341 out string response)
343 if (authctx != null)
344 throw new Exception("Expected no authctx");
346 byte[] outData = null;
348 switch (tokens[0]) {
349 case "AUTH":
351 SaslMechResponse rv;
353 switch (tokens.Length) {
354 case 1:
355 rv = SaslMechResponse.Reject;
356 break;
357 case 2:
358 rv = Auth(tokens[1], null, out outData);
359 break;
360 case 3:
362 byte[] initialData;
363 try {
364 initialData = FromHex(tokens[2]);
365 } catch (ArgumentException) {
366 response = "ERROR Invalid "
367 + "initial response "
368 + "data";
369 state = ServerState.WaitingForAuth;
370 return;
373 rv = Auth(tokens[1], initialData,
374 out outData);
375 break;
377 default:
378 response = "ERROR Invalid AUTH command";
379 state = ServerState.WaitingForAuth;
380 return;
383 switch (rv) {
384 case SaslMechResponse.Continue:
385 response = "DATA " + ToHex(outData);
386 state = ServerState.WaitingForData;
387 return;
388 case SaslMechResponse.OK:
389 response = "OK";
390 state = ServerState.WaitingForBegin;
391 return;
392 case SaslMechResponse.Reject:
393 response = CreateRejection();
394 state = ServerState.WaitingForAuth;
395 return;
396 default:
397 throw new Exception("Invalid SASL Mechanism "
398 +"return value");
401 case "BEGIN":
402 response = null;
403 state = ServerState.Terminate;
404 return;
405 case "ERROR":
406 response = CreateRejection();
407 state = ServerState.WaitingForAuth;
408 return;
409 default:
410 response = "ERROR Invalid command";
411 state = ServerState.WaitingForAuth;
412 return;
416 private void ProcessWaitData(string[] tokens,
417 out string response)
419 if (authctx == null)
420 throw new Exception("Expected to have an authctx");
422 if (authctx.Accepted)
423 throw new Exception("Expected authentication to not be completed");
425 switch (tokens[0]) {
426 case "DATA":
428 if (tokens.Length != 2) {
429 response = "ERROR Invalid DATA command";
430 state = ServerState.WaitingForData;
431 return;
434 byte[] inData;
435 try {
436 inData = FromHex(tokens[1]);
437 } catch (ArgumentException) {
438 response = "ERROR Invalid response data";
439 state = ServerState.WaitingForData;
440 return;
443 byte[] outData = null;
444 SaslMechResponse rv = authctx.Data(inData, out outData);
446 switch (rv) {
447 case SaslMechResponse.OK:
448 response = "OK";
449 state = ServerState.WaitingForBegin;
450 return;
452 case SaslMechResponse.Continue:
453 response = "DATA " + ToHex(outData);
454 state = ServerState.WaitingForData;
455 return;
457 case SaslMechResponse.Reject:
458 authctx = null;
460 response = CreateRejection();
461 state = ServerState.WaitingForAuth;
462 return;
463 default:
464 throw new Exception("Invalid SASL Mechanism "
465 +"return value");
469 case "CANCEL":
470 case "ERROR":
471 authctx.Aborted();
472 authctx = null;
474 response = CreateRejection();
475 state = ServerState.WaitingForAuth;
476 return;
478 case "BEGIN":
479 response = null;
480 state = ServerState.Terminate;
481 return;
483 default:
484 response = "ERROR Invalid command";
485 state = ServerState.WaitingForData;
486 return;
490 private void ProcessWaitBegin(string[] tokens,
491 out string response)
493 if (authctx == null)
494 throw new Exception("Expected to have an authctx");
496 if (authctx.Accepted)
497 throw new Exception("Expected authentication to be completed");
499 switch (tokens[0]) {
500 case "ERROR":
501 case "CANCEL":
502 authctx.Aborted();
503 authctx = null;
505 response = CreateRejection();
506 state = ServerState.WaitingForAuth;
507 return;
509 case "BEGIN":
510 authctx.Completed();
512 response = null;
513 state = ServerState.DoneAuth;
514 return;
516 default:
517 response = "ERROR Invalid command";
518 state = ServerState.WaitingForBegin;
519 break;
523 private SaslMechResponse Auth(string mech,
524 byte[] initialData, out byte[] responseData)
526 authctx = null;
528 SaslAuthCtxFactory factory;
530 if (!mechs.TryGetValue(mech, out factory)) {
531 responseData = null;
532 return SaslMechResponse.Reject;
535 SaslAuthCtx ctx;
536 SaslMechResponse response;
538 response = factory(initialData, out ctx, out responseData);
540 if (response != SaslMechResponse.Reject)
541 authctx = ctx;
543 return response;
546 private string CreateRejection()
548 StringBuilder sb = new StringBuilder("REJECT ");
550 foreach (string key in mechs.Keys) {
551 sb.AppendFormat(" {0}", key);
554 return sb.ToString();