2003-02-18 Joe Shaw <joe@assbarn.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  * @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   return TRUE;
622
623  nomem:
624   _dbus_string_free (&command);
625   return FALSE;
626 }
627
628 static dbus_bool_t
629 process_auth (DBusAuth         *auth,
630               const DBusString *command,
631               const DBusString *args)
632 {
633   if (auth->mech)
634     {
635       /* We are already using a mechanism, client is on crack */
636       if (!_dbus_string_append (&auth->outgoing,
637                                 "ERROR \"Sent AUTH while another AUTH in progress\"\r\n"))
638         return FALSE;
639
640       return TRUE;
641     }
642   else if (_dbus_string_get_length (args) == 0)
643     {
644       /* No args to the auth, send mechanisms */
645       if (!send_rejected (auth))
646         return FALSE;
647
648       return TRUE;
649     }
650   else
651     {
652       int i;
653       DBusString mech;
654       DBusString base64_response;
655       DBusString decoded_response;
656       
657       _dbus_string_find_blank (args, 0, &i);
658
659       if (!_dbus_string_init (&mech, _DBUS_INT_MAX))
660         return FALSE;
661
662       if (!_dbus_string_init (&base64_response, _DBUS_INT_MAX))
663         {
664           _dbus_string_free (&mech);
665           return FALSE;
666         }
667
668       if (!_dbus_string_init (&decoded_response, _DBUS_INT_MAX))
669         {
670           _dbus_string_free (&mech);
671           _dbus_string_free (&base64_response);
672           return FALSE;
673         }
674       
675       if (!_dbus_string_copy_len (args, 0, i, &mech, 0))
676         goto failed;
677
678       if (!_dbus_string_copy (args, i, &base64_response, 0))
679         goto failed;
680
681       if (!_dbus_string_base64_decode (&base64_response, 0,
682                                        &decoded_response, 0))
683         goto failed;
684
685       auth->mech = find_mech (&mech);
686       if (auth->mech != NULL)
687         {
688           _dbus_verbose ("Trying mechanism %s with initial response of %d bytes\n",
689                          auth->mech->mechanism,
690                          _dbus_string_get_length (&decoded_response));
691           
692           if (!(* auth->mech->server_data_func) (auth,
693                                                  &decoded_response))
694             goto failed;
695         }
696       else
697         {
698           /* Unsupported mechanism */
699           if (!send_rejected (auth))
700             return FALSE;
701         }
702
703       _dbus_string_free (&mech);      
704       _dbus_string_free (&base64_response);
705       _dbus_string_free (&decoded_response);
706
707       return TRUE;
708       
709     failed:
710       auth->mech = NULL;
711       _dbus_string_free (&mech);
712       _dbus_string_free (&base64_response);
713       _dbus_string_free (&decoded_response);
714       return FALSE;
715     }
716 }
717
718 static dbus_bool_t
719 process_cancel (DBusAuth         *auth,
720                 const DBusString *command,
721                 const DBusString *args)
722 {
723   shutdown_mech (auth);
724   
725   return TRUE;
726 }
727
728 static dbus_bool_t
729 process_begin (DBusAuth         *auth,
730                const DBusString *command,
731                const DBusString *args)
732 {
733   if (auth->authenticated_pending_begin)
734     auth->authenticated = TRUE;
735   else
736     {
737       auth->need_disconnect = TRUE; /* client trying to send data before auth,
738                                      * kick it
739                                      */
740       shutdown_mech (auth);
741     }
742   
743   return TRUE;
744 }
745
746 static dbus_bool_t
747 process_data_server (DBusAuth         *auth,
748                      const DBusString *command,
749                      const DBusString *args)
750 {
751   if (auth->mech != NULL)
752     {
753       DBusString decoded;
754
755       if (!_dbus_string_init (&decoded, _DBUS_INT_MAX))
756         return FALSE;
757
758       if (!_dbus_string_base64_decode (args, 0, &decoded, 0))
759         {
760           _dbus_string_free (&decoded);
761           return FALSE;
762         }
763       
764       if (!(* auth->mech->server_data_func) (auth, &decoded))
765         {
766           _dbus_string_free (&decoded);
767           return FALSE;
768         }
769
770       _dbus_string_free (&decoded);
771     }
772   else
773     {
774       if (!_dbus_string_append (&auth->outgoing,
775                                 "ERROR \"Not currently in an auth conversation\"\r\n"))
776         return FALSE;
777     }
778   
779   return TRUE;
780 }
781
782 static dbus_bool_t
783 process_error_server (DBusAuth         *auth,
784                       const DBusString *command,
785                       const DBusString *args)
786 {
787   
788   return TRUE;
789 }
790
791 /* return FALSE if no memory, TRUE if all OK */
792 static dbus_bool_t
793 get_word (const DBusString *str,
794           int              *start,
795           DBusString       *word)
796 {
797   int i;
798
799   _dbus_string_skip_blank (str, *start, start);
800   _dbus_string_find_blank (str, *start, &i);
801   
802   if (i > *start)
803     {
804       if (!_dbus_string_copy_len (str, *start, i - *start, word, 0))
805         return FALSE;
806       
807       *start = i;
808     }
809
810   return TRUE;
811 }
812
813 static dbus_bool_t
814 record_mechanisms (DBusAuth         *auth,
815                    const DBusString *command,
816                    const DBusString *args)
817 {
818   int next;
819   int len;
820
821   if (auth->already_got_mechanisms)
822     return TRUE;
823   
824   len = _dbus_string_get_length (args);
825   
826   next = 0;
827   while (next < len)
828     {
829       DBusString m;
830       const DBusAuthMechanismHandler *mech;
831       
832       if (!_dbus_string_init (&m, _DBUS_INT_MAX))
833         goto nomem;
834       
835       if (!get_word (args, &next, &m))
836         goto nomem;
837
838       mech = find_mech (&m);
839
840       if (mech != NULL)
841         {
842           /* FIXME right now we try mechanisms in the order
843            * the server lists them; should we do them in
844            * some more deterministic order?
845            *
846            * Probably in all_mechanisms order, our order of
847            * preference. Of course when the server is us,
848            * it lists things in that order anyhow.
849            */
850
851           _dbus_verbose ("Adding mechanism %s to list we will try\n",
852                          mech->mechanism);
853           
854           if (!_dbus_list_append (& DBUS_AUTH_CLIENT (auth)->mechs_to_try,
855                                   (void*) mech))
856             goto nomem;
857         }
858       else
859         {
860           const char *s;
861
862           _dbus_string_get_const_data (&m, &s);
863           _dbus_verbose ("Server offered mechanism \"%s\" that we don't know how to use\n",
864                          s);
865         }
866
867       _dbus_string_free (&m);
868     }
869   
870   auth->already_got_mechanisms = TRUE;
871   
872   return TRUE;
873
874  nomem:
875   _dbus_list_clear (& DBUS_AUTH_CLIENT (auth)->mechs_to_try);
876   
877   return FALSE;
878 }
879
880 static dbus_bool_t
881 client_try_next_mechanism (DBusAuth *auth)
882 {
883   const DBusAuthMechanismHandler *mech;
884   DBusString auth_command;
885
886   if (DBUS_AUTH_CLIENT (auth)->mechs_to_try == NULL)
887     return FALSE;
888
889   mech = DBUS_AUTH_CLIENT (auth)->mechs_to_try->data;
890
891   if (!_dbus_string_init (&auth_command, _DBUS_INT_MAX))
892     return FALSE;
893       
894   if (!_dbus_string_append (&auth_command,
895                             "AUTH "))
896     {
897       _dbus_string_free (&auth_command);
898       return FALSE;
899     }  
900   
901   if (!_dbus_string_append (&auth_command,
902                             mech->mechanism))
903     {
904       _dbus_string_free (&auth_command);
905       return FALSE;
906     }
907
908   if (mech->client_initial_response_func != NULL)
909     {
910       if (!_dbus_string_append (&auth_command, " "))
911         {
912           _dbus_string_free (&auth_command);
913           return FALSE;
914         }
915       
916       if (!(* mech->client_initial_response_func) (auth, &auth_command))
917         {
918           _dbus_string_free (&auth_command);
919           return FALSE;
920         }
921     }
922   
923   if (!_dbus_string_append (&auth_command,
924                             "\r\n"))
925     {
926       _dbus_string_free (&auth_command);
927       return FALSE;
928     }
929
930   if (!_dbus_string_copy (&auth_command, 0,
931                           &auth->outgoing,
932                           _dbus_string_get_length (&auth->outgoing)))
933     {
934       _dbus_string_free (&auth_command);
935       return FALSE;
936     }
937
938   auth->mech = mech;      
939   _dbus_list_pop_first (& DBUS_AUTH_CLIENT (auth)->mechs_to_try);
940
941   _dbus_verbose ("Trying mechanism %s\n",
942                  auth->mech->mechanism);
943
944   _dbus_string_free (&auth_command);
945   
946   return TRUE;
947 }
948
949 static dbus_bool_t
950 process_rejected (DBusAuth         *auth,
951                   const DBusString *command,
952                   const DBusString *args)
953 {
954   shutdown_mech (auth);
955   
956   if (!auth->already_got_mechanisms)
957     {
958       if (!record_mechanisms (auth, command, args))
959         return FALSE;
960     }
961   
962   if (DBUS_AUTH_CLIENT (auth)->mechs_to_try != NULL)
963     {
964       client_try_next_mechanism (auth);
965     }
966   else
967     {
968       /* Give up */
969       auth->need_disconnect = TRUE;
970     }
971   
972   return TRUE;
973 }
974
975 static dbus_bool_t
976 process_ok (DBusAuth         *auth,
977             const DBusString *command,
978             const DBusString *args)
979 {
980   if (!_dbus_string_append (&auth->outgoing,
981                             "BEGIN\r\n"))
982     return FALSE;
983   
984   auth->authenticated_pending_output = TRUE;
985   
986   return TRUE;
987 }
988
989
990 static dbus_bool_t
991 process_data_client (DBusAuth         *auth,
992                      const DBusString *command,
993                      const DBusString *args)
994 {
995   if (auth->mech != NULL)
996     {
997       DBusString decoded;
998
999       if (!_dbus_string_init (&decoded, _DBUS_INT_MAX))
1000         return FALSE;
1001
1002       if (!_dbus_string_base64_decode (args, 0, &decoded, 0))
1003         {
1004           _dbus_string_free (&decoded);
1005           return FALSE;
1006         }
1007       
1008       if (!(* auth->mech->client_data_func) (auth, &decoded))
1009         {
1010           _dbus_string_free (&decoded);
1011           return FALSE;
1012         }
1013
1014       _dbus_string_free (&decoded);
1015     }
1016   else
1017     {
1018       if (!_dbus_string_append (&auth->outgoing,
1019                                 "ERROR \"Got DATA when not in an auth exchange\"\r\n"))
1020         return FALSE;
1021     }
1022   
1023   return TRUE;
1024 }
1025
1026 static dbus_bool_t
1027 process_error_client (DBusAuth         *auth,
1028                       const DBusString *command,
1029                       const DBusString *args)
1030 {
1031   return TRUE;
1032 }
1033
1034 static dbus_bool_t
1035 process_unknown (DBusAuth         *auth,
1036                  const DBusString *command,
1037                  const DBusString *args)
1038 {
1039   if (!_dbus_string_append (&auth->outgoing,
1040                             "ERROR \"Unknown command\"\r\n"))
1041     return FALSE;
1042
1043   return TRUE;
1044 }
1045
1046 /* returns whether to call it again right away */
1047 static dbus_bool_t
1048 process_command (DBusAuth *auth)
1049 {
1050   DBusString command;
1051   DBusString args;
1052   int eol;
1053   int i, j;
1054   dbus_bool_t retval;
1055
1056   /* _dbus_verbose ("  trying process_command()\n"); */
1057   
1058   retval = FALSE;
1059   
1060   eol = 0;
1061   if (!_dbus_string_find (&auth->incoming, 0, "\r\n", &eol))
1062     return FALSE;
1063   
1064   if (!_dbus_string_init (&command, _DBUS_INT_MAX))
1065     {
1066       auth->needed_memory = TRUE;
1067       return FALSE;
1068     }
1069
1070   if (!_dbus_string_init (&args, _DBUS_INT_MAX))
1071     {
1072       auth->needed_memory = TRUE;
1073       return FALSE;
1074     }
1075   
1076   if (eol > _DBUS_ONE_MEGABYTE)
1077     {
1078       /* This is a giant line, someone is trying to hose us. */
1079       if (!_dbus_string_append (&auth->outgoing, "ERROR \"Command too long\"\r\n"))
1080         goto out;
1081       else
1082         goto next_command;
1083     }
1084
1085   if (!_dbus_string_copy_len (&auth->incoming, 0, eol, &command, 0))
1086     goto out;
1087
1088   if (!_dbus_string_validate_ascii (&command, 0,
1089                                     _dbus_string_get_length (&command)))
1090     {
1091       _dbus_verbose ("Command contained non-ASCII chars or embedded nul\n");
1092       if (!_dbus_string_append (&auth->outgoing, "ERROR \"Command contained non-ASCII\"\r\n"))
1093         goto out;
1094       else
1095         goto next_command;
1096     }
1097   
1098   {
1099     const char *q;
1100     _dbus_string_get_const_data (&command, &q);
1101     _dbus_verbose ("got command \"%s\"\n", q);
1102   }
1103   
1104   _dbus_string_find_blank (&command, 0, &i);
1105   _dbus_string_skip_blank (&command, i, &j);
1106
1107   if (j > i)
1108     _dbus_string_delete (&command, i, j - i);
1109   
1110   if (!_dbus_string_move (&command, i, &args, 0))
1111     goto out;
1112   
1113   i = 0;
1114   while (auth->handlers[i].command != NULL)
1115     {
1116       if (_dbus_string_equal_c_str (&command,
1117                                     auth->handlers[i].command))
1118         {
1119           _dbus_verbose ("Processing auth command %s\n",
1120                          auth->handlers[i].command);
1121           
1122           if (!(* auth->handlers[i].func) (auth, &command, &args))
1123             goto out;
1124
1125           break;
1126         }
1127       ++i;
1128     }
1129
1130   if (auth->handlers[i].command == NULL)
1131     {
1132       if (!process_unknown (auth, &command, &args))
1133         goto out;
1134     }
1135
1136  next_command:
1137   
1138   /* We've succeeded in processing the whole command so drop it out
1139    * of the incoming buffer and return TRUE to try another command.
1140    */
1141
1142   _dbus_string_delete (&auth->incoming, 0, eol);
1143   
1144   /* kill the \r\n */
1145   _dbus_string_delete (&auth->incoming, 0, 2);
1146
1147   retval = TRUE;
1148   
1149  out:
1150   _dbus_string_free (&args);
1151   _dbus_string_free (&command);
1152
1153   if (!retval)
1154     auth->needed_memory = TRUE;
1155   else
1156     auth->needed_memory = FALSE;
1157   
1158   return retval;
1159 }
1160
1161
1162 /** @} */
1163
1164 /**
1165  * @addtogroup DBusAuth
1166  * @{
1167  */
1168
1169 /**
1170  * Creates a new auth conversation object for the server side.
1171  * See doc/dbus-sasl-profile.txt for full details on what
1172  * this object does.
1173  *
1174  * @returns the new object or #NULL if no memory
1175  */
1176 DBusAuth*
1177 _dbus_auth_server_new (void)
1178 {
1179   DBusAuth *auth;
1180   DBusAuthServer *server_auth;
1181
1182   auth = _dbus_auth_new (sizeof (DBusAuthServer));
1183   if (auth == NULL)
1184     return NULL;
1185
1186   auth->handlers = server_handlers;
1187
1188   server_auth = DBUS_AUTH_SERVER (auth);
1189
1190   /* perhaps this should be per-mechanism with a lower
1191    * max
1192    */
1193   server_auth->failures = 0;
1194   server_auth->max_failures = 6;
1195   
1196   return auth;
1197 }
1198
1199 /**
1200  * Creates a new auth conversation object for the client side.
1201  * See doc/dbus-sasl-profile.txt for full details on what
1202  * this object does.
1203  *
1204  * @returns the new object or #NULL if no memory
1205  */
1206 DBusAuth*
1207 _dbus_auth_client_new (void)
1208 {
1209   DBusAuth *auth;
1210
1211   auth = _dbus_auth_new (sizeof (DBusAuthClient));
1212   if (auth == NULL)
1213     return NULL;
1214
1215   auth->handlers = client_handlers;
1216
1217   /* Add a default mechanism to try */
1218   if (!_dbus_list_append (& DBUS_AUTH_CLIENT (auth)->mechs_to_try,
1219                           (void*) &all_mechanisms[0]))
1220     {
1221       _dbus_auth_unref (auth);
1222       return NULL;
1223     }
1224
1225   /* Now try the mechanism we just added */
1226   if (!client_try_next_mechanism (auth))
1227     {
1228       _dbus_auth_unref (auth);
1229       return NULL;
1230     }
1231   
1232   return auth;
1233 }
1234
1235 /**
1236  * Increments the refcount of an auth object.
1237  *
1238  * @param auth the auth conversation
1239  */
1240 void
1241 _dbus_auth_ref (DBusAuth *auth)
1242 {
1243   _dbus_assert (auth != NULL);
1244   
1245   auth->refcount += 1;
1246 }
1247
1248 /**
1249  * Decrements the refcount of an auth object.
1250  *
1251  * @param auth the auth conversation
1252  */
1253 void
1254 _dbus_auth_unref (DBusAuth *auth)
1255 {
1256   _dbus_assert (auth != NULL);
1257   _dbus_assert (auth->refcount > 0);
1258
1259   auth->refcount -= 1;
1260   if (auth->refcount == 0)
1261     {
1262       shutdown_mech (auth);
1263
1264       if (DBUS_AUTH_IS_CLIENT (auth))
1265         {
1266           _dbus_list_clear (& DBUS_AUTH_CLIENT (auth)->mechs_to_try);
1267         }
1268
1269       _dbus_string_free (&auth->identity);
1270       _dbus_string_free (&auth->incoming);
1271       _dbus_string_free (&auth->outgoing);
1272       dbus_free (auth);
1273     }
1274 }
1275
1276 /**
1277  * @param auth the auth conversation object
1278  * @returns #TRUE if we're in a final state
1279  */
1280 #define DBUS_AUTH_IN_END_STATE(auth) ((auth)->need_disconnect || (auth)->authenticated)
1281
1282 /**
1283  * Analyzes buffered input and moves the auth conversation forward,
1284  * returning the new state of the auth conversation.
1285  *
1286  * @param auth the auth conversation
1287  * @returns the new state
1288  */
1289 DBusAuthState
1290 _dbus_auth_do_work (DBusAuth *auth)
1291 {
1292   auth->needed_memory = FALSE;
1293
1294   /* Max amount we'll buffer up before deciding someone's on crack */
1295 #define MAX_BUFFER (16 * _DBUS_ONE_KILOBYTE)
1296
1297   do
1298     {
1299       if (DBUS_AUTH_IN_END_STATE (auth))
1300         break;
1301       
1302       if (_dbus_string_get_length (&auth->incoming) > MAX_BUFFER ||
1303           _dbus_string_get_length (&auth->outgoing) > MAX_BUFFER)
1304         {
1305           auth->need_disconnect = TRUE;
1306           _dbus_verbose ("Disconnecting due to excessive data buffered in auth phase\n");
1307           break;
1308         }
1309
1310       if (auth->mech == NULL &&
1311           auth->already_got_mechanisms &&
1312           DBUS_AUTH_CLIENT (auth)->mechs_to_try == NULL)
1313         {
1314           auth->need_disconnect = TRUE;
1315           _dbus_verbose ("Disconnecting because we are out of mechanisms to try using\n");
1316           break;
1317         }
1318     }
1319   while (process_command (auth));
1320
1321   if (DBUS_AUTH_IS_SERVER (auth) &&
1322       DBUS_AUTH_SERVER (auth)->failures >=
1323       DBUS_AUTH_SERVER (auth)->max_failures)
1324     auth->need_disconnect = TRUE;
1325
1326   if (auth->need_disconnect)
1327     return DBUS_AUTH_STATE_NEED_DISCONNECT;
1328   else if (auth->authenticated)
1329     {
1330       if (_dbus_string_get_length (&auth->incoming) > 0)
1331         return DBUS_AUTH_STATE_AUTHENTICATED_WITH_UNUSED_BYTES;
1332       else
1333         return DBUS_AUTH_STATE_AUTHENTICATED;
1334     }
1335   else if (auth->needed_memory)
1336     return DBUS_AUTH_STATE_WAITING_FOR_MEMORY;
1337   else if (_dbus_string_get_length (&auth->outgoing) > 0)
1338     return DBUS_AUTH_STATE_HAVE_BYTES_TO_SEND;
1339   else
1340     return DBUS_AUTH_STATE_WAITING_FOR_INPUT;
1341 }
1342
1343 /**
1344  * Gets bytes that need to be sent to the peer we're conversing with.
1345  * After writing some bytes, _dbus_auth_bytes_sent() must be called
1346  * to notify the auth object that they were written.
1347  *
1348  * @param auth the auth conversation
1349  * @param str return location for a ref to the buffer to send
1350  * @returns #FALSE if nothing to send
1351  */
1352 dbus_bool_t
1353 _dbus_auth_get_bytes_to_send (DBusAuth          *auth,
1354                               const DBusString **str)
1355 {
1356   _dbus_assert (auth != NULL);
1357   _dbus_assert (str != NULL);
1358
1359   *str = NULL;
1360   
1361   if (DBUS_AUTH_IN_END_STATE (auth))
1362     return FALSE;
1363
1364   if (_dbus_string_get_length (&auth->outgoing) == 0)
1365     return FALSE;
1366
1367   *str = &auth->outgoing;
1368
1369   return TRUE;
1370 }
1371
1372 /**
1373  * Notifies the auth conversation object that
1374  * the given number of bytes of the outgoing buffer
1375  * have been written out.
1376  *
1377  * @param auth the auth conversation
1378  * @param bytes_sent number of bytes written out
1379  */
1380 void
1381 _dbus_auth_bytes_sent (DBusAuth *auth,
1382                        int       bytes_sent)
1383 {
1384   _dbus_string_delete (&auth->outgoing,
1385                        0, bytes_sent);
1386   
1387   if (auth->authenticated_pending_output &&
1388       _dbus_string_get_length (&auth->outgoing) == 0)
1389     auth->authenticated = TRUE;
1390 }
1391
1392 /**
1393  * Stores bytes received from the peer we're conversing with.
1394  *
1395  * @param auth the auth conversation
1396  * @param str the received bytes.
1397  * @returns #FALSE if not enough memory to store the bytes or we were already authenticated.
1398  */
1399 dbus_bool_t
1400 _dbus_auth_bytes_received (DBusAuth   *auth,
1401                            const DBusString *str)
1402 {
1403   _dbus_assert (auth != NULL);
1404   _dbus_assert (str != NULL);
1405   
1406   if (DBUS_AUTH_IN_END_STATE (auth))
1407     return FALSE;
1408
1409   auth->needed_memory = FALSE;
1410   
1411   if (!_dbus_string_copy (str, 0,
1412                           &auth->incoming,
1413                           _dbus_string_get_length (&auth->incoming)))
1414     {
1415       auth->needed_memory = TRUE;
1416       return FALSE;
1417     }
1418
1419   _dbus_auth_do_work (auth);
1420   
1421   return TRUE;
1422 }
1423
1424 /**
1425  * Returns leftover bytes that were not used as part of the auth
1426  * conversation.  These bytes will be part of the message stream
1427  * instead. This function may not be called until authentication has
1428  * succeeded.
1429  *
1430  * @param auth the auth conversation
1431  * @param str string to append the unused bytes to
1432  * @returns #FALSE if not enough memory to return the bytes
1433  */
1434 dbus_bool_t
1435 _dbus_auth_get_unused_bytes (DBusAuth   *auth,
1436                              DBusString *str)
1437 {
1438   if (!DBUS_AUTH_IN_END_STATE (auth))
1439     return FALSE;
1440   
1441   if (!_dbus_string_move (&auth->incoming,
1442                           0, str,
1443                           _dbus_string_get_length (str)))
1444     return FALSE;
1445
1446   return TRUE;
1447 }
1448
1449 /**
1450  * Called post-authentication, indicates whether we need to encode
1451  * the message stream with _dbus_auth_encode_data() prior to
1452  * sending it to the peer.
1453  *
1454  * @param auth the auth conversation
1455  * @returns #TRUE if we need to encode the stream
1456  */
1457 dbus_bool_t
1458 _dbus_auth_needs_encoding (DBusAuth *auth)
1459 {
1460   if (!auth->authenticated)
1461     return FALSE;
1462   
1463   if (auth->mech != NULL)
1464     {
1465       if (DBUS_AUTH_IS_CLIENT (auth))
1466         return auth->mech->client_encode_func != NULL;
1467       else
1468         return auth->mech->server_encode_func != NULL;
1469     }
1470   else
1471     return FALSE;
1472 }
1473
1474 /**
1475  * Called post-authentication, encodes a block of bytes for sending to
1476  * the peer. If no encoding was negotiated, just copies the bytes
1477  * (you can avoid this by checking _dbus_auth_needs_encoding()).
1478  *
1479  * @param auth the auth conversation
1480  * @param plaintext the plain text data
1481  * @param encoded initialized string to where encoded data is appended
1482  * @returns #TRUE if we had enough memory and successfully encoded
1483  */
1484 dbus_bool_t
1485 _dbus_auth_encode_data (DBusAuth         *auth,
1486                         const DBusString *plaintext,
1487                         DBusString       *encoded)
1488 {
1489   _dbus_assert (plaintext != encoded);
1490   
1491   if (!auth->authenticated)
1492     return FALSE;
1493   
1494   if (_dbus_auth_needs_encoding (auth))
1495     {
1496       if (DBUS_AUTH_IS_CLIENT (auth))
1497         return (* auth->mech->client_encode_func) (auth, plaintext, encoded);
1498       else
1499         return (* auth->mech->server_encode_func) (auth, plaintext, encoded);
1500     }
1501   else
1502     {
1503       return _dbus_string_copy (plaintext, 0, encoded,
1504                                 _dbus_string_get_length (encoded));
1505     }
1506 }
1507
1508 /**
1509  * Called post-authentication, indicates whether we need to decode
1510  * the message stream with _dbus_auth_decode_data() after
1511  * receiving it from the peer.
1512  *
1513  * @param auth the auth conversation
1514  * @returns #TRUE if we need to encode the stream
1515  */
1516 dbus_bool_t
1517 _dbus_auth_needs_decoding (DBusAuth *auth)
1518 {
1519   if (!auth->authenticated)
1520     return FALSE;
1521     
1522   if (auth->mech != NULL)
1523     {
1524       if (DBUS_AUTH_IS_CLIENT (auth))
1525         return auth->mech->client_decode_func != NULL;
1526       else
1527         return auth->mech->server_decode_func != NULL;
1528     }
1529   else
1530     return FALSE;
1531 }
1532
1533
1534 /**
1535  * Called post-authentication, decodes a block of bytes received from
1536  * the peer. If no encoding was negotiated, just copies the bytes (you
1537  * can avoid this by checking _dbus_auth_needs_decoding()).
1538  *
1539  * @todo We need to be able to distinguish "out of memory" error
1540  * from "the data is hosed" error.
1541  *
1542  * @param auth the auth conversation
1543  * @param encoded the encoded data
1544  * @param plaintext initialized string where decoded data is appended
1545  * @returns #TRUE if we had enough memory and successfully decoded
1546  */
1547 dbus_bool_t
1548 _dbus_auth_decode_data (DBusAuth         *auth,
1549                         const DBusString *encoded,
1550                         DBusString       *plaintext)
1551 {
1552   _dbus_assert (plaintext != encoded);
1553   
1554   if (!auth->authenticated)
1555     return FALSE;
1556   
1557   if (_dbus_auth_needs_decoding (auth))
1558     {
1559       if (DBUS_AUTH_IS_CLIENT (auth))
1560         return (* auth->mech->client_decode_func) (auth, encoded, plaintext);
1561       else
1562         return (* auth->mech->server_decode_func) (auth, encoded, plaintext);
1563     }
1564   else
1565     {
1566       return _dbus_string_copy (encoded, 0, plaintext,
1567                                 _dbus_string_get_length (plaintext));
1568     }
1569 }
1570
1571 /**
1572  * Sets credentials received via reliable means from the operating
1573  * system.
1574  *
1575  * @param auth the auth conversation
1576  * @param credentials the credentials received
1577  */
1578 void
1579 _dbus_auth_set_credentials (DBusAuth               *auth,
1580                             const DBusCredentials  *credentials)
1581 {
1582   auth->credentials = *credentials;
1583 }
1584
1585 /**
1586  * Gets the identity we authorized the client as.  Apps may have
1587  * different policies as to what identities they allow.
1588  *
1589  * @param auth the auth conversation
1590  * @param credentials the credentials we've authorized
1591  */
1592 void
1593 _dbus_auth_get_identity (DBusAuth               *auth,
1594                          DBusCredentials        *credentials)
1595 {
1596   if (auth->authenticated)
1597     {
1598       *credentials = auth->authorized_identity;
1599     }
1600   else
1601     {
1602       credentials->pid = -1;
1603       credentials->uid = -1;
1604       credentials->gid = -1;
1605     }
1606 }
1607
1608 /** @} */
1609
1610 #ifdef DBUS_BUILD_TESTS
1611 #include "dbus-test.h"
1612 #include "dbus-auth-script.h"
1613 #include <stdio.h>
1614
1615 static dbus_bool_t
1616 process_test_subdir (const DBusString          *test_base_dir,
1617                      const char                *subdir)
1618 {
1619   DBusString test_directory;
1620   DBusString filename;
1621   DBusDirIter *dir;
1622   dbus_bool_t retval;
1623   DBusResultCode result;
1624
1625   retval = FALSE;
1626   dir = NULL;
1627   
1628   if (!_dbus_string_init (&test_directory, _DBUS_INT_MAX))
1629     _dbus_assert_not_reached ("didn't allocate test_directory\n");
1630
1631   _dbus_string_init_const (&filename, subdir);
1632   
1633   if (!_dbus_string_copy (test_base_dir, 0,
1634                           &test_directory, 0))
1635     _dbus_assert_not_reached ("couldn't copy test_base_dir to test_directory");
1636   
1637   if (!_dbus_concat_dir_and_file (&test_directory, &filename))    
1638     _dbus_assert_not_reached ("couldn't allocate full path");
1639
1640   _dbus_string_free (&filename);
1641   if (!_dbus_string_init (&filename, _DBUS_INT_MAX))
1642     _dbus_assert_not_reached ("didn't allocate filename string\n");
1643   
1644   dir = _dbus_directory_open (&test_directory, &result);
1645   if (dir == NULL)
1646     {
1647       const char *s;
1648       _dbus_string_get_const_data (&test_directory, &s);
1649       _dbus_warn ("Could not open %s: %s\n", s,
1650                   dbus_result_to_string (result));
1651       goto failed;
1652     }
1653
1654   printf ("Testing:\n");
1655   
1656   result = DBUS_RESULT_SUCCESS;
1657  next:
1658   while (_dbus_directory_get_next_file (dir, &filename, &result))
1659     {
1660       DBusString full_path;
1661       
1662       if (!_dbus_string_init (&full_path, _DBUS_INT_MAX))
1663         _dbus_assert_not_reached ("couldn't init string");
1664
1665       if (!_dbus_string_copy (&test_directory, 0, &full_path, 0))
1666         _dbus_assert_not_reached ("couldn't copy dir to full_path");
1667
1668       if (!_dbus_concat_dir_and_file (&full_path, &filename))
1669         _dbus_assert_not_reached ("couldn't concat file to dir");
1670
1671       if (!_dbus_string_ends_with_c_str (&filename, ".auth-script"))
1672         {
1673           const char *filename_c;
1674           _dbus_string_get_const_data (&filename, &filename_c);
1675           _dbus_verbose ("Skipping non-.auth-script file %s\n",
1676                          filename_c);
1677           goto next;
1678         }
1679
1680       {
1681         const char *s;
1682         _dbus_string_get_const_data (&filename, &s);
1683         printf ("    %s\n", s);
1684       }
1685       
1686       if (!_dbus_auth_script_run (&full_path))
1687         {
1688           _dbus_string_free (&full_path);
1689           goto failed;
1690         }
1691       else
1692         _dbus_string_free (&full_path);
1693     }
1694
1695   if (result != DBUS_RESULT_SUCCESS)
1696     {
1697       const char *s;
1698       _dbus_string_get_const_data (&test_directory, &s);
1699       _dbus_warn ("Could not get next file in %s: %s\n",
1700                   s, dbus_result_to_string (result));
1701       goto failed;
1702     }
1703     
1704   retval = TRUE;
1705   
1706  failed:
1707
1708   if (dir)
1709     _dbus_directory_close (dir);
1710   _dbus_string_free (&test_directory);
1711   _dbus_string_free (&filename);
1712
1713   return retval;
1714 }
1715
1716 static dbus_bool_t
1717 process_test_dirs (const char *test_data_dir)
1718 {
1719   DBusString test_directory;
1720   dbus_bool_t retval;
1721
1722   retval = FALSE;
1723   
1724   _dbus_string_init_const (&test_directory, test_data_dir);
1725
1726   if (!process_test_subdir (&test_directory, "auth"))
1727     goto failed;
1728
1729   retval = TRUE;
1730   
1731  failed:
1732
1733   _dbus_string_free (&test_directory);
1734   
1735   return retval;
1736 }
1737
1738 dbus_bool_t
1739 _dbus_auth_test (const char *test_data_dir)
1740 {
1741   
1742   if (test_data_dir == NULL)
1743     return TRUE;
1744   
1745   if (!process_test_dirs (test_data_dir))
1746     return FALSE;
1747
1748   return TRUE;
1749 }
1750
1751 #endif /* DBUS_BUILD_TESTS */