2003-02-12 Havoc Pennington <hp@pobox.com>
[platform/upstream/dbus.git] / dbus / dbus-auth.c
1 /* -*- mode: C; c-file-style: "gnu" -*- */
2 /* dbus-auth.c Authentication
3  *
4  * Copyright (C) 2002, 2003 Red Hat Inc.
5  *
6  * Licensed under the Academic Free License version 1.2
7  * 
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  * 
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  *
22  */
23 #include "dbus-auth.h"
24 #include "dbus-string.h"
25 #include "dbus-list.h"
26 #include "dbus-internals.h"
27
28 /* See doc/dbus-sasl-profile.txt */
29
30 /**
31  * @defgroup DBusAuth Authentication
32  * @ingroup  DBusInternals
33  * @brief DBusAuth object
34  *
35  * DBusAuth manages the authentication negotiation when a connection
36  * is first established, and also manage any encryption used over a
37  * connection.
38  *
39  * The file doc/dbus-sasl-profile.txt documents the network protocol
40  * used for authentication.
41  */
42
43 /**
44  * @defgroup DBusAuthInternals Authentication implementation details
45  * @ingroup  DBusInternals
46  * @brief DBusAuth implementation details
47  *
48  * Private details of authentication code.
49  *
50  * @{
51  */
52
53 /**
54  * Processes a command. Returns whether we had enough memory to
55  * complete the operation.
56  */
57 typedef dbus_bool_t (* DBusProcessAuthCommandFunction) (DBusAuth         *auth,
58                                                         const DBusString *command,
59                                                         const DBusString *args);
60
61 typedef struct
62 {
63   const char *command;
64   DBusProcessAuthCommandFunction func;
65 } DBusAuthCommandHandler;
66
67 /**
68  * This function appends an initial client response to the given string
69  */
70 typedef dbus_bool_t (* DBusInitialResponseFunction)  (DBusAuth         *auth,
71                                                       DBusString       *response);
72
73 /**
74  * This function processes a block of data received from the peer.
75  * i.e. handles a DATA command.
76  */
77 typedef dbus_bool_t (* DBusAuthDataFunction)     (DBusAuth         *auth,
78                                                   const DBusString *data);
79
80 /**
81  * This function encodes a block of data from the peer.
82  */
83 typedef dbus_bool_t (* DBusAuthEncodeFunction)   (DBusAuth         *auth,
84                                                   const DBusString *data,
85                                                   DBusString       *encoded);
86
87 /**
88  * This function decodes a block of data from the peer.
89  */
90 typedef dbus_bool_t (* DBusAuthDecodeFunction)   (DBusAuth         *auth,
91                                                   const DBusString *data,
92                                                   DBusString       *decoded);
93
94 /**
95  * This function is called when the mechanism is abandoned.
96  */
97 typedef void        (* DBusAuthShutdownFunction) (DBusAuth       *auth);
98
99 typedef struct
100 {
101   const char *mechanism;
102   DBusAuthDataFunction server_data_func;
103   DBusAuthEncodeFunction server_encode_func;
104   DBusAuthDecodeFunction server_decode_func;
105   DBusAuthShutdownFunction server_shutdown_func;
106   DBusInitialResponseFunction client_initial_response_func;
107   DBusAuthDataFunction client_data_func;
108   DBusAuthEncodeFunction client_encode_func;
109   DBusAuthDecodeFunction client_decode_func;
110   DBusAuthShutdownFunction client_shutdown_func;
111 } DBusAuthMechanismHandler;
112
113 /**
114  * Internal members of DBusAuth.
115  */
116 struct DBusAuth
117 {
118   int refcount;           /**< reference count */
119
120   DBusString incoming;    /**< Incoming data buffer */
121   DBusString outgoing;    /**< Outgoing data buffer */
122   
123   const DBusAuthCommandHandler *handlers; /**< Handlers for commands */
124
125   const DBusAuthMechanismHandler *mech;   /**< Current auth mechanism */
126
127   DBusString identity;                   /**< Current identity we're authorizing
128                                           *   as.
129                                           */
130   
131   DBusCredentials credentials;      /**< Credentials, fields may be -1 */
132
133   DBusCredentials authorized_identity; /**< Credentials that are authorized */
134   
135   unsigned int needed_memory : 1;   /**< We needed memory to continue since last
136                                      * successful getting something done
137                                      */
138   unsigned int need_disconnect : 1; /**< We've given up, time to disconnect */
139   unsigned int authenticated : 1;   /**< We are authenticated */
140   unsigned int authenticated_pending_output : 1; /**< Authenticated once we clear outgoing buffer */
141   unsigned int authenticated_pending_begin : 1;  /**< Authenticated once we get BEGIN */
142   unsigned int already_got_mechanisms : 1;       /**< Client already got mech list */
143   unsigned int already_asked_for_initial_response : 1; /**< Already sent a blank challenge to get an initial response */
144 };
145
146 typedef struct
147 {
148   DBusAuth base;
149
150   DBusList *mechs_to_try; /**< Mechanisms we got from the server that we're going to try using */
151   
152 } DBusAuthClient;
153
154 typedef struct
155 {
156   DBusAuth base;
157
158   int failures;     /**< Number of times client has been rejected */
159   int max_failures; /**< Number of times we reject before disconnect */
160   
161 } DBusAuthServer;
162
163 static dbus_bool_t process_auth         (DBusAuth         *auth,
164                                          const DBusString *command,
165                                          const DBusString *args);
166 static dbus_bool_t process_cancel       (DBusAuth         *auth,
167                                          const DBusString *command,
168                                          const DBusString *args);
169 static dbus_bool_t process_begin        (DBusAuth         *auth,
170                                          const DBusString *command,
171                                          const DBusString *args);
172 static dbus_bool_t process_data_server  (DBusAuth         *auth,
173                                          const DBusString *command,
174                                          const DBusString *args);
175 static dbus_bool_t process_error_server (DBusAuth         *auth,
176                                          const DBusString *command,
177                                          const DBusString *args);
178 static dbus_bool_t process_rejected     (DBusAuth         *auth,
179                                          const DBusString *command,
180                                          const DBusString *args);
181 static dbus_bool_t process_ok           (DBusAuth         *auth,
182                                          const DBusString *command,
183                                          const DBusString *args);
184 static dbus_bool_t process_data_client  (DBusAuth         *auth,
185                                          const DBusString *command,
186                                          const DBusString *args);
187 static dbus_bool_t process_error_client (DBusAuth         *auth,
188                                          const DBusString *command,
189                                          const DBusString *args);
190
191
192 static dbus_bool_t client_try_next_mechanism (DBusAuth *auth);
193 static dbus_bool_t send_rejected             (DBusAuth *auth);
194
195 static DBusAuthCommandHandler
196 server_handlers[] = {
197   { "AUTH", process_auth },
198   { "CANCEL", process_cancel },
199   { "BEGIN", process_begin },
200   { "DATA", process_data_server },
201   { "ERROR", process_error_server },
202   { NULL, NULL }
203 };
204
205 static DBusAuthCommandHandler
206 client_handlers[] = {
207   { "REJECTED", process_rejected },
208   { "OK", process_ok },
209   { "DATA", process_data_client },
210   { "ERROR", process_error_client },
211   { NULL, NULL }
212 };
213
214 /**
215  * @param auth the auth conversation
216  * @returns #TRUE if the conversation is the server side
217  */
218 #define DBUS_AUTH_IS_SERVER(auth) ((auth)->handlers == server_handlers)
219 /**
220  * @param auth the auth conversation
221  * @returns #TRUE if the conversation is the client side
222  */
223 #define DBUS_AUTH_IS_CLIENT(auth) ((auth)->handlers == client_handlers)
224 /**
225  * @param auth the auth conversation
226  * @returns auth cast to DBusAuthClient
227  */
228 #define DBUS_AUTH_CLIENT(auth)    ((DBusAuthClient*)(auth))
229 /**
230  * @param auth the auth conversation
231  * @returns auth cast to DBusAuthServer
232  */
233 #define DBUS_AUTH_SERVER(auth)    ((DBusAuthServer*)(auth))
234
235 static DBusAuth*
236 _dbus_auth_new (int size)
237 {
238   DBusAuth *auth;
239   
240   auth = dbus_malloc0 (size);
241   if (auth == NULL)
242     return NULL;
243   
244   auth->refcount = 1;
245
246   auth->credentials.pid = -1;
247   auth->credentials.uid = -1;
248   auth->credentials.gid = -1;
249
250   auth->authorized_identity.pid = -1;
251   auth->authorized_identity.uid = -1;
252   auth->authorized_identity.gid = -1;
253   
254   /* note that we don't use the max string length feature,
255    * because you can't use that feature if you're going to
256    * try to recover from out-of-memory (it creates
257    * what looks like unrecoverable inability to alloc
258    * more space in the string). But we do handle
259    * overlong buffers in _dbus_auth_do_work().
260    */
261   
262   if (!_dbus_string_init (&auth->incoming, _DBUS_INT_MAX))
263     {
264       dbus_free (auth);
265       return NULL;
266     }
267
268   if (!_dbus_string_init (&auth->outgoing, _DBUS_INT_MAX))
269     {
270       _dbus_string_free (&auth->incoming);
271       dbus_free (auth);
272       return NULL;
273     }
274   
275   if (!_dbus_string_init (&auth->identity, _DBUS_INT_MAX))
276     {
277       _dbus_string_free (&auth->incoming);
278       _dbus_string_free (&auth->outgoing);
279       dbus_free (auth);
280       return NULL;
281     }
282   
283   return auth;
284 }
285
286 static DBusAuthState
287 get_state (DBusAuth *auth)
288 {
289   if (DBUS_AUTH_IS_SERVER (auth) &&
290       DBUS_AUTH_SERVER (auth)->failures >=
291       DBUS_AUTH_SERVER (auth)->max_failures)
292     auth->need_disconnect = TRUE;
293   
294   if (auth->need_disconnect)
295     return DBUS_AUTH_STATE_NEED_DISCONNECT;
296   else if (auth->authenticated)
297     {
298       if (_dbus_string_get_length (&auth->incoming) > 0)
299         return DBUS_AUTH_STATE_AUTHENTICATED_WITH_UNUSED_BYTES;
300       else
301         return DBUS_AUTH_STATE_AUTHENTICATED;
302     }
303   else if (auth->needed_memory)
304     return DBUS_AUTH_STATE_WAITING_FOR_MEMORY;
305   else if (_dbus_string_get_length (&auth->outgoing) > 0)
306     return DBUS_AUTH_STATE_HAVE_BYTES_TO_SEND;
307   else
308     return DBUS_AUTH_STATE_WAITING_FOR_INPUT;
309 }
310
311 static void
312 shutdown_mech (DBusAuth *auth)
313 {
314   /* Cancel any auth */
315   auth->authenticated_pending_begin = FALSE;
316   auth->authenticated = FALSE;
317   auth->already_asked_for_initial_response = FALSE;
318   _dbus_string_set_length (&auth->identity, 0);
319   auth->authorized_identity.pid = -1;
320   auth->authorized_identity.uid = -1;
321   auth->authorized_identity.gid = -1;
322   
323   if (auth->mech != NULL)
324     {
325       _dbus_verbose ("Shutting down mechanism %s\n",
326                      auth->mech->mechanism);
327       
328       if (DBUS_AUTH_IS_CLIENT (auth))
329         (* auth->mech->client_shutdown_func) (auth);
330       else
331         (* auth->mech->server_shutdown_func) (auth);
332       
333       auth->mech = NULL;
334     }
335 }
336
337 static dbus_bool_t
338 handle_server_data_stupid_test_mech (DBusAuth         *auth,
339                                      const DBusString *data)
340 {
341   if (!_dbus_string_append (&auth->outgoing,
342                             "OK\r\n"))
343     return FALSE;
344
345   auth->authenticated_pending_begin = TRUE;
346   
347   return TRUE;
348 }
349
350 static void
351 handle_server_shutdown_stupid_test_mech (DBusAuth *auth)
352 {
353
354 }
355
356 static dbus_bool_t
357 handle_client_data_stupid_test_mech (DBusAuth         *auth,
358                                      const DBusString *data)
359 {
360   
361   return TRUE;
362 }
363
364 static void
365 handle_client_shutdown_stupid_test_mech (DBusAuth *auth)
366 {
367
368 }
369
370 /* the stupid test mech is a base64-encoded string;
371  * all the inefficiency, none of the security!
372  */
373 static dbus_bool_t
374 handle_encode_stupid_test_mech (DBusAuth         *auth,
375                                 const DBusString *plaintext,
376                                 DBusString       *encoded)
377 {
378   if (!_dbus_string_base64_encode (plaintext, 0, encoded,
379                                    _dbus_string_get_length (encoded)))
380     return FALSE;
381   
382   return TRUE;
383 }
384
385 static dbus_bool_t
386 handle_decode_stupid_test_mech (DBusAuth         *auth,
387                                 const DBusString *encoded,
388                                 DBusString       *plaintext)
389 {
390   if (!_dbus_string_base64_decode (encoded, 0, plaintext,
391                                    _dbus_string_get_length (plaintext)))
392     return FALSE;
393   
394   return TRUE;
395 }
396
397 static dbus_bool_t
398 handle_server_data_external_mech (DBusAuth         *auth,
399                                   const DBusString *data)
400 {
401   DBusCredentials desired_identity;
402
403   if (auth->credentials.uid < 0)
404     {
405       _dbus_verbose ("no credentials, mechanism EXTERNAL can't authenticate\n");
406       return send_rejected (auth);
407     }
408   
409   if (_dbus_string_get_length (data) > 0)
410     {
411       if (_dbus_string_get_length (&auth->identity) > 0)
412         {
413           /* Tried to send two auth identities, wtf */
414           return send_rejected (auth);
415         }
416       else
417         {
418           /* this is our auth identity */
419           if (!_dbus_string_copy (data, 0, &auth->identity, 0))
420             return FALSE;
421         }
422     }
423
424   /* Poke client for an auth identity, if none given */
425   if (_dbus_string_get_length (&auth->identity) == 0 &&
426       !auth->already_asked_for_initial_response)
427     {
428       if (_dbus_string_append (&auth->outgoing,
429                                "DATA\r\n"))
430         {
431           _dbus_verbose ("sending empty challenge asking client for auth identity\n");
432           auth->already_asked_for_initial_response = TRUE;
433           return TRUE;
434         }
435       else
436         return FALSE;
437     }
438
439   desired_identity.pid = -1;
440   desired_identity.uid = -1;
441   desired_identity.gid = -1;
442   
443   /* If auth->identity is still empty here, then client
444    * responded with an empty string after we poked it for
445    * an initial response. This means to try to auth the
446    * identity provided in the credentials.
447    */
448   if (_dbus_string_get_length (&auth->identity) == 0)
449     {
450       desired_identity.uid = auth->credentials.uid;
451     }
452   else
453     {
454       if (!_dbus_credentials_from_uid_string (&auth->identity,
455                                               &desired_identity))
456         return send_rejected (auth);
457     }
458
459   if (desired_identity.uid < 0)
460     {
461       _dbus_verbose ("desired UID %d is no good\n", desired_identity.uid);
462       return send_rejected (auth);
463     }
464   
465   if (_dbus_credentials_match (&auth->credentials,
466                                &desired_identity))
467     {
468       /* client has authenticated */
469       _dbus_verbose ("authenticated client with UID %d matching socket credentials UID %d\n",
470                      desired_identity.uid,
471                      auth->credentials.uid);
472       
473       if (!_dbus_string_append (&auth->outgoing,
474                                 "OK\r\n"))
475         return FALSE;
476
477       auth->authorized_identity.uid = desired_identity.uid;
478       
479       auth->authenticated_pending_begin = TRUE;
480       
481       return TRUE;
482     }
483   else
484     {
485       return send_rejected (auth);
486     }
487 }
488
489 static void
490 handle_server_shutdown_external_mech (DBusAuth *auth)
491 {
492
493 }
494
495 static dbus_bool_t
496 handle_client_initial_response_external_mech (DBusAuth         *auth,
497                                               DBusString       *response)
498 {
499   /* We always append our UID as an initial response, so the server
500    * doesn't have to send back an empty challenge to check whether we
501    * want to specify an identity. i.e. this avoids a round trip that
502    * the spec for the EXTERNAL mechanism otherwise requires.
503    */
504   DBusString plaintext;
505
506   if (!_dbus_string_init (&plaintext, _DBUS_INT_MAX))
507     return FALSE;
508   
509   if (!_dbus_string_append_our_uid (&plaintext))
510     goto failed;
511
512   if (!_dbus_string_base64_encode (&plaintext, 0,
513                                    response,
514                                    _dbus_string_get_length (response)))
515     goto failed;
516
517   _dbus_string_free (&plaintext);
518   
519   return TRUE;
520
521  failed:
522   _dbus_string_free (&plaintext);
523   return FALSE;  
524 }
525
526 static dbus_bool_t
527 handle_client_data_external_mech (DBusAuth         *auth,
528                                   const DBusString *data)
529 {
530   
531   return TRUE;
532 }
533
534 static void
535 handle_client_shutdown_external_mech (DBusAuth *auth)
536 {
537
538 }
539
540 /* Put mechanisms here in order of preference.
541  * What I eventually want to have is:
542  *
543  *  - a mechanism that checks UNIX domain socket credentials
544  *  - a simple magic cookie mechanism like X11 or ICE
545  *  - mechanisms that chain to Cyrus SASL, so we can use anything it
546  *    offers such as Kerberos, X509, whatever.
547  * 
548  */
549 static const DBusAuthMechanismHandler
550 all_mechanisms[] = {
551   { "EXTERNAL",
552     handle_server_data_external_mech,
553     NULL, NULL,
554     handle_server_shutdown_external_mech,
555     handle_client_initial_response_external_mech,
556     handle_client_data_external_mech,
557     NULL, NULL,
558     handle_client_shutdown_external_mech },
559   /* Obviously this has to die for production use */
560   { "DBUS_STUPID_TEST_MECH",
561     handle_server_data_stupid_test_mech,
562     handle_encode_stupid_test_mech,
563     handle_decode_stupid_test_mech,
564     handle_server_shutdown_stupid_test_mech,
565     NULL,
566     handle_client_data_stupid_test_mech,
567     handle_encode_stupid_test_mech,
568     handle_decode_stupid_test_mech,
569     handle_client_shutdown_stupid_test_mech },
570   { NULL, NULL }
571 };
572
573 static const DBusAuthMechanismHandler*
574 find_mech (const DBusString *name)
575 {
576   int i;
577   
578   i = 0;
579   while (all_mechanisms[i].mechanism != NULL)
580     {
581       if (_dbus_string_equal_c_str (name,
582                                     all_mechanisms[i].mechanism))
583
584         return &all_mechanisms[i];
585       
586       ++i;
587     }
588   
589   return NULL;
590 }
591
592 static dbus_bool_t
593 send_rejected (DBusAuth *auth)
594 {
595   DBusString command;
596   DBusAuthServer *server_auth;
597   int i;
598   
599   if (!_dbus_string_init (&command, _DBUS_INT_MAX))
600     return FALSE;
601   
602   if (!_dbus_string_append (&command,
603                             "REJECTED"))
604     goto nomem;
605
606   i = 0;
607   while (all_mechanisms[i].mechanism != NULL)
608     {
609       if (!_dbus_string_append (&command,
610                                 " "))
611         goto nomem;
612
613       if (!_dbus_string_append (&command,
614                                 all_mechanisms[i].mechanism))
615         goto nomem;
616       
617       ++i;
618     }
619   
620   if (!_dbus_string_append (&command, "\r\n"))
621     goto nomem;
622
623   if (!_dbus_string_copy (&command, 0, &auth->outgoing,
624                           _dbus_string_get_length (&auth->outgoing)))
625     goto nomem;
626
627   _dbus_assert (DBUS_AUTH_IS_SERVER (auth));
628   server_auth = DBUS_AUTH_SERVER (auth);
629   server_auth->failures += 1;
630   
631   return TRUE;
632
633  nomem:
634   _dbus_string_free (&command);
635   return FALSE;
636 }
637
638 static dbus_bool_t
639 process_auth (DBusAuth         *auth,
640               const DBusString *command,
641               const DBusString *args)
642 {
643   if (auth->mech)
644     {
645       /* We are already using a mechanism, client is on crack */
646       if (!_dbus_string_append (&auth->outgoing,
647                                 "ERROR \"Sent AUTH while another AUTH in progress\"\r\n"))
648         return FALSE;
649
650       return TRUE;
651     }
652   else if (_dbus_string_get_length (args) == 0)
653     {
654       /* No args to the auth, send mechanisms */
655       if (!send_rejected (auth))
656         return FALSE;
657
658       return TRUE;
659     }
660   else
661     {
662       int i;
663       DBusString mech;
664       DBusString base64_response;
665       DBusString decoded_response;
666       
667       _dbus_string_find_blank (args, 0, &i);
668
669       if (!_dbus_string_init (&mech, _DBUS_INT_MAX))
670         return FALSE;
671
672       if (!_dbus_string_init (&base64_response, _DBUS_INT_MAX))
673         {
674           _dbus_string_free (&mech);
675           return FALSE;
676         }
677
678       if (!_dbus_string_init (&decoded_response, _DBUS_INT_MAX))
679         {
680           _dbus_string_free (&mech);
681           _dbus_string_free (&base64_response);
682           return FALSE;
683         }
684       
685       if (!_dbus_string_copy_len (args, 0, i, &mech, 0))
686         goto failed;
687
688       if (!_dbus_string_copy (args, i, &base64_response, 0))
689         goto failed;
690
691       if (!_dbus_string_base64_decode (&base64_response, 0,
692                                        &decoded_response, 0))
693         goto failed;
694
695       auth->mech = find_mech (&mech);
696       if (auth->mech != NULL)
697         {
698           _dbus_verbose ("Trying mechanism %s with initial response of %d bytes\n",
699                          auth->mech->mechanism,
700                          _dbus_string_get_length (&decoded_response));
701           
702           if (!(* auth->mech->server_data_func) (auth,
703                                                  &decoded_response))
704             goto failed;
705         }
706       else
707         {
708           /* Unsupported mechanism */
709           if (!send_rejected (auth))
710             return FALSE;
711         }
712
713       _dbus_string_free (&mech);      
714       _dbus_string_free (&base64_response);
715       _dbus_string_free (&decoded_response);
716
717       return TRUE;
718       
719     failed:
720       auth->mech = NULL;
721       _dbus_string_free (&mech);
722       _dbus_string_free (&base64_response);
723       _dbus_string_free (&decoded_response);
724       return FALSE;
725     }
726 }
727
728 static dbus_bool_t
729 process_cancel (DBusAuth         *auth,
730                 const DBusString *command,
731                 const DBusString *args)
732 {
733   shutdown_mech (auth);
734   
735   return TRUE;
736 }
737
738 static dbus_bool_t
739 process_begin (DBusAuth         *auth,
740                const DBusString *command,
741                const DBusString *args)
742 {
743   if (auth->authenticated_pending_begin)
744     auth->authenticated = TRUE;
745   else
746     {
747       auth->need_disconnect = TRUE; /* client trying to send data before auth,
748                                      * kick it
749                                      */
750       shutdown_mech (auth);
751     }
752   
753   return TRUE;
754 }
755
756 static dbus_bool_t
757 process_data_server (DBusAuth         *auth,
758                      const DBusString *command,
759                      const DBusString *args)
760 {
761   if (auth->mech != NULL)
762     {
763       DBusString decoded;
764
765       if (!_dbus_string_init (&decoded, _DBUS_INT_MAX))
766         return FALSE;
767
768       if (!_dbus_string_base64_decode (args, 0, &decoded, 0))
769         {
770           _dbus_string_free (&decoded);
771           return FALSE;
772         }
773       
774       if (!(* auth->mech->server_data_func) (auth, &decoded))
775         {
776           _dbus_string_free (&decoded);
777           return FALSE;
778         }
779
780       _dbus_string_free (&decoded);
781     }
782   else
783     {
784       if (!_dbus_string_append (&auth->outgoing,
785                                 "ERROR \"Not currently in an auth conversation\"\r\n"))
786         return FALSE;
787     }
788   
789   return TRUE;
790 }
791
792 static dbus_bool_t
793 process_error_server (DBusAuth         *auth,
794                       const DBusString *command,
795                       const DBusString *args)
796 {
797   
798   return TRUE;
799 }
800
801 /* return FALSE if no memory, TRUE if all OK */
802 static dbus_bool_t
803 get_word (const DBusString *str,
804           int              *start,
805           DBusString       *word)
806 {
807   int i;
808
809   _dbus_string_skip_blank (str, *start, start);
810   _dbus_string_find_blank (str, *start, &i);
811   
812   if (i > *start)
813     {
814       if (!_dbus_string_copy_len (str, *start, i, word, 0))
815         return FALSE;
816       
817       *start = i;
818     }
819
820   return TRUE;
821 }
822
823 static dbus_bool_t
824 record_mechanisms (DBusAuth         *auth,
825                    const DBusString *command,
826                    const DBusString *args)
827 {
828   int next;
829   int len;
830
831   if (auth->already_got_mechanisms)
832     return TRUE;
833   
834   len = _dbus_string_get_length (args);
835   
836   next = 0;
837   while (next < len)
838     {
839       DBusString m;
840       const DBusAuthMechanismHandler *mech;
841       
842       if (!_dbus_string_init (&m, _DBUS_INT_MAX))
843         goto nomem;
844       
845       if (!get_word (args, &next, &m))
846         goto nomem;
847
848       mech = find_mech (&m);
849
850       if (mech != NULL)
851         {
852           /* FIXME right now we try mechanisms in the order
853            * the server lists them; should we do them in
854            * some more deterministic order?
855            *
856            * Probably in all_mechanisms order, our order of
857            * preference. Of course when the server is us,
858            * it lists things in that order anyhow.
859            */
860
861           _dbus_verbose ("Adding mechanism %s to list we will try\n",
862                          mech->mechanism);
863           
864           if (!_dbus_list_append (& DBUS_AUTH_CLIENT (auth)->mechs_to_try,
865                                   (void*) mech))
866             goto nomem;
867         }
868       else
869         {
870           const char *s;
871
872           _dbus_string_get_const_data (&m, &s);
873           _dbus_verbose ("Server offered mechanism \"%s\" that we don't know how to use\n",
874                          s);
875         }
876
877       _dbus_string_free (&m);
878     }
879   
880   auth->already_got_mechanisms = TRUE;
881   
882   return TRUE;
883
884  nomem:
885   _dbus_list_clear (& DBUS_AUTH_CLIENT (auth)->mechs_to_try);
886   
887   return FALSE;
888 }
889
890 static dbus_bool_t
891 client_try_next_mechanism (DBusAuth *auth)
892 {
893   const DBusAuthMechanismHandler *mech;
894   DBusString auth_command;
895
896   if (DBUS_AUTH_CLIENT (auth)->mechs_to_try == NULL)
897     return FALSE;
898
899   mech = DBUS_AUTH_CLIENT (auth)->mechs_to_try->data;
900
901   if (!_dbus_string_init (&auth_command, _DBUS_INT_MAX))
902     return FALSE;
903       
904   if (!_dbus_string_append (&auth_command,
905                             "AUTH "))
906     {
907       _dbus_string_free (&auth_command);
908       return FALSE;
909     }  
910   
911   if (!_dbus_string_append (&auth_command,
912                             mech->mechanism))
913     {
914       _dbus_string_free (&auth_command);
915       return FALSE;
916     }
917
918   if (mech->client_initial_response_func != NULL)
919     {
920       if (!_dbus_string_append (&auth_command, " "))
921         {
922           _dbus_string_free (&auth_command);
923           return FALSE;
924         }
925       
926       if (!(* mech->client_initial_response_func) (auth, &auth_command))
927         {
928           _dbus_string_free (&auth_command);
929           return FALSE;
930         }
931     }
932   
933   if (!_dbus_string_append (&auth_command,
934                             "\r\n"))
935     {
936       _dbus_string_free (&auth_command);
937       return FALSE;
938     }
939
940   if (!_dbus_string_copy (&auth_command, 0,
941                           &auth->outgoing,
942                           _dbus_string_get_length (&auth->outgoing)))
943     {
944       _dbus_string_free (&auth_command);
945       return FALSE;
946     }
947
948   auth->mech = mech;      
949   _dbus_list_pop_first (& DBUS_AUTH_CLIENT (auth)->mechs_to_try);
950
951   _dbus_verbose ("Trying mechanism %s\n",
952                  auth->mech->mechanism);
953
954   return TRUE;
955 }
956
957 static dbus_bool_t
958 process_rejected (DBusAuth         *auth,
959                   const DBusString *command,
960                   const DBusString *args)
961 {
962   shutdown_mech (auth);
963   
964   if (!auth->already_got_mechanisms)
965     {
966       if (!record_mechanisms (auth, command, args))
967         return FALSE;
968     }
969   
970   if (DBUS_AUTH_CLIENT (auth)->mechs_to_try != NULL)
971     {
972       client_try_next_mechanism (auth);
973     }
974   else
975     {
976       /* Give up */
977       auth->need_disconnect = TRUE;
978     }
979   
980   return TRUE;
981 }
982
983 static dbus_bool_t
984 process_ok (DBusAuth         *auth,
985             const DBusString *command,
986             const DBusString *args)
987 {
988   if (!_dbus_string_append (&auth->outgoing,
989                             "BEGIN\r\n"))
990     return FALSE;
991   
992   auth->authenticated_pending_output = TRUE;
993   
994   return TRUE;
995 }
996
997
998 static dbus_bool_t
999 process_data_client (DBusAuth         *auth,
1000                      const DBusString *command,
1001                      const DBusString *args)
1002 {
1003   if (auth->mech != NULL)
1004     {
1005       DBusString decoded;
1006
1007       if (!_dbus_string_init (&decoded, _DBUS_INT_MAX))
1008         return FALSE;
1009
1010       if (!_dbus_string_base64_decode (args, 0, &decoded, 0))
1011         {
1012           _dbus_string_free (&decoded);
1013           return FALSE;
1014         }
1015       
1016       if (!(* auth->mech->client_data_func) (auth, &decoded))
1017         {
1018           _dbus_string_free (&decoded);
1019           return FALSE;
1020         }
1021
1022       _dbus_string_free (&decoded);
1023     }
1024   else
1025     {
1026       if (!_dbus_string_append (&auth->outgoing,
1027                                 "ERROR \"Got DATA when not in an auth exchange\"\r\n"))
1028         return FALSE;
1029     }
1030   
1031   return TRUE;
1032 }
1033
1034 static dbus_bool_t
1035 process_error_client (DBusAuth         *auth,
1036                       const DBusString *command,
1037                       const DBusString *args)
1038 {
1039   return TRUE;
1040 }
1041
1042 static dbus_bool_t
1043 process_unknown (DBusAuth         *auth,
1044                  const DBusString *command,
1045                  const DBusString *args)
1046 {
1047   if (!_dbus_string_append (&auth->outgoing,
1048                             "ERROR \"Unknown command\"\r\n"))
1049     return FALSE;
1050
1051   return TRUE;
1052 }
1053
1054 /* returns whether to call it again right away */
1055 static dbus_bool_t
1056 process_command (DBusAuth *auth)
1057 {
1058   DBusString command;
1059   DBusString args;
1060   int eol;
1061   int i, j;
1062   dbus_bool_t retval;
1063
1064   _dbus_verbose ("  trying process_command()\n");
1065   
1066   retval = FALSE;
1067   
1068   eol = 0;
1069   if (!_dbus_string_find (&auth->incoming, 0, "\r\n", &eol))
1070     return FALSE;
1071   
1072   if (!_dbus_string_init (&command, _DBUS_INT_MAX))
1073     {
1074       auth->needed_memory = TRUE;
1075       return FALSE;
1076     }
1077
1078   if (!_dbus_string_init (&args, _DBUS_INT_MAX))
1079     {
1080       auth->needed_memory = TRUE;
1081       return FALSE;
1082     }
1083   
1084   if (eol > _DBUS_ONE_MEGABYTE)
1085     {
1086       /* This is a giant line, someone is trying to hose us. */
1087       if (!_dbus_string_append (&auth->outgoing, "ERROR \"Command too long\"\r\n"))
1088         goto out;
1089       else
1090         goto next_command;
1091     }
1092
1093   if (!_dbus_string_copy_len (&auth->incoming, 0, eol, &command, 0))
1094     goto out;
1095
1096   if (!_dbus_string_validate_ascii (&command, 0,
1097                                     _dbus_string_get_length (&command)))
1098     {
1099       _dbus_verbose ("Command contained non-ASCII chars or embedded nul\n");
1100       if (!_dbus_string_append (&auth->outgoing, "ERROR \"Command contained non-ASCII\"\r\n"))
1101         goto out;
1102       else
1103         goto next_command;
1104     }
1105   
1106   {
1107     const char *q;
1108     _dbus_string_get_const_data (&command, &q);
1109     _dbus_verbose ("got command \"%s\"\n", q);
1110   }
1111   
1112   _dbus_string_find_blank (&command, 0, &i);
1113   _dbus_string_skip_blank (&command, i, &j);
1114
1115   if (j > i)
1116     _dbus_string_delete (&command, i, j - i);
1117   
1118   if (!_dbus_string_move (&command, i, &args, 0))
1119     goto out;
1120   
1121   i = 0;
1122   while (auth->handlers[i].command != NULL)
1123     {
1124       if (_dbus_string_equal_c_str (&command,
1125                                     auth->handlers[i].command))
1126         {
1127           _dbus_verbose ("Processing auth command %s\n",
1128                          auth->handlers[i].command);
1129           
1130           if (!(* auth->handlers[i].func) (auth, &command, &args))
1131             goto out;
1132
1133           break;
1134         }
1135       ++i;
1136     }
1137
1138   if (auth->handlers[i].command == NULL)
1139     {
1140       if (!process_unknown (auth, &command, &args))
1141         goto out;
1142     }
1143
1144  next_command:
1145   
1146   /* We've succeeded in processing the whole command so drop it out
1147    * of the incoming buffer and return TRUE to try another command.
1148    */
1149
1150   _dbus_string_delete (&auth->incoming, 0, eol);
1151   
1152   /* kill the \r\n */
1153   _dbus_string_delete (&auth->incoming, 0, 2);
1154
1155   retval = TRUE;
1156   
1157  out:
1158   _dbus_string_free (&args);
1159   _dbus_string_free (&command);
1160
1161   if (!retval)
1162     auth->needed_memory = TRUE;
1163   else
1164     auth->needed_memory = FALSE;
1165   
1166   return retval;
1167 }
1168
1169
1170 /** @} */
1171
1172 /**
1173  * @addtogroup DBusAuth
1174  * @{
1175  */
1176
1177 /**
1178  * Creates a new auth conversation object for the server side.
1179  * See doc/dbus-sasl-profile.txt for full details on what
1180  * this object does.
1181  *
1182  * @returns the new object or #NULL if no memory
1183  */
1184 DBusAuth*
1185 _dbus_auth_server_new (void)
1186 {
1187   DBusAuth *auth;
1188   DBusAuthServer *server_auth;
1189
1190   auth = _dbus_auth_new (sizeof (DBusAuthServer));
1191   if (auth == NULL)
1192     return NULL;
1193
1194   auth->handlers = server_handlers;
1195
1196   server_auth = DBUS_AUTH_SERVER (auth);
1197
1198   /* perhaps this should be per-mechanism with a lower
1199    * max
1200    */
1201   server_auth->failures = 0;
1202   server_auth->max_failures = 6;
1203   
1204   return auth;
1205 }
1206
1207 /**
1208  * Creates a new auth conversation object for the client side.
1209  * See doc/dbus-sasl-profile.txt for full details on what
1210  * this object does.
1211  *
1212  * @returns the new object or #NULL if no memory
1213  */
1214 DBusAuth*
1215 _dbus_auth_client_new (void)
1216 {
1217   DBusAuth *auth;
1218
1219   auth = _dbus_auth_new (sizeof (DBusAuthClient));
1220   if (auth == NULL)
1221     return NULL;
1222
1223   auth->handlers = client_handlers;
1224
1225   /* Add a default mechanism to try */
1226   if (!_dbus_list_append (& DBUS_AUTH_CLIENT (auth)->mechs_to_try,
1227                           (void*) &all_mechanisms[0]))
1228     {
1229       _dbus_auth_unref (auth);
1230       return NULL;
1231     }
1232
1233   /* Now try the mechanism we just added */
1234   if (!client_try_next_mechanism (auth))
1235     {
1236       _dbus_auth_unref (auth);
1237       return NULL;
1238     }
1239   
1240   return auth;
1241 }
1242
1243 /**
1244  * Increments the refcount of an auth object.
1245  *
1246  * @param auth the auth conversation
1247  */
1248 void
1249 _dbus_auth_ref (DBusAuth *auth)
1250 {
1251   _dbus_assert (auth != NULL);
1252   
1253   auth->refcount += 1;
1254 }
1255
1256 /**
1257  * Decrements the refcount of an auth object.
1258  *
1259  * @param auth the auth conversation
1260  */
1261 void
1262 _dbus_auth_unref (DBusAuth *auth)
1263 {
1264   _dbus_assert (auth != NULL);
1265   _dbus_assert (auth->refcount > 0);
1266
1267   auth->refcount -= 1;
1268   if (auth->refcount == 0)
1269     {
1270       shutdown_mech (auth);
1271
1272       if (DBUS_AUTH_IS_CLIENT (auth))
1273         {
1274           _dbus_list_clear (& DBUS_AUTH_CLIENT (auth)->mechs_to_try);
1275         }
1276
1277       _dbus_string_free (&auth->identity);
1278       _dbus_string_free (&auth->incoming);
1279       _dbus_string_free (&auth->outgoing);
1280       dbus_free (auth);
1281     }
1282 }
1283
1284 /**
1285  * @param auth the auth conversation object
1286  * @returns #TRUE if we're in a final state
1287  */
1288 #define DBUS_AUTH_IN_END_STATE(auth) ((auth)->need_disconnect || (auth)->authenticated)
1289
1290 /**
1291  * Analyzes buffered input and moves the auth conversation forward,
1292  * returning the new state of the auth conversation.
1293  *
1294  * @param auth the auth conversation
1295  * @returns the new state
1296  */
1297 DBusAuthState
1298 _dbus_auth_do_work (DBusAuth *auth)
1299 {  
1300   if (DBUS_AUTH_IN_END_STATE (auth))
1301     return get_state (auth);
1302
1303   auth->needed_memory = FALSE;
1304
1305   /* Max amount we'll buffer up before deciding someone's on crack */
1306 #define MAX_BUFFER (16 * _DBUS_ONE_KILOBYTE)
1307
1308   do
1309     {
1310       if (_dbus_string_get_length (&auth->incoming) > MAX_BUFFER ||
1311           _dbus_string_get_length (&auth->outgoing) > MAX_BUFFER)
1312         {
1313           auth->need_disconnect = TRUE;
1314           _dbus_verbose ("Disconnecting due to excessive data buffered in auth phase\n");
1315           break;
1316         }
1317
1318       if (auth->mech == NULL &&
1319           auth->already_got_mechanisms &&
1320           DBUS_AUTH_CLIENT (auth)->mechs_to_try == NULL)
1321         {
1322           auth->need_disconnect = TRUE;
1323           _dbus_verbose ("Disconnecting because we are out of mechanisms to try using\n");
1324           break;
1325         }
1326     }
1327   while (process_command (auth));
1328   
1329   return get_state (auth);
1330 }
1331
1332 /**
1333  * Gets bytes that need to be sent to the peer we're conversing with.
1334  * After writing some bytes, _dbus_auth_bytes_sent() must be called
1335  * to notify the auth object that they were written.
1336  *
1337  * @param auth the auth conversation
1338  * @param str return location for a ref to the buffer to send
1339  * @returns #FALSE if nothing to send
1340  */
1341 dbus_bool_t
1342 _dbus_auth_get_bytes_to_send (DBusAuth          *auth,
1343                               const DBusString **str)
1344 {
1345   _dbus_assert (auth != NULL);
1346   _dbus_assert (str != NULL);
1347
1348   *str = NULL;
1349   
1350   if (DBUS_AUTH_IN_END_STATE (auth))
1351     return FALSE;
1352
1353   if (_dbus_string_get_length (&auth->outgoing) == 0)
1354     return FALSE;
1355
1356   *str = &auth->outgoing;
1357
1358   return TRUE;
1359 }
1360
1361 /**
1362  * Notifies the auth conversation object that
1363  * the given number of bytes of the outgoing buffer
1364  * have been written out.
1365  *
1366  * @param auth the auth conversation
1367  * @param bytes_sent number of bytes written out
1368  */
1369 void
1370 _dbus_auth_bytes_sent (DBusAuth *auth,
1371                        int       bytes_sent)
1372 {
1373   _dbus_string_delete (&auth->outgoing,
1374                        0, bytes_sent);
1375   
1376   if (auth->authenticated_pending_output &&
1377       _dbus_string_get_length (&auth->outgoing) == 0)
1378     auth->authenticated = TRUE;
1379 }
1380
1381 /**
1382  * Stores bytes received from the peer we're conversing with.
1383  *
1384  * @param auth the auth conversation
1385  * @param str the received bytes.
1386  * @returns #FALSE if not enough memory to store the bytes.
1387  */
1388 dbus_bool_t
1389 _dbus_auth_bytes_received (DBusAuth   *auth,
1390                            const DBusString *str)
1391 {
1392   _dbus_assert (auth != NULL);
1393   _dbus_assert (str != NULL);
1394   
1395   if (DBUS_AUTH_IN_END_STATE (auth))
1396     return FALSE;
1397
1398   auth->needed_memory = FALSE;
1399   
1400   if (!_dbus_string_copy (str, 0,
1401                           &auth->incoming,
1402                           _dbus_string_get_length (&auth->incoming)))
1403     {
1404       auth->needed_memory = TRUE;
1405       return FALSE;
1406     }
1407
1408   _dbus_auth_do_work (auth);
1409   
1410   return TRUE;
1411 }
1412
1413 /**
1414  * Returns leftover bytes that were not used as part of the auth
1415  * conversation.  These bytes will be part of the message stream
1416  * instead. This function may not be called until authentication has
1417  * succeeded.
1418  *
1419  * @param auth the auth conversation
1420  * @param str string to append the unused bytes to
1421  * @returns #FALSE if not enough memory to return the bytes
1422  */
1423 dbus_bool_t
1424 _dbus_auth_get_unused_bytes (DBusAuth   *auth,
1425                              DBusString *str)
1426 {
1427   if (!DBUS_AUTH_IN_END_STATE (auth))
1428     return FALSE;
1429   
1430   if (!_dbus_string_move (&auth->incoming,
1431                           0, str,
1432                           _dbus_string_get_length (str)))
1433     return FALSE;
1434
1435   return TRUE;
1436 }
1437
1438 /**
1439  * Called post-authentication, indicates whether we need to encode
1440  * the message stream with _dbus_auth_encode_data() prior to
1441  * sending it to the peer.
1442  *
1443  * @param auth the auth conversation
1444  * @returns #TRUE if we need to encode the stream
1445  */
1446 dbus_bool_t
1447 _dbus_auth_needs_encoding (DBusAuth *auth)
1448 {
1449   if (!auth->authenticated)
1450     return FALSE;
1451   
1452   if (auth->mech != NULL)
1453     {
1454       if (DBUS_AUTH_IS_CLIENT (auth))
1455         return auth->mech->client_encode_func != NULL;
1456       else
1457         return auth->mech->server_encode_func != NULL;
1458     }
1459   else
1460     return FALSE;
1461 }
1462
1463 /**
1464  * Called post-authentication, encodes a block of bytes for sending to
1465  * the peer. If no encoding was negotiated, just copies the bytes
1466  * (you can avoid this by checking _dbus_auth_needs_encoding()).
1467  *
1468  * @param auth the auth conversation
1469  * @param plaintext the plain text data
1470  * @param encoded initialized string to where encoded data is appended
1471  * @returns #TRUE if we had enough memory and successfully encoded
1472  */
1473 dbus_bool_t
1474 _dbus_auth_encode_data (DBusAuth         *auth,
1475                         const DBusString *plaintext,
1476                         DBusString       *encoded)
1477 {
1478   _dbus_assert (plaintext != encoded);
1479   
1480   if (!auth->authenticated)
1481     return FALSE;
1482   
1483   if (_dbus_auth_needs_encoding (auth))
1484     {
1485       if (DBUS_AUTH_IS_CLIENT (auth))
1486         return (* auth->mech->client_encode_func) (auth, plaintext, encoded);
1487       else
1488         return (* auth->mech->server_encode_func) (auth, plaintext, encoded);
1489     }
1490   else
1491     {
1492       return _dbus_string_copy (plaintext, 0, encoded,
1493                                 _dbus_string_get_length (encoded));
1494     }
1495 }
1496
1497 /**
1498  * Called post-authentication, indicates whether we need to decode
1499  * the message stream with _dbus_auth_decode_data() after
1500  * receiving it from the peer.
1501  *
1502  * @param auth the auth conversation
1503  * @returns #TRUE if we need to encode the stream
1504  */
1505 dbus_bool_t
1506 _dbus_auth_needs_decoding (DBusAuth *auth)
1507 {
1508   if (!auth->authenticated)
1509     return FALSE;
1510     
1511   if (auth->mech != NULL)
1512     {
1513       if (DBUS_AUTH_IS_CLIENT (auth))
1514         return auth->mech->client_decode_func != NULL;
1515       else
1516         return auth->mech->server_decode_func != NULL;
1517     }
1518   else
1519     return FALSE;
1520 }
1521
1522
1523 /**
1524  * Called post-authentication, decodes a block of bytes received from
1525  * the peer. If no encoding was negotiated, just copies the bytes (you
1526  * can avoid this by checking _dbus_auth_needs_decoding()).
1527  *
1528  * @todo We need to be able to distinguish "out of memory" error
1529  * from "the data is hosed" error.
1530  *
1531  * @param auth the auth conversation
1532  * @param encoded the encoded data
1533  * @param plaintext initialized string where decoded data is appended
1534  * @returns #TRUE if we had enough memory and successfully decoded
1535  */
1536 dbus_bool_t
1537 _dbus_auth_decode_data (DBusAuth         *auth,
1538                         const DBusString *encoded,
1539                         DBusString       *plaintext)
1540 {
1541   _dbus_assert (plaintext != encoded);
1542   
1543   if (!auth->authenticated)
1544     return FALSE;
1545   
1546   if (_dbus_auth_needs_decoding (auth))
1547     {
1548       if (DBUS_AUTH_IS_CLIENT (auth))
1549         return (* auth->mech->client_decode_func) (auth, encoded, plaintext);
1550       else
1551         return (* auth->mech->server_decode_func) (auth, encoded, plaintext);
1552     }
1553   else
1554     {
1555       return _dbus_string_copy (encoded, 0, plaintext,
1556                                 _dbus_string_get_length (plaintext));
1557     }
1558 }
1559
1560 /**
1561  * Sets credentials received via reliable means from the operating
1562  * system.
1563  *
1564  * @param auth the auth conversation
1565  * @param credentials the credentials received
1566  */
1567 void
1568 _dbus_auth_set_credentials (DBusAuth               *auth,
1569                             const DBusCredentials  *credentials)
1570 {
1571   auth->credentials = *credentials;
1572 }
1573
1574 /**
1575  * Gets the identity we authorized the client as.  Apps may have
1576  * different policies as to what identities they allow.
1577  *
1578  * @param auth the auth conversation
1579  * @param credentials the credentials we've authorized
1580  */
1581 void
1582 _dbus_auth_get_identity (DBusAuth               *auth,
1583                          DBusCredentials        *credentials)
1584 {
1585   if (auth->authenticated)
1586     {
1587       *credentials = auth->authorized_identity;
1588     }
1589   else
1590     {
1591       credentials->pid = -1;
1592       credentials->uid = -1;
1593       credentials->gid = -1;
1594     }
1595 }
1596
1597 /** @} */
1598
1599 #ifdef DBUS_BUILD_TESTS
1600 #include "dbus-test.h"
1601 #include "dbus-auth-script.h"
1602 #include <stdio.h>
1603
1604 static dbus_bool_t
1605 process_test_subdir (const DBusString          *test_base_dir,
1606                      const char                *subdir)
1607 {
1608   DBusString test_directory;
1609   DBusString filename;
1610   DBusDirIter *dir;
1611   dbus_bool_t retval;
1612   DBusResultCode result;
1613
1614   retval = FALSE;
1615   dir = NULL;
1616   
1617   if (!_dbus_string_init (&test_directory, _DBUS_INT_MAX))
1618     _dbus_assert_not_reached ("didn't allocate test_directory\n");
1619
1620   _dbus_string_init_const (&filename, subdir);
1621   
1622   if (!_dbus_string_copy (test_base_dir, 0,
1623                           &test_directory, 0))
1624     _dbus_assert_not_reached ("couldn't copy test_base_dir to test_directory");
1625   
1626   if (!_dbus_concat_dir_and_file (&test_directory, &filename))    
1627     _dbus_assert_not_reached ("couldn't allocate full path");
1628
1629   _dbus_string_free (&filename);
1630   if (!_dbus_string_init (&filename, _DBUS_INT_MAX))
1631     _dbus_assert_not_reached ("didn't allocate filename string\n");
1632   
1633   dir = _dbus_directory_open (&test_directory, &result);
1634   if (dir == NULL)
1635     {
1636       const char *s;
1637       _dbus_string_get_const_data (&test_directory, &s);
1638       _dbus_warn ("Could not open %s: %s\n", s,
1639                   dbus_result_to_string (result));
1640       goto failed;
1641     }
1642
1643   printf ("Testing:\n");
1644   
1645   result = DBUS_RESULT_SUCCESS;
1646  next:
1647   while (_dbus_directory_get_next_file (dir, &filename, &result))
1648     {
1649       DBusString full_path;
1650       
1651       if (!_dbus_string_init (&full_path, _DBUS_INT_MAX))
1652         _dbus_assert_not_reached ("couldn't init string");
1653
1654       if (!_dbus_string_copy (&test_directory, 0, &full_path, 0))
1655         _dbus_assert_not_reached ("couldn't copy dir to full_path");
1656
1657       if (!_dbus_concat_dir_and_file (&full_path, &filename))
1658         _dbus_assert_not_reached ("couldn't concat file to dir");
1659
1660       if (!_dbus_string_ends_with_c_str (&filename, ".auth-script"))
1661         {
1662           const char *filename_c;
1663           _dbus_string_get_const_data (&filename, &filename_c);
1664           _dbus_verbose ("Skipping non-.auth-script file %s\n",
1665                          filename_c);
1666           goto next;
1667         }
1668
1669       {
1670         const char *s;
1671         _dbus_string_get_const_data (&filename, &s);
1672         printf ("    %s\n", s);
1673       }
1674       
1675       if (!_dbus_auth_script_run (&full_path))
1676         {
1677           _dbus_string_free (&full_path);
1678           goto failed;
1679         }
1680       else
1681         _dbus_string_free (&full_path);
1682     }
1683
1684   if (result != DBUS_RESULT_SUCCESS)
1685     {
1686       const char *s;
1687       _dbus_string_get_const_data (&test_directory, &s);
1688       _dbus_warn ("Could not get next file in %s: %s\n",
1689                   s, dbus_result_to_string (result));
1690       goto failed;
1691     }
1692     
1693   retval = TRUE;
1694   
1695  failed:
1696
1697   if (dir)
1698     _dbus_directory_close (dir);
1699   _dbus_string_free (&test_directory);
1700   _dbus_string_free (&filename);
1701
1702   return retval;
1703 }
1704
1705 static dbus_bool_t
1706 process_test_dirs (const char *test_data_dir)
1707 {
1708   DBusString test_directory;
1709   dbus_bool_t retval;
1710
1711   retval = FALSE;
1712   
1713   _dbus_string_init_const (&test_directory, test_data_dir);
1714
1715   if (!process_test_subdir (&test_directory, "auth"))
1716     goto failed;
1717
1718   retval = TRUE;
1719   
1720  failed:
1721
1722   _dbus_string_free (&test_directory);
1723   
1724   return retval;
1725 }
1726
1727 dbus_bool_t
1728 _dbus_auth_test (const char *test_data_dir)
1729 {
1730   
1731   if (test_data_dir == NULL)
1732     return TRUE;
1733   
1734   if (!process_test_dirs (test_data_dir))
1735     return FALSE;
1736
1737   return TRUE;
1738 }
1739
1740 #endif /* DBUS_BUILD_TESTS */