source: trunk/third/cyrus-sasl/sample/sample-client.c @ 17977

Revision 17977, 18.4 KB checked in by ghudson, 22 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r17976, which included commits to RCS files with non-trunk default branches.
Line 
1/* sample-client.c -- sample SASL client
2 * Rob Earhart
3 * $Id: sample-client.c,v 1.1.1.1 2002-10-13 18:00:17 ghudson Exp $
4 */
5/*
6 * Copyright (c) 2001 Carnegie Mellon University.  All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 *
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in
17 *    the documentation and/or other materials provided with the
18 *    distribution.
19 *
20 * 3. The name "Carnegie Mellon University" must not be used to
21 *    endorse or promote products derived from this software without
22 *    prior written permission. For permission or any other legal
23 *    details, please contact 
24 *      Office of Technology Transfer
25 *      Carnegie Mellon University
26 *      5000 Forbes Avenue
27 *      Pittsburgh, PA  15213-3890
28 *      (412) 268-4387, fax: (412) 268-7395
29 *      tech-transfer@andrew.cmu.edu
30 *
31 * 4. Redistributions of any form whatsoever must retain the following
32 *    acknowledgment:
33 *    "This product includes software developed by Computing Services
34 *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
35 *
36 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
37 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
38 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
39 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
40 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
41 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
42 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
43 */
44#include <config.h>
45#include <limits.h>
46#include <stdio.h>
47#include <string.h>
48#include <stdlib.h>
49#ifdef WIN32
50# include <winsock.h>
51__declspec(dllimport) char *optarg;
52__declspec(dllimport) int optind;
53__declspec(dllimport) int getsubopt(char **optionp, const char * const *tokens, char **valuep);
54#else  /* WIN32 */
55# include <netinet/in.h>
56#endif /* WIN32 */
57#include <sasl.h>
58#include <saslutil.h>
59
60#ifdef macintosh
61#include <sioux.h>
62#include <parse_cmd_line.h>
63#define MAX_ARGC (100)
64int xxx_main(int argc, char *argv[]);
65int main(void)
66{
67        char *argv[MAX_ARGC];
68        int argc;
69        char line[400];
70        SIOUXSettings.asktosaveonclose = 0;
71        SIOUXSettings.showstatusline = 1;
72        argc=parse_cmd_line(MAX_ARGC,argv,sizeof(line),line);
73        return xxx_main(argc,argv);
74}
75#define main xxx_main
76#endif
77
78#ifdef HAVE_GETOPT_H
79#include <getopt.h>
80#endif
81#ifdef HAVE_UNISTD_H
82#include <unistd.h>
83#endif
84
85#ifndef HAVE_GETSUBOPT
86int getsubopt(char **optionp, const char * const *tokens, char **valuep);
87#endif
88
89static const char
90build_ident[] = "$Build: sample-client " PACKAGE "-" VERSION " $";
91
92static const char *progname = NULL;
93static int verbose;
94
95#define SAMPLE_SEC_BUF_SIZE (2048)
96
97#define N_CALLBACKS (16)
98
99static const char
100message[] = "Come here Watson, I want you.";
101
102char buf[SAMPLE_SEC_BUF_SIZE];
103
104static const char *bit_subopts[] = {
105#define OPT_MIN (0)
106  "min",
107#define OPT_MAX (1)
108  "max",
109  NULL
110};
111
112static const char *ext_subopts[] = {
113#define OPT_EXT_SSF (0)
114  "ssf",
115#define OPT_EXT_ID (1)
116  "id",
117  NULL
118};
119
120static const char *flag_subopts[] = {
121#define OPT_NOPLAIN (0)
122  "noplain",
123#define OPT_NOACTIVE (1)
124  "noactive",
125#define OPT_NODICT (2)
126  "nodict",
127#define OPT_FORWARDSEC (3)
128  "forwardsec",
129#define OPT_NOANONYMOUS (4)
130  "noanonymous",
131#define OPT_PASSCRED (5)
132  "passcred",
133  NULL
134};
135
136static const char *ip_subopts[] = {
137#define OPT_IP_LOCAL (0)
138  "local",
139#define OPT_IP_REMOTE (1)
140  "remote",
141  NULL
142};
143
144static sasl_conn_t *conn = NULL;
145
146static void
147free_conn(void)
148{
149  if (conn)
150    sasl_dispose(&conn);
151}
152
153static int
154sasl_my_log(void *context __attribute__((unused)),
155            int priority,
156            const char *message)
157{
158  const char *label;
159
160  if (! message)
161    return SASL_BADPARAM;
162
163  switch (priority) {
164  case SASL_LOG_ERR:
165    label = "Error";
166    break;
167  case SASL_LOG_NOTE:
168    label = "Info";
169    break;
170  default:
171    label = "Other";
172    break;
173  }
174
175  fprintf(stderr, "%s: SASL %s: %s\n",
176          progname, label, message);
177
178  return SASL_OK;
179}
180
181static int getrealm(void *context,
182                    int id,
183                    const char **availrealms __attribute__((unused)),
184                    const char **result)
185{
186  if (id!=SASL_CB_GETREALM) return SASL_FAIL;
187
188  *result=(char *) context;
189 
190  return SASL_OK;
191}
192
193static int
194getpath(void *context,
195        const char ** path)
196{
197  const char *searchpath = (const char *) context;
198
199  if (! path)
200    return SASL_BADPARAM;
201
202  if (searchpath) {
203      *path = searchpath;
204  } else {
205      *path = PLUGINDIR;
206  }
207
208  return SASL_OK;
209}
210
211static int
212simple(void *context,
213       int id,
214       const char **result,
215       unsigned *len)
216{
217  const char *value = (const char *)context;
218
219  if (! result)
220    return SASL_BADPARAM;
221
222  switch (id) {
223  case SASL_CB_USER:
224    *result = value;
225    if (len)
226      *len = value ? strlen(value) : 0;
227    break;
228  case SASL_CB_AUTHNAME:
229    *result = value;
230    if (len)
231      *len = value ? strlen(value) : 0;
232    break;
233  case SASL_CB_LANGUAGE:
234    *result = NULL;
235    if (len)
236      *len = 0;
237    break;
238  default:
239    return SASL_BADPARAM;
240  }
241
242  printf("returning OK: %s\n", *result);
243
244  return SASL_OK;
245}
246
247#ifndef HAVE_GETPASSPHRASE
248static char *
249getpassphrase(const char *prompt)
250{
251  return getpass(prompt);
252}
253#endif /* ! HAVE_GETPASSPHRASE */
254
255static int
256getsecret(sasl_conn_t *conn,
257          void *context __attribute__((unused)),
258          int id,
259          sasl_secret_t **psecret)
260{
261  char *password;
262  size_t len;
263
264  if (! conn || ! psecret || id != SASL_CB_PASS)
265    return SASL_BADPARAM;
266
267  password = getpassphrase("Password: ");
268  if (! password)
269    return SASL_FAIL;
270
271  len = strlen(password);
272
273  *psecret = (sasl_secret_t *) malloc(sizeof(sasl_secret_t) + len);
274 
275  if (! *psecret) {
276    memset(password, 0, len);
277    return SASL_NOMEM;
278  }
279
280  (*psecret)->len = len;
281  strcpy((char *)(*psecret)->data, password);
282  memset(password, 0, len);
283   
284  return SASL_OK;
285}
286
287static int
288prompt(void *context __attribute__((unused)),
289       int id,
290       const char *challenge,
291       const char *prompt,
292       const char *defresult,
293       const char **result,
294       unsigned *len)
295{
296  if ((id != SASL_CB_ECHOPROMPT && id != SASL_CB_NOECHOPROMPT)
297      || !prompt || !result || !len)
298    return SASL_BADPARAM;
299
300  if (! defresult)
301    defresult = "";
302 
303  fputs(prompt, stdout);
304  if (challenge)
305    printf(" [challenge: %s]", challenge);
306  printf(" [%s]: ", defresult);
307  fflush(stdout);
308 
309  if (id == SASL_CB_ECHOPROMPT) {
310    char *original = getpassphrase("");
311    if (! original)
312      return SASL_FAIL;
313    if (*original)
314      *result = strdup(original);
315    else
316      *result = strdup(defresult);
317    memset(original, 0L, strlen(original));
318  } else {
319    char buf[1024];
320    fgets(buf, 1024, stdin);
321    if (buf[0]) {
322      *result = strdup(buf);
323    } else {
324      *result = strdup(defresult);
325    }
326    memset(buf, 0L, sizeof(buf));
327  }
328  if (! *result)
329    return SASL_NOMEM;
330
331  *len = strlen(*result);
332 
333  return SASL_OK;
334}
335
336static void
337sasldebug(int why, const char *what, const char *errstr)
338{
339  fprintf(stderr, "%s: %s: %s",
340          progname,
341          what,
342          sasl_errstring(why, NULL, NULL));
343  if (errstr)
344    fprintf(stderr, " (%s)\n", errstr);
345  else
346    putc('\n', stderr);
347}
348
349static void
350saslfail(int why, const char *what, const char *errstr)
351{
352  sasldebug(why, what, errstr);
353  free_conn();
354  sasl_done();
355  exit(EXIT_FAILURE);
356}
357
358static void
359fail(const char *what)
360{
361  fprintf(stderr, "%s: %s\n",
362          progname, what);
363  exit(EXIT_FAILURE);
364}
365
366static void
367osfail()
368{
369  perror(progname);
370  exit(EXIT_FAILURE);
371}
372
373static void
374samp_send(const char *buffer,
375          unsigned length)
376{
377  char *buf;
378  unsigned len, alloclen;
379  int result;
380
381  alloclen = ((length / 3) + 1) * 4 + 1;
382  buf = malloc(alloclen);
383  if (! buf)
384    osfail();
385  result = sasl_encode64(buffer, length, buf, alloclen, &len);
386  if (result != SASL_OK)
387    saslfail(result, "Encoding data in base64", NULL);
388  printf("C: %s\n", buf);
389  free(buf);
390}
391
392static unsigned
393samp_recv()
394{
395  unsigned len;
396  int result;
397 
398  if (! fgets(buf, SAMPLE_SEC_BUF_SIZE, stdin)
399      || strncmp(buf, "S: ", 3))
400    fail("Unable to parse input");
401  result = sasl_decode64(buf + 3, strlen(buf + 3), buf,
402                         SAMPLE_SEC_BUF_SIZE, &len);
403  if (result != SASL_OK)
404    saslfail(result, "Decoding data from base64", NULL);
405  buf[len] = '\0';
406  printf("recieved %d byte message\n",len);
407  if (verbose) { printf("got '%s'\n", buf); }
408  return len;
409}
410
411int
412main(int argc, char *argv[])
413{
414  int c = 0;
415  int errflag = 0;
416  int result;
417  sasl_security_properties_t secprops;
418  sasl_ssf_t extssf = 0;
419  const char *ext_authid = NULL;
420  char *options, *value;
421  const char *data;
422  const char *chosenmech;
423  int serverlast = 0;
424  unsigned len;
425  int clientfirst = 1;
426  sasl_callback_t callbacks[N_CALLBACKS], *callback;
427  char *realm = NULL;
428  char *mech = NULL,
429    *iplocal = NULL,
430    *ipremote = NULL,
431    *searchpath = NULL,
432    *service = "rcmd",
433    *fqdn = "",
434    *userid = NULL,
435    *authid = NULL;
436  sasl_ssf_t *ssf;
437   
438  progname = strrchr(argv[0], '/');
439  if (progname)
440    progname++;
441  else
442    progname = argv[0];
443
444  /* Init defaults... */
445  memset(&secprops, 0L, sizeof(secprops));
446  secprops.maxbufsize = SAMPLE_SEC_BUF_SIZE;
447  secprops.max_ssf = UINT_MAX;
448
449  verbose = 0;
450  while ((c = getopt(argc, argv, "vhldb:e:m:f:i:p:r:s:n:u:a:?")) != EOF)
451    switch (c) {
452    case 'v':
453        verbose = 1;
454        break;
455    case 'b':
456      options = optarg;
457      while (*options != '\0')
458        switch(getsubopt(&options, (const char * const *)bit_subopts, &value)) {
459        case OPT_MIN:
460          if (! value)
461            errflag = 1;
462          else
463            secprops.min_ssf = atoi(value);
464          break;
465        case OPT_MAX:
466          if (! value)
467            errflag = 1;
468          else
469            secprops.max_ssf = atoi(value);
470          break;
471        default:
472          errflag = 1;
473          break;         
474          }
475      break;
476
477    case 'l':
478        serverlast = SASL_SUCCESS_DATA;
479        break;
480       
481    case 'd':
482        clientfirst = 0;
483        break;
484
485    case 'e':
486      options = optarg;
487      while (*options != '\0')
488        switch(getsubopt(&options, (const char * const *)ext_subopts, &value)) {
489        case OPT_EXT_SSF:
490          if (! value)
491            errflag = 1;
492          else
493            extssf = atoi(value);
494          break;
495        case OPT_MAX:
496          if (! value)
497            errflag = 1;
498          else
499            ext_authid = value;
500          break;
501        default:
502          errflag = 1;
503          break;
504          }
505      break;
506
507    case 'm':
508      mech = optarg;
509      break;
510
511    case 'f':
512      options = optarg;
513      while (*options != '\0') {
514        switch(getsubopt(&options, (const char * const *)flag_subopts, &value)) {
515        case OPT_NOPLAIN:
516          secprops.security_flags |= SASL_SEC_NOPLAINTEXT;
517          break;
518        case OPT_NOACTIVE:
519          secprops.security_flags |= SASL_SEC_NOACTIVE;
520          break;
521        case OPT_NODICT:
522          secprops.security_flags |= SASL_SEC_NODICTIONARY;
523          break;
524        case OPT_FORWARDSEC:
525          secprops.security_flags |= SASL_SEC_FORWARD_SECRECY;
526          break;
527        case OPT_NOANONYMOUS:
528          secprops.security_flags |= SASL_SEC_NOANONYMOUS;
529          break;
530        case OPT_PASSCRED:
531          secprops.security_flags |= SASL_SEC_PASS_CREDENTIALS;
532          break;
533        default:
534          errflag = 1;
535          break;
536          }
537        if (value) errflag = 1;
538        }
539      break;
540
541    case 'i':
542      options = optarg;
543      while (*options != '\0')
544        switch(getsubopt(&options, (const char * const *)ip_subopts, &value)) {
545        case OPT_IP_LOCAL:
546          if (! value)
547            errflag = 1;
548          else
549            iplocal = value;
550          break;
551        case OPT_IP_REMOTE:
552          if (! value)
553            errflag = 1;
554          else
555            ipremote = value;
556          break;
557        default:
558          errflag = 1;
559          break;
560          }
561      break;
562
563    case 'p':
564      searchpath = optarg;
565      break;
566
567    case 'r':
568      realm = optarg;
569      break;
570
571    case 's':
572      service=malloc(1000);
573      strcpy(service,optarg);
574      /*      service = optarg;*/
575      printf("service=%s\n",service);
576      break;
577
578    case 'n':
579      fqdn = optarg;
580      break;
581
582    case 'u':
583      userid = optarg;
584      break;
585
586    case 'a':
587      authid = optarg;
588      break;
589
590    default:                    /* unknown flag */
591      errflag = 1;
592      break;
593    }
594
595  if (optind != argc) {
596    /* We don't *have* extra arguments */
597    errflag = 1;
598  }
599
600  if (errflag) {
601    fprintf(stderr, "%s: Usage: %s [-b min=N,max=N] [-e ssf=N,id=ID] [-m MECH] [-f FLAGS] [-i local=IP,remote=IP] [-p PATH] [-s NAME] [-n FQDN] [-u ID] [-a ID]\n"
602            "\t-b ...\t#bits to use for encryption\n"
603            "\t\tmin=N\tminumum #bits to use (1 => integrity)\n"
604            "\t\tmax=N\tmaximum #bits to use\n"
605            "\t-e ...\tassume external encryption\n"
606            "\t\tssf=N\texternal mech provides N bits of encryption\n"
607            "\t\tid=ID\texternal mech provides authentication id ID\n"
608            "\t-m MECH\tforce use of MECH for security\n"
609            "\t-f ...\tset security flags\n"
610            "\t\tnoplain\t\trequire security vs. passive attacks\n"
611            "\t\tnoactive\trequire security vs. active attacks\n"
612            "\t\tnodict\t\trequire security vs. passive dictionary attacks\n"
613            "\t\tforwardsec\trequire forward secrecy\n"
614            "\t\tmaximum\t\trequire all security flags\n"
615            "\t\tpasscred\tattempt to pass client credentials\n"
616            "\t-i ...\tset IP addresses (required by some mechs)\n"
617            "\t\tlocal=IP;PORT\tset local address to IP, port PORT\n"
618            "\t\tremote=IP;PORT\tset remote address to IP, port PORT\n"
619            "\t-p PATH\tcolon-seperated search path for mechanisms\n"
620            "\t-r REALM\trealm to use"
621            "\t-s NAME\tservice name pass to mechanisms\n"
622            "\t-n FQDN\tserver fully-qualified domain name\n"
623            "\t-u ID\tuser (authorization) id to request\n"
624            "\t-a ID\tid to authenticate as\n"
625            "\t-d\tDisable client-send-first\n"
626            "\t-l\tEnable server-send-last\n",
627            progname, progname);
628    exit(EXIT_FAILURE);
629  }
630
631  /* Fill in the callbacks that we're providing... */
632  callback = callbacks;
633
634  /* log */
635  callback->id = SASL_CB_LOG;
636  callback->proc = &sasl_my_log;
637  callback->context = NULL;
638  ++callback;
639 
640  /* getpath */
641  if (searchpath) {
642    callback->id = SASL_CB_GETPATH;
643    callback->proc = &getpath;
644    callback->context = searchpath;
645    ++callback;
646  }
647
648  /* user */
649  if (userid) {
650    callback->id = SASL_CB_USER;
651    callback->proc = &simple;
652    callback->context = userid;
653    ++callback;
654  }
655
656  /* authname */
657  if (authid) {
658    callback->id = SASL_CB_AUTHNAME;
659    callback->proc = &simple;
660    callback->context = authid;
661    ++callback;
662  }
663
664  if (realm!=NULL)
665  {
666    callback->id = SASL_CB_GETREALM;
667    callback->proc = &getrealm;
668    callback->context = realm;
669    callback++;
670  }
671
672  /* password */
673  callback->id = SASL_CB_PASS;
674  callback->proc = &getsecret;
675  callback->context = NULL;
676  ++callback;
677
678  /* echoprompt */
679  callback->id = SASL_CB_ECHOPROMPT;
680  callback->proc = &prompt;
681  callback->context = NULL;
682  ++callback;
683
684  /* noechoprompt */
685  callback->id = SASL_CB_NOECHOPROMPT;
686  callback->proc = &prompt;
687  callback->context = NULL;
688  ++callback;
689
690  /* termination */
691  callback->id = SASL_CB_LIST_END;
692  callback->proc = NULL;
693  callback->context = NULL;
694  ++callback;
695
696  if (N_CALLBACKS < callback - callbacks)
697    fail("Out of callback space; recompile with larger N_CALLBACKS");
698
699  result = sasl_client_init(callbacks);
700  if (result != SASL_OK)
701    saslfail(result, "Initializing libsasl", NULL);
702
703  result = sasl_client_new(service,
704                           fqdn,
705                           iplocal,ipremote,
706                           NULL,serverlast,
707                           &conn);
708  if (result != SASL_OK)
709    saslfail(result, "Allocating sasl connection state", NULL);
710
711  if(extssf) {
712      result = sasl_setprop(conn,
713                            SASL_SSF_EXTERNAL,
714                            &extssf);
715
716      if (result != SASL_OK)
717          saslfail(result, "Setting external SSF", NULL);
718  }
719 
720  if(ext_authid) {
721      result = sasl_setprop(conn,
722                            SASL_AUTH_EXTERNAL,
723                            &ext_authid);
724
725      if (result != SASL_OK)
726          saslfail(result, "Setting external authid", NULL);
727  }
728 
729  result = sasl_setprop(conn,
730                        SASL_SEC_PROPS,
731                        &secprops);
732
733  if (result != SASL_OK)
734    saslfail(result, "Setting security properties", NULL);
735
736  puts("Waiting for mechanism list from server...");
737  len = samp_recv();
738
739  if (mech) {
740    printf("Forcing use of mechanism %s\n", mech);
741    strncpy(buf, mech, SAMPLE_SEC_BUF_SIZE);
742    buf[SAMPLE_SEC_BUF_SIZE - 1] = '\0';
743  }
744
745  printf("Choosing best mechanism from: %s\n", buf);
746
747  if(clientfirst) {
748      result = sasl_client_start(conn,
749                                 buf,
750                                 NULL,
751                                 &data,
752                                 &len,
753                                 &chosenmech);
754  } else {
755      data = "";
756      len = 0;
757      result = sasl_client_start(conn,
758                                 buf,
759                                 NULL,
760                                 NULL,
761                                 0,
762                                 &chosenmech);
763  }
764 
765 
766  if (result != SASL_OK && result != SASL_CONTINUE) {
767      printf("error was %s\n", sasl_errdetail(conn));
768      saslfail(result, "Starting SASL negotiation", NULL);
769  }
770
771  printf("Using mechanism %s\n", chosenmech);
772  strcpy(buf, chosenmech);
773  if (data) {
774    if (SAMPLE_SEC_BUF_SIZE - strlen(buf) - 1 < len)
775      fail("Not enough buffer space");
776    puts("Preparing initial.");
777    memcpy(buf + strlen(buf) + 1, data, len);
778    len += strlen(buf) + 1;
779    data = NULL;
780  } else {
781    len = strlen(buf);
782  }
783 
784  puts("Sending initial response...");
785  samp_send(buf, len);
786
787  while (result == SASL_CONTINUE) {
788    puts("Waiting for server reply...");
789    len = samp_recv();
790    result = sasl_client_step(conn, buf, len, NULL,
791                              &data, &len);
792    if (result != SASL_OK && result != SASL_CONTINUE)
793        saslfail(result, "Performing SASL negotiation", NULL);
794    if (data && len) {
795        puts("Sending response...");
796        samp_send(data, len);
797    } else if (result != SASL_OK || !serverlast) {
798        samp_send("",0);
799    }
800   
801  }
802  puts("Negotiation complete");
803
804  result = sasl_getprop(conn, SASL_USERNAME, (const void **)&data);
805  if (result != SASL_OK)
806    sasldebug(result, "username", NULL);
807  else
808    printf("Username: %s\n", data);
809
810#define CLIENT_MSG1 "client message 1"
811#define SERVER_MSG1 "srv message 1"
812
813  result = sasl_getprop(conn, SASL_SSF, (const void **)&ssf);
814  if (result != SASL_OK)
815    sasldebug(result, "ssf", NULL);
816  else
817    printf("SSF: %d\n", *ssf);
818 
819 printf("Waiting for encoded message...\n");
820 len=samp_recv();
821 {
822        unsigned int recv_len;
823        const char *recv_data;
824        result=sasl_decode(conn,buf,len,&recv_data,&recv_len);
825        if (result != SASL_OK)
826            saslfail(result, "sasl_decode", NULL);
827        printf("recieved decoded message '%s'\n",recv_data);
828        if(strcmp(recv_data,SERVER_MSG1)!=0)
829            saslfail(1,"recive decoded server message",NULL);
830 }
831  result=sasl_encode(conn,CLIENT_MSG1,sizeof(CLIENT_MSG1),
832        &data,&len);
833  if (result != SASL_OK)
834      saslfail(result, "sasl_encode", NULL);
835  printf("sending encrypted message '%s'\n",CLIENT_MSG1);
836  samp_send(data,len);
837
838  free_conn();
839  sasl_done();
840
841  return (EXIT_SUCCESS);
842}
Note: See TracBrowser for help on using the repository browser.