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