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
7 using System
.Collections
.Generic
;
10 using System
.Globalization
;
12 namespace Wv
.Authentication
31 public class ExternalAuthClient
: SaslProcess
33 public override bool SupportNonBlocking { get { return false; }
}
36 public override bool Done { get { return done; }
}
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
);
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
);
59 string ok_rep
= sr
.ReadLine ();
62 parts
= ok_rep
.Split (' ');
64 if (parts
.Length
< 1 || parts
[0] != "OK") {
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");
84 public enum SaslMechResponse
{
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
)
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
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
134 byte[] b
= new byte[1];
137 sb
.Append(e
.GetString(b
));
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"))
150 // Line shouldn't be this big
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
)
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
)
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];
217 while (n
< result
.Length
) {
218 result
[n
] = (byte) (FromHexChar (hex
[i
++]) << 4);
219 result
[n
++] += FromHexChar (hex
[i
++]);
225 public abstract class SaslAuthCtx
227 protected Connection conn
;
229 protected SaslAuthCtx (Connection 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
{
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 "
278 this.mechs
= new Dictionary
<string,SaslAuthCtxFactory
>(mechs
);
281 public override bool ProcessLine()
283 string line
= GetLine(conn
.Transport
.Stream
);
286 // Didn't get a complete line
287 state
= ServerState
.Terminate
;
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.
297 // Log("Line didn't end with CRLF");
298 state
= ServerState
.Terminate
;
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
);
314 case ServerState
.WaitingForData
:
315 ProcessWaitData(tokens
, out response
);
317 case ServerState
.WaitingForBegin
:
318 ProcessWaitBegin(tokens
, out response
);
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");
329 } else switch (state
) {
330 case ServerState
.Terminate
:
331 case ServerState
.DoneAuth
:
334 throw new Exception("Auth mechanism "
340 private void ProcessWaitAuth(string[] tokens
,
344 throw new Exception("Expected no authctx");
346 byte[] outData
= null;
353 switch (tokens
.Length
) {
355 rv
= SaslMechResponse
.Reject
;
358 rv
= Auth(tokens
[1], null, out outData
);
364 initialData
= FromHex(tokens
[2]);
365 } catch (ArgumentException
) {
366 response
= "ERROR Invalid "
367 + "initial response "
369 state
= ServerState
.WaitingForAuth
;
373 rv
= Auth(tokens
[1], initialData
,
378 response
= "ERROR Invalid AUTH command";
379 state
= ServerState
.WaitingForAuth
;
384 case SaslMechResponse
.Continue
:
385 response
= "DATA " + ToHex(outData
);
386 state
= ServerState
.WaitingForData
;
388 case SaslMechResponse
.OK
:
390 state
= ServerState
.WaitingForBegin
;
392 case SaslMechResponse
.Reject
:
393 response
= CreateRejection();
394 state
= ServerState
.WaitingForAuth
;
397 throw new Exception("Invalid SASL Mechanism "
403 state
= ServerState
.Terminate
;
406 response
= CreateRejection();
407 state
= ServerState
.WaitingForAuth
;
410 response
= "ERROR Invalid command";
411 state
= ServerState
.WaitingForAuth
;
416 private void ProcessWaitData(string[] tokens
,
420 throw new Exception("Expected to have an authctx");
422 if (authctx
.Accepted
)
423 throw new Exception("Expected authentication to not be completed");
428 if (tokens
.Length
!= 2) {
429 response
= "ERROR Invalid DATA command";
430 state
= ServerState
.WaitingForData
;
436 inData
= FromHex(tokens
[1]);
437 } catch (ArgumentException
) {
438 response
= "ERROR Invalid response data";
439 state
= ServerState
.WaitingForData
;
443 byte[] outData
= null;
444 SaslMechResponse rv
= authctx
.Data(inData
, out outData
);
447 case SaslMechResponse
.OK
:
449 state
= ServerState
.WaitingForBegin
;
452 case SaslMechResponse
.Continue
:
453 response
= "DATA " + ToHex(outData
);
454 state
= ServerState
.WaitingForData
;
457 case SaslMechResponse
.Reject
:
460 response
= CreateRejection();
461 state
= ServerState
.WaitingForAuth
;
464 throw new Exception("Invalid SASL Mechanism "
474 response
= CreateRejection();
475 state
= ServerState
.WaitingForAuth
;
480 state
= ServerState
.Terminate
;
484 response
= "ERROR Invalid command";
485 state
= ServerState
.WaitingForData
;
490 private void ProcessWaitBegin(string[] tokens
,
494 throw new Exception("Expected to have an authctx");
496 if (authctx
.Accepted
)
497 throw new Exception("Expected authentication to be completed");
505 response
= CreateRejection();
506 state
= ServerState
.WaitingForAuth
;
513 state
= ServerState
.DoneAuth
;
517 response
= "ERROR Invalid command";
518 state
= ServerState
.WaitingForBegin
;
523 private SaslMechResponse
Auth(string mech
,
524 byte[] initialData
, out byte[] responseData
)
528 SaslAuthCtxFactory factory
;
530 if (!mechs
.TryGetValue(mech
, out factory
)) {
532 return SaslMechResponse
.Reject
;
536 SaslMechResponse response
;
538 response
= factory(initialData
, out ctx
, out responseData
);
540 if (response
!= SaslMechResponse
.Reject
)
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();