Merge of rpcecho pipe for testing large dcerpc requests and responses.
[samba.git] / source / rpcclient / rpcclient.c
1 /* 
2    Unix SMB/CIFS implementation.
3    RPC pipe client
4
5    Copyright (C) Tim Potter 2000-2001
6    Copyright (C) Martin Pool 2003
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., 675 Mass Ave, Cambridge, MA 02139, USA.
21 */
22
23 #include "includes.h"
24 #include "rpcclient.h"
25
26 DOM_SID domain_sid;
27
28
29 /* List to hold groups of commands.
30  *
31  * Commands are defined in a list of arrays: arrays are easy to
32  * statically declare, and lists are easier to dynamically extend.
33  */
34
35 static struct cmd_list {
36         struct cmd_list *prev, *next;
37         struct cmd_set *cmd_set;
38 } *cmd_list;
39
40 /****************************************************************************
41 handle completion of commands for readline
42 ****************************************************************************/
43 static char **completion_fn(char *text, int start, int end)
44 {
45 #define MAX_COMPLETIONS 100
46         char **matches;
47         int i, count=0;
48         struct cmd_list *commands = cmd_list;
49
50 #if 0   /* JERRY */
51         /* FIXME!!!  -- what to do when completing argument? */
52         /* for words not at the start of the line fallback 
53            to filename completion */
54         if (start) 
55                 return NULL;
56 #endif
57
58         /* make sure we have a list of valid commands */
59         if (!commands) 
60                 return NULL;
61
62         matches = (char **)malloc(sizeof(matches[0])*MAX_COMPLETIONS);
63         if (!matches) return NULL;
64
65         matches[count++] = strdup(text);
66         if (!matches[0]) return NULL;
67
68         while (commands && count < MAX_COMPLETIONS-1) 
69         {
70                 if (!commands->cmd_set)
71                         break;
72                 
73                 for (i=0; commands->cmd_set[i].name; i++)
74                 {
75                         if ((strncmp(text, commands->cmd_set[i].name, strlen(text)) == 0) &&
76                                 (( commands->cmd_set[i].returntype == RPC_RTYPE_NTSTATUS &&
77                         commands->cmd_set[i].ntfn ) || 
78                       ( commands->cmd_set[i].returntype == RPC_RTYPE_WERROR &&
79                         commands->cmd_set[i].wfn)))
80                         {
81                                 matches[count] = strdup(commands->cmd_set[i].name);
82                                 if (!matches[count]) 
83                                         return NULL;
84                                 count++;
85                         }
86                 }
87                 
88                 commands = commands->next;
89                 
90         }
91
92         if (count == 2) {
93                 SAFE_FREE(matches[0]);
94                 matches[0] = strdup(matches[1]);
95         }
96         matches[count] = NULL;
97         return matches;
98 }
99
100 /***********************************************************************
101  * read in username/password credentials from a file
102  */
103 static void read_authfile (
104         char *filename, 
105         char* username, 
106         char* password, 
107         char* domain
108 )
109 {
110         FILE *auth;
111         fstring buf;
112         uint16 len = 0;
113         char *ptr, *val, *param;
114                                
115         if ((auth=sys_fopen(filename, "r")) == NULL)
116         {
117                 printf ("ERROR: Unable to open credentials file!\n");
118                 return;
119         }
120                                 
121         while (!feof(auth))
122         {  
123                 /* get a line from the file */
124                 if (!fgets (buf, sizeof(buf), auth))
125                         continue;
126                 
127                 len = strlen(buf);
128                 
129                 /* skip empty lines */                  
130                 if ((len) && (buf[len-1]=='\n'))
131                 {
132                         buf[len-1] = '\0';
133                         len--;
134                 }       
135                 if (len == 0)
136                         continue;
137                                         
138                 /* break up the line into parameter & value.
139                    will need to eat a little whitespace possibly */
140                 param = buf;
141                 if (!(ptr = strchr_m(buf, '=')))
142                         continue;
143                 val = ptr+1;
144                 *ptr = '\0';
145                                         
146                 /* eat leading white space */
147                 while ((*val!='\0') && ((*val==' ') || (*val=='\t')))
148                         val++;
149                                         
150                 if (strwicmp("password", param) == 0)
151                         fstrcpy (password, val);
152                 else if (strwicmp("username", param) == 0)
153                         fstrcpy (username, val);
154                 else if (strwicmp("domain", param) == 0)
155                         fstrcpy (domain, val);
156                                                 
157                 memset(buf, 0, sizeof(buf));
158         }
159         fclose(auth);
160         
161         return;
162 }
163
164 static char* next_command (char** cmdstr)
165 {
166         static pstring          command;
167         char                    *p;
168         
169         if (!cmdstr || !(*cmdstr))
170                 return NULL;
171         
172         p = strchr_m(*cmdstr, ';');
173         if (p)
174                 *p = '\0';
175         pstrcpy(command, *cmdstr);
176         if (p)
177                 *cmdstr = p + 1;
178         else
179                 *cmdstr = NULL;
180         
181         return command;
182 }
183
184
185 /**
186  * Find default username from environment variables.
187  *
188  * @param username fstring to receive username; not touched if none is
189  * known.
190  **/
191 static void get_username (char *username)
192 {
193         if (getenv("USER"))
194                 fstrcpy(username,getenv("USER"));
195  
196         if (*username == 0 && getenv("LOGNAME"))
197                 fstrcpy(username,getenv("LOGNAME"));
198  
199         if (*username == 0) {
200                 fstrcpy(username,"GUEST");
201         }
202
203         return;
204 }
205
206 /* Fetch the SID for this computer */
207
208 static void fetch_machine_sid(struct cli_state *cli)
209 {
210         POLICY_HND pol;
211         NTSTATUS result = NT_STATUS_OK;
212         uint32 info_class = 5;
213         fstring domain_name;
214         static BOOL got_domain_sid;
215         TALLOC_CTX *mem_ctx;
216
217         if (got_domain_sid) return;
218
219         if (!(mem_ctx=talloc_init("fetch_machine_sid")))
220         {
221                 DEBUG(0,("fetch_machine_sid: talloc_init returned NULL!\n"));
222                 goto error;
223         }
224
225
226         if (!cli_nt_session_open (cli, PI_LSARPC)) {
227                 fprintf(stderr, "could not initialise lsa pipe\n");
228                 goto error;
229         }
230         
231         result = cli_lsa_open_policy(cli, mem_ctx, True, 
232                                      SEC_RIGHTS_MAXIMUM_ALLOWED,
233                                      &pol);
234         if (!NT_STATUS_IS_OK(result)) {
235                 goto error;
236         }
237
238         result = cli_lsa_query_info_policy(cli, mem_ctx, &pol, info_class, 
239                                            domain_name, &domain_sid);
240         if (!NT_STATUS_IS_OK(result)) {
241                 goto error;
242         }
243
244         got_domain_sid = True;
245
246         cli_lsa_close(cli, mem_ctx, &pol);
247         cli_nt_session_close(cli);
248         talloc_destroy(mem_ctx);
249
250         return;
251
252  error:
253         fprintf(stderr, "could not obtain sid for domain %s\n", cli->domain);
254
255         if (!NT_STATUS_IS_OK(result)) {
256                 fprintf(stderr, "error: %s\n", nt_errstr(result));
257         }
258
259         exit(1);
260 }
261
262 /* List the available commands on a given pipe */
263
264 static NTSTATUS cmd_listcommands(struct cli_state *cli, TALLOC_CTX *mem_ctx,
265                                  int argc, const char **argv)
266 {
267         struct cmd_list *tmp;
268         struct cmd_set *tmp_set;
269         int i;
270
271         /* Usage */
272
273         if (argc != 2) {
274                 printf("Usage: %s <pipe>\n", argv[0]);
275                 return NT_STATUS_OK;
276         }
277
278         /* Help on one command */
279
280         for (tmp = cmd_list; tmp; tmp = tmp->next) 
281         {
282                 tmp_set = tmp->cmd_set;
283                 
284                 if (!StrCaseCmp(argv[1], tmp_set->name))
285                 {
286                         printf("Available commands on the %s pipe:\n\n", tmp_set->name);
287
288                         i = 0;
289                         tmp_set++;
290                         while(tmp_set->name) {
291                                 printf("%20s", tmp_set->name);
292                                 tmp_set++;
293                                 i++;
294                                 if (i%4 == 0)
295                                         printf("\n");
296                         }
297                         
298                         /* drop out of the loop */
299                         break;
300                 }
301         }
302         printf("\n\n");
303
304         return NT_STATUS_OK;
305 }
306
307 /* Display help on commands */
308
309 static NTSTATUS cmd_help(struct cli_state *cli, TALLOC_CTX *mem_ctx,
310                          int argc, const char **argv)
311 {
312         struct cmd_list *tmp;
313         struct cmd_set *tmp_set;
314
315         /* Usage */
316
317         if (argc > 2) {
318                 printf("Usage: %s [command]\n", argv[0]);
319                 return NT_STATUS_OK;
320         }
321
322         /* Help on one command */
323
324         if (argc == 2) {
325                 for (tmp = cmd_list; tmp; tmp = tmp->next) {
326                         
327                         tmp_set = tmp->cmd_set;
328
329                         while(tmp_set->name) {
330                                 if (strequal(argv[1], tmp_set->name)) {
331                                         if (tmp_set->usage &&
332                                             tmp_set->usage[0])
333                                                 printf("%s\n", tmp_set->usage);
334                                         else
335                                                 printf("No help for %s\n", tmp_set->name);
336
337                                         return NT_STATUS_OK;
338                                 }
339
340                                 tmp_set++;
341                         }
342                 }
343
344                 printf("No such command: %s\n", argv[1]);
345                 return NT_STATUS_OK;
346         }
347
348         /* List all commands */
349
350         for (tmp = cmd_list; tmp; tmp = tmp->next) {
351
352                 tmp_set = tmp->cmd_set;
353
354                 while(tmp_set->name) {
355
356                         printf("%15s\t\t%s\n", tmp_set->name,
357                                tmp_set->description ? tmp_set->description:
358                                "");
359
360                         tmp_set++;
361                 }
362         }
363
364         return NT_STATUS_OK;
365 }
366
367 /* Change the debug level */
368
369 static NTSTATUS cmd_debuglevel(struct cli_state *cli, TALLOC_CTX *mem_ctx,
370                                int argc, const char **argv)
371 {
372         if (argc > 2) {
373                 printf("Usage: %s [debuglevel]\n", argv[0]);
374                 return NT_STATUS_OK;
375         }
376
377         if (argc == 2) {
378                 DEBUGLEVEL = atoi(argv[1]);
379         }
380
381         printf("debuglevel is %d\n", DEBUGLEVEL);
382
383         return NT_STATUS_OK;
384 }
385
386 static NTSTATUS cmd_quit(struct cli_state *cli, TALLOC_CTX *mem_ctx,
387                          int argc, const char **argv)
388 {
389         exit(0);
390         return NT_STATUS_OK; /* NOTREACHED */
391 }
392
393 /* Built in rpcclient commands */
394
395 static struct cmd_set rpcclient_commands[] = {
396
397         { "GENERAL OPTIONS" },
398
399         { "help", RPC_RTYPE_NTSTATUS, cmd_help, NULL,     -1,   "Get help on commands", "[command]" },
400         { "?",  RPC_RTYPE_NTSTATUS, cmd_help, NULL,       -1,   "Get help on commands", "[command]" },
401         { "debuglevel", RPC_RTYPE_NTSTATUS, cmd_debuglevel, NULL,   -1, "Set debug level", "level" },
402         { "list",       RPC_RTYPE_NTSTATUS, cmd_listcommands, NULL, -1, "List available commands on <pipe>", "pipe" },
403         { "exit", RPC_RTYPE_NTSTATUS, cmd_quit, NULL,   -1,     "Exit program", "" },
404         { "quit", RPC_RTYPE_NTSTATUS, cmd_quit, NULL,     -1,   "Exit program", "" },
405
406         { NULL }
407 };
408
409 static struct cmd_set separator_command[] = {
410         { "---------------", MAX_RPC_RETURN_TYPE, NULL, NULL,   -1,     "----------------------" },
411         { NULL }
412 };
413
414
415 /* Various pipe commands */
416
417 extern struct cmd_set lsarpc_commands[];
418 extern struct cmd_set samr_commands[];
419 extern struct cmd_set spoolss_commands[];
420 extern struct cmd_set netlogon_commands[];
421 extern struct cmd_set srvsvc_commands[];
422 extern struct cmd_set dfs_commands[];
423 extern struct cmd_set reg_commands[];
424 extern struct cmd_set ds_commands[];
425 extern struct cmd_set echo_commands[];
426
427 static struct cmd_set *rpcclient_command_list[] = {
428         rpcclient_commands,
429         lsarpc_commands,
430         ds_commands,
431         samr_commands,
432         spoolss_commands,
433         netlogon_commands,
434         srvsvc_commands,
435         dfs_commands,
436         reg_commands,
437         echo_commands,
438         NULL
439 };
440
441 static void add_command_set(struct cmd_set *cmd_set)
442 {
443         struct cmd_list *entry;
444
445         if (!(entry = (struct cmd_list *)malloc(sizeof(struct cmd_list)))) {
446                 DEBUG(0, ("out of memory\n"));
447                 return;
448         }
449
450         ZERO_STRUCTP(entry);
451
452         entry->cmd_set = cmd_set;
453         DLIST_ADD(cmd_list, entry);
454 }
455
456
457 /**
458  * Call an rpcclient function, passing an argv array.
459  *
460  * @param cmd Command to run, as a single string.
461  **/
462 static NTSTATUS do_cmd(struct cli_state *cli,
463                        struct cmd_set *cmd_entry,
464                        int argc, char **argv)
465 {
466      NTSTATUS ntresult;
467      WERROR wresult;
468         
469         TALLOC_CTX *mem_ctx;
470
471         /* Create mem_ctx */
472
473         if (!(mem_ctx = talloc_init("do_cmd"))) {
474                 DEBUG(0, ("talloc_init() failed\n"));
475                 return NT_STATUS_UNSUCCESSFUL;
476         }
477
478         /* Open pipe */
479
480         if (cmd_entry->pipe_idx == PI_NETLOGON) {
481                 uchar trust_password[16];
482
483                 if (!secrets_fetch_trust_account_password(lp_workgroup(),
484                                                           trust_password,
485                                                           NULL)) {
486                         return NT_STATUS_UNSUCCESSFUL;
487                 }
488
489                 if (!cli_nt_open_netlogon(cli, trust_password,
490                                           SEC_CHAN_WKSTA)) {
491                         DEBUG(0, ("Could not initialise NETLOGON pipe\n"));
492                         return NT_STATUS_UNSUCCESSFUL;
493                 }
494         } else {
495                 if (cmd_entry->pipe_idx != -1) {
496                         if (!cli_nt_session_open(cli, cmd_entry->pipe_idx)) {
497                                 DEBUG(0, ("Could not initialise %s\n",
498                                           get_pipe_name_from_index(cmd_entry->pipe_idx)));
499                                 return NT_STATUS_UNSUCCESSFUL;
500                         }
501                 }
502         }
503
504      /* Run command */
505
506      if ( cmd_entry->returntype == RPC_RTYPE_NTSTATUS ) {
507           ntresult = cmd_entry->ntfn(cli, mem_ctx, argc, (const char **) argv);
508           if (!NT_STATUS_IS_OK(ntresult)) {
509               printf("result was %s\n", nt_errstr(ntresult));
510           }
511      } else {
512           wresult = cmd_entry->wfn( cli, mem_ctx, argc, (const char **) argv);
513           /* print out the DOS error */
514           if (!W_ERROR_IS_OK(wresult)) {
515                   printf( "result was %s\n", dos_errstr(wresult));
516           }
517           ntresult = W_ERROR_IS_OK(wresult)?NT_STATUS_OK:NT_STATUS_UNSUCCESSFUL;
518      }
519             
520
521         /* Cleanup */
522
523         if (cmd_entry->pipe_idx != -1)
524                 cli_nt_session_close(cli);
525
526         talloc_destroy(mem_ctx);
527
528         return ntresult;
529 }
530
531
532 /**
533  * Process a command entered at the prompt or as part of -c
534  *
535  * @returns The NTSTATUS from running the command.
536  **/
537 static NTSTATUS process_cmd(struct cli_state *cli, char *cmd)
538 {
539         struct cmd_list *temp_list;
540         NTSTATUS result = NT_STATUS_OK;
541         int ret;
542         int argc;
543         char **argv = NULL;
544
545         if ((ret = poptParseArgvString(cmd, &argc, (const char ***) &argv)) != 0) {
546                 fprintf(stderr, "rpcclient: %s\n", poptStrerror(ret));
547                 return NT_STATUS_UNSUCCESSFUL;
548         }
549
550
551         /* Walk through a dlist of arrays of commands. */
552         for (temp_list = cmd_list; temp_list; temp_list = temp_list->next) {
553                 struct cmd_set *temp_set = temp_list->cmd_set;
554
555                 while (temp_set->name) {
556                         if (strequal(argv[0], temp_set->name)) {
557                                 if (!(temp_set->returntype == RPC_RTYPE_NTSTATUS && temp_set->ntfn ) &&
558                          !(temp_set->returntype == RPC_RTYPE_WERROR && temp_set->wfn )) {
559                                         fprintf (stderr, "Invalid command\n");
560                                         goto out_free;
561                                 }
562
563                                 result = do_cmd(cli, temp_set, argc, argv);
564
565                                 goto out_free;
566                         }
567                         temp_set++;
568                 }
569         }
570
571         if (argv[0]) {
572                 printf("command not found: %s\n", argv[0]);
573         }
574
575 out_free:
576 /* moved to do_cmd()
577         if (!NT_STATUS_IS_OK(result)) {
578                 printf("result was %s\n", nt_errstr(result));
579         }
580 */
581
582         if (argv) {
583                 /* NOTE: popt allocates the whole argv, including the
584                  * strings, as a single block.  So a single free is
585                  * enough to release it -- we don't free the
586                  * individual strings.  rtfm. */
587                 free(argv);
588         }
589         
590         return result;
591 }
592
593
594 /* Main function */
595
596  int main(int argc, char *argv[])
597 {
598         static int              got_pass = 0;
599         BOOL                    interactive = True;
600         int                     opt;
601         static char             *cmdstr = "";
602         const char *server;
603         struct cli_state        *cli;
604         fstring                 password="",
605                                 username="",
606                 domain="";
607         static char             *opt_authfile=NULL,
608                                 *opt_username=NULL,
609                                 *opt_domain=NULL,
610                                 *opt_logfile=NULL,
611                                 *opt_ipaddr=NULL;
612         pstring                 logfile;
613         struct cmd_set          **cmd_set;
614         struct in_addr          server_ip;
615         NTSTATUS                nt_status;
616
617         /* make sure the vars that get altered (4th field) are in
618            a fixed location or certain compilers complain */
619         poptContext pc;
620         struct poptOption long_options[] = {
621                 POPT_AUTOHELP
622                 {"authfile",    'A', POPT_ARG_STRING,   &opt_authfile, 'A', "File containing user credentials", "AUTHFILE"},
623                 {"nopass",      'N', POPT_ARG_NONE,     &got_pass, 'N', "Don't ask for a password"},
624                 {"user", 'U', POPT_ARG_STRING,  &opt_username, 'U', "Set the network username", "USER"},
625                 {"workgroup", 'W', POPT_ARG_STRING,     &opt_domain, 'W', "Set the domain name for user account", "DOMAIN"},
626                 {"command",     'c', POPT_ARG_STRING,   &cmdstr, 'c', "Execute semicolon separated cmds", "COMMANDS"},
627                 {"logfile",     'l', POPT_ARG_STRING,   &opt_logfile, 'l', "Logfile to use instead of stdout", "LOGFILE" },
628                 {"dest-ip", 'I', POPT_ARG_STRING,   &opt_ipaddr, 'I', "Specify destination IP address", "IP"},
629                 { NULL, 0, POPT_ARG_INCLUDE_TABLE, popt_common_debug },
630                 { NULL, 0, POPT_ARG_INCLUDE_TABLE, popt_common_configfile },
631                 { NULL, 0, POPT_ARG_INCLUDE_TABLE, popt_common_version},
632                 { NULL }
633         };
634
635         ZERO_STRUCT(server_ip);
636
637         setlinebuf(stdout);
638
639         /* Parse options */
640
641         pc = poptGetContext("rpcclient", argc, (const char **) argv,
642                             long_options, 0);
643
644         if (argc == 1) {
645                 poptPrintHelp(pc, stderr, 0);
646                 return 0;
647         }
648         
649         while((opt = poptGetNextOpt(pc)) != -1) {
650                 switch (opt) {
651                 case 'A':
652                         /* only get the username, password, and domain from the file */
653                         read_authfile (opt_authfile, username, password, domain);
654                         if (strlen (password))
655                                 got_pass = 1;
656                         break;
657                         
658                 case 'l':
659                         slprintf(logfile, sizeof(logfile) - 1, "%s.client", 
660                                  opt_logfile);
661                         lp_set_logfile(logfile);
662                         interactive = False;
663                         break;
664                         
665                 case 'U': {
666                         char *lp;
667
668                         fstrcpy(username,opt_username);
669
670                         if ((lp=strchr_m(username,'%'))) {
671                                 *lp = 0;
672                                 fstrcpy(password,lp+1);
673                                 got_pass = 1;
674                                 memset(strchr_m(opt_username,'%') + 1, 'X',
675                                        strlen(password));
676                         }
677                         break;
678                 }
679                 case 'I':
680                         if ( (server_ip.s_addr=inet_addr(opt_ipaddr)) == INADDR_NONE ) {
681                                 fprintf(stderr, "%s not a valid IP address\n",
682                                         opt_ipaddr);
683                                 return 1;
684                         }
685                 case 'W':
686                         fstrcpy(domain, opt_domain);
687                         break;
688                 }
689         }
690
691         /* Get server as remaining unparsed argument.  Print usage if more
692            than one unparsed argument is present. */
693
694         server = poptGetArg(pc);
695         
696         if (!server || poptGetArg(pc)) {
697                 poptPrintHelp(pc, stderr, 0);
698                 return 1;
699         }
700
701         poptFreeContext(pc);
702
703         /* the following functions are part of the Samba debugging
704            facilities.  See lib/debug.c */
705         setup_logging("rpcclient", interactive);
706         if (!interactive) 
707                 reopen_logs();
708         
709         /* Load smb.conf file */
710
711         if (!lp_load(dyn_CONFIGFILE,True,False,False))
712                 fprintf(stderr, "Can't load %s\n", dyn_CONFIGFILE);
713
714         load_interfaces();
715
716         if (!init_names())
717                 return 1;
718
719         /*
720          * Get password
721          * from stdin if necessary
722          */
723
724         if (!got_pass) {
725                 char *pass = getpass("Password:");
726                 if (pass) {
727                         fstrcpy(password, pass);
728                 }
729         }
730         
731         if (!strlen(username) && !got_pass)
732                 get_username(username);
733                 
734         nt_status = cli_full_connection(&cli, global_myname(), server, 
735                                         opt_ipaddr ? &server_ip : NULL, 0,
736                                         "IPC$", "IPC",  
737                                         username, domain,
738                                         password, 0, NULL);
739         
740         if (!NT_STATUS_IS_OK(nt_status)) {
741                 DEBUG(0,("Cannot connect to server.  Error was %s\n", nt_errstr(nt_status)));
742                 return 1;
743         }
744
745         memset(password,'X',sizeof(password));
746
747         /* Load command lists */
748
749         cmd_set = rpcclient_command_list;
750
751         while(*cmd_set) {
752                 add_command_set(*cmd_set);
753                 add_command_set(separator_command);
754                 cmd_set++;
755         }
756
757         fetch_machine_sid(cli);
758  
759        /* Do anything specified with -c */
760         if (cmdstr[0]) {
761                 char    *cmd;
762                 char    *p = cmdstr;
763  
764                 while((cmd=next_command(&p)) != NULL) {
765                         process_cmd(cli, cmd);
766                 }
767                 
768                 cli_shutdown(cli);
769                 return 0;
770         }
771
772         /* Loop around accepting commands */
773
774         while(1) {
775                 pstring prompt;
776                 char *line;
777
778                 slprintf(prompt, sizeof(prompt) - 1, "rpcclient $> ");
779
780                 line = smb_readline(prompt, NULL, completion_fn);
781
782                 if (line == NULL)
783                         break;
784
785                 if (line[0] != '\n')
786                         process_cmd(cli, line);
787         }
788         
789         cli_shutdown(cli);
790         return 0;
791 }