1/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2/* dbus-auth-script.c Test DBusAuth using a special script file (internal to D-Bus implementation)
3 *
4 * Copyright (C) 2003 Red Hat, Inc.
5 *
6 * Licensed under the Academic Free License version 2.1
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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
21 *
22 */
23#include <config.h>
24
25#ifdef DBUS_BUILD_TESTS
26
27#include "dbus-auth-script.h"
28#include "dbus-auth.h"
29#include "dbus-string.h"
30#include "dbus-hash.h"
31#include "dbus-credentials.h"
32#include "dbus-internals.h"
33
34/**
35 * @defgroup DBusAuthScript code for running unit test scripts for DBusAuth
36 * @ingroup  DBusInternals
37 * @brief DBusAuth unit test scripting
38 *
39 * The code in here is used for unit testing, it loads
40 * up a script that tests DBusAuth.
41 *
42 * @{
43 */
44
45/* this is slightly different from the other append_quoted_string
46 * in dbus-message-builder.c
47 */
48static dbus_bool_t
49append_quoted_string (DBusString       *dest,
50                      const DBusString *quoted)
51{
52  dbus_bool_t in_quotes = FALSE;
53  dbus_bool_t in_backslash = FALSE;
54  int i;
55
56  i = 0;
57  while (i < _dbus_string_get_length (quoted))
58    {
59      unsigned char b;
60
61      b = _dbus_string_get_byte (quoted, i);
62
63      if (in_backslash)
64        {
65          unsigned char a;
66
67          if (b == 'r')
68            a = '\r';
69          else if (b == 'n')
70            a = '\n';
71          else if (b == '\\')
72            a = '\\';
73          else
74            {
75              _dbus_warn ("bad backslashed byte %c\n", b);
76              return FALSE;
77            }
78
79          if (!_dbus_string_append_byte (dest, a))
80            return FALSE;
81
82          in_backslash = FALSE;
83        }
84      else if (b == '\\')
85        {
86          in_backslash = TRUE;
87        }
88      else if (in_quotes)
89        {
90          if (b == '\'')
91            in_quotes = FALSE;
92          else
93            {
94              if (!_dbus_string_append_byte (dest, b))
95                return FALSE;
96            }
97        }
98      else
99        {
100          if (b == '\'')
101            in_quotes = TRUE;
102          else if (b == ' ' || b == '\n' || b == '\t')
103            break; /* end on whitespace if not quoted */
104          else
105            {
106              if (!_dbus_string_append_byte (dest, b))
107                return FALSE;
108            }
109        }
110
111      ++i;
112    }
113
114  return TRUE;
115}
116
117static dbus_bool_t
118same_first_word (const DBusString *a,
119                 const DBusString *b)
120{
121  int first_a_blank, first_b_blank;
122
123  _dbus_string_find_blank (a, 0, &first_a_blank);
124  _dbus_string_find_blank (b, 0, &first_b_blank);
125
126  if (first_a_blank != first_b_blank)
127    return FALSE;
128
129  return _dbus_string_equal_len (a, b, first_a_blank);
130}
131
132static DBusAuthState
133auth_state_from_string (const DBusString *str)
134{
135  if (_dbus_string_starts_with_c_str (str, "WAITING_FOR_INPUT"))
136    return DBUS_AUTH_STATE_WAITING_FOR_INPUT;
137  else if (_dbus_string_starts_with_c_str (str, "WAITING_FOR_MEMORY"))
138    return DBUS_AUTH_STATE_WAITING_FOR_MEMORY;
139  else if (_dbus_string_starts_with_c_str (str, "HAVE_BYTES_TO_SEND"))
140    return DBUS_AUTH_STATE_HAVE_BYTES_TO_SEND;
141  else if (_dbus_string_starts_with_c_str (str, "NEED_DISCONNECT"))
142    return DBUS_AUTH_STATE_NEED_DISCONNECT;
143  else if (_dbus_string_starts_with_c_str (str, "AUTHENTICATED"))
144    return DBUS_AUTH_STATE_AUTHENTICATED;
145  else
146    return -1;
147}
148
149static const char*
150auth_state_to_string (DBusAuthState state)
151{
152  switch (state)
153    {
154    case DBUS_AUTH_STATE_WAITING_FOR_INPUT:
155      return "WAITING_FOR_INPUT";
156    case DBUS_AUTH_STATE_WAITING_FOR_MEMORY:
157      return "WAITING_FOR_MEMORY";
158    case DBUS_AUTH_STATE_HAVE_BYTES_TO_SEND:
159      return "HAVE_BYTES_TO_SEND";
160    case DBUS_AUTH_STATE_NEED_DISCONNECT:
161      return "NEED_DISCONNECT";
162    case DBUS_AUTH_STATE_AUTHENTICATED:
163      return "AUTHENTICATED";
164    }
165
166  return "unknown";
167}
168
169static char **
170split_string (DBusString *str)
171{
172  int i, j, k, count, end;
173  char **array;
174
175  end = _dbus_string_get_length (str);
176
177  i = 0;
178  _dbus_string_skip_blank (str, i, &i);
179  for (count = 0; i < end; count++)
180    {
181      _dbus_string_find_blank (str, i, &i);
182      _dbus_string_skip_blank (str, i, &i);
183    }
184
185  array = dbus_new0 (char *, count + 1);
186  if (array == NULL)
187    return NULL;
188
189  i = 0;
190  _dbus_string_skip_blank (str, i, &i);
191  for (k = 0; k < count; k++)
192    {
193      _dbus_string_find_blank (str, i, &j);
194
195      array[k] = dbus_malloc (j - i + 1);
196      if (array[k] == NULL)
197        {
198          dbus_free_string_array (array);
199          return NULL;
200        }
201      memcpy (array[k],
202              _dbus_string_get_const_data_len (str, i, j - i), j - i);
203      array[k][j - i] = '\0';
204
205      _dbus_string_skip_blank (str, j, &i);
206    }
207  array[k] = NULL;
208
209  return array;
210}
211
212static void
213auth_set_unix_credentials(DBusAuth  *auth,
214                          dbus_uid_t uid,
215                          dbus_pid_t pid)
216{
217  DBusCredentials *credentials;
218
219  credentials = _dbus_credentials_new ();
220  if (credentials == NULL)
221    _dbus_assert_not_reached ("no memory");
222
223  if (uid != DBUS_UID_UNSET)
224    _dbus_credentials_add_unix_uid (credentials, uid);
225  if (pid != DBUS_PID_UNSET)
226    _dbus_credentials_add_unix_pid (credentials, pid);
227
228  _dbus_auth_set_credentials (auth, credentials);
229
230  _dbus_credentials_unref (credentials);
231}
232
233/**
234 * Runs an "auth script" which is a script for testing the
235 * authentication protocol. Scripts send and receive data, and then
236 * include assertions about the state of both ends of the connection
237 * after processing the data. A script succeeds if these assertions
238 * hold.
239 *
240 * @param filename the file containing the script to run
241 * @returns #TRUE if the script succeeds, #FALSE otherwise
242 */
243dbus_bool_t
244_dbus_auth_script_run (const DBusString *filename)
245{
246  DBusString file;
247  DBusError error = DBUS_ERROR_INIT;
248  DBusString line;
249  dbus_bool_t retval;
250  int line_no;
251  DBusAuth *auth;
252  DBusString from_auth;
253  DBusAuthState state;
254  DBusString context;
255  DBusString guid;
256
257  retval = FALSE;
258  auth = NULL;
259
260  _dbus_string_init_const (&guid, "5fa01f4202cd837709a3274ca0df9d00");
261  _dbus_string_init_const (&context, "org_freedesktop_test");
262
263  if (!_dbus_string_init (&file))
264    return FALSE;
265
266  if (!_dbus_string_init (&line))
267    {
268      _dbus_string_free (&file);
269      return FALSE;
270    }
271
272  if (!_dbus_string_init (&from_auth))
273    {
274      _dbus_string_free (&file);
275      _dbus_string_free (&line);
276      return FALSE;
277    }
278
279  if (!_dbus_file_get_contents (&file, filename, &error))    {
280      _dbus_warn ("Getting contents of %s failed: %s\n",
281                  _dbus_string_get_const_data (filename), error.message);
282      dbus_error_free (&error);
283      goto out;
284    }
285
286  state = DBUS_AUTH_STATE_NEED_DISCONNECT;
287  line_no = 0;
288
289 next_iteration:
290  while (_dbus_string_pop_line (&file, &line))
291    {
292      line_no += 1;
293
294      /* _dbus_warn ("%s\n", _dbus_string_get_const_data (&line)); */
295
296      _dbus_string_delete_leading_blanks (&line);
297
298      if (auth != NULL)
299        {
300          while ((state = _dbus_auth_do_work (auth)) ==
301                 DBUS_AUTH_STATE_HAVE_BYTES_TO_SEND)
302            {
303              const DBusString *tmp;
304              if (_dbus_auth_get_bytes_to_send (auth, &tmp))
305                {
306                  int count = _dbus_string_get_length (tmp);
307
308                  if (_dbus_string_copy (tmp, 0, &from_auth,
309                                         _dbus_string_get_length (&from_auth)))
310                    _dbus_auth_bytes_sent (auth, count);
311                }
312            }
313        }
314
315      if (_dbus_string_get_length (&line) == 0)
316        {
317          /* empty line */
318          goto next_iteration;
319        }
320      else if (_dbus_string_starts_with_c_str (&line,
321                                               "#"))
322        {
323          /* Ignore this comment */
324          goto next_iteration;
325        }
326#ifdef DBUS_WIN
327      else if (_dbus_string_starts_with_c_str (&line,
328                                               "WIN_ONLY"))
329        {
330          /* Ignore this line */
331          goto next_iteration;
332        }
333      else if (_dbus_string_starts_with_c_str (&line,
334                                               "UNIX_ONLY"))
335        {
336          /* skip this file */
337          _dbus_warn ("skipping unix only auth script\n");
338          retval = TRUE;
339          goto out;
340        }
341#endif
342#ifdef DBUS_UNIX
343      else if (_dbus_string_starts_with_c_str (&line,
344                                               "UNIX_ONLY"))
345        {
346          /* Ignore this line */
347          goto next_iteration;
348        }
349      else if (_dbus_string_starts_with_c_str (&line,
350                                               "WIN_ONLY"))
351        {
352          /* skip this file */
353          _dbus_warn ("skipping windows only auth script\n");
354          retval = TRUE;
355          goto out;
356        }
357#endif
358      else if (_dbus_string_starts_with_c_str (&line,
359                                               "CLIENT"))
360        {
361          DBusCredentials *creds;
362
363          if (auth != NULL)
364            {
365              _dbus_warn ("already created a DBusAuth (CLIENT or SERVER given twice)\n");
366              goto out;
367            }
368
369          auth = _dbus_auth_client_new ();
370          if (auth == NULL)
371            {
372              _dbus_warn ("no memory to create DBusAuth\n");
373              goto out;
374            }
375
376          /* test ref/unref */
377          _dbus_auth_ref (auth);
378          _dbus_auth_unref (auth);
379
380          creds = _dbus_credentials_new_from_current_process ();
381          if (creds == NULL)
382            {
383              _dbus_warn ("no memory for credentials\n");
384              _dbus_auth_unref (auth);
385              auth = NULL;
386              goto out;
387            }
388
389          if (!_dbus_auth_set_credentials (auth, creds))
390            {
391              _dbus_warn ("no memory for setting credentials\n");
392              _dbus_auth_unref (auth);
393              auth = NULL;
394              _dbus_credentials_unref (creds);
395              goto out;
396            }
397
398          _dbus_credentials_unref (creds);
399        }
400      else if (_dbus_string_starts_with_c_str (&line,
401                                               "SERVER"))
402        {
403          DBusCredentials *creds;
404
405          if (auth != NULL)
406            {
407              _dbus_warn ("already created a DBusAuth (CLIENT or SERVER given twice)\n");
408              goto out;
409            }
410
411          auth = _dbus_auth_server_new (&guid);
412          if (auth == NULL)
413            {
414              _dbus_warn ("no memory to create DBusAuth\n");
415              goto out;
416            }
417
418          /* test ref/unref */
419          _dbus_auth_ref (auth);
420          _dbus_auth_unref (auth);
421
422          creds = _dbus_credentials_new_from_current_process ();
423          if (creds == NULL)
424            {
425              _dbus_warn ("no memory for credentials\n");
426              _dbus_auth_unref (auth);
427              auth = NULL;
428              goto out;
429            }
430
431          if (!_dbus_auth_set_credentials (auth, creds))
432            {
433              _dbus_warn ("no memory for setting credentials\n");
434              _dbus_auth_unref (auth);
435              auth = NULL;
436              _dbus_credentials_unref (creds);
437              goto out;
438            }
439
440          _dbus_credentials_unref (creds);
441
442          _dbus_auth_set_context (auth, &context);
443        }
444      else if (auth == NULL)
445        {
446          _dbus_warn ("must specify CLIENT or SERVER\n");
447          goto out;
448
449        }
450      else if (_dbus_string_starts_with_c_str (&line,
451                                               "NO_CREDENTIALS"))
452        {
453          auth_set_unix_credentials (auth, DBUS_UID_UNSET, DBUS_PID_UNSET);
454        }
455      else if (_dbus_string_starts_with_c_str (&line,
456                                               "ROOT_CREDENTIALS"))
457        {
458          auth_set_unix_credentials (auth, 0, DBUS_PID_UNSET);
459        }
460      else if (_dbus_string_starts_with_c_str (&line,
461                                               "SILLY_CREDENTIALS"))
462        {
463          auth_set_unix_credentials (auth, 4312, DBUS_PID_UNSET);
464        }
465      else if (_dbus_string_starts_with_c_str (&line,
466                                               "ALLOWED_MECHS"))
467        {
468          char **mechs;
469
470          _dbus_string_delete_first_word (&line);
471          mechs = split_string (&line);
472          _dbus_auth_set_mechanisms (auth, (const char **) mechs);
473          dbus_free_string_array (mechs);
474        }
475      else if (_dbus_string_starts_with_c_str (&line,
476                                               "SEND"))
477        {
478          DBusString to_send;
479
480          _dbus_string_delete_first_word (&line);
481
482          if (!_dbus_string_init (&to_send))
483            {
484              _dbus_warn ("no memory to allocate string\n");
485              goto out;
486            }
487
488          if (!append_quoted_string (&to_send, &line))
489            {
490              _dbus_warn ("failed to append quoted string line %d\n",
491                          line_no);
492              _dbus_string_free (&to_send);
493              goto out;
494            }
495
496          _dbus_verbose ("Sending '%s'\n", _dbus_string_get_const_data (&to_send));
497
498          if (!_dbus_string_append (&to_send, "\r\n"))
499            {
500              _dbus_warn ("failed to append \r\n from line %d\n",
501                          line_no);
502              _dbus_string_free (&to_send);
503              goto out;
504            }
505
506          /* Replace USERID_HEX with our username in hex */
507          {
508            int where;
509
510            if (_dbus_string_find (&to_send, 0,
511                                   "USERID_HEX", &where))
512              {
513                DBusString username;
514
515                if (!_dbus_string_init (&username))
516                  {
517                    _dbus_warn ("no memory for userid\n");
518                    _dbus_string_free (&to_send);
519                    goto out;
520                  }
521
522                if (!_dbus_append_user_from_current_process (&username))
523                  {
524                    _dbus_warn ("no memory for userid\n");
525                    _dbus_string_free (&username);
526                    _dbus_string_free (&to_send);
527                    goto out;
528                  }
529
530                _dbus_string_delete (&to_send, where, strlen ("USERID_HEX"));
531
532                if (!_dbus_string_hex_encode (&username, 0,
533					      &to_send, where))
534                  {
535                    _dbus_warn ("no memory to subst USERID_HEX\n");
536                    _dbus_string_free (&username);
537                    _dbus_string_free (&to_send);
538                    goto out;
539                  }
540
541                _dbus_string_free (&username);
542              }
543            else if (_dbus_string_find (&to_send, 0,
544                                        "USERNAME_HEX", &where))
545              {
546                DBusString username;
547
548                if (!_dbus_string_init (&username))
549                  {
550                    _dbus_warn ("no memory for username\n");
551                    _dbus_string_free (&to_send);
552                    goto out;
553                  }
554
555                if (!_dbus_append_user_from_current_process (&username))
556                  {
557                    _dbus_warn ("no memory for username\n");
558                    _dbus_string_free (&username);
559                    _dbus_string_free (&to_send);
560                    goto out;
561                  }
562
563                _dbus_string_delete (&to_send, where, strlen ("USERNAME_HEX"));
564
565                if (!_dbus_string_hex_encode (&username, 0,
566					      &to_send, where))
567                  {
568                    _dbus_warn ("no memory to subst USERNAME_HEX\n");
569                    _dbus_string_free (&username);
570                    _dbus_string_free (&to_send);
571                    goto out;
572                  }
573
574                _dbus_string_free (&username);
575              }
576          }
577
578          {
579            DBusString *buffer;
580
581            _dbus_auth_get_buffer (auth, &buffer);
582            if (!_dbus_string_copy (&to_send, 0,
583                                    buffer, _dbus_string_get_length (buffer)))
584              {
585                _dbus_warn ("not enough memory to call bytes_received, or can't add bytes to auth object already in end state\n");
586                _dbus_string_free (&to_send);
587                _dbus_auth_return_buffer (auth, buffer, 0);
588                goto out;
589              }
590
591            _dbus_auth_return_buffer (auth, buffer, _dbus_string_get_length (&to_send));
592          }
593
594          _dbus_string_free (&to_send);
595        }
596      else if (_dbus_string_starts_with_c_str (&line,
597                                               "EXPECT_STATE"))
598        {
599          DBusAuthState expected;
600
601          _dbus_string_delete_first_word (&line);
602
603          expected = auth_state_from_string (&line);
604          if (expected < 0)
605            {
606              _dbus_warn ("bad auth state given to EXPECT_STATE\n");
607              goto parse_failed;
608            }
609
610          if (expected != state)
611            {
612              _dbus_warn ("expected auth state %s but got %s on line %d\n",
613                          auth_state_to_string (expected),
614                          auth_state_to_string (state),
615                          line_no);
616              goto out;
617            }
618        }
619      else if (_dbus_string_starts_with_c_str (&line,
620                                               "EXPECT_COMMAND"))
621        {
622          DBusString received;
623
624          _dbus_string_delete_first_word (&line);
625
626          if (!_dbus_string_init (&received))
627            {
628              _dbus_warn ("no mem to allocate string received\n");
629              goto out;
630            }
631
632          if (!_dbus_string_pop_line (&from_auth, &received))
633            {
634              _dbus_warn ("no line popped from the DBusAuth being tested, expected command %s on line %d\n",
635                          _dbus_string_get_const_data (&line), line_no);
636              _dbus_string_free (&received);
637              goto out;
638            }
639
640          if (!same_first_word (&received, &line))
641            {
642              _dbus_warn ("line %d expected command '%s' and got '%s'\n",
643                          line_no,
644                          _dbus_string_get_const_data (&line),
645                          _dbus_string_get_const_data (&received));
646              _dbus_string_free (&received);
647              goto out;
648            }
649
650          _dbus_string_free (&received);
651        }
652      else if (_dbus_string_starts_with_c_str (&line,
653                                               "EXPECT_UNUSED"))
654        {
655          DBusString expected;
656          const DBusString *unused;
657
658          _dbus_string_delete_first_word (&line);
659
660          if (!_dbus_string_init (&expected))
661            {
662              _dbus_warn ("no mem to allocate string expected\n");
663              goto out;
664            }
665
666          if (!append_quoted_string (&expected, &line))
667            {
668              _dbus_warn ("failed to append quoted string line %d\n",
669                          line_no);
670              _dbus_string_free (&expected);
671              goto out;
672            }
673
674          _dbus_auth_get_unused_bytes (auth, &unused);
675
676          if (_dbus_string_equal (&expected, unused))
677            {
678              _dbus_auth_delete_unused_bytes (auth);
679              _dbus_string_free (&expected);
680            }
681          else
682            {
683              _dbus_warn ("Expected unused bytes '%s' and have '%s'\n",
684                          _dbus_string_get_const_data (&expected),
685                          _dbus_string_get_const_data (unused));
686              _dbus_string_free (&expected);
687              goto out;
688            }
689        }
690      else if (_dbus_string_starts_with_c_str (&line,
691                                               "EXPECT_HAVE_NO_CREDENTIALS"))
692        {
693          DBusCredentials *authorized_identity;
694
695          authorized_identity = _dbus_auth_get_identity (auth);
696          if (!_dbus_credentials_are_anonymous (authorized_identity))
697            {
698              _dbus_warn ("Expected anonymous login or failed login, but some credentials were authorized\n");
699              goto out;
700            }
701        }
702      else if (_dbus_string_starts_with_c_str (&line,
703                                               "EXPECT_HAVE_SOME_CREDENTIALS"))
704        {
705          DBusCredentials *authorized_identity;
706
707          authorized_identity = _dbus_auth_get_identity (auth);
708          if (_dbus_credentials_are_anonymous (authorized_identity))
709            {
710              _dbus_warn ("Expected to have some credentials, but we don't\n");
711              goto out;
712            }
713        }
714      else if (_dbus_string_starts_with_c_str (&line,
715                                               "EXPECT"))
716        {
717          DBusString expected;
718
719          _dbus_string_delete_first_word (&line);
720
721          if (!_dbus_string_init (&expected))
722            {
723              _dbus_warn ("no mem to allocate string expected\n");
724              goto out;
725            }
726
727          if (!append_quoted_string (&expected, &line))
728            {
729              _dbus_warn ("failed to append quoted string line %d\n",
730                          line_no);
731              _dbus_string_free (&expected);
732              goto out;
733            }
734
735          if (_dbus_string_equal_len (&expected, &from_auth,
736                                      _dbus_string_get_length (&expected)))
737            {
738              _dbus_string_delete (&from_auth, 0,
739                                   _dbus_string_get_length (&expected));
740              _dbus_string_free (&expected);
741            }
742          else
743            {
744              _dbus_warn ("Expected exact string '%s' and have '%s'\n",
745                          _dbus_string_get_const_data (&expected),
746                          _dbus_string_get_const_data (&from_auth));
747              _dbus_string_free (&expected);
748              goto out;
749            }
750        }
751      else
752        goto parse_failed;
753
754      goto next_iteration; /* skip parse_failed */
755
756    parse_failed:
757      {
758        _dbus_warn ("couldn't process line %d \"%s\"\n",
759                    line_no, _dbus_string_get_const_data (&line));
760        goto out;
761      }
762    }
763
764  if (auth == NULL)
765    {
766      _dbus_warn ("Auth script is bogus, did not even have CLIENT or SERVER\n");
767      goto out;
768    }
769  else if (state == DBUS_AUTH_STATE_AUTHENTICATED)
770    {
771      const DBusString *unused;
772
773      _dbus_auth_get_unused_bytes (auth, &unused);
774
775      if (_dbus_string_get_length (unused) > 0)
776        {
777          _dbus_warn ("did not expect unused bytes (scripts must specify explicitly if they are expected)\n");
778          goto out;
779        }
780    }
781
782  if (_dbus_string_get_length (&from_auth) > 0)
783    {
784      _dbus_warn ("script did not have EXPECT_ statements for all the data received from the DBusAuth\n");
785      _dbus_warn ("Leftover data: %s\n", _dbus_string_get_const_data (&from_auth));
786      goto out;
787    }
788
789  retval = TRUE;
790
791 out:
792  if (auth)
793    _dbus_auth_unref (auth);
794
795  _dbus_string_free (&file);
796  _dbus_string_free (&line);
797  _dbus_string_free (&from_auth);
798
799  return retval;
800}
801
802/** @} */
803#endif /* DBUS_BUILD_TESTS */
804